rely less on the ability to enumerate the routing table reliably. Any IP address

explicitly specified is assumed to represent an externally available IP, unless
marked with the l-flag. If a device name or an unspecified address is used, they
are expanded and only for such expanded entries is there a heuristic to
determine which addresses are externally available and which are local. The
default is to assume it's local only, unless it has a globally routable IP
address or a default route can be found for the device.
This commit is contained in:
arvidn 2020-02-18 22:50:19 +01:00 committed by Arvid Norberg
parent 47012b506a
commit 1e4083b3fb
16 changed files with 350 additions and 193 deletions

View File

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

View File

@ -556,3 +556,4 @@ netmask
fe80
vcpkg
leecher
6881l

View File

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

View File

@ -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<listen_endpoint_t>& eps
, std::vector<std::shared_ptr<aux::listen_socket_t>>& sockets);
TORRENT_EXTRA_EXPORT void interface_to_endpoints(
listen_interface_t const& iface
, listen_socket_flags_t flags
, span<ip_interface const> const ifs
, span<ip_route const> const routes
, std::vector<listen_endpoint_t>& eps);
// expand [::] to all IPv6 interfaces for BEP 45 compliance
TORRENT_EXTRA_EXPORT void expand_unspecified_address(
span<ip_interface const> ifs
, span<ip_route const> routes
, std::vector<listen_endpoint_t>& eps);
TORRENT_EXTRA_EXPORT void expand_devices(span<ip_interface const>
, span<ip_route const> routes
, std::vector<listen_endpoint_t>& eps);
// this is the link between the main thread and the
@ -851,9 +863,6 @@ namespace aux {
void set_external_address(std::shared_ptr<listen_socket_t> 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<listen_endpoint_t>& eps);
counters m_stats_counters;
// this is a pool allocator for torrent_peer objects

View File

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

View File

@ -87,6 +87,9 @@ namespace libtorrent {
TORRENT_EXTRA_EXPORT std::vector<ip_route> 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<address> get_gateway(
ip_interface const& iface, span<ip_route const> routes);
TORRENT_EXTRA_EXPORT bool has_default_route(char const* device, int family
, span<ip_route const> 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

View File

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

View File

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

View File

@ -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())

View File

@ -249,8 +249,7 @@ namespace {
{
rtmsg* rt_msg = reinterpret_cast<rtmsg*>(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
} // <anonymous>
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<ip_route const> 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<ip_route> enum_routes(io_service& ios, error_code& ec)
{
std::vector<ip_route> ret;
TORRENT_UNUSED(ios);
TORRENT_UNUSED(ec);
ec.clear();
#ifdef TORRENT_BUILD_SIMULATOR

View File

@ -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<ip_interface const> const ifs
, span<ip_route const> const routes
, std::vector<listen_endpoint_t>& 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<ip_interface const> const ifs
, span<ip_route const> const routes
, std::vector<listen_endpoint_t>& 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<listen_endpoint_t>& 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<ip_interface const> const ifs
, span<ip_route const> const routes
, std::vector<listen_endpoint_t>& 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<ip_interface> 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<listen_failed_alert>())
{
m_alerts.emplace_alert<listen_failed_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<listen_failed_alert>())
{
m_alerts.emplace_alert<listen_failed_alert>(device
m_alerts.emplace_alert<listen_failed_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)

View File

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

View File

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

View File

@ -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<aux::listen_socket_t> 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<ip_route> const routes = {
rt("0.0.0.0", "eth0", "1.2.3.4"),
rt("::", "eth0", "1234:5678::1"),
};
std::vector<ip_interface> 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<aux::listen_endpoint_t>{
{
@ -295,6 +311,12 @@ TORRENT_TEST(expand_devices)
TORRENT_TEST(expand_unspecified)
{
// this causes us to only expand IPv6 addresses on eth0
std::vector<ip_route> const routes = {
rt("0.0.0.0", "eth0", "1.2.3.4"),
rt("::", "eth0", "1234:5678::1"),
};
std::vector<ip_interface> 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<aux::listen_endpoint_t> 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<aux::listen_endpoint_t> to_endpoint(listen_interface_t const& iface
, span<ip_interface const> const ifs
, span<ip_route const> const routes)
{
std::vector<aux::listen_endpoint_t> ret;
interface_to_endpoints(iface, aux::listen_socket_t::accept_incoming, ifs, routes, ret);
return ret;
}
using eps = std::vector<aux::listen_endpoint_t>;
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<ip_interface> 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<ip_route> 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<ip_interface> 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)}));
}

View File

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

View File

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