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:
parent
571952fd19
commit
a53d3a8746
|
@ -50,6 +50,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include "libtorrent/error_code.hpp"
|
#include "libtorrent/error_code.hpp"
|
||||||
#include "libtorrent/socket.hpp"
|
#include "libtorrent/socket.hpp"
|
||||||
#include "libtorrent/aux_/bind_to_device.hpp"
|
#include "libtorrent/aux_/bind_to_device.hpp"
|
||||||
|
#include "libtorrent/span.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -101,6 +102,11 @@ namespace libtorrent {
|
||||||
TORRENT_EXTRA_EXPORT bool in_local_network(std::vector<ip_interface> const& net
|
TORRENT_EXTRA_EXPORT bool in_local_network(std::vector<ip_interface> const& net
|
||||||
, address const& addr);
|
, 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
|
TORRENT_EXTRA_EXPORT boost::optional<ip_route> get_default_route(io_service& ios
|
||||||
, string_view device, bool v6, error_code& ec);
|
, string_view device, bool v6, error_code& ec);
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include "libtorrent/broadcast_socket.hpp"
|
#include "libtorrent/broadcast_socket.hpp"
|
||||||
#include "libtorrent/assert.hpp"
|
#include "libtorrent/assert.hpp"
|
||||||
#include "libtorrent/aux_/socket_type.hpp"
|
#include "libtorrent/aux_/socket_type.hpp"
|
||||||
|
#include "libtorrent/span.hpp"
|
||||||
#ifdef TORRENT_WINDOWS
|
#ifdef TORRENT_WINDOWS
|
||||||
#include "libtorrent/aux_/win_util.hpp"
|
#include "libtorrent/aux_/win_util.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
@ -793,6 +794,29 @@ int _System __libsocket_sysctl(int* mib, u_int namelen, void *oldp, size_t *oldl
|
||||||
return ret;
|
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
|
boost::optional<ip_route> get_default_route(io_service& ios
|
||||||
, string_view const device, bool const v6, error_code& ec)
|
, string_view const device, bool const v6, error_code& ec)
|
||||||
{
|
{
|
||||||
|
|
|
@ -305,21 +305,10 @@ namespace aux {
|
||||||
|
|
||||||
ep.netmask = iface->netmask;
|
ep.netmask = iface->netmask;
|
||||||
|
|
||||||
bool const v4 = iface->interface_address.is_v4();
|
|
||||||
|
|
||||||
// also record whether the device has a gateway associated with it
|
// also record whether the device has a gateway associated with it
|
||||||
// (which indicates it can be used to reach the internet)
|
// (which indicates it can be used to reach the internet)
|
||||||
// only gateways inside the interface's network count
|
// only gateways inside the interface's network count
|
||||||
bool const has_gateway = std::find_if(routes.begin(), routes.end(), [&](ip_route const& r)
|
if(get_gateway(*iface, routes))
|
||||||
{
|
|
||||||
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)
|
|
||||||
ep.flags |= listen_socket_t::has_gateway;
|
ep.flags |= listen_socket_t::has_gateway;
|
||||||
|
|
||||||
ep.device = iface->name;
|
ep.device = iface->name;
|
||||||
|
|
|
@ -66,7 +66,7 @@ int main()
|
||||||
std::printf("%-18s%-18s%-35s%-7d%s\n"
|
std::printf("%-18s%-18s%-35s%-7d%s\n"
|
||||||
, r.destination.to_string(ec).c_str()
|
, r.destination.to_string(ec).c_str()
|
||||||
, r.netmask.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.mtu
|
||||||
, r.name);
|
, r.name);
|
||||||
}
|
}
|
||||||
|
@ -80,11 +80,11 @@ int main()
|
||||||
return 1;
|
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)
|
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"
|
std::printf("%-34s%-45s%-20s%s%s%-20s%-34s%s %s\n"
|
||||||
, i.interface_address.to_string(ec).c_str()
|
, i.interface_address.to_string(ec).c_str()
|
||||||
, i.netmask.to_string(ec).c_str()
|
, i.netmask.to_string(ec).c_str()
|
||||||
|
@ -92,7 +92,7 @@ int main()
|
||||||
, (i.interface_address.is_multicast()?"multicast ":"")
|
, (i.interface_address.is_multicast()?"multicast ":"")
|
||||||
, (is_local(i.interface_address)?"local ":"")
|
, (is_local(i.interface_address)?"local ":"")
|
||||||
, (is_loopback(i.interface_address)?"loopback ":"")
|
, (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);
|
, i.friendly_name, i.description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,10 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include "libtorrent/broadcast_socket.hpp"
|
#include "libtorrent/broadcast_socket.hpp"
|
||||||
#include "libtorrent/address.hpp"
|
#include "libtorrent/address.hpp"
|
||||||
#include "libtorrent/error_code.hpp"
|
#include "libtorrent/error_code.hpp"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
using namespace lt;
|
using namespace lt;
|
||||||
|
using boost::none;
|
||||||
|
|
||||||
TORRENT_TEST(is_local)
|
TORRENT_TEST(is_local)
|
||||||
{
|
{
|
||||||
|
@ -176,3 +178,95 @@ TORRENT_TEST(build_netmask_unknown)
|
||||||
{
|
{
|
||||||
TEST_CHECK(build_netmask(0, -1) == address{});
|
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"));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue