diff --git a/include/libtorrent/enum_net.hpp b/include/libtorrent/enum_net.hpp index e777d0144..3e176b188 100644 --- a/include/libtorrent/enum_net.hpp +++ b/include/libtorrent/enum_net.hpp @@ -124,6 +124,13 @@ namespace libtorrent address ip = address::from_string(device_name, ec); if (!ec) { +#if TORRENT_USE_IPV6 + // this is to cover the case where "0.0.0.0" is considered any IPv4 or + // IPv6 address. If we're asking to be bound to an IPv6 address and + // providing 0.0.0.0 as the device, turn it into "::0" + if (ip == address_v4::any() && !ipv4) + ip = address_v6::any(); +#endif bind_ep.address(ip); // it appears to be an IP. Just bind to that address sock.bind(bind_ep, ec); diff --git a/simulation/libsimulator b/simulation/libsimulator index dedd40932..47ccf576c 160000 --- a/simulation/libsimulator +++ b/simulation/libsimulator @@ -1 +1 @@ -Subproject commit dedd409321589f8ed11d244e1b65772435ef91d1 +Subproject commit 47ccf576c6ed7700752ba294962d8e297b1ce76f diff --git a/simulation/setup_swarm.cpp b/simulation/setup_swarm.cpp index 28f0d69a6..29c151636 100644 --- a/simulation/setup_swarm.cpp +++ b/simulation/setup_swarm.cpp @@ -127,11 +127,8 @@ struct swarm bool term = false; ses->pop_alerts(&alerts); - for (std::vector::iterator i = alerts.begin(); - i != alerts.end(); ++i) + for (lt::alert* a : alerts) { - lt::alert* a = *i; - lt::time_duration d = a->timestamp() - m_start_time; boost::uint32_t millis = lt::duration_cast(d).count(); printf("%4d.%03d: [%02d] %s\n", millis / 1000, millis % 1000, diff --git a/simulation/test_tracker.cpp b/simulation/test_tracker.cpp index 349899ba1..3ada90c55 100644 --- a/simulation/test_tracker.cpp +++ b/simulation/test_tracker.cpp @@ -42,6 +42,8 @@ using namespace libtorrent; using namespace sim; namespace lt = libtorrent; +using chrono::duration_cast; + // seconds const int duration = 10000; @@ -257,6 +259,131 @@ TORRENT_TEST(announce_interval_1200) test_interval(3600); } +struct sim_config : sim::default_config +{ + chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) + { + if (hostname == "tracker.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); + } +}; + +void on_alert_notify(lt::session* ses) +{ + std::vector alerts; + ses->pop_alerts(&alerts); + + for (lt::alert* a : alerts) + { + lt::time_duration d = a->timestamp().time_since_epoch(); + boost::uint32_t millis = lt::duration_cast(d).count(); + printf("%4d.%03d: %s\n", millis / 1000, millis % 1000, + a->message().c_str()); + } +} + +// this test makes sure that a tracker whose host name resolves to both IPv6 and +// IPv4 addresses will be announced to twice, once for each address family +TORRENT_TEST(ipv6_support) +{ + using sim::asio::ip::address_v4; + sim_config network_cfg; + sim::simulation sim{network_cfg}; + + sim::asio::io_service web_server_v4(sim, address_v4::from_string("10.0.0.2")); + sim::asio::io_service web_server_v6(sim, address_v6::from_string("ff::dead:beef")); + + // listen on port 8080 + sim::http_server http_v4(web_server_v4, 8080); + sim::http_server http_v6(web_server_v6, 8080); + + // the timestamps (in seconds) of all announces + std::vector announces; + + int v4_announces = 0; + int v6_announces = 0; + + http_v4.register_handler("/announce" + , [&v4_announces](std::string method, std::string req) + { + ++v4_announces; + TEST_EQUAL(method, "GET"); + + char response[500]; + int size = snprintf(response, sizeof(response), "d8:intervali1800e5:peers0:e"); + return sim::send_response(200, "OK", size) + response; + }); + + http_v6.register_handler("/announce" + , [&v6_announces](std::string method, std::string req) + { + ++v6_announces; + TEST_EQUAL(method, "GET"); + + char response[500]; + int size = snprintf(response, sizeof(response), "d8:intervali1800e5:peers0:e"); + return sim::send_response(200, "OK", size) + response; + }); + + { + lt::session_proxy zombie; + + asio::io_service ios(sim, { address_v4::from_string("10.0.0.3") + , address_v6::from_string("ffff::1337") }); + lt::settings_pack sett = settings(); + std::unique_ptr ses(new lt::session(sett, ios)); + + ses->set_alert_notify(std::bind(&on_alert_notify, ses.get())); + + + lt::add_torrent_params p; + p.name = "test-torrent"; + p.save_path = "."; + p.info_hash.assign("abababababababababab"); + +//TODO: parameterize http vs. udp here + p.trackers.push_back("http://tracker.com:8080/announce"); + ses->async_add_torrent(p); + + // stop the torrent 5 seconds in + asio::high_resolution_timer stop(ios); + stop.expires_from_now(chrono::seconds(5)); + stop.async_wait([&ses](boost::system::error_code const& ec) + { + std::vector torrents = ses->get_torrents(); + for (auto const& t : torrents) + { + t.pause(); + } + }); + + // then shut down 10 seconds in + asio::high_resolution_timer terminate(ios); + terminate.expires_from_now(chrono::seconds(10)); + terminate.async_wait([&ses,&zombie](boost::system::error_code const& ec) + { + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); + } + + // 2 because there's one announce on startup and one when shutting down + TEST_EQUAL(v4_announces, 2); + TEST_EQUAL(v6_announces, 2); +} + // TODO: test with different queuing settings // TODO: test when a torrent transitions from downloading to finished and // finished to seeding diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 378882b83..85051c9f9 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -1940,7 +1940,9 @@ retry: { error_code err; address test_family = address::from_string(device.c_str(), err); - if (!err && test_family.is_v4() != address_family) + if (!err + && test_family.is_v4() != address_family + && !is_any(test_family)) continue; listen_socket_t s = setup_listener(device, address_family, port diff --git a/src/torrent.cpp b/src/torrent.cpp index 3a9dfca18..6bdf6136b 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -3531,13 +3531,11 @@ namespace libtorrent // do it if the bind IP for the tracker request that just completed // matches one of the listen interfaces, since that means this // announce was the second one - // don't connect twice just to tell it we're stopping if (((!is_any(m_ses.get_ipv6_interface().address()) && tracker_ip.is_v4()) || (!is_any(m_ses.get_ipv4_interface().address()) && tracker_ip.is_v6())) && r.bind_ip != m_ses.get_ipv4_interface().address() - && r.bind_ip != m_ses.get_ipv6_interface().address() - && r.event != tracker_request::stopped) + && r.bind_ip != m_ses.get_ipv6_interface().address()) { std::list
::const_iterator i = std::find_if(tracker_ips.begin() , tracker_ips.end(), boost::bind(&address::is_v4, _1) != tracker_ip.is_v4()); @@ -3546,15 +3544,22 @@ namespace libtorrent // the tracker did resolve to a different type of address, so announce // to that as well + // TODO 2: there's a bug when removing a torrent or shutting down the session, + // where the second announce is skipped (in this case, the one to the IPv6 + // name). This should be fixed by generalizing the tracker list structure to + // separate the IPv6 and IPv4 addresses as conceptually separate trackers, + // and they should be announced to in parallel + + tracker_request req = r; // tell the tracker to bind to the opposite protocol type - address bind_interface = tracker_ip.is_v4() - ?m_ses.get_ipv6_interface().address() - :m_ses.get_ipv4_interface().address(); - announce_with_tracker(r.event, bind_interface); + req.bind_ip = tracker_ip.is_v4() + ? m_ses.get_ipv6_interface().address() + : m_ses.get_ipv4_interface().address(); #ifndef TORRENT_DISABLE_LOGGING debug_log("announce again using %s as the bind interface" - , print_address(bind_interface).c_str()); + , print_address(req.bind_ip).c_str()); #endif + m_ses.queue_tracker_request(req, shared_from_this()); } }