From a53d3a8746dbcf29fba1350f5bbf2b50df08e987 Mon Sep 17 00:00:00 2001 From: arvidn Date: Sat, 18 Jan 2020 11:03:58 +0100 Subject: [PATCH] factor out get_gateway function and add unit tests. IPv6 gateways are not addressed in the same network, so we can't use match_addr_mask(). Assume all local IPv6 addresses do not have a gateway --- include/libtorrent/enum_net.hpp | 6 +++ src/enum_net.cpp | 24 +++++++++ src/session_impl.cpp | 13 +---- test/enum_if.cpp | 8 +-- test/test_enum_net.cpp | 94 +++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 16 deletions(-) diff --git a/include/libtorrent/enum_net.hpp b/include/libtorrent/enum_net.hpp index 23758836c..033c6dc47 100644 --- a/include/libtorrent/enum_net.hpp +++ b/include/libtorrent/enum_net.hpp @@ -50,6 +50,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/error_code.hpp" #include "libtorrent/socket.hpp" #include "libtorrent/aux_/bind_to_device.hpp" +#include "libtorrent/span.hpp" #include @@ -101,6 +102,11 @@ namespace libtorrent { TORRENT_EXTRA_EXPORT bool in_local_network(std::vector const& net , address const& addr); + // return the gateway for the given ip_interface, if there is one. Otherwise + // return nullopt. + TORRENT_EXTRA_EXPORT boost::optional
get_gateway( + ip_interface const& iface, span routes); + TORRENT_EXTRA_EXPORT boost::optional get_default_route(io_service& ios , string_view device, bool v6, error_code& ec); diff --git a/src/enum_net.cpp b/src/enum_net.cpp index 3e483a478..c5d74034e 100644 --- a/src/enum_net.cpp +++ b/src/enum_net.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/broadcast_socket.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/span.hpp" #ifdef TORRENT_WINDOWS #include "libtorrent/aux_/win_util.hpp" #endif @@ -793,6 +794,29 @@ int _System __libsocket_sysctl(int* mib, u_int namelen, void *oldp, size_t *oldl return ret; } + boost::optional
get_gateway(ip_interface const& iface, span routes) + { + bool const v4 = iface.interface_address.is_v4(); + + // local IPv6 addresses can never be used to reach the internet + if (!v4 && is_local(iface.interface_address)) return {}; + + auto const it = std::find_if(routes.begin(), routes.end() + , [&](ip_route const& r) -> bool + { + 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, iface.netmask)) + && strcmp(r.name, iface.name) == 0; + }); + if (it != routes.end()) return it->gateway; + return {}; + } + boost::optional get_default_route(io_service& ios , string_view const device, bool const v6, error_code& ec) { diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 9d4f34729..8ae59af11 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -305,21 +305,10 @@ namespace aux { ep.netmask = iface->netmask; - bool const v4 = iface->interface_address.is_v4(); - // 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 - bool const has_gateway = std::find_if(routes.begin(), routes.end(), [&](ip_route const& r) - { - return r.destination.is_unspecified() - && r.destination.is_v4() == v4 - && !r.gateway.is_unspecified() - && match_addr_mask(r.gateway, iface->interface_address, iface->netmask) - && strcmp(r.name, iface->name) == 0; - }) != routes.end(); - - if (has_gateway) + if(get_gateway(*iface, routes)) ep.flags |= listen_socket_t::has_gateway; ep.device = iface->name; diff --git a/test/enum_if.cpp b/test/enum_if.cpp index 2e35c83c4..4164ad866 100644 --- a/test/enum_if.cpp +++ b/test/enum_if.cpp @@ -66,7 +66,7 @@ int main() std::printf("%-18s%-18s%-35s%-7d%s\n" , r.destination.to_string(ec).c_str() , r.netmask.to_string(ec).c_str() - , r.gateway.to_string(ec).c_str() + , r.gateway.is_unspecified() ? "-" : r.gateway.to_string(ec).c_str() , r.mtu , r.name); } @@ -80,11 +80,11 @@ int main() return 1; } - std::printf("%-34s%-45s%-20s%-20s%-34sdescription\n", "address", "netmask", "name", "flags", "default gateway"); + std::printf("%-34s%-45s%-20s%-20s%-34sdescription\n", "address", "netmask", "name", "flags", "gateway"); for (auto const& i : net) { - address const iface_def_gw = get_default_gateway(ios, i.name, i.interface_address.is_v6(), ec); + boost::optional
const gateway = get_gateway(i, routes); std::printf("%-34s%-45s%-20s%s%s%-20s%-34s%s %s\n" , i.interface_address.to_string(ec).c_str() , i.netmask.to_string(ec).c_str() @@ -92,7 +92,7 @@ int main() , (i.interface_address.is_multicast()?"multicast ":"") , (is_local(i.interface_address)?"local ":"") , (is_loopback(i.interface_address)?"loopback ":"") - , iface_def_gw.to_string(ec).c_str() + , gateway ? gateway->to_string(ec).c_str() : "-" , i.friendly_name, i.description); } } diff --git a/test/test_enum_net.cpp b/test/test_enum_net.cpp index 7ffe6f348..3e17587ea 100644 --- a/test/test_enum_net.cpp +++ b/test/test_enum_net.cpp @@ -35,8 +35,10 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/broadcast_socket.hpp" #include "libtorrent/address.hpp" #include "libtorrent/error_code.hpp" +#include using namespace lt; +using boost::none; TORRENT_TEST(is_local) { @@ -176,3 +178,95 @@ TORRENT_TEST(build_netmask_unknown) { TEST_CHECK(build_netmask(0, -1) == address{}); } + +namespace { + ip_route rt(char const* ip, char const* device, char const* gateway) + { + ip_route ret; + ret.destination = address::from_string(ip); + ret.gateway = address::from_string(gateway); + std::strncpy(ret.name, device, sizeof(ret.name)); + ret.name[sizeof(ret.name) - 1] = '\0'; + return ret; + } + + ip_interface ip(char const* addr, char const* mask, char const* name) + { + ip_interface ret; + ret.interface_address = address::from_string(addr); + ret.netmask = address::from_string(mask); + std::strncpy(ret.name, name, sizeof(ret.name)); + return ret; + } +} + +TORRENT_TEST(get_gateway_basic) +{ + std::vector const routes = { + rt("0.0.0.0", "eth0", "192.168.0.1"), + rt("::", "eth0", "2a02::1234") + }; + + TEST_CHECK(get_gateway(ip("192.168.0.130", "255.255.0.0", "eth0"), routes) == address::from_string("192.168.0.1")); + TEST_CHECK(get_gateway(ip("2a02::4567", "ffff::", "eth0"), routes) == address::from_string("2a02::1234")); + + // the device name does not match the route + TEST_CHECK(get_gateway(ip("192.168.0.130", "255.255.0.0", "eth1"), routes) == none); + TEST_CHECK(get_gateway(ip("2a02::4567", "ffff::", "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", "255.255.255.0", "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", "ffff:ffff::", "eth0"), routes) == address::from_string("2a02::1234")); +} + +TORRENT_TEST(get_gateway_no_default_route) +{ + std::vector const routes = { + rt("192.168.0.0", "eth0", "0.0.0.0"), + rt("2a02::", "eth0", "::") + }; + + // no default route + TEST_CHECK(get_gateway(ip("192.168.1.130", "255.255.0.0", "eth0"), routes) == none); + TEST_CHECK(get_gateway(ip("2a02::1234", "ffff::", "eth0"), routes) == none); +} + +TORRENT_TEST(get_gateway_local_v6) +{ + std::vector const routes = { + rt("2a02::", "eth0", "::") + }; + + // local IPv6 addresses never have a gateway + TEST_CHECK(get_gateway(ip("fe80::1234", "ffff::", "eth0"), routes) == none); +} + +// an odd, imaginary setup, where the loopback network has a gateway +TORRENT_TEST(get_gateway_loopback) +{ + std::vector const routes = { + rt("0.0.0.0", "eth0", "192.168.0.1"), + rt("0.0.0.0", "lo", "127.1.1.1"), + rt("::", "eth0", "fec0::1234"), + rt("::", "lo", "::2") + }; + + TEST_CHECK(get_gateway(ip("127.0.0.1", "255.0.0.0", "lo"), routes) == address::from_string("127.1.1.1")); + + // with IPv6, there are no gateways for local or loopback addresses + TEST_CHECK(get_gateway(ip("::1", "ffff:ffff:ffff:ffff::", "lo"), routes) == none); +} + +TORRENT_TEST(get_gateway_multi_homed) +{ + std::vector const routes = { + rt("0.0.0.0", "eth0", "192.168.0.1"), + rt("0.0.0.0", "eth0", "10.0.0.1") + }; + + TEST_CHECK(get_gateway(ip("192.168.0.130", "255.255.0.0", "eth0"), routes) == address::from_string("192.168.0.1")); + TEST_CHECK(get_gateway(ip("10.0.1.130", "255.0.0.0", "eth0"), routes) == address::from_string("10.0.0.1")); +}