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

This commit is contained in:
arvidn 2020-01-18 11:03:58 +01:00 committed by Arvid Norberg
parent 571952fd19
commit a53d3a8746
5 changed files with 129 additions and 16 deletions

View File

@ -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 <vector>
@ -101,6 +102,11 @@ namespace libtorrent {
TORRENT_EXTRA_EXPORT bool in_local_network(std::vector<ip_interface> 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<address> get_gateway(
ip_interface const& iface, span<ip_route const> routes);
TORRENT_EXTRA_EXPORT boost::optional<ip_route> get_default_route(io_service& ios
, string_view device, bool v6, error_code& ec);

View File

@ -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<address> get_gateway(ip_interface const& iface, span<ip_route const> 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<ip_route> get_default_route(io_service& ios
, string_view const device, bool const v6, error_code& ec)
{

View File

@ -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;

View File

@ -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<address> 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);
}
}

View File

@ -35,8 +35,10 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/broadcast_socket.hpp"
#include "libtorrent/address.hpp"
#include "libtorrent/error_code.hpp"
#include <cstring>
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<ip_route> 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<ip_route> 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<ip_route> 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<ip_route> 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<ip_route> 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"));
}