diff --git a/ChangeLog b/ChangeLog index 4fe8af572..12ac76686 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * fix http connection timeout on multi-homed hosts * removed depdendency on boost::uintptr_t for better compatibility * fix memory leak in the disk cache * fix double free in disk cache diff --git a/simulation/libsimulator b/simulation/libsimulator index 60d786b8f..0e8d74baf 160000 --- a/simulation/libsimulator +++ b/simulation/libsimulator @@ -1 +1 @@ -Subproject commit 60d786b8fa6ddaacdc98bdf691220660bc194494 +Subproject commit 0e8d74baf1f6d9db19857eaa87734faecd530f94 diff --git a/simulation/test_http_connection.cpp b/simulation/test_http_connection.cpp index c9d059a65..74de4c777 100644 --- a/simulation/test_http_connection.cpp +++ b/simulation/test_http_connection.cpp @@ -83,6 +83,13 @@ struct sim_config : sim::default_config return duration_cast(chrono::milliseconds(100)); } + if (hostname == "dual-stack.test-hostname.com") + { + result.push_back(address_v4::from_string("10.0.0.2")); + result.push_back(address_v6::from_string("ff::dead:beef")); + return duration_cast(chrono::milliseconds(100)); + } + return default_config::hostname_lookup(requestor, hostname, result, ec); } }; @@ -225,8 +232,12 @@ void run_suite(lt::aux::proxy_settings ps) if (ps.type != settings_pack::socks5 && ps.type != settings_pack::http) { + const auto expected_code = ps.type == settings_pack::socks4 ? + boost::system::errc::address_family_not_supported : + boost::system::errc::address_not_available; + run_test(ps, "http://[ff::dead:beef]:8080/test_file", 0, -1 - , error_condition(boost::system::errc::address_family_not_supported, generic_category()) + , error_condition(expected_code, generic_category()) , {0,1}); } @@ -438,6 +449,102 @@ TORRENT_TEST(http_connection_socks5_proxy_names) run_suite(ps); } +// tests the error scenario of a http server listening on two sockets (ipv4/ipv6) which +// both accept the incoming connection but never send anything back. we test that +// both ip addresses get tried in turn and that the connection attempts time out as expected. +TORRENT_TEST(http_connection_timeout_server_stalls) +{ + sim_config network_cfg; + sim::simulation sim{network_cfg}; + // server has two ip addresses (ipv4/ipv6) + sim::asio::io_service server_ios(sim, address_v4::from_string("10.0.0.2")); + sim::asio::io_service server_ios_ipv6(sim, address_v6::from_string("ff::dead:beef")); + // same for client + sim::asio::io_service client_ios(sim, { + address_v4::from_string("10.0.0.1"), + address_v6::from_string("ff::abad:cafe") + }); + lt::resolver resolver(client_ios); + + const unsigned short http_port = 8080; + sim::http_server http(server_ios, http_port); + sim::http_server http_ipv6(server_ios_ipv6, http_port); + + http.register_stall_handler("/timeout"); + http_ipv6.register_stall_handler("/timeout"); + + char data_buffer[4000]; + std::generate(data_buffer, data_buffer + sizeof(data_buffer), &std::rand); + + int connect_counter = 0; + int handler_counter = 0; + + error_condition timed_out(boost::system::errc::timed_out, boost::system::generic_category()); + + auto c = test_request(client_ios, resolver + , "http://dual-stack.test-hostname.com:8080/timeout", data_buffer, -1, -1 + , timed_out, lt::aux::proxy_settings() + , &connect_counter, &handler_counter); + + error_code e; + sim.run(e); + TEST_CHECK(!e); + TEST_EQUAL(2, connect_counter); // both endpoints are connected to + TEST_EQUAL(1, handler_counter); // the handler only gets called once with error_code == timed_out +} + +// tests the error scenario of a http server listening on two sockets (ipv4/ipv6) neither of which +// accept incoming connections. we test that both ip addresses get tried in turn and that the +// connection attempts time out as expected. +TORRENT_TEST(http_connection_timeout_server_does_not_accept) +{ + sim_config network_cfg; + sim::simulation sim{network_cfg}; + // server has two ip addresses (ipv4/ipv6) + sim::asio::io_service server_ios(sim, { + address_v4::from_string("10.0.0.2"), + address_v6::from_string("ff::dead:beef") + }); + // same for client + sim::asio::io_service client_ios(sim, { + address_v4::from_string("10.0.0.1"), + address_v6::from_string("ff::abad:cafe") + }); + lt::resolver resolver(client_ios); + + const unsigned short http_port = 8080; + + // listen on two sockets, but don't accept connections + asio::ip::tcp::acceptor server_socket_ipv4(server_ios); + server_socket_ipv4.open(tcp::v4()); + server_socket_ipv4.bind(tcp::endpoint(address_v4::any(), http_port)); + server_socket_ipv4.listen(); + + asio::ip::tcp::acceptor server_socket_ipv6(server_ios); + server_socket_ipv6.open(tcp::v6()); + server_socket_ipv6.bind(tcp::endpoint(address_v6::any(), http_port)); + server_socket_ipv6.listen(); + + int connect_counter = 0; + int handler_counter = 0; + + error_condition timed_out(boost::system::errc::timed_out, boost::system::generic_category()); + + char data_buffer[4000]; + std::generate(data_buffer, data_buffer + sizeof(data_buffer), &std::rand); + + auto c = test_request(client_ios, resolver + , "http://dual-stack.test-hostname.com:8080/timeout_server_does_not_accept", data_buffer, -1, -1 + , timed_out, lt::aux::proxy_settings() + , &connect_counter, &handler_counter); + + error_code e; + sim.run(e); + TEST_CHECK(!e); + TEST_EQUAL(0, connect_counter); // no connection takes place + TEST_EQUAL(1, handler_counter); // the handler only gets called once with error_code == timed_out +} + void test_proxy_failure(lt::settings_pack::proxy_type_t proxy_type) { using sim::asio::ip::address_v4; diff --git a/src/http_connection.cpp b/src/http_connection.cpp index 37b28aa38..f599e8356 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -449,12 +449,14 @@ void http_connection::on_timeout(boost::weak_ptr p error_code ec; c->m_sock.close(ec); if (!c->m_connecting) c->connect(); + c->m_last_receive = now; + c->m_start_time = c->m_last_receive; } else { c->callback(boost::asio::error::timed_out); + return; } - return; } else {