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 * undeprecate settings_pack::dht_upload_rate_limit
1.2.4 release 1.2.4 release

View File

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

View File

@ -733,12 +733,11 @@ setting.
Multi-homed hosts 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. to listen on, and accept incoming connections via.
Each entry in ``listen_interfaces`` is an IP address or a device name, followed Each item 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 by a listen port number. Each item (called ``listen_socket_t``) will have the
following objects associated with it: following objects associated with it:
* a listen socket accepting incoming TCP connections * 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 * a UPnP port mapper (if applicable), to map ports on any
* ``InternetGatewayDevice`` found on the specified local subnet. * ``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 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 are expected to be externally available (i.e. not "local network"). If there are
the internet. If there are multiple subnets connected to the internet, they will multiple subnets connected to the internet, they will each have a separate
each have a separate gateway, and separate port mappings. 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 This section describes the logic for determining whether an address has a
considered having a gateway if: 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 A network is considered having a default route if there is a default route with
* the gateway configured for the route is inside the local network. a matching egress network device name and address family.
* 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.
routing 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: hold:
* the destination address falls inside its subnet (i.e. interface address masked * 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). 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 * the ``listen_socket_t`` does not have the "local network" flag set, and the
family matches the destination address. address family matches the destination address.
The ability to route to an address is used when determining whether to announce 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 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 Trackers are announced to from all network interfaces listening for incoming
connections. However, interfaces that cannot be used to reach the tracker, such connections. However, interfaces that cannot be used to reach the tracker, such
as loopback, are not used as the source address for announces. A 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 ``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 entry addresses will be used as the source address for an announce. Each such item
will also have an announce_endpoint entry associated with it, in the tracker will also have an announce_endpoint item associated with it, in the tracker
list. list.
If a tracker can be reached on a loopback address, then the loopback interface 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 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 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 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. the IPv6 connection will not be used, since it cannot use the proxy.
predictive piece announce predictive piece announce

View File

@ -142,9 +142,10 @@ namespace aux {
// we accept incoming connections on this interface // we accept incoming connections on this interface
static constexpr listen_socket_flags_t accept_incoming = 0_bit; static constexpr listen_socket_flags_t accept_incoming = 0_bit;
// this interface has a gateway associated with it, and can // this interface was specified to be just the local network. If this flag
// route to the internet (of the same address family) // is not set, this interface is assumed to have a path to the internet
static constexpr listen_socket_flags_t has_gateway = 1_bit; // (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 // this interface was expanded from the user requesting to
// listen on an unspecified address (either IPv4 or IPv6) // listen on an unspecified address (either IPv4 or IPv6)
@ -260,7 +261,11 @@ namespace aux {
bool operator==(listen_endpoint_t const& o) const 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; address addr;
@ -283,13 +288,20 @@ namespace aux {
std::vector<listen_endpoint_t>& eps std::vector<listen_endpoint_t>& eps
, std::vector<std::shared_ptr<aux::listen_socket_t>>& sockets); , 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 // expand [::] to all IPv6 interfaces for BEP 45 compliance
TORRENT_EXTRA_EXPORT void expand_unspecified_address( TORRENT_EXTRA_EXPORT void expand_unspecified_address(
span<ip_interface const> ifs span<ip_interface const> ifs
, span<ip_route const> routes
, std::vector<listen_endpoint_t>& eps); , std::vector<listen_endpoint_t>& eps);
TORRENT_EXTRA_EXPORT void expand_devices(span<ip_interface const> TORRENT_EXTRA_EXPORT void expand_devices(span<ip_interface const>
, span<ip_route const> routes
, std::vector<listen_endpoint_t>& eps); , std::vector<listen_endpoint_t>& eps);
// this is the link between the main thread and the // 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 void set_external_address(std::shared_ptr<listen_socket_t> const& sock, address const& ip
, ip_source_t source_type, address const& source); , 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; counters m_stats_counters;
// this is a pool allocator for torrent_peer objects // this is a pool allocator for torrent_peer objects

View File

@ -48,7 +48,9 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent { namespace libtorrent {
// TODO: 2 factor these functions out // 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_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_loopback(address const& addr);
TORRENT_EXTRA_EXPORT bool is_any(address const& addr); TORRENT_EXTRA_EXPORT bool is_any(address const& addr);
TORRENT_EXTRA_EXPORT bool is_teredo(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 TORRENT_EXTRA_EXPORT std::vector<ip_route> enum_routes(io_service& ios
, error_code& ec); , 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) // return (a1 & mask) == (a2 & mask)
TORRENT_EXTRA_EXPORT bool match_addr_mask(address const& a1 TORRENT_EXTRA_EXPORT bool match_addr_mask(address const& a1
, address const& a2, address const& mask); , address const& a2, address const& mask);
@ -101,6 +104,9 @@ namespace libtorrent {
TORRENT_EXTRA_EXPORT boost::optional<address> get_gateway( TORRENT_EXTRA_EXPORT boost::optional<address> get_gateway(
ip_interface const& iface, span<ip_route const> routes); 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 // 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 // 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 // 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 // a port that has an "s" suffix will accept SSL connections. (note
// that SSL sockets are not enabled by default). // 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 // if binding fails, the listen_failed_alert is posted. If or once a
// socket binding succeeds, the listen_succeeded_alert is posted. There // socket binding succeeds, the listen_succeeded_alert is posted. There
// may be multiple failures before a success. // may be multiple failures before a success.
@ -236,7 +241,13 @@ namespace aux {
// ``[::]:0s`` - will accept SSL connections on a port chosen by the // ``[::]:0s`` - will accept SSL connections on a port chosen by the
// OS. And not accept non-SSL connections at all. // 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. // Windows OS network adapter device name can be specified with GUID.
// It can be obtained from "netsh lan show interfaces" command output. // It can be obtained from "netsh lan show interfaces" command output.

View File

@ -84,11 +84,13 @@ namespace libtorrent {
std::string device; std::string device;
int port; int port;
bool ssl; bool ssl;
bool local;
friend bool operator==(listen_interface_t const& lhs, listen_interface_t const& rhs) friend bool operator==(listen_interface_t const& lhs, listen_interface_t const& rhs)
{ {
return lhs.device == rhs.device return lhs.device == rhs.device
&& lhs.port == rhs.port && 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; 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) bool is_local(address const& a)
{ {
if (a.is_v6()) if (a.is_v6())

View File

@ -249,8 +249,7 @@ namespace {
{ {
rtmsg* rt_msg = reinterpret_cast<rtmsg*>(NLMSG_DATA(nl_hdr)); 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 if (!valid_addr_family(rt_msg->rtm_family))
&& rt_msg->rtm_table != RT_TABLE_LOCAL))
return false; return false;
// make sure the defaults have the right address family // 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> } // <anonymous>
int family(address const& a) { return a.is_v4() ? AF_INET : AF_INET6; }
address build_netmask(int prefix_bits, int const family) address build_netmask(int prefix_bits, int const family)
{ {
if (family == AF_INET) 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() return r.destination.is_unspecified()
&& r.destination.is_v4() == iface.interface_address.is_v4() && r.destination.is_v4() == iface.interface_address.is_v4()
&& !r.gateway.is_unspecified() && !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 // in case there are multiple networks on the same networking
// device, the source hint may be the only thing telling them // device, the source hint may be the only thing telling them
// apart // apart
&& (r.source_hint.is_unspecified() || r.source_hint == iface.interface_address) && (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; if (it != routes.end()) return it->gateway;
return {}; 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> enum_routes(io_service& ios, error_code& ec)
{ {
std::vector<ip_route> ret; std::vector<ip_route> ret;
TORRENT_UNUSED(ios); TORRENT_UNUSED(ios);
TORRENT_UNUSED(ec); ec.clear();
#ifdef TORRENT_BUILD_SIMULATOR #ifdef TORRENT_BUILD_SIMULATOR

View File

@ -199,7 +199,7 @@ namespace libtorrent {
namespace aux { namespace aux {
constexpr listen_socket_flags_t listen_socket_t::accept_incoming; 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 listen_socket_flags_t listen_socket_t::was_expanded;
constexpr ip_source_t session_interface::source_dht; 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 // 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. // interfaces is enumerated and sockets are created for each of them.
void expand_unspecified_address(span<ip_interface const> const ifs void expand_unspecified_address(span<ip_interface const> const ifs
, span<ip_route const> const routes
, std::vector<listen_endpoint_t>& eps) , std::vector<listen_endpoint_t>& eps)
{ {
auto unspecified_begin = std::partition(eps.begin(), eps.end() auto unspecified_begin = std::partition(eps.begin(), eps.end()
@ -273,14 +274,23 @@ namespace aux {
continue; 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 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 void expand_devices(span<ip_interface const> const ifs
, span<ip_route const> const routes
, std::vector<listen_endpoint_t>& eps) , std::vector<listen_endpoint_t>& eps)
{ {
for (auto& ep : eps) for (auto& ep : eps)
@ -305,13 +315,6 @@ namespace aux {
} }
ep.netmask = iface->netmask; 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; ep.device = iface->name;
} }
} }
@ -324,12 +327,10 @@ namespace aux {
&& local_endpoint.address().to_v6().scope_id() != addr.to_v6().scope_id()) && local_endpoint.address().to_v6().scope_id() != addr.to_v6().scope_id())
return false; return false;
if (flags & has_gateway) return true;
if (local_endpoint.address() == addr) return true; if (local_endpoint.address() == addr) return true;
if (local_endpoint.address().is_unspecified()) return true; if (local_endpoint.address().is_unspecified()) return true;
if (match_addr_mask(addr, local_endpoint.address(), netmask)) return true; if (match_addr_mask(addr, local_endpoint.address(), netmask)) return true;
return !(flags & local_network);
return false;
} }
void session_impl::init_peer_class_filter(bool unlimited_local) 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" 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() , print_endpoint(bind_ep).c_str(), lep.device.c_str()
, (lep.ssl == transport::ssl) ? "ssl " : "" , (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::accept_incoming) ? "accept-incoming " : "no-incoming "
, (lep.flags & listen_socket_t::was_expanded) ? "expanded-ip " : ""); , (lep.flags & listen_socket_t::was_expanded) ? "expanded-ip " : "");
} }
@ -1740,48 +1741,50 @@ namespace aux {
reopen_network_sockets({}); reopen_network_sockets({});
} }
void session_impl::interface_to_endpoints(std::string const& device, int const port // TODO: could this function be merged with expand_unspecified_addresses?
, transport const ssl, listen_socket_flags_t const flags, std::vector<listen_endpoint_t>& eps) // 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 // First, check to see if it's an IP address
error_code err; error_code err;
address const adr = make_address(device.c_str(), err); address const adr = make_address(iface.device.c_str(), err);
if (!err) if (!err)
{ {
eps.emplace_back(adr, port, std::string(), ssl, flags); eps.emplace_back(adr, iface.port, std::string{}, ssl, flags);
} }
else else
{ {
flags |= listen_socket_t::was_expanded;
// this is the case where device names a network device. We need to // this is the case where device names a network device. We need to
// enumerate all IPs associated with this device // 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) for (auto const& ipface : ifs)
{ {
// we're looking for a specific interface, and its address // we're looking for a specific interface, and its address
// (which must be of the same family as the address we're // (which must be of the same family as the address we're
// connecting to) // connecting to)
if (device != ipface.name) continue; if (iface.device != ipface.name) continue;
eps.emplace_back(ipface.interface_address, port, device, ssl, flags);
// 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 // expand device names and populate eps
for (auto const& iface : m_listen_interfaces) 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 #ifndef TORRENT_USE_OPENSSL
if (ssl == transport::ssl) if (iface.ssl)
{ {
#ifndef TORRENT_DISABLE_LOGGING #ifndef TORRENT_DISABLE_LOGGING
session_log("attempted to listen ssl with no library support on device: \"%s\"" session_log("attempted to listen ssl with no library support on device: \"%s\""
, device.c_str()); , iface.device.c_str());
#endif #endif
if (m_alerts.should_post<listen_failed_alert>()) 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 , operation_t::sock_open
, boost::asio::error::operation_not_supported , boost::asio::error::operation_not_supported
, socket_type_t::tcp_ssl); , 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 // 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 // (potentially) end up binding a socket for each IP address associated
// with that device. // 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 // if no listen interfaces are specified, create sockets to use
@ -1865,8 +1864,8 @@ namespace aux {
, listen_socket_flags_t{}); , listen_socket_flags_t{});
} }
expand_unspecified_address(ifs, eps); expand_unspecified_address(ifs, routes, eps);
expand_devices(ifs, routes, eps); expand_devices(ifs, eps);
auto remove_iter = partition_listen_sockets(eps, m_listen_sockets); auto remove_iter = partition_listen_sockets(eps, m_listen_sockets);
@ -1915,15 +1914,9 @@ namespace aux {
m_listen_sockets.emplace_back(s); m_listen_sockets.emplace_back(s);
#ifndef TORRENT_DISABLE_DHT #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 if (m_dht
&& s->ssl != transport::ssl && s->ssl != transport::ssl
&& ((s->flags & listen_socket_t::has_gateway) && !(s->flags & listen_socket_t::local_network))
|| !(s->flags & listen_socket_t::was_expanded)))
{ {
m_dht->new_socket(m_listen_sockets.back()); 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 (is_v4(ls->local_endpoint) != remote_address.is_v4()) continue;
if (ls->ssl != ssl) 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); with_gateways.push_back(ls);
if (match_addr_mask(ls->local_endpoint.address(), remote_address, ls->netmask)) 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())) if (is_v6(s.local_endpoint) && is_local(s.local_endpoint.address()))
return; 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 // the natpmp constructor may fail and call the callbacks
// into the session_impl. // into the session_impl.
@ -5783,8 +5776,7 @@ namespace aux {
for (auto& s : m_listen_sockets) for (auto& s : m_listen_sockets)
{ {
if (s->ssl != transport::ssl if (s->ssl != transport::ssl
&& ((s->flags & listen_socket_t::has_gateway) && !(s->flags & listen_socket_t::local_network))
|| !(s->flags & listen_socket_t::was_expanded)))
{ {
m_dht->new_socket(s); m_dht->new_socket(s);
} }
@ -6740,9 +6732,10 @@ namespace aux {
if (is_v6(s.local_endpoint)) if (is_v6(s.local_endpoint))
return; return;
// there's no point in starting the UPnP mapper for a network that doesn't // there's no point in starting the UPnP mapper for a network that isn't
// have a gateway. The whole point is to forward ports through the gateway // connected to the internet. The whole point is to forward ports through
if (!(s.flags & listen_socket_t::has_gateway)) // the gateway
if (s.flags & listen_socket_t::local_network)
return; return;
if (!s.upnp_mapper) if (!s.upnp_mapper)

View File

@ -174,6 +174,7 @@ namespace libtorrent {
ret += ':'; ret += ':';
ret += to_string(i.port).data(); ret += to_string(i.port).data();
if (i.ssl) ret += 's'; if (i.ssl) ret += 's';
if (i.local) ret += 'l';
} }
return ret; return ret;
@ -211,6 +212,7 @@ namespace libtorrent {
listen_interface_t iface; listen_interface_t iface;
iface.ssl = false; iface.ssl = false;
iface.local = false;
string_view port; string_view port;
if (element.front() == '[') if (element.front() == '[')
@ -268,6 +270,7 @@ namespace libtorrent {
switch (c) switch (c)
{ {
case 's': iface.ssl = true; break; 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("192.168.0.130", "eth1"), routes) == none);
TEST_CHECK(get_gateway(ip("2a02::4567", "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 // 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")); 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); 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) TORRENT_TEST(get_gateway_multi_homed)
{ {
std::vector<ip_route> const routes = { 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", "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("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 "test.hpp"
#include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/aux_/session_impl.hpp"
#include "libtorrent/string_util.hpp"
using namespace lt; using namespace lt;
@ -76,6 +77,13 @@ namespace
return ret; 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 aux::listen_endpoint_t ep(char const* ip, int port
, tp ssl = tp::plaintext , tp ssl = tp::plaintext
, std::string device = {}) , std::string device = {})
@ -92,6 +100,21 @@ namespace
, aux::listen_socket_t::accept_incoming); , 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 std::shared_ptr<aux::listen_socket_t> sock(char const* ip, int const port
, int const original_port, char const* device = "") , int const original_port, char const* device = "")
{ {
@ -242,13 +265,6 @@ TORRENT_TEST(partition_listen_sockets_op_ports)
TORRENT_TEST(expand_devices) 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 = { std::vector<ip_interface> const ifs = {
ifc("127.0.0.1", "lo", "255.0.0.0") ifc("127.0.0.1", "lo", "255.0.0.0")
, ifc("192.168.1.2", "eth0", "255.255.255.0") , ifc("192.168.1.2", "eth0", "255.255.255.0")
@ -273,7 +289,7 @@ TORRENT_TEST(expand_devices)
aux::listen_socket_flags_t{} } aux::listen_socket_flags_t{} }
}; };
expand_devices(ifs, routes, eps); expand_devices(ifs, eps);
TEST_CHECK((eps == std::vector<aux::listen_endpoint_t>{ TEST_CHECK((eps == std::vector<aux::listen_endpoint_t>{
{ {
@ -295,6 +311,12 @@ TORRENT_TEST(expand_devices)
TORRENT_TEST(expand_unspecified) 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 = { std::vector<ip_interface> const ifs = {
ifc("127.0.0.1", "lo") ifc("127.0.0.1", "lo")
, ifc("192.168.1.2", "eth0") , ifc("192.168.1.2", "eth0")
@ -304,28 +326,34 @@ TORRENT_TEST(expand_unspecified)
, ifc("2601:646:c600:a3:d250:99ff:fe0c:9b74", "eth0") , 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_nossl = ep("0.0.0.0", 6881);
auto v4_ssl = ep("0.0.0.0", 6882, tp::ssl); auto v4_ssl = ep("0.0.0.0", 6882, tp::ssl);
auto v4_loopb_nossl= ep("127.0.0.1", 6881); auto v4_loopb_nossl= ep("127.0.0.1", 6881, local);
auto v4_loopb_ssl = ep("127.0.0.1", 6882, tp::ssl); auto v4_loopb_ssl = ep("127.0.0.1", 6882, tp::ssl, local);
auto v4_g1_nossl = ep("192.168.1.2", 6881); auto v4_g1_nossl = ep("192.168.1.2", 6881, global);
auto v4_g1_ssl = ep("192.168.1.2", 6882, tp::ssl); auto v4_g1_ssl = ep("192.168.1.2", 6882, tp::ssl, global);
auto v4_g2_nossl = ep("24.172.48.90", 6881); auto v4_g2_nossl = ep("24.172.48.90", 6881, global);
auto v4_g2_ssl = ep("24.172.48.90", 6882, tp::ssl); auto v4_g2_ssl = ep("24.172.48.90", 6882, tp::ssl, global);
auto v6_unsp_nossl = ep("::", 6883); auto v6_unsp_nossl = ep("::", 6883, global);
auto v6_unsp_ssl = ep("::", 6884, tp::ssl); auto v6_unsp_ssl = ep("::", 6884, tp::ssl, global);
auto v6_ll_nossl = ep("fe80::d250:99ff:fe0c:9b74", 6883); 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); 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); 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); 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); auto v6_loopb_ssl = ep("::1", 6884, tp::ssl, local);
auto v6_loopb_nossl= ep("::1", 6883); auto v6_loopb_nossl= ep("::1", 6883, local);
std::vector<aux::listen_endpoint_t> eps = { std::vector<aux::listen_endpoint_t> eps = {
v4_nossl, v4_ssl, v6_unsp_nossl, v6_unsp_ssl 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_EQUAL(eps.size(), 12);
TEST_CHECK(std::count(eps.begin(), eps.end(), v4_g1_nossl) == 1); 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_unsp_nossl);
eps.push_back(v6_g_nossl_dev); 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_EQUAL(eps.size(), 3);
TEST_CHECK(std::count(eps.begin(), eps.end(), v6_ll_nossl) == 1); 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); 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(); settings_pack p = settings();
p.set_int(settings_pack::alert_mask, alert::all_categories); 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_upnp, true);
p.set_bool(settings_pack::enable_natpmp, true); p.set_bool(settings_pack::enable_natpmp, true);

View File

@ -377,26 +377,30 @@ TORRENT_TEST(parse_list)
TORRENT_TEST(parse_interface) 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" 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} , {{"a", 4, false, false}, {"b", 35, false, false}
, {"e", 42, false}, {"foobar", 1337, true}, {"2001::1", 6881, 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"); , "a:4,b:35,c:1000s,d:351,e:42,foobar:1337s,[2001::1]:6881");
// IPv6 address // IPv6 address
test_parse_interface("[2001:ffff::1]:6882s" test_parse_interface("[2001:ffff::1]:6882s"
, {{"2001:ffff::1", 6882, true}} , {{"2001:ffff::1", 6882, true, false}}
, {} , {}
, "[2001:ffff::1]:6882s"); , "[2001:ffff::1]:6882s");
// IPv4 address // IPv4 address
test_parse_interface("127.0.0.1:6882" 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"); , "127.0.0.1:6882");
// maximum padding // maximum padding
test_parse_interface(" nic\r\n:\t 12\r s " test_parse_interface(" nic\r\n:\t 12\r s "
, {{"nic", 12, true}} , {{"nic", 12, true, false}}
, {} , {}
, "nic:12s"); , "nic:12s");
@ -409,7 +413,19 @@ TORRENT_TEST(parse_interface)
test_parse_interface("nic s", {}, {"nic s"}, ""); test_parse_interface("nic s", {}, {"nic s"}, "");
// parse interface with port 0 // 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 // IPv6 error
test_parse_interface("[aaaa::1", {}, {"[aaaa::1"}, ""); 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]:", {}, {"[aaaa::1]:"}, "");
test_parse_interface("[aaaa::1]:s", {}, {"[aaaa::1]:s"}, ""); 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"}, "");
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 [ // 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 // multiple errors
test_parse_interface("foo:,bar", {}, {"foo:", "bar"}, ""); test_parse_interface("foo:,bar", {}, {"foo:", "bar"}, "");
@ -432,7 +448,7 @@ TORRENT_TEST(parse_interface)
test_parse_interface("\"", {}, {"\""}, ""); test_parse_interface("\"", {}, {"\""}, "");
// multiple errors and one correct // 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) TORRENT_TEST(split_string)