From babb93fb1e82bb58a32eda2d280dd518f85bfb1f Mon Sep 17 00:00:00 2001 From: Steven Siloti Date: Sat, 19 Nov 2016 19:48:24 -0800 Subject: [PATCH] keep old listen sockets if they're still valid This is to support multi-home. We need to be able to keep track of which socket a DHT node or UTP connection should use. We also need to generate notifications when local endpoints come and go so that the DHT tracker knows when to create or delete nodes. The easiest way to do this is to keep the same socket for as long as its local endpoint is valid. This way the nodes and connections can simply reference the socket itself and generating notifications is trivial. --- include/libtorrent/aux_/session_impl.hpp | 4 + src/session_impl.cpp | 119 ++++++++++++++++------- 2 files changed, 89 insertions(+), 34 deletions(-) diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index cdca05ff8..d42a0b5a7 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -138,6 +138,10 @@ namespace libtorrent // this is a cached local endpoint for the listen TCP socket tcp::endpoint local_endpoint; + // the name of the device the socket is bound to, may be empty + // if the socket is not bound to a device + std::string device; + // this is typically set to the same as the local // listen port. In case a NAT port forward was // successfully opened, this will be set to the diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 4ba50474f..9f5d0a59c 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -1554,6 +1554,7 @@ namespace aux { return ret; } ret.local_endpoint = ret.sock->local_endpoint(ec); + ret.device = device; last_op = listen_failed_alert::get_socket_name; if (ec) { @@ -1701,6 +1702,20 @@ namespace aux { reopen_listen_sockets(); } + namespace + { + struct listen_endpoint_t + { + listen_endpoint_t(address adr, int p, std::string dev, bool s) + : addr(adr), port(p), device(dev), ssl(s) {} + + address addr; + int port; + std::string device; + bool ssl; + }; + } + void session_impl::reopen_listen_sockets() { #ifndef TORRENT_DISABLE_LOGGING @@ -1712,28 +1727,17 @@ namespace aux { TORRENT_ASSERT(!m_abort); int flags = m_settings.get_bool(settings_pack::listen_system_port_fallback) ? 0 : listen_no_system_port; + + m_stats_counters.set_value(counters::has_incoming_connections, 0); error_code ec; - // close the open listen sockets - // close the listen sockets -#ifndef TORRENT_DISABLE_LOGGING - if (m_listen_sockets.empty()) - session_log("no currently open sockets to close"); - else - session_log("closing all listen sockets (%d)", int(m_listen_sockets.size())); -#endif - for (auto const& s : m_listen_sockets) - { - if (s.sock) s.sock->close(ec); - if (s.udp_sock) s.udp_sock->close(); - } - - m_listen_sockets.clear(); - m_stats_counters.set_value(counters::has_incoming_connections, 0); - ec.clear(); - if (m_abort) return; + // first build a list of endpoints we should be listening on + // we need to remove any unneeded sockets first to avoid the posibility + // of a new socket failing to bind due to a conflict with a stale socket + std::vector eps; + for (int i = 0; i < m_listen_interfaces.size(); ++i) { std::string const& device = m_listen_interfaces[i].device; @@ -1768,13 +1772,7 @@ namespace aux { address const adr = address::from_string(device.c_str(), err); if (!err) { - listen_socket_t const s = setup_listener("", tcp::endpoint(adr, port) - , flags | (ssl ? open_ssl_socket : 0), ec); - - if (!ec && s.sock) - { - m_listen_sockets.push_back(s); - } + eps.emplace_back(adr, port, std::string(), ssl); } else { @@ -1807,19 +1805,71 @@ namespace aux { // (which must be of the same family as the address we're // connecting to) if (device != ifs[k].name) continue; - - listen_socket_t const s = setup_listener(device - , tcp::endpoint(ifs[k].interface_address, port) - , flags | (ssl ? open_ssl_socket : 0), ec); - - if (!ec && s.sock) - { - m_listen_sockets.push_back(s); - } + eps.emplace_back(ifs[k].interface_address, port, device, ssl); } } } + int const port_retries = m_settings.get_int(settings_pack::max_retry_port_bind); + + // sockets we are keeping get moved to this list to prevent a socket from matching + // multiple endpoints + std::list keep; + + // remove any sockets which are no longer in the set of endpoints + // to listen on + // warning: O(n^2) operation! + // hopefully the system doesn't have too many interfaces + for (auto sock = m_listen_sockets.begin() + ; sock != m_listen_sockets.end();) + { + auto match = std::find_if(eps.begin(), eps.end() + , [sock, port_retries](listen_endpoint_t const& ep) + { return ep.ssl == sock->ssl + && (ep.port == 0 || (sock->local_endpoint.port() >= ep.port + && sock->local_endpoint.port() - ep.port < port_retries)) + && ep.device == sock->device + && ep.addr == sock->local_endpoint.address(); }); + + if (match != eps.end()) + { + // we don't need to create a new listen socket for this endpoint + // so remove it from the list + eps.erase(match); + keep.splice(keep.end(), m_listen_sockets, sock++); + continue; + } + + // this socket's local_endpoint is not on the list of endpoints to listen on + // it has got to go + // TODO notify interested parties of this socket's demise +#ifndef TORRENT_DISABLE_LOGGING + session_log("Closing listen socket for %s on device \"%s\"" + , print_endpoint(sock->local_endpoint).c_str(), sock->device.c_str()); +#endif + if (sock->sock) sock->sock->close(ec); + if (sock->udp_sock) sock->udp_sock->close(); + sock = m_listen_sockets.erase(sock); + } + + TORRENT_ASSERT(m_listen_sockets.empty()); + m_listen_sockets.swap(keep); + + // open new sockets on any endpoints that didn't match with + // an existing socket + for (auto const& ep : eps) + { + listen_socket_t const s = setup_listener(ep.device + , tcp::endpoint(ep.addr, ep.port) + , flags | (ep.ssl ? open_ssl_socket : 0), ec); + + if (!ec && s.sock) + { + // TODO notify interested parties of this socket's creation + m_listen_sockets.push_back(s); + } + } + if (m_listen_sockets.empty()) { #ifndef TORRENT_DISABLE_LOGGING @@ -1830,6 +1880,7 @@ namespace aux { // now, send out listen_succeeded_alert for the listen sockets we are // listening on + // TODO only post alerts for new sockets? if (m_alerts.should_post()) { for (auto const& l : m_listen_sockets)