diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 6bdb7f012..4dc99f65e 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -128,6 +128,12 @@ namespace aux { struct tracker_logger; #endif + enum class duplex : std::uint8_t + { + accept_incoming, + only_outgoing + }; + struct listen_socket_t { listen_socket_t() @@ -181,6 +187,8 @@ namespace aux { // indicates whether this is an SSL listen socket or not transport ssl = transport::plaintext; + duplex incoming = duplex::accept_incoming; + // the actual sockets (TCP listen socket and UDP socket) // An entry does not necessarily have a UDP or TCP socket. One of these // pointers may be nullptr! @@ -194,8 +202,9 @@ namespace aux { struct TORRENT_EXTRA_EXPORT listen_endpoint_t { - listen_endpoint_t(address adr, int p, std::string dev, transport s) - : addr(adr), port(p), device(dev), ssl(s) {} + listen_endpoint_t(address adr, int p, std::string dev, transport s + , duplex d = duplex::accept_incoming) + : addr(adr), port(p), device(dev), ssl(s), incoming(d) {} bool operator==(listen_endpoint_t const& o) const { @@ -206,6 +215,7 @@ namespace aux { int port; std::string device; transport ssl; + duplex incoming; }; // partitions sockets based on whether they match one of the given endpoints @@ -675,6 +685,7 @@ namespace aux { // implements session_interface tcp::endpoint bind_outgoing_socket(socket_type& s, address const& remote_address, error_code& ec) const override; + bool verify_incoming_interface(address const& addr); bool verify_bound_address(address const& addr, bool utp , error_code& ec) override; @@ -761,8 +772,8 @@ namespace aux { void set_external_address(std::shared_ptr const& sock, address const& ip , ip_source_t const source_type, address const& source); - void interface_to_endpoints(std::string const& device, int const port - , bool const ssl, std::vector& eps); + void interface_to_endpoints(std::string const& device, int port + , transport ssl, duplex incoming, std::vector& eps); // the settings for the client aux::session_settings m_settings; @@ -937,8 +948,8 @@ namespace aux { // round-robin index into m_outgoing_interfaces mutable std::uint8_t m_interface_index = 0; - std::shared_ptr setup_listener(std::string const& device - , tcp::endpoint bind_ep, transport ssl, error_code& ec); + std::shared_ptr setup_listener( + listen_endpoint_t const& lep, error_code& ec); #ifndef TORRENT_DISABLE_DHT dht::dht_state m_dht_state; diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp index f3dc3e16b..3fe2a4ea0 100644 --- a/include/libtorrent/tracker_manager.hpp +++ b/include/libtorrent/tracker_manager.hpp @@ -155,6 +155,7 @@ namespace libtorrent { #endif sha1_hash info_hash; peer_id pid; + aux::listen_socket_handle outgoing_socket; // set to true if the .torrent file this tracker announce is for is marked diff --git a/include/libtorrent/udp_tracker_connection.hpp b/include/libtorrent/udp_tracker_connection.hpp index 5feab1fc1..bc79d41da 100644 --- a/include/libtorrent/udp_tracker_connection.hpp +++ b/include/libtorrent/udp_tracker_connection.hpp @@ -97,7 +97,7 @@ namespace libtorrent { void fail(error_code const& ec, int code = -1 , char const* msg = "" , seconds32 interval = seconds32(0) - , seconds32 min_interval = seconds32(0)); + , seconds32 min_interval = seconds32(30)); void send_udp_connect(); void send_udp_announce(); diff --git a/simulation/test_tracker.cpp b/simulation/test_tracker.cpp index ac80bfb91..f2c0dfe00 100644 --- a/simulation/test_tracker.cpp +++ b/simulation/test_tracker.cpp @@ -406,6 +406,104 @@ void test_ipv6_support(char const* listen_interfaces TEST_EQUAL(v6_announces, expect_v6); } +void test_udpv6_support(char const* listen_interfaces + , int const expect_v4, int const expect_v6) +{ + 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")); + + int v4_announces = 0; + int v6_announces = 0; + + { + lt::session_proxy zombie; + + std::vector ips; + + for (int i = 0; i < num_interfaces; i++) + { + char ep[30]; + std::snprintf(ep, sizeof(ep), "10.0.0.%d", i + 1); + ips.push_back(address::from_string(ep)); + std::snprintf(ep, sizeof(ep), "ffff::1337:%d", i + 1); + ips.push_back(address::from_string(ep)); + } + + asio::io_service ios(sim, ips); + lt::settings_pack sett = settings(); + if (listen_interfaces) + { + sett.set_str(settings_pack::listen_interfaces, listen_interfaces); + } + std::unique_ptr ses(new lt::session(sett, ios)); + + // since we don't have a udp tracker to run in the sim, looking for the + // alerts is the closest proxy + ses->set_alert_notify([&]{ + ses->get_io_service().post([&] { + std::vector alerts; + ses->pop_alerts(&alerts); + + for (lt::alert* a : alerts) + { + lt::time_duration d = a->timestamp().time_since_epoch(); + std::uint32_t const millis = std::uint32_t( + lt::duration_cast(d).count()); + std::printf("%4d.%03d: %s\n", millis / 1000, millis % 1000, + a->message().c_str()); + if (auto tr = alert_cast(a)) + { + if (tr->local_endpoint.address().is_v4()) + ++v4_announces; + else + ++v6_announces; + } + else if (alert_cast(a)) + { + TEST_CHECK(false && "unexpected tracker error"); + } + } + }); + }); + + lt::add_torrent_params p; + p.name = "test-torrent"; + p.save_path = "."; + p.info_hash.assign("abababababababababab"); + + p.trackers.push_back("udp://tracker.com:8080/announce"); + ses->async_add_torrent(p); + + // stop the torrent 5 seconds in + sim::timer t1(sim, lt::seconds(5) + , [&ses](boost::system::error_code const&) + { + std::vector torrents = ses->get_torrents(); + for (auto const& t : torrents) + { + t.pause(); + } + }); + + // then shut down 10 seconds in + sim::timer t2(sim, lt::seconds(10) + , [&ses,&zombie](boost::system::error_code const&) + { + zombie = ses->abort(); + ses.reset(); + }); + + sim.run(); + } + + TEST_EQUAL(v4_announces, expect_v4); + TEST_EQUAL(v6_announces, expect_v6); +} + // 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) @@ -414,6 +512,20 @@ TORRENT_TEST(ipv6_support) test_ipv6_support(nullptr, 2, num_interfaces * 2); } +TORRENT_TEST(announce_no_listen) +{ + // if we don't listen on any sockets at all (but only make outgoing peer + // connections) we still need to make sure we announce to trackers + test_ipv6_support("", 2, 2); +} + +TORRENT_TEST(announce_udp_no_listen) +{ + // since there's no actual udp tracker in this test, we will only try to + // announce once, and fail. We won't announce the event=stopped + test_udpv6_support("", 1, 1); +} + TORRENT_TEST(ipv6_support_bind_v4_v6_any) { // 2 because there's one announce on startup and one when shutting down diff --git a/src/session_impl.cpp b/src/session_impl.cpp index d215a275c..84a605f1b 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -1348,7 +1348,10 @@ namespace { != m_settings.get_int(settings_pack::ssl_listen)) || #endif - (pack.has_val(settings_pack::listen_interfaces) + (pack.has_val(settings_pack::force_proxy) + && !pack.get_bool(settings_pack::force_proxy) + && m_settings.get_bool(settings_pack::force_proxy)) + || (pack.has_val(settings_pack::listen_interfaces) && pack.get_str(settings_pack::listen_interfaces) != m_settings.get_str(settings_pack::listen_interfaces)); @@ -1381,25 +1384,27 @@ namespace { reopen_outgoing_sockets(); } - std::shared_ptr session_impl::setup_listener(std::string const& device - , tcp::endpoint bind_ep, transport const ssl, error_code& ec) + std::shared_ptr session_impl::setup_listener( + listen_endpoint_t const& lep, error_code& ec) { int retries = m_settings.get_int(settings_pack::max_retry_port_bind); + tcp::endpoint bind_ep(lep.addr, std::uint16_t(lep.port)); #ifndef TORRENT_DISABLE_LOGGING if (should_log()) { session_log("attempting to open listen socket to: %s on device: %s ssl: %x" - , print_endpoint(bind_ep).c_str(), device.c_str(), static_cast(ssl)); + , print_endpoint(bind_ep).c_str(), lep.device.c_str(), static_cast(lep.ssl)); } #endif auto ret = std::make_shared(); - ret->ssl = ssl; + ret->ssl = lep.ssl; ret->original_port = bind_ep.port(); + ret->incoming = lep.incoming; operation_t last_op = operation_t::unknown; socket_type_t const sock_type - = (ssl == transport::ssl) + = (lep.ssl == transport::ssl) ? socket_type_t::tcp_ssl : socket_type_t::tcp; @@ -1407,7 +1412,7 @@ namespace { // accept connections on our local machine in this case. // TODO: 3 the logic in this if-block should be factored out into a // separate function. At least most of it - if (!m_settings.get_bool(settings_pack::force_proxy)) + if (ret->incoming == duplex::accept_incoming) { ret->sock = std::make_shared(m_io_service); ret->sock->open(bind_ep.protocol(), ec); @@ -1423,7 +1428,7 @@ namespace { #endif if (m_alerts.should_post()) - m_alerts.emplace_alert(device, bind_ep, last_op + m_alerts.emplace_alert(lep.device, bind_ep, last_op , ec, sock_type); return ret; } @@ -1484,26 +1489,26 @@ namespace { } #endif // TORRENT_USE_IPV6 - if (!device.empty()) + if (!lep.device.empty()) { // we have an actual device we're interested in listening on, if we // have SO_BINDTODEVICE functionality, use it now. #if TORRENT_HAS_BINDTODEVICE - ret->sock->set_option(bind_to_device(device.c_str()), ec); + ret->sock->set_option(bind_to_device(lep.device.c_str()), ec); if (ec) { #ifndef TORRENT_DISABLE_LOGGING if (should_log()) { session_log("bind to device failed (device: %s): %s" - , device.c_str(), ec.message().c_str()); + , lep.device.c_str(), ec.message().c_str()); } #endif // TORRENT_DISABLE_LOGGING last_op = operation_t::sock_bind_to_device; if (m_alerts.should_post()) { - m_alerts.emplace_alert(device, bind_ep + m_alerts.emplace_alert(lep.device, bind_ep , last_op, ec, sock_type); } return ret; @@ -1523,7 +1528,7 @@ namespace { session_log("failed to bind listen socket to: %s on device: %s :" " [%s] (%d) %s (retries: %d)" , print_endpoint(bind_ep).c_str() - , device.c_str() + , lep.device.c_str() , ec.category().name(), ec.value(), ec.message().c_str() , retries); } @@ -1555,20 +1560,20 @@ namespace { session_log("failed to bind listen socket to: %s on device: %s :" " [%s] (%d) %s (giving up)" , print_endpoint(bind_ep).c_str() - , device.c_str() + , lep.device.c_str() , ec.category().name(), ec.value(), ec.message().c_str()); } #endif if (m_alerts.should_post()) { - m_alerts.emplace_alert(device, bind_ep + m_alerts.emplace_alert(lep.device, bind_ep , last_op, ec, sock_type); } ret->sock.reset(); return ret; } ret->local_endpoint = ret->sock->local_endpoint(ec); - ret->device = device; + ret->device = lep.device; last_op = operation_t::getname; if (ec) { @@ -1581,7 +1586,7 @@ namespace { #endif if (m_alerts.should_post()) { - m_alerts.emplace_alert(device, bind_ep + m_alerts.emplace_alert(lep.device, bind_ep , last_op, ec, sock_type); } return ret; @@ -1599,20 +1604,20 @@ namespace { if (should_log()) { session_log("cannot listen on interface \"%s\": %s" - , device.c_str(), ec.message().c_str()); + , lep.device.c_str(), ec.message().c_str()); } #endif if (m_alerts.should_post()) { - m_alerts.emplace_alert(device, bind_ep + m_alerts.emplace_alert(lep.device, bind_ep , last_op, ec, sock_type); } return ret; } - } // force-proxy mode + } // accept incoming socket_type_t const udp_sock_type - = (ssl == transport::ssl) + = (lep.ssl == transport::ssl) ? socket_type_t::utp_ssl : socket_type_t::udp; udp::endpoint const udp_bind_ep(bind_ep.address(), bind_ep.port()); @@ -1625,36 +1630,36 @@ namespace { if (should_log()) { session_log("failed to open UDP socket: %s: %s" - , device.c_str(), ec.message().c_str()); + , lep.device.c_str(), ec.message().c_str()); } #endif last_op = operation_t::sock_open; if (m_alerts.should_post()) - m_alerts.emplace_alert(device + m_alerts.emplace_alert(lep.device , bind_ep, last_op, ec, udp_sock_type); return ret; } #if TORRENT_HAS_BINDTODEVICE - if (!device.empty()) + if (!lep.device.empty()) { - ret->udp_sock->sock.set_option(bind_to_device(device.c_str()), ec); + ret->udp_sock->sock.set_option(bind_to_device(lep.device.c_str()), ec); if (ec) { #ifndef TORRENT_DISABLE_LOGGING if (should_log()) { session_log("bind to device failed (device: %s): %s" - , device.c_str(), ec.message().c_str()); + , lep.device.c_str(), ec.message().c_str()); } #endif // TORRENT_DISABLE_LOGGING last_op = operation_t::sock_bind_to_device; if (m_alerts.should_post()) { - m_alerts.emplace_alert(device, bind_ep + m_alerts.emplace_alert(lep.device, bind_ep , last_op, ec, udp_sock_type); } return ret; @@ -1670,18 +1675,26 @@ namespace { if (should_log()) { session_log("failed to bind UDP socket: %s: %s" - , device.c_str(), ec.message().c_str()); + , lep.device.c_str(), ec.message().c_str()); } #endif if (m_alerts.should_post()) - m_alerts.emplace_alert(device + m_alerts.emplace_alert(lep.device , bind_ep, last_op, ec, udp_sock_type); return ret; } ret->udp_external_port = ret->udp_sock->sock.local_port(); + // if we did not open a TCP listen socket, ret->local_endpoint was never + // initialized, so do that now, based on the UDP socket + if (ret->incoming != duplex::accept_incoming) + { + auto const udp_ep = ret->udp_sock->local_endpoint(); + ret->local_endpoint = tcp::endpoint(udp_ep.address(), udp_ep.port()); + } + error_code err; set_socket_buffer_size(ret->udp_sock->sock, m_settings, err); if (err) @@ -1747,7 +1760,7 @@ namespace { } void session_impl::interface_to_endpoints(std::string const& device, int const port - , bool const ssl, std::vector& eps) + , transport const ssl, duplex const incoming, std::vector& eps) { // First, check to see if it's an IP address error_code err; @@ -1757,8 +1770,7 @@ namespace { #if !TORRENT_USE_IPV6 if (adr.is_v4()) #endif - eps.emplace_back(adr, port, std::string() - , ssl ? transport::ssl : transport::plaintext); + eps.emplace_back(adr, port, std::string(), ssl, incoming); } else { @@ -1791,8 +1803,7 @@ namespace { // (which must be of the same family as the address we're // connecting to) if (device != ipface.name) continue; - eps.emplace_back(ipface.interface_address, port, device - , ssl ? transport::ssl : transport::plaintext); + eps.emplace_back(ipface.interface_address, port, device, ssl, incoming); } } } @@ -1817,14 +1828,18 @@ namespace { // of a new socket failing to bind due to a conflict with a stale socket std::vector eps; + duplex const incoming = m_settings.get_bool(settings_pack::force_proxy) + ? duplex::only_outgoing + : duplex::accept_incoming; + for (auto const& iface : m_listen_interfaces) { std::string const& device = iface.device; int const port = iface.port; - bool const ssl = iface.ssl; + transport const ssl = iface.ssl ? transport::ssl : transport::plaintext; #ifndef TORRENT_USE_OPENSSL - if (ssl) + if (ssl == transport::ssl) { #ifndef TORRENT_DISABLE_LOGGING session_log("attempted to listen ssl with no library support on device: \"%s\"" @@ -1845,7 +1860,7 @@ namespace { // IP address or a device name. In case it's a device name, we want to // (potentially) end up binding a socket for each IP address associated // with that device. - interface_to_endpoints(device, port, ssl, eps); + interface_to_endpoints(device, port, ssl, incoming, eps); } #if TORRENT_USE_IPV6 @@ -1856,6 +1871,18 @@ namespace { } #endif + // if no listen interfaces are specified, create sockets to use + // any interface + if (eps.empty()) + { + eps.emplace_back(address_v4(), 0, "", transport::plaintext + , duplex::only_outgoing); +#if TORRENT_USE_IPV6 + eps.emplace_back(address_v6(), 0, "", transport::plaintext + , duplex::only_outgoing); +#endif + } + auto remove_iter = partition_listen_sockets(eps, m_listen_sockets); while (remove_iter != m_listen_sockets.end()) @@ -1882,8 +1909,7 @@ namespace { // an existing socket for (auto const& ep : eps) { - std::shared_ptr s = setup_listener(ep.device - , tcp::endpoint(ep.addr, std::uint16_t(ep.port)), ep.ssl, ec); + std::shared_ptr s = setup_listener(ep, ec); if (!ec && (s->sock || s->udp_sock)) { @@ -1954,6 +1980,7 @@ namespace { // initiate accepting on the listen sockets for (auto& s : m_listen_sockets) { + TORRENT_ASSERT((s->incoming == duplex::accept_incoming) == bool(s->sock)); if (s->sock) async_accept(s->sock, s->ssl); if (map_ports) remap_ports(remap_natpmp_and_upnp, *s); } @@ -1972,9 +1999,9 @@ namespace { for (auto const& iface : m_outgoing_interfaces) { - interface_to_endpoints(iface, 0, false, eps); + interface_to_endpoints(iface, 0, transport::plaintext, duplex::accept_incoming, eps); #ifdef TORRENT_USE_OPENSSL - interface_to_endpoints(iface, 0, true, eps); + interface_to_endpoints(iface, 0, transport::ssl, duplex::accept_incoming, eps); #endif } @@ -2411,7 +2438,7 @@ namespace { span const buf = packet.data; - // give the uTP socket manager first dis on the packet. Presumably + // give the uTP socket manager first dibs on the packet. Presumably // the majority of packets are uTP packets. if (!mgr.incoming_packet(socket, packet.from, buf)) { @@ -2746,7 +2773,7 @@ namespace { } // if there are outgoing interfaces specified, verify this - // peer is correctly bound to on of them + // peer is correctly bound to one of them if (!m_settings.get_str(settings_pack::outgoing_interfaces).empty()) { tcp::endpoint local = s->local_endpoint(ec); @@ -2761,6 +2788,22 @@ namespace { #endif return; } + + if (!verify_incoming_interface(local.address())) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + error_code err; + session_log(" rejected connection, local interface has incoming connections disabled: %s" + , local.address().to_string(err).c_str()); + } +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp, peer_blocked_alert::invalid_local_interface); + return; + } if (!verify_bound_address(local.address() , is_utp(*s), ec)) { @@ -5022,6 +5065,19 @@ namespace { return bind_ep; } + // verify that the interface ``addr`` belongs to, allows incoming connections + bool session_impl::verify_incoming_interface(address const& addr) + { + for (auto const& s : m_listen_sockets) + { + if (s->local_endpoint.address() == addr) + { + return s->incoming == duplex::accept_incoming; + } + } + return false; + } + // verify that the given local address satisfies the requirements of // the outgoing interfaces. i.e. that one of the allowed outgoing // interfaces has this address. For uTP sockets, which are all backed @@ -5042,7 +5098,7 @@ namespace { for (auto const& s : m_outgoing_interfaces) { error_code err; - address ip = address::from_string(s.c_str(), err); + address const ip = address::from_string(s.c_str(), err); if (err) continue; if (ip == addr) return true; } diff --git a/src/torrent.cpp b/src/torrent.cpp index 18d0addfb..0402d9baa 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -2771,7 +2771,7 @@ namespace libtorrent { { // update the endpoint list by adding entries for new listen sockets // and removing entries for non-existent ones - std::vector::size_type valid_endpoints = 0; + std::size_t valid_endpoints = 0; m_ses.for_each_listen_socket([&](aux::listen_socket_handle const& s) { if (s.is_ssl() != is_ssl_torrent()) return; @@ -10930,6 +10930,7 @@ namespace { , retry_interval); aep->last_error = ec; aep->message = msg; + fails = aep->fails; #ifndef TORRENT_DISABLE_LOGGING debug_log("*** increment tracker fail count [%d]", aep->fails); #endif