diff --git a/ChangeLog b/ChangeLog index 18c6131e2..396e7b457 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * fix issue with knowing which interfaces to announce to trackers and DHT * undeprecate settings_pack::dht_upload_rate_limit 1.2.4 release diff --git a/docs/hunspell/libtorrent.dic b/docs/hunspell/libtorrent.dic index 847aaaf86..06403eb9d 100644 --- a/docs/hunspell/libtorrent.dic +++ b/docs/hunspell/libtorrent.dic @@ -556,3 +556,4 @@ netmask fe80 vcpkg leecher +6881l diff --git a/docs/manual.rst b/docs/manual.rst index 27fe55e76..9ba9acbb4 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -733,12 +733,11 @@ setting. Multi-homed hosts ================= -libtorrent has solid support for multi-homed hosts, starting with version 1.2.4. -the settings_pack::listen_interfaces setting is used to specify which interfaces/IP addresses +The settings_pack::listen_interfaces setting is used to specify which interfaces/IP addresses to listen on, and accept incoming connections via. -Each entry in ``listen_interfaces`` is an IP address or a device name, followed -by a listen port number. Each entry (called ``listen_socket_t``) will have the +Each item in ``listen_interfaces`` is an IP address or a device name, followed +by a listen port number. Each item (called ``listen_socket_t``) will have the following objects associated with it: * a listen socket accepting incoming TCP connections @@ -754,70 +753,58 @@ following objects associated with it: * a UPnP port mapper (if applicable), to map ports on any * ``InternetGatewayDevice`` found on the specified local subnet. +A ``listen_socket_t`` item may be specified to only be a local network (with +the ``l`` suffix). Such listen socket will only be used to talk to peers and +trackers within the same local network. The netmask defining the network is +queried from the operating system by enumerating network interfaces. + +An item that's considered to be "local network" will not be used to announce to +trackers outside of that network. For example, ``10.0.0.2:6881l`` is marked as "local +network" and it will only be used as the source address announcing to a tracker +if the tracker is also within the same local network (e.g. ``10.0.0.0/8``). + +If an IP address is the *unspecified* address (i.e. ``0.0.0.0`` or ``::``), +libtorrent will enumerate all addresses it can find for the corresponding +address family. If a device name is specified instead of an IP, it will expand +to all IP addresses associated with that device. + +Listen IP addresses that are automatically expanded by libtorrent have some +special rules. They are all assumed to be restricted to be "local network" +unless the following conditions are met: + +* the IP address is not in a known link-local range +* the IP address is not in a known loopback range +* the item the IP address was expanded from was not marked local (``l``) +* the IP address is in a globally reachable IP address range OR the routing + table contains a default route with a gateway for the corresponding network + The NAT-PMP/PCP and UPnP port mapper objects are only created for networks that -have a gateway configured. This typically means the network that's connected to -the internet. If there are multiple subnets connected to the internet, they will -each have a separate gateway, and separate port mappings. +are expected to be externally available (i.e. not "local network"). If there are +multiple subnets connected to the internet, they will each have a separate +gateway, and separate port mappings. -Networks that are not connected to the internet, like loopback, won't have a -gateway configured and will hence not have any port-mappers associated with -them. They will still be able to accept incoming connections from within the -local network. -gateways --------- +default routes +-------------- -The logic for IPv4 and IPv6 are slightly different. An IPv4 network is -considered having a gateway if: +This section describes the logic for determining whether an address has a +default route associated with it or not. This is only used for listen addresses that +are *expanded* from either an unspecified listen address (``0.0.0.0`` or ``::``) +or from a device name (e.g. ``eth0``). -* there is a default route with a matching egress network device name -* the gateway configured for the route is inside the local network. -* if the route has a source-hint, it matches the network's address - -For example, if there are two ``listen_socket_t`` entries associated with the -following networks:: - - 1: 192.168.0.10/16 eth0 - 2: 10.0.0.10/8 eth0 - -And these two entries in the routing table:: - - 0.0.0.0 eth0 gateway: 192.168.0.1 - 0.0.0.0 eth0 gateway: 10.0.0.1 - -Network (1) will be associated with gateway 192.168.0.1 and network (2) will be -associated with gateway 10.0.0.1. - -An IPv6 network is considered having a gateway if: - -* there is a default route with a matching egress network device name -* the IPv6 network address is not a local IPv6 address -* if the route has a source-hint, it matches the network's address - -For example, if there are two ``listen_socket_t`` entries associated with the -following networks:: - - 1. 2a00:5678:::2/64 eth0 - 2. 2b00:1234:::3/64 eth1 - -And these two routes:: - - :: eth0 gateway: fe80::1%eth0 - :: eth1 gateway: fe80::2%eth1 - -Network (1) will be associated with ``fe80::1%eth0``, and network (2) will be -associated with ``fe80::2%eth1``, because of the interface name matching. +A network is considered having a default route if there is a default route with +a matching egress network device name and address family. routing ------- -A ``listen_socket_t`` entry can route to a destination address if any of these +A ``listen_socket_t`` item can route to a destination address if any of these hold: * the destination address falls inside its subnet (i.e. interface address masked by netmask is the same as the destination address masked by the netmask). -* the ``listen_socket_t`` has a gateway associated with it, and the address - family matches the destination address. +* the ``listen_socket_t`` does not have the "local network" flag set, and the + address family matches the destination address. The ability to route to an address is used when determining whether to announce to a tracker from a ``listen_socket_t`` and whether to open a SOCKS5 UDP tunnel @@ -829,9 +816,9 @@ tracker announces Trackers are announced to from all network interfaces listening for incoming connections. However, interfaces that cannot be used to reach the tracker, such as loopback, are not used as the source address for announces. A -``listen_socket_t`` entry that can route to at least one of the tracker IP -addresses will be used as the source address for an announce. Each such entry -will also have an announce_endpoint entry associated with it, in the tracker +``listen_socket_t`` item that can route to at least one of the tracker IP +addresses will be used as the source address for an announce. Each such item +will also have an announce_endpoint item associated with it, in the tracker list. If a tracker can be reached on a loopback address, then the loopback interface @@ -848,7 +835,7 @@ SOCKS5 UDP tunnels When using a SOCKS5 proxy, each interface that can route to one of the SOCKS5 proxy's addresses will be used to open a UDP tunnel, via that proxy. For example, if a client has both IPv4 and IPv6 connectivity, but the socks5 proxy -only resolves to IPv4, only the IPv4 entry will have a UDP tunnel. In that case, +only resolves to IPv4, only the IPv4 address will have a UDP tunnel. In that case, the IPv6 connection will not be used, since it cannot use the proxy. predictive piece announce diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 72ed1fa42..e075d3c28 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -142,9 +142,10 @@ namespace aux { // we accept incoming connections on this interface static constexpr listen_socket_flags_t accept_incoming = 0_bit; - // this interface has a gateway associated with it, and can - // route to the internet (of the same address family) - static constexpr listen_socket_flags_t has_gateway = 1_bit; + // this interface was specified to be just the local network. If this flag + // is not set, this interface is assumed to have a path to the internet + // (i.e. have a gateway configured) + static constexpr listen_socket_flags_t local_network = 1_bit; // this interface was expanded from the user requesting to // listen on an unspecified address (either IPv4 or IPv6) @@ -260,7 +261,11 @@ namespace aux { bool operator==(listen_endpoint_t const& o) const { - return addr == o.addr && port == o.port && device == o.device && ssl == o.ssl; + return addr == o.addr + && port == o.port + && device == o.device + && ssl == o.ssl + && flags == o.flags; } address addr; @@ -283,13 +288,20 @@ namespace aux { std::vector& eps , std::vector>& sockets); + TORRENT_EXTRA_EXPORT void interface_to_endpoints( + listen_interface_t const& iface + , listen_socket_flags_t flags + , span const ifs + , span const routes + , std::vector& eps); + // expand [::] to all IPv6 interfaces for BEP 45 compliance TORRENT_EXTRA_EXPORT void expand_unspecified_address( span ifs + , span routes , std::vector& eps); TORRENT_EXTRA_EXPORT void expand_devices(span - , span routes , std::vector& eps); // this is the link between the main thread and the @@ -851,9 +863,6 @@ namespace aux { void set_external_address(std::shared_ptr const& sock, address const& ip , ip_source_t source_type, address const& source); - void interface_to_endpoints(std::string const& device, int port - , transport ssl, listen_socket_flags_t flags, std::vector& eps); - counters m_stats_counters; // this is a pool allocator for torrent_peer objects diff --git a/include/libtorrent/broadcast_socket.hpp b/include/libtorrent/broadcast_socket.hpp index c6280f8e2..e577d9873 100644 --- a/include/libtorrent/broadcast_socket.hpp +++ b/include/libtorrent/broadcast_socket.hpp @@ -48,7 +48,9 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { // TODO: 2 factor these functions out + TORRENT_EXTRA_EXPORT bool is_global(address const& a); TORRENT_EXTRA_EXPORT bool is_local(address const& a); + TORRENT_EXTRA_EXPORT bool is_link_local(address const& addr); TORRENT_EXTRA_EXPORT bool is_loopback(address const& addr); TORRENT_EXTRA_EXPORT bool is_any(address const& addr); TORRENT_EXTRA_EXPORT bool is_teredo(address const& addr); diff --git a/include/libtorrent/enum_net.hpp b/include/libtorrent/enum_net.hpp index b0a41f324..152063550 100644 --- a/include/libtorrent/enum_net.hpp +++ b/include/libtorrent/enum_net.hpp @@ -87,6 +87,9 @@ namespace libtorrent { TORRENT_EXTRA_EXPORT std::vector enum_routes(io_service& ios , error_code& ec); + // returns AF_INET or AF_INET6, depending on the address' family + TORRENT_EXTRA_EXPORT int family(address const& a); + // return (a1 & mask) == (a2 & mask) TORRENT_EXTRA_EXPORT bool match_addr_mask(address const& a1 , address const& a2, address const& mask); @@ -101,6 +104,9 @@ namespace libtorrent { TORRENT_EXTRA_EXPORT boost::optional
get_gateway( ip_interface const& iface, span routes); + TORRENT_EXTRA_EXPORT bool has_default_route(char const* device, int family + , span routes); + // attempt to bind socket to the device with the specified name. For systems // that don't support SO_BINDTODEVICE the socket will be bound to one of the // IP addresses of the specified device. In this case it is necessary to diff --git a/include/libtorrent/settings_pack.hpp b/include/libtorrent/settings_pack.hpp index 6ee5e1e1f..e36d87cba 100644 --- a/include/libtorrent/settings_pack.hpp +++ b/include/libtorrent/settings_pack.hpp @@ -222,6 +222,11 @@ namespace aux { // a port that has an "s" suffix will accept SSL connections. (note // that SSL sockets are not enabled by default). // + // a port that has an "l" suffix will be considered a local network. + // i.e. it's assumed to only be able to reach hosts in the same local + // network as the IP address (based on the netmask associated with the + // IP, queried from the operating system). + // // if binding fails, the listen_failed_alert is posted. If or once a // socket binding succeeds, the listen_succeeded_alert is posted. There // may be multiple failures before a success. @@ -236,7 +241,13 @@ namespace aux { // ``[::]:0s`` - will accept SSL connections on a port chosen by the // OS. And not accept non-SSL connections at all. // - // ``0.0.0.0:6881,[::]:6881`` - binds to all interfaces on port 6881 + // ``0.0.0.0:6881,[::]:6881`` - binds to all interfaces on port 6881. + // + // ``10.0.1.13:6881l`` - binds to the local IP address, port 6881, but + // only allow talking to peers on the same local network. The netmask + // is queried from the operating system. Interfaces marked ``l`` are + // not announced to trackers, unless the tracker is also on the same + // local network. // // Windows OS network adapter device name can be specified with GUID. // It can be obtained from "netsh lan show interfaces" command output. diff --git a/include/libtorrent/string_util.hpp b/include/libtorrent/string_util.hpp index 9b5333295..3116a1e04 100644 --- a/include/libtorrent/string_util.hpp +++ b/include/libtorrent/string_util.hpp @@ -84,11 +84,13 @@ namespace libtorrent { std::string device; int port; bool ssl; + bool local; friend bool operator==(listen_interface_t const& lhs, listen_interface_t const& rhs) { return lhs.device == rhs.device && lhs.port == rhs.port - && lhs.ssl == rhs.ssl; + && lhs.ssl == rhs.ssl + && lhs.local == rhs.local; } }; diff --git a/src/broadcast_socket.cpp b/src/broadcast_socket.cpp index b0088ddd9..42495f559 100644 --- a/src/broadcast_socket.cpp +++ b/src/broadcast_socket.cpp @@ -53,6 +53,34 @@ namespace libtorrent { return !ec; } + bool is_global(address const& a) + { + if (a.is_v6()) + { + // https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml + address_v6 const a6 = a.to_v6(); + return (a6.to_bytes()[0] & 0xe0) == 0x20; + } + else + { + address_v4 const a4 = a.to_v4(); + return !(a4.is_multicast() || a4.is_unspecified() || is_local(a)); + } + } + + bool is_link_local(address const& a) + { + if (a.is_v6()) + { + address_v6 const a6 = a.to_v6(); + return a6.is_link_local() + || a6.is_multicast_link_local(); + } + address_v4 const a4 = a.to_v4(); + unsigned long ip = a4.to_ulong(); + return (ip & 0xffff0000) == 0xa9fe0000; // 169.254.x.x + } + bool is_local(address const& a) { if (a.is_v6()) diff --git a/src/enum_net.cpp b/src/enum_net.cpp index 8fac39faa..4e325da68 100644 --- a/src/enum_net.cpp +++ b/src/enum_net.cpp @@ -249,8 +249,7 @@ namespace { { rtmsg* rt_msg = reinterpret_cast(NLMSG_DATA(nl_hdr)); - if (!valid_addr_family(rt_msg->rtm_family) || (rt_msg->rtm_table != RT_TABLE_MAIN - && rt_msg->rtm_table != RT_TABLE_LOCAL)) + if (!valid_addr_family(rt_msg->rtm_family)) return false; // make sure the defaults have the right address family @@ -451,6 +450,8 @@ int _System __libsocket_sysctl(int* mib, u_int namelen, void *oldp, size_t *oldl } // + int family(address const& a) { return a.is_v4() ? AF_INET : AF_INET6; } + address build_netmask(int prefix_bits, int const family) { if (family == AF_INET) @@ -798,25 +799,32 @@ int _System __libsocket_sysctl(int* mib, u_int namelen, void *oldp, size_t *oldl return r.destination.is_unspecified() && r.destination.is_v4() == iface.interface_address.is_v4() && !r.gateway.is_unspecified() - // IPv6 gateways aren't addressed in the same network as the - // interface, but they are addressed by the local network address - // space. So this check only works for IPv4. - && (!v4 || match_addr_mask(r.gateway, iface.interface_address, r.netmask)) // in case there are multiple networks on the same networking // device, the source hint may be the only thing telling them // apart && (r.source_hint.is_unspecified() || r.source_hint == iface.interface_address) - && strcmp(r.name, iface.name) == 0; + && std::strcmp(r.name, iface.name) == 0; }); if (it != routes.end()) return it->gateway; return {}; } + bool has_default_route(char const* device, int const fam, span routes) + { + return std::find_if(routes.begin(), routes.end() + , [&](ip_route const& r) -> bool + { + return r.destination.is_unspecified() + && family(r.destination) == fam + && std::strcmp(r.name, device) == 0; + }) != routes.end(); + } + std::vector enum_routes(io_service& ios, error_code& ec) { std::vector ret; TORRENT_UNUSED(ios); - TORRENT_UNUSED(ec); + ec.clear(); #ifdef TORRENT_BUILD_SIMULATOR diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 4340e7435..1f42815e8 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -199,7 +199,7 @@ namespace libtorrent { namespace aux { constexpr listen_socket_flags_t listen_socket_t::accept_incoming; - constexpr listen_socket_flags_t listen_socket_t::has_gateway; + constexpr listen_socket_flags_t listen_socket_t::local_network; constexpr listen_socket_flags_t listen_socket_t::was_expanded; constexpr ip_source_t session_interface::source_dht; @@ -243,6 +243,7 @@ namespace aux { // by prohibiting creating a listen socket on [::] and 0.0.0.0. Instead the list of // interfaces is enumerated and sockets are created for each of them. void expand_unspecified_address(span const ifs + , span const routes , std::vector& eps) { auto unspecified_begin = std::partition(eps.begin(), eps.end() @@ -273,14 +274,23 @@ namespace aux { continue; } + // record whether the device has a gateway associated with it + // (which indicates it can be used to reach the internet) + // if the IP address tell us it's loopback or link-local, don't + // bother looking for the gateway + bool const local = ipface.interface_address.is_loopback() + || is_link_local(ipface.interface_address) + || (!is_global(ipface.interface_address) + && !has_default_route(ipface.name, family(ipface.interface_address), routes)); + eps.emplace_back(ipface.interface_address, uep.port, uep.device - , uep.ssl, uep.flags | listen_socket_t::was_expanded); + , uep.ssl, uep.flags | listen_socket_t::was_expanded + | (local ? listen_socket_t::local_network : listen_socket_flags_t{})); } } } void expand_devices(span const ifs - , span const routes , std::vector& eps) { for (auto& ep : eps) @@ -305,13 +315,6 @@ namespace aux { } ep.netmask = iface->netmask; - - // also record whether the device has a gateway associated with it - // (which indicates it can be used to reach the internet) - // only gateways inside the interface's network count - if(get_gateway(*iface, routes)) - ep.flags |= listen_socket_t::has_gateway; - ep.device = iface->name; } } @@ -324,12 +327,10 @@ namespace aux { && local_endpoint.address().to_v6().scope_id() != addr.to_v6().scope_id()) return false; - if (flags & has_gateway) return true; if (local_endpoint.address() == addr) return true; if (local_endpoint.address().is_unspecified()) return true; if (match_addr_mask(addr, local_endpoint.address(), netmask)) return true; - - return false; + return !(flags & local_network); } void session_impl::init_peer_class_filter(bool unlimited_local) @@ -1362,7 +1363,7 @@ namespace aux { session_log("attempting to open listen socket to: %s on device: %s %s%s%s%s" , print_endpoint(bind_ep).c_str(), lep.device.c_str() , (lep.ssl == transport::ssl) ? "ssl " : "" - , (lep.flags & listen_socket_t::has_gateway) ? "has-gateway " : "" + , (lep.flags & listen_socket_t::local_network) ? "local-network " : "" , (lep.flags & listen_socket_t::accept_incoming) ? "accept-incoming " : "no-incoming " , (lep.flags & listen_socket_t::was_expanded) ? "expanded-ip " : ""); } @@ -1740,48 +1741,50 @@ namespace aux { reopen_network_sockets({}); } - void session_impl::interface_to_endpoints(std::string const& device, int const port - , transport const ssl, listen_socket_flags_t const flags, std::vector& eps) + // TODO: could this function be merged with expand_unspecified_addresses? + // right now both listen_endpoint_t and listen_interface_t are almost + // identical, maybe the latter could be removed too + void interface_to_endpoints(listen_interface_t const& iface + , listen_socket_flags_t flags + , span const ifs + , span const routes + , std::vector& eps) { + flags |= iface.local ? listen_socket_t::local_network : listen_socket_flags_t{}; + transport const ssl = iface.ssl ? transport::ssl : transport::plaintext; + // First, check to see if it's an IP address error_code err; - address const adr = make_address(device.c_str(), err); + address const adr = make_address(iface.device.c_str(), err); if (!err) { - eps.emplace_back(adr, port, std::string(), ssl, flags); + eps.emplace_back(adr, iface.port, std::string{}, ssl, flags); } else { + flags |= listen_socket_t::was_expanded; + // this is the case where device names a network device. We need to // enumerate all IPs associated with this device - - // TODO: 3 only run this once in the caller - std::vector const ifs = enum_net_interfaces(m_io_service, err); - if (err) - { -#ifndef TORRENT_DISABLE_LOGGING - if (should_log()) - { - session_log("failed to enumerate IPs on device: \"%s\": %s" - , device.c_str(), err.message().c_str()); - } -#endif - if (m_alerts.should_post()) - { - m_alerts.emplace_alert(device - , operation_t::enum_if, err - , socket_type_t::tcp); - } - return; - } - for (auto const& ipface : ifs) { // we're looking for a specific interface, and its address // (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, flags); + if (iface.device != ipface.name) continue; + + // record whether the device has a gateway associated with it + // (which indicates it can be used to reach the internet) + // if the IP address tell us it's loopback or link-local, don't + // bother looking for the gateway + bool const local = iface.local + || ipface.interface_address.is_loopback() + || is_link_local(ipface.interface_address) + || (!is_global(ipface.interface_address) + && !has_default_route(ipface.name, family(ipface.interface_address), routes)); + + eps.emplace_back(ipface.interface_address, iface.port, iface.device + , ssl, flags | (local ? listen_socket_t::local_network : listen_socket_flags_t{})); } } } @@ -1826,20 +1829,16 @@ namespace aux { // expand device names and populate eps for (auto const& iface : m_listen_interfaces) { - std::string const& device = iface.device; - int const port = iface.port; - transport const ssl = iface.ssl ? transport::ssl : transport::plaintext; - #ifndef TORRENT_USE_OPENSSL - if (ssl == transport::ssl) + if (iface.ssl) { #ifndef TORRENT_DISABLE_LOGGING session_log("attempted to listen ssl with no library support on device: \"%s\"" - , device.c_str()); + , iface.device.c_str()); #endif if (m_alerts.should_post()) { - m_alerts.emplace_alert(device + m_alerts.emplace_alert(iface.device , operation_t::sock_open , boost::asio::error::operation_not_supported , socket_type_t::tcp_ssl); @@ -1852,7 +1851,7 @@ namespace aux { // 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, flags, eps); + interface_to_endpoints(iface, flags, ifs, routes, eps); } // if no listen interfaces are specified, create sockets to use @@ -1865,8 +1864,8 @@ namespace aux { , listen_socket_flags_t{}); } - expand_unspecified_address(ifs, eps); - expand_devices(ifs, routes, eps); + expand_unspecified_address(ifs, routes, eps); + expand_devices(ifs, eps); auto remove_iter = partition_listen_sockets(eps, m_listen_sockets); @@ -1915,15 +1914,9 @@ namespace aux { m_listen_sockets.emplace_back(s); #ifndef TORRENT_DISABLE_DHT - // addresses that we expanded from an - // unspecified address don't get a DHT running - // on them, unless they have a gateway (in - // which case we believe they can reach the - // internet) if (m_dht && s->ssl != transport::ssl - && ((s->flags & listen_socket_t::has_gateway) - || !(s->flags & listen_socket_t::was_expanded))) + && !(s->flags & listen_socket_t::local_network)) { m_dht->new_socket(m_listen_sockets.back()); } @@ -4955,7 +4948,7 @@ namespace aux { { if (is_v4(ls->local_endpoint) != remote_address.is_v4()) continue; if (ls->ssl != ssl) continue; - if (ls->flags & listen_socket_t::has_gateway) + if (!(ls->flags & listen_socket_t::local_network)) with_gateways.push_back(ls); if (match_addr_mask(ls->local_endpoint.address(), remote_address, ls->netmask)) @@ -5510,7 +5503,7 @@ namespace aux { if (is_v6(s.local_endpoint) && is_local(s.local_endpoint.address())) return; - if (!s.natpmp_mapper && (s.flags & listen_socket_t::has_gateway)) + if (!s.natpmp_mapper && !(s.flags & listen_socket_t::local_network)) { // the natpmp constructor may fail and call the callbacks // into the session_impl. @@ -5783,8 +5776,7 @@ namespace aux { for (auto& s : m_listen_sockets) { if (s->ssl != transport::ssl - && ((s->flags & listen_socket_t::has_gateway) - || !(s->flags & listen_socket_t::was_expanded))) + && !(s->flags & listen_socket_t::local_network)) { m_dht->new_socket(s); } @@ -6740,9 +6732,10 @@ namespace aux { if (is_v6(s.local_endpoint)) return; - // there's no point in starting the UPnP mapper for a network that doesn't - // have a gateway. The whole point is to forward ports through the gateway - if (!(s.flags & listen_socket_t::has_gateway)) + // there's no point in starting the UPnP mapper for a network that isn't + // connected to the internet. The whole point is to forward ports through + // the gateway + if (s.flags & listen_socket_t::local_network) return; if (!s.upnp_mapper) diff --git a/src/string_util.cpp b/src/string_util.cpp index 215459fbd..6d29e105c 100644 --- a/src/string_util.cpp +++ b/src/string_util.cpp @@ -174,6 +174,7 @@ namespace libtorrent { ret += ':'; ret += to_string(i.port).data(); if (i.ssl) ret += 's'; + if (i.local) ret += 'l'; } return ret; @@ -211,6 +212,7 @@ namespace libtorrent { listen_interface_t iface; iface.ssl = false; + iface.local = false; string_view port; if (element.front() == '[') @@ -268,6 +270,7 @@ namespace libtorrent { switch (c) { case 's': iface.ssl = true; break; + case 'l': iface.local = true; break; } } diff --git a/test/test_enum_net.cpp b/test/test_enum_net.cpp index 186f81bbb..e3f4781ce 100644 --- a/test/test_enum_net.cpp +++ b/test/test_enum_net.cpp @@ -215,10 +215,6 @@ TORRENT_TEST(get_gateway_basic) TEST_CHECK(get_gateway(ip("192.168.0.130", "eth1"), routes) == none); TEST_CHECK(get_gateway(ip("2a02::4567", "eth1"), routes) == none); - // the gateway for the route is outside of this local network, it cannot be - // used for this network - TEST_CHECK(get_gateway(ip("192.168.1.130", "eth0"), routes) == none); - // for IPv6, the address family and device name matches, so it's a match TEST_CHECK(get_gateway(ip("2a02:8000::0123:4567", "eth0"), routes) == address::from_string("2a02::1234")); } @@ -261,24 +257,34 @@ TORRENT_TEST(get_gateway_loopback) TEST_CHECK(get_gateway(ip("::1", "lo"), routes) == none); } -TORRENT_TEST(get_gateway_netmask) -{ - std::vector const routes = { - rt("0.0.0.0", "eth0", "192.168.1.1", "255.255.255.0"), - rt("0.0.0.0", "eth0", "192.168.2.1", "0.0.0.0") - }; - - TEST_CHECK(get_gateway(ip("192.168.0.130", "eth0"), routes) == address::from_string("192.168.2.1")); - TEST_CHECK(get_gateway(ip("192.168.1.130", "eth0"), routes) == address::from_string("192.168.1.1")); -} - TORRENT_TEST(get_gateway_multi_homed) { std::vector const routes = { rt("0.0.0.0", "eth0", "192.168.0.1", "255.255.0.0"), - rt("0.0.0.0", "eth0", "10.0.0.1", "255.0.0.0") + rt("0.0.0.0", "eth1", "10.0.0.1", "255.0.0.0") }; TEST_CHECK(get_gateway(ip("192.168.0.130", "eth0"), routes) == address::from_string("192.168.0.1")); - TEST_CHECK(get_gateway(ip("10.0.1.130", "eth0"), routes) == address::from_string("10.0.0.1")); + TEST_CHECK(get_gateway(ip("10.0.1.130", "eth1"), routes) == address::from_string("10.0.0.1")); +} + +TORRENT_TEST(has_default_route) +{ + std::vector const routes = { + rt("0.0.0.0", "eth0", "192.168.0.1", "255.255.0.0"), + rt("0.0.0.0", "eth1", "0.0.0.0", "255.0.0.0"), + rt("127.0.0.0", "lo", "0.0.0.0", "255.0.0.0") + }; + + TEST_CHECK(has_default_route("eth0", AF_INET, routes)); + TEST_CHECK(!has_default_route("eth0", AF_INET6, routes)); + + TEST_CHECK(has_default_route("eth1", AF_INET, routes)); + TEST_CHECK(!has_default_route("eth1", AF_INET6, routes)); + + TEST_CHECK(!has_default_route("lo", AF_INET, routes)); + TEST_CHECK(!has_default_route("lo", AF_INET6, routes)); + + TEST_CHECK(!has_default_route("eth2", AF_INET, routes)); + TEST_CHECK(!has_default_route("eth2", AF_INET6, routes)); } diff --git a/test/test_listen_socket.cpp b/test/test_listen_socket.cpp index 6e80c58f3..efc98693f 100644 --- a/test/test_listen_socket.cpp +++ b/test/test_listen_socket.cpp @@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "test.hpp" #include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/string_util.hpp" using namespace lt; @@ -76,6 +77,13 @@ namespace return ret; } + aux::listen_endpoint_t ep(char const* ip, int port + , tp ssl, aux::listen_socket_flags_t const flags) + { + return aux::listen_endpoint_t(address::from_string(ip), port, std::string{} + , ssl, flags); + } + aux::listen_endpoint_t ep(char const* ip, int port , tp ssl = tp::plaintext , std::string device = {}) @@ -92,6 +100,21 @@ namespace , aux::listen_socket_t::accept_incoming); } + aux::listen_endpoint_t ep(char const* ip, int port + , std::string device + , aux::listen_socket_flags_t const flags) + { + return aux::listen_endpoint_t(address::from_string(ip), port, device + , tp::plaintext, flags); + } + + aux::listen_endpoint_t ep(char const* ip, int port + , aux::listen_socket_flags_t const flags) + { + return aux::listen_endpoint_t(address::from_string(ip), port, std::string{} + , tp::plaintext, flags); + } + std::shared_ptr sock(char const* ip, int const port , int const original_port, char const* device = "") { @@ -242,13 +265,6 @@ TORRENT_TEST(partition_listen_sockets_op_ports) TORRENT_TEST(expand_devices) { - - // this causes us to only expand IPv6 addresses on eth0 - std::vector const routes = { - rt("0.0.0.0", "eth0", "1.2.3.4"), - rt("::", "eth0", "1234:5678::1"), - }; - std::vector const ifs = { ifc("127.0.0.1", "lo", "255.0.0.0") , ifc("192.168.1.2", "eth0", "255.255.255.0") @@ -273,7 +289,7 @@ TORRENT_TEST(expand_devices) aux::listen_socket_flags_t{} } }; - expand_devices(ifs, routes, eps); + expand_devices(ifs, eps); TEST_CHECK((eps == std::vector{ { @@ -295,6 +311,12 @@ TORRENT_TEST(expand_devices) TORRENT_TEST(expand_unspecified) { + // this causes us to only expand IPv6 addresses on eth0 + std::vector const routes = { + rt("0.0.0.0", "eth0", "1.2.3.4"), + rt("::", "eth0", "1234:5678::1"), + }; + std::vector const ifs = { ifc("127.0.0.1", "lo") , ifc("192.168.1.2", "eth0") @@ -304,28 +326,34 @@ TORRENT_TEST(expand_unspecified) , ifc("2601:646:c600:a3:d250:99ff:fe0c:9b74", "eth0") }; + aux::listen_socket_flags_t const global = aux::listen_socket_t::accept_incoming + | aux::listen_socket_t::was_expanded; + aux::listen_socket_flags_t const local = aux::listen_socket_t::accept_incoming + | aux::listen_socket_t::was_expanded + | aux::listen_socket_t::local_network; + auto v4_nossl = ep("0.0.0.0", 6881); auto v4_ssl = ep("0.0.0.0", 6882, tp::ssl); - auto v4_loopb_nossl= ep("127.0.0.1", 6881); - auto v4_loopb_ssl = ep("127.0.0.1", 6882, tp::ssl); - auto v4_g1_nossl = ep("192.168.1.2", 6881); - auto v4_g1_ssl = ep("192.168.1.2", 6882, tp::ssl); - auto v4_g2_nossl = ep("24.172.48.90", 6881); - auto v4_g2_ssl = ep("24.172.48.90", 6882, tp::ssl); - auto v6_unsp_nossl = ep("::", 6883); - auto v6_unsp_ssl = ep("::", 6884, tp::ssl); - auto v6_ll_nossl = ep("fe80::d250:99ff:fe0c:9b74", 6883); - auto v6_ll_ssl = ep("fe80::d250:99ff:fe0c:9b74", 6884, tp::ssl); - auto v6_g_nossl = ep("2601:646:c600:a3:d250:99ff:fe0c:9b74", 6883); - auto v6_g_ssl = ep("2601:646:c600:a3:d250:99ff:fe0c:9b74", 6884, tp::ssl); - auto v6_loopb_ssl = ep("::1", 6884, tp::ssl); - auto v6_loopb_nossl= ep("::1", 6883); + auto v4_loopb_nossl= ep("127.0.0.1", 6881, local); + auto v4_loopb_ssl = ep("127.0.0.1", 6882, tp::ssl, local); + auto v4_g1_nossl = ep("192.168.1.2", 6881, global); + auto v4_g1_ssl = ep("192.168.1.2", 6882, tp::ssl, global); + auto v4_g2_nossl = ep("24.172.48.90", 6881, global); + auto v4_g2_ssl = ep("24.172.48.90", 6882, tp::ssl, global); + auto v6_unsp_nossl = ep("::", 6883, global); + auto v6_unsp_ssl = ep("::", 6884, tp::ssl, global); + auto v6_ll_nossl = ep("fe80::d250:99ff:fe0c:9b74", 6883, local); + auto v6_ll_ssl = ep("fe80::d250:99ff:fe0c:9b74", 6884, tp::ssl, local); + auto v6_g_nossl = ep("2601:646:c600:a3:d250:99ff:fe0c:9b74", 6883, global); + auto v6_g_ssl = ep("2601:646:c600:a3:d250:99ff:fe0c:9b74", 6884, tp::ssl, global); + auto v6_loopb_ssl = ep("::1", 6884, tp::ssl, local); + auto v6_loopb_nossl= ep("::1", 6883, local); std::vector eps = { v4_nossl, v4_ssl, v6_unsp_nossl, v6_unsp_ssl }; - aux::expand_unspecified_address(ifs, eps); + aux::expand_unspecified_address(ifs, routes, eps); TEST_EQUAL(eps.size(), 12); TEST_CHECK(std::count(eps.begin(), eps.end(), v4_g1_nossl) == 1); @@ -352,7 +380,7 @@ TORRENT_TEST(expand_unspecified) eps.push_back(v6_unsp_nossl); eps.push_back(v6_g_nossl_dev); - aux::expand_unspecified_address(ifs, eps); + aux::expand_unspecified_address(ifs, routes, eps); TEST_EQUAL(eps.size(), 3); TEST_CHECK(std::count(eps.begin(), eps.end(), v6_ll_nossl) == 1); @@ -361,3 +389,59 @@ TORRENT_TEST(expand_unspecified) TEST_CHECK(std::count(eps.begin(), eps.end(), v6_g_nossl_dev) == 1); } +namespace { +std::vector to_endpoint(listen_interface_t const& iface + , span const ifs + , span const routes) +{ + std::vector ret; + interface_to_endpoints(iface, aux::listen_socket_t::accept_incoming, ifs, routes, ret); + return ret; +} + +using eps = std::vector; + +listen_interface_t ift(char const* dev, int const port, bool const ssl = false + , bool const local = false) +{ + return {std::string(dev), port, ssl, local}; +} +} +using ls = aux::listen_socket_t; + +TORRENT_TEST(interface_to_endpoint) +{ + TEST_CHECK(to_endpoint(ift("10.0.1.1", 6881), {}, {}) == eps{ep("10.0.1.1", 6881)}); + + + std::vector const ifs = { + // this is a global IPv4 address, not a private network + ifc("185.0.1.2", "eth0") + , ifc("192.168.2.2", "eth1") + , ifc("fe80::d250:99ff:fe0c:9b74", "eth0") + // this is a global IPv6 address, not a private network + , ifc("2601:646:c600:a3:d250:99ff:fe0c:9b74", "eth1") + }; + + TEST_CHECK((to_endpoint(ift("eth0", 1234), ifs, {}) + == eps{ep("185.0.1.2", 1234, "eth0", ls::was_expanded | ls::accept_incoming) + , ep("fe80::d250:99ff:fe0c:9b74", 1234, "eth0", ls::was_expanded | ls::accept_incoming | ls::local_network)})); + + TEST_CHECK((to_endpoint(ift("eth1", 1234), ifs, {}) + == eps{ep("192.168.2.2", 1234, "eth1", ls::was_expanded | ls::accept_incoming | ls::local_network) + , ep("2601:646:c600:a3:d250:99ff:fe0c:9b74", 1234, "eth1", ls::was_expanded | ls::accept_incoming)})); + + std::vector const routes = { + rt("0.0.0.0", "eth1", "3.4.5.6"), + rt("0.0.0.0", "eth0", "1.2.3.4"), + rt("::", "eth0", "1234:5678::1"), + }; + + std::vector const ifs2 = { + ifc("10.0.1.1", "eth0") + }; + + TEST_CHECK((to_endpoint(ift("eth0", 1234), ifs2, routes) + == eps{ep("10.0.1.1", 1234, "eth0", ls::was_expanded | ls::accept_incoming)})); +} + diff --git a/test/test_session.cpp b/test/test_session.cpp index 13f87c010..ed24eb17e 100644 --- a/test/test_session.cpp +++ b/test/test_session.cpp @@ -578,7 +578,7 @@ TORRENT_TEST(reopen_network_sockets) settings_pack p = settings(); p.set_int(settings_pack::alert_mask, alert::all_categories); - p.set_str(settings_pack::listen_interfaces, "127.0.0.1:6881"); + p.set_str(settings_pack::listen_interfaces, "127.0.0.1:6881l"); p.set_bool(settings_pack::enable_upnp, true); p.set_bool(settings_pack::enable_natpmp, true); diff --git a/test/test_string.cpp b/test/test_string.cpp index ab7a936b1..f0f446518 100644 --- a/test/test_string.cpp +++ b/test/test_string.cpp @@ -377,26 +377,30 @@ TORRENT_TEST(parse_list) TORRENT_TEST(parse_interface) { test_parse_interface(" a:4,b:35, c : 1000s, d: 351 ,e \t:42,foobar:1337s\n\r,[2001::1]:6881" - , {{"a", 4, false}, {"b", 35, false}, {"c", 1000, true}, {"d", 351, false} - , {"e", 42, false}, {"foobar", 1337, true}, {"2001::1", 6881, false}} + , {{"a", 4, false, false}, {"b", 35, false, false} + , {"c", 1000, true, false} + , {"d", 351, false, false} + , {"e", 42, false, false} + , {"foobar", 1337, true, false} + , {"2001::1", 6881, false, false}} , {} , "a:4,b:35,c:1000s,d:351,e:42,foobar:1337s,[2001::1]:6881"); // IPv6 address test_parse_interface("[2001:ffff::1]:6882s" - , {{"2001:ffff::1", 6882, true}} + , {{"2001:ffff::1", 6882, true, false}} , {} , "[2001:ffff::1]:6882s"); // IPv4 address test_parse_interface("127.0.0.1:6882" - , {{"127.0.0.1", 6882, false}} + , {{"127.0.0.1", 6882, false, false}} , {} , "127.0.0.1:6882"); // maximum padding test_parse_interface(" nic\r\n:\t 12\r s " - , {{"nic", 12, true}} + , {{"nic", 12, true, false}} , {} , "nic:12s"); @@ -409,7 +413,19 @@ TORRENT_TEST(parse_interface) test_parse_interface("nic s", {}, {"nic s"}, ""); // parse interface with port 0 - test_parse_interface("127.0.0.1:0", {{"127.0.0.1", 0, false}}, {}, "127.0.0.1:0"); + test_parse_interface("127.0.0.1:0", {{"127.0.0.1", 0, false, false}} + , {}, "127.0.0.1:0"); + + // SSL flag + test_parse_interface("127.0.0.1:1234s", {{"127.0.0.1", 1234, true, false}} + , {}, "127.0.0.1:1234s"); + // local flag + test_parse_interface("127.0.0.1:1234l", {{"127.0.0.1", 1234, false, true}} + , {}, "127.0.0.1:1234l"); + + // both + test_parse_interface("127.0.0.1:1234ls", {{"127.0.0.1", 1234, true, true}} + , {}, "127.0.0.1:1234sl"); // IPv6 error test_parse_interface("[aaaa::1", {}, {"[aaaa::1"}, ""); @@ -417,10 +433,10 @@ TORRENT_TEST(parse_interface) test_parse_interface("[aaaa::1]:", {}, {"[aaaa::1]:"}, ""); test_parse_interface("[aaaa::1]:s", {}, {"[aaaa::1]:s"}, ""); test_parse_interface("[aaaa::1] :6881", {}, {"[aaaa::1] :6881"}, ""); - test_parse_interface("[aaaa::1]:6881", {{"aaaa::1", 6881, false}}, {}, "[aaaa::1]:6881"); + test_parse_interface("[aaaa::1]:6881", {{"aaaa::1", 6881, false, false}}, {}, "[aaaa::1]:6881"); // unterminated [ - test_parse_interface("[aaaa::1,foobar:0", {{"foobar", 0, false}}, {"[aaaa::1"}, "foobar:0"); + test_parse_interface("[aaaa::1,foobar:0", {{"foobar", 0, false, false}}, {"[aaaa::1"}, "foobar:0"); // multiple errors test_parse_interface("foo:,bar", {}, {"foo:", "bar"}, ""); @@ -432,7 +448,7 @@ TORRENT_TEST(parse_interface) test_parse_interface("\"", {}, {"\""}, ""); // multiple errors and one correct - test_parse_interface("foo,bar,0.0.0.0:6881", {{"0.0.0.0", 6881, false}}, {"foo", "bar"}, "0.0.0.0:6881"); + test_parse_interface("foo,bar,0.0.0.0:6881", {{"0.0.0.0", 6881, false, false}}, {"foo", "bar"}, "0.0.0.0:6881"); } TORRENT_TEST(split_string)