add support for multi-home NAT-PMP and PCP
This commit is contained in:
commit
6647b0988d
|
@ -1,3 +1,4 @@
|
|||
* add support for Port Control Protocol (PCP)
|
||||
* deliver notification of alerts being dropped via alerts_dropped_alert
|
||||
* deprecated alert::progress_notification alert category, split into
|
||||
finer grained categories
|
||||
|
|
|
@ -201,6 +201,8 @@ namespace aux {
|
|||
// time on handler allocation every time we read again.
|
||||
aux::handler_storage<TORRENT_READ_HANDLER_MAX_SIZE> udp_handler_storage;
|
||||
|
||||
std::shared_ptr<natpmp> natpmp_mapper;
|
||||
|
||||
// the key is an id that is used to identify the
|
||||
// client with the tracker only.
|
||||
std::uint32_t tracker_key = 0;
|
||||
|
@ -636,7 +638,7 @@ namespace aux {
|
|||
|
||||
void start_ip_notifier();
|
||||
void start_lsd();
|
||||
natpmp* start_natpmp();
|
||||
void start_natpmp();
|
||||
upnp* start_upnp();
|
||||
|
||||
void stop_ip_notifier();
|
||||
|
@ -644,7 +646,7 @@ namespace aux {
|
|||
void stop_natpmp();
|
||||
void stop_upnp();
|
||||
|
||||
port_mapping_t add_port_mapping(portmap_protocol t, int external_port
|
||||
std::vector<port_mapping_t> add_port_mapping(portmap_protocol t, int external_port
|
||||
, int local_port);
|
||||
void delete_port_mapping(port_mapping_t handle);
|
||||
|
||||
|
@ -777,6 +779,8 @@ namespace aux {
|
|||
|
||||
void on_lsd_peer(tcp::endpoint const& peer, sha1_hash const& ih) override;
|
||||
|
||||
void start_natpmp(aux::listen_socket_t& s);
|
||||
|
||||
void set_external_address(std::shared_ptr<listen_socket_t> const& sock, address const& ip
|
||||
, ip_source_t source_type, address const& source);
|
||||
|
||||
|
@ -1136,7 +1140,6 @@ namespace aux {
|
|||
// this is deducted from the connect speed
|
||||
int m_boost_connections = 0;
|
||||
|
||||
std::shared_ptr<natpmp> m_natpmp;
|
||||
std::shared_ptr<upnp> m_upnp;
|
||||
std::shared_ptr<lsd> m_lsd;
|
||||
|
||||
|
|
|
@ -94,7 +94,9 @@ namespace libtorrent {
|
|||
TORRENT_EXTRA_EXPORT bool in_local_network(std::vector<ip_interface> const& net
|
||||
, address const& addr);
|
||||
|
||||
TORRENT_EXTRA_EXPORT address get_default_gateway(io_service& ios, error_code& ec);
|
||||
// returns the first default gateway found if device is empty
|
||||
TORRENT_EXTRA_EXPORT address get_default_gateway(io_service& ios
|
||||
, string_view device, bool v6, error_code& ec);
|
||||
|
||||
// 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
|
||||
|
|
|
@ -326,17 +326,19 @@ namespace libtorrent {
|
|||
// the listen socket associated with this request was closed
|
||||
invalid_listen_socket,
|
||||
|
||||
// these error codes are deprecated, NAT-PMP/PCP error codes have
|
||||
// been moved to their own category
|
||||
|
||||
// The NAT-PMP router responded with an unsupported protocol version
|
||||
unsupported_protocol_version = 120,
|
||||
unsupported_protocol_version TORRENT_DEPRECATED_ENUM = 120,
|
||||
// You are not authorized to map ports on this NAT-PMP router
|
||||
natpmp_not_authorized,
|
||||
natpmp_not_authorized TORRENT_DEPRECATED_ENUM,
|
||||
// The NAT-PMP router failed because of a network failure
|
||||
network_failure,
|
||||
network_failure TORRENT_DEPRECATED_ENUM,
|
||||
// The NAT-PMP router failed because of lack of resources
|
||||
no_resources,
|
||||
no_resources TORRENT_DEPRECATED_ENUM,
|
||||
// The NAT-PMP router failed because an unsupported opcode was sent
|
||||
unsupported_opcode,
|
||||
unsupported_opcode TORRENT_DEPRECATED_ENUM,
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,40 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "libtorrent/aux_/portmap.hpp"
|
||||
#include "libtorrent/aux_/vector.hpp"
|
||||
|
||||
namespace libtorrent {
|
||||
|
||||
namespace errors
|
||||
{
|
||||
// See RFC 6887 Section 7.4
|
||||
enum pcp_errors
|
||||
{
|
||||
pcp_success = 0,
|
||||
pcp_unsupp_version,
|
||||
pcp_not_authorized,
|
||||
pcp_malformed_request,
|
||||
pcp_unsupp_opcode,
|
||||
pcp_unsupp_option,
|
||||
pcp_malformed_option,
|
||||
pcp_network_failure,
|
||||
pcp_no_resources,
|
||||
pcp_unsupp_protocol,
|
||||
pcp_user_ex_quota,
|
||||
pcp_cannot_provide_external,
|
||||
pcp_address_mismatch,
|
||||
pcp_excessive_remote_peers,
|
||||
};
|
||||
|
||||
boost::system::error_code make_error_code(pcp_errors e);
|
||||
}
|
||||
|
||||
boost::system::error_category& pcp_category();
|
||||
}
|
||||
|
||||
namespace boost { namespace system {
|
||||
template<> struct is_error_code_enum<libtorrent::errors::pcp_errors>
|
||||
{ static const bool value = true; };
|
||||
} }
|
||||
|
||||
namespace libtorrent {
|
||||
|
||||
struct TORRENT_EXTRA_EXPORT natpmp
|
||||
|
@ -51,7 +85,7 @@ struct TORRENT_EXTRA_EXPORT natpmp
|
|||
{
|
||||
natpmp(io_service& ios, aux::portmap_callback& cb);
|
||||
|
||||
void start();
|
||||
void start(address local_address, std::string device);
|
||||
|
||||
// maps the ports, if a port is set to 0
|
||||
// it will not be mapped
|
||||
|
@ -63,6 +97,7 @@ struct TORRENT_EXTRA_EXPORT natpmp
|
|||
void close();
|
||||
|
||||
private:
|
||||
static error_code from_result_code(int version, int result);
|
||||
|
||||
std::shared_ptr<natpmp> self() { return shared_from_this(); }
|
||||
|
||||
|
@ -79,8 +114,30 @@ private:
|
|||
|
||||
void disable(error_code const& ec);
|
||||
|
||||
enum protocol_version
|
||||
{
|
||||
version_natpmp = 0,
|
||||
version_pcp = 2,
|
||||
};
|
||||
|
||||
static char const* version_to_string(protocol_version version);
|
||||
|
||||
// See RFC 6887 Section 19.2
|
||||
enum pcp_opcode
|
||||
{
|
||||
opcode_announce = 0,
|
||||
opcode_map,
|
||||
opcode_peer,
|
||||
};
|
||||
|
||||
struct mapping_t : aux::base_mapping
|
||||
{
|
||||
// random identifier, used by PCP
|
||||
std::array<char, 12> nonce;
|
||||
|
||||
// only valid if the router supports PCP
|
||||
address external_address;
|
||||
|
||||
// the local port for this mapping. If this is set
|
||||
// to 0, the mapping is not in use
|
||||
int local_port = 0;
|
||||
|
@ -100,6 +157,8 @@ private:
|
|||
|
||||
aux::portmap_callback& m_callback;
|
||||
|
||||
protocol_version m_version = version_pcp;
|
||||
|
||||
aux::vector<mapping_t, port_mapping_t> m_mappings;
|
||||
|
||||
// the endpoint to the nat router
|
||||
|
@ -114,9 +173,12 @@ private:
|
|||
int m_retry_count = 0;
|
||||
|
||||
// used to receive responses in
|
||||
char m_response_buffer[16];
|
||||
// 1100 octets is the maximum size of a PCP packet
|
||||
char m_response_buffer[1100];
|
||||
|
||||
// router external IP address
|
||||
// this is only used if the router does not support PCP
|
||||
// with PCP the external IP is stored with the mapping
|
||||
address m_external_ip;
|
||||
|
||||
// the endpoint we received the message from
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace libtorrent {
|
|||
|
||||
enum class portmap_transport : std::uint8_t
|
||||
{
|
||||
// natpmp can be NAT-PMP or PCP
|
||||
natpmp, upnp
|
||||
};
|
||||
|
||||
|
|
|
@ -1012,7 +1012,7 @@ namespace libtorrent {
|
|||
// whichever is enabled. The return value is a handle referring to the
|
||||
// port mapping that was just created. Pass it to delete_port_mapping()
|
||||
// to remove it.
|
||||
port_mapping_t add_port_mapping(portmap_protocol t, int external_port, int local_port);
|
||||
std::vector<port_mapping_t> add_port_mapping(portmap_protocol t, int external_port, int local_port);
|
||||
void delete_port_mapping(port_mapping_t handle);
|
||||
|
||||
// This option indicates if the ports are mapped using natpmp
|
||||
|
|
146
src/enum_net.cpp
146
src/enum_net.cpp
|
@ -173,6 +173,52 @@ namespace {
|
|||
);
|
||||
}
|
||||
|
||||
#if TORRENT_USE_GETIPFORWARDTABLE || TORRENT_USE_NETLINK
|
||||
address build_netmask(int bits, int family)
|
||||
{
|
||||
if (family == AF_INET)
|
||||
{
|
||||
using bytes_t = boost::asio::ip::address_v4::bytes_type;
|
||||
bytes_t b;
|
||||
std::memset(&b[0], 0xff, b.size());
|
||||
for (int i = int(b.size()) - 1; i > 0; --i)
|
||||
{
|
||||
if (bits < 8)
|
||||
{
|
||||
b[i] <<= bits;
|
||||
break;
|
||||
}
|
||||
b[i] = 0;
|
||||
bits -= 8;
|
||||
}
|
||||
return address_v4(b);
|
||||
}
|
||||
#if TORRENT_USE_IPV6
|
||||
else if (family == AF_INET6)
|
||||
{
|
||||
using bytes_t = boost::asio::ip::address_v6::bytes_type;
|
||||
bytes_t b;
|
||||
std::memset(&b[0], 0xff, b.size());
|
||||
for (int i = int(b.size()) - 1; i > 0; --i)
|
||||
{
|
||||
if (bits < 8)
|
||||
{
|
||||
b[i] <<= bits;
|
||||
break;
|
||||
}
|
||||
b[i] = 0;
|
||||
bits -= 8;
|
||||
}
|
||||
return address_v6(b);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
return address();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TORRENT_USE_NETLINK
|
||||
|
||||
int read_nl_sock(int sock, span<char> buf, std::uint32_t const seq, std::uint32_t const pid)
|
||||
|
@ -254,6 +300,16 @@ namespace {
|
|||
&& rt_msg->rtm_table != RT_TABLE_LOCAL))
|
||||
return false;
|
||||
|
||||
#if TORRENT_USE_IPV6
|
||||
// make sure the defaults have the right address family
|
||||
// in case the attributes are not present
|
||||
if (rt_msg->rtm_family == AF_INET6)
|
||||
{
|
||||
rt_info->gateway = address_v6();
|
||||
rt_info->destination = address_v6();
|
||||
}
|
||||
#endif
|
||||
|
||||
int if_index = 0;
|
||||
int rt_len = int(RTM_PAYLOAD(nl_hdr));
|
||||
#ifdef __clang__
|
||||
|
@ -298,16 +354,22 @@ namespace {
|
|||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#if TORRENT_USE_IPV6
|
||||
if (rt_info->gateway.is_v6() && rt_info->gateway.to_v6().is_link_local())
|
||||
{
|
||||
address_v6 gateway6 = rt_info->gateway.to_v6();
|
||||
gateway6.scope_id(if_index);
|
||||
rt_info->gateway = gateway6;
|
||||
}
|
||||
#endif
|
||||
|
||||
ifreq req = {};
|
||||
::if_indextoname(std::uint32_t(if_index), req.ifr_name);
|
||||
static_assert(sizeof(rt_info->name) >= sizeof(req.ifr_name), "ip_route::name is too small");
|
||||
std::memcpy(rt_info->name, req.ifr_name, sizeof(req.ifr_name));
|
||||
::ioctl(s, ::siocgifmtu, &req);
|
||||
rt_info->mtu = req.ifr_mtu;
|
||||
// obviously this doesn't work correctly. How do you get the netmask for a route?
|
||||
// if (ioctl(s, SIOCGIFNETMASK, &req) == 0) {
|
||||
// rt_info->netmask = sockaddr_to_address(&req.ifr_addr, req.ifr_addr.sa_family);
|
||||
// }
|
||||
rt_info->netmask = build_netmask(rt_msg->rtm_dst_len, rt_msg->rtm_family);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -509,52 +571,6 @@ int _System __libsocket_sysctl(int* mib, u_int namelen, void *oldp, size_t *oldl
|
|||
return false;
|
||||
}
|
||||
|
||||
#if TORRENT_USE_GETIPFORWARDTABLE
|
||||
address build_netmask(int bits, int family)
|
||||
{
|
||||
if (family == AF_INET)
|
||||
{
|
||||
using bytes_t = boost::asio::ip::address_v4::bytes_type;
|
||||
bytes_t b;
|
||||
std::memset(&b[0], 0xff, b.size());
|
||||
for (int i = int(sizeof(bytes_t)) / 8 - 1; i > 0; --i)
|
||||
{
|
||||
if (bits < 8)
|
||||
{
|
||||
b[i] <<= bits;
|
||||
break;
|
||||
}
|
||||
b[i] = 0;
|
||||
bits -= 8;
|
||||
}
|
||||
return address_v4(b);
|
||||
}
|
||||
#if TORRENT_USE_IPV6
|
||||
else if (family == AF_INET6)
|
||||
{
|
||||
using bytes_t = boost::asio::ip::address_v6::bytes_type;
|
||||
bytes_t b;
|
||||
std::memset(&b[0], 0xff, b.size());
|
||||
for (int i = int(sizeof(bytes_t)) / 8 - 1; i > 0; --i)
|
||||
{
|
||||
if (bits < 8)
|
||||
{
|
||||
b[i] <<= bits;
|
||||
break;
|
||||
}
|
||||
b[i] = 0;
|
||||
bits -= 8;
|
||||
}
|
||||
return address_v6(b);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
return address();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<ip_interface> enum_net_interfaces(io_service& ios, error_code& ec)
|
||||
{
|
||||
TORRENT_UNUSED(ios); // this may be unused depending on configuration
|
||||
|
@ -830,11 +846,17 @@ int _System __libsocket_sysctl(int* mib, u_int namelen, void *oldp, size_t *oldl
|
|||
return ret;
|
||||
}
|
||||
|
||||
address get_default_gateway(io_service& ios, error_code& ec)
|
||||
address get_default_gateway(io_service& ios
|
||||
, string_view device, bool v6, error_code& ec)
|
||||
{
|
||||
std::vector<ip_route> ret = enum_routes(ios, ec);
|
||||
auto const i = std::find_if(ret.begin(), ret.end()
|
||||
, [](ip_route const& r) { return r.destination == address(); });
|
||||
, [device,v6](ip_route const& r)
|
||||
{
|
||||
return r.destination.is_unspecified()
|
||||
&& r.destination.is_v6() == v6
|
||||
&& (device.empty() || r.name == device);
|
||||
});
|
||||
if (i == ret.end()) return address();
|
||||
return i->gateway;
|
||||
}
|
||||
|
@ -1114,15 +1136,31 @@ int _System __libsocket_sysctl(int* mib, u_int namelen, void *oldp, size_t *oldl
|
|||
{
|
||||
ip_route r;
|
||||
r.gateway = sockaddr_to_address((const sockaddr*)&routes->Table[i].NextHop);
|
||||
#if TORRENT_USE_IPV6
|
||||
// The scope_id in NextHop is always zero because that would make
|
||||
// things too easy apparently
|
||||
if (r.gateway.is_v6() && r.gateway.to_v6().is_link_local())
|
||||
{
|
||||
address_v6 gateway6 = r.gateway.to_v6();
|
||||
gateway6.scope_id(routes->Table[i].InterfaceIndex);
|
||||
r.gateway = gateway6;
|
||||
}
|
||||
#endif
|
||||
r.destination = sockaddr_to_address(
|
||||
(const sockaddr*)&routes->Table[i].DestinationPrefix.Prefix);
|
||||
r.netmask = build_netmask(routes->Table[i].SitePrefixLength
|
||||
r.netmask = build_netmask(routes->Table[i].DestinationPrefix.PrefixLength
|
||||
, routes->Table[i].DestinationPrefix.Prefix.si_family);
|
||||
MIB_IFROW ifentry;
|
||||
ifentry.dwIndex = routes->Table[i].InterfaceIndex;
|
||||
if (GetIfEntry(&ifentry) == NO_ERROR)
|
||||
{
|
||||
wcstombs(r.name, ifentry.wszName, sizeof(r.name));
|
||||
WCHAR* name = ifentry.wszName;
|
||||
// strip UNC prefix to match the names returned by enum_net_interfaces
|
||||
if (wcsncmp(name, L"\\DEVICE\\TCPIP_", wcslen(L"\\DEVICE\\TCPIP_")) == 0)
|
||||
{
|
||||
name += wcslen(L"\\DEVICE\\TCPIP_");
|
||||
}
|
||||
wcstombs(r.name, name, sizeof(r.name));
|
||||
r.name[sizeof(r.name) - 1] = 0;
|
||||
r.mtu = ifentry.dwMtu;
|
||||
ret.push_back(r);
|
||||
|
|
360
src/natpmp.cpp
360
src/natpmp.cpp
|
@ -55,11 +55,81 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "libtorrent/io_service.hpp"
|
||||
#include "libtorrent/aux_/time.hpp"
|
||||
#include "libtorrent/debug.hpp"
|
||||
#include "libtorrent/random.hpp"
|
||||
#include "libtorrent/broadcast_socket.hpp" // for is_local
|
||||
#include "libtorrent/aux_/escape_string.hpp"
|
||||
#include "libtorrent/aux_/numeric_cast.hpp"
|
||||
|
||||
namespace libtorrent {
|
||||
|
||||
struct TORRENT_EXPORT pcp_error_category : boost::system::error_category
|
||||
{
|
||||
const char* name() const BOOST_SYSTEM_NOEXCEPT override
|
||||
{ return "pcp error"; }
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
static char const* msgs[] =
|
||||
{
|
||||
"success",
|
||||
"unsupported version",
|
||||
"not authorized",
|
||||
"malformed request",
|
||||
"unsupported opcode",
|
||||
"unsupported option",
|
||||
"malformed option",
|
||||
"network failure",
|
||||
"no resources",
|
||||
"unsupported protocol",
|
||||
"user exceeded quota",
|
||||
"cannot provide external",
|
||||
"address mismatch",
|
||||
"excessive remote peers",
|
||||
};
|
||||
if (ev < 0 || ev >= int(sizeof(msgs)/sizeof(msgs[0])))
|
||||
return "Unknown error";
|
||||
return msgs[ev];
|
||||
}
|
||||
boost::system::error_condition default_error_condition(
|
||||
int ev) const BOOST_SYSTEM_NOEXCEPT override
|
||||
{ return boost::system::error_condition(ev, *this); }
|
||||
};
|
||||
|
||||
boost::system::error_category& pcp_category()
|
||||
{
|
||||
static pcp_error_category pcp_category;
|
||||
return pcp_category;
|
||||
}
|
||||
|
||||
namespace errors
|
||||
{
|
||||
// hidden
|
||||
boost::system::error_code make_error_code(pcp_errors e)
|
||||
{
|
||||
return boost::system::error_code(e, pcp_category());
|
||||
}
|
||||
}
|
||||
|
||||
error_code natpmp::from_result_code(int const version, int result)
|
||||
{
|
||||
if (version == version_natpmp)
|
||||
{
|
||||
// a few nat-pmp result codes map to different codes
|
||||
// in pcp
|
||||
switch (result)
|
||||
{
|
||||
case 3:result = 7; break;
|
||||
case 4:result = 8; break;
|
||||
case 5:result = 4; break;
|
||||
}
|
||||
}
|
||||
return errors::pcp_errors(result);
|
||||
}
|
||||
|
||||
char const* natpmp::version_to_string(protocol_version version)
|
||||
{
|
||||
return version == version_natpmp ? "NAT-PMP" : "PCP";
|
||||
}
|
||||
|
||||
using namespace aux;
|
||||
using namespace std::placeholders;
|
||||
|
||||
|
@ -76,19 +146,77 @@ natpmp::natpmp(io_service& ios
|
|||
m_mappings.reserve(10);
|
||||
}
|
||||
|
||||
void natpmp::start()
|
||||
void natpmp::start(address local_address, std::string device)
|
||||
{
|
||||
TORRENT_ASSERT(is_single_thread());
|
||||
|
||||
error_code ec;
|
||||
address const gateway = get_default_gateway(m_socket.get_io_service(), ec);
|
||||
if (ec)
|
||||
|
||||
// assume servers support PCP and fall back to NAT-PMP
|
||||
// if necessary
|
||||
m_version = version_pcp;
|
||||
|
||||
// PCP requires reporting the source address at the application
|
||||
// layer so the socket MUST be bound to a specific address
|
||||
// if the caller didn't specify one then get the first suitable
|
||||
// address from the OS
|
||||
if (local_address.is_unspecified())
|
||||
{
|
||||
for (auto const& a : enum_net_interfaces(m_socket.get_io_service(), ec))
|
||||
{
|
||||
if (a.interface_address.is_loopback()) continue;
|
||||
if (a.interface_address.is_v4() != local_address.is_v4()) continue;
|
||||
if (a.interface_address.is_v6() && is_local(a.interface_address)) continue;
|
||||
if (!device.empty() && a.name != device) continue;
|
||||
local_address = a.interface_address;
|
||||
device = a.name;
|
||||
break;
|
||||
}
|
||||
|
||||
if (local_address.is_unspecified())
|
||||
{
|
||||
// if we can't get a specific address to bind to we'll have
|
||||
// to fall back to NAT-PMP
|
||||
// but NAT-PMP doesn't support IPv6 so if that's what is being
|
||||
// requested we can't do anything
|
||||
if (local_address.is_v6())
|
||||
{
|
||||
if (!ec) ec = boost::asio::error::address_family_not_supported;
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
if (should_log())
|
||||
{
|
||||
log("cannot map IPv6 without a local address, %s"
|
||||
, convert_from_native(ec.message()).c_str());
|
||||
}
|
||||
#endif
|
||||
disable(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
m_version = version_natpmp;
|
||||
ec.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// we really want a device name to get the right default gateway
|
||||
// try to find one even if the listen socket isn't bound to a device
|
||||
if (device.empty())
|
||||
{
|
||||
device = device_for_address(local_address, m_socket.get_io_service(), ec);
|
||||
// if this fails fall back to using the first default gateway in the
|
||||
// routing table
|
||||
ec.clear();
|
||||
}
|
||||
|
||||
address const gateway = get_default_gateway(m_socket.get_io_service()
|
||||
, device, local_address.is_v6(), ec);
|
||||
if (ec || gateway.is_unspecified())
|
||||
{
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
if (should_log())
|
||||
{
|
||||
log("failed to find default route: %s"
|
||||
, convert_from_native(ec.message()).c_str());
|
||||
log("failed to find default route for %s: %s"
|
||||
, local_address.to_string().c_str(), convert_from_native(ec.message()).c_str());
|
||||
}
|
||||
#endif
|
||||
disable(ec);
|
||||
|
@ -109,13 +237,13 @@ void natpmp::start()
|
|||
}
|
||||
#endif
|
||||
|
||||
m_socket.open(udp::v4(), ec);
|
||||
m_socket.open(local_address.is_v4() ? udp::v4() : udp::v6(), ec);
|
||||
if (ec)
|
||||
{
|
||||
disable(ec);
|
||||
return;
|
||||
}
|
||||
m_socket.bind({address_v4::any(), 0}, ec);
|
||||
m_socket.bind({local_address, 0}, ec);
|
||||
if (ec)
|
||||
{
|
||||
disable(ec);
|
||||
|
@ -126,7 +254,8 @@ void natpmp::start()
|
|||
m_socket.async_receive_from(boost::asio::buffer(&m_response_buffer[0]
|
||||
, sizeof(m_response_buffer))
|
||||
, m_remote, std::bind(&natpmp::on_reply, self(), _1, _2));
|
||||
send_get_ip_address_request();
|
||||
if (m_version == version_natpmp)
|
||||
send_get_ip_address_request();
|
||||
|
||||
for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i)
|
||||
{
|
||||
|
@ -143,9 +272,15 @@ void natpmp::send_get_ip_address_request()
|
|||
TORRENT_ASSERT(is_single_thread());
|
||||
using namespace libtorrent::detail;
|
||||
|
||||
// this opcode only exists in NAT-PMP
|
||||
// PCP routers report the external IP in the response to a MAP operation
|
||||
TORRENT_ASSERT(m_version == version_natpmp);
|
||||
if (m_version != version_natpmp)
|
||||
return;
|
||||
|
||||
char buf[2];
|
||||
char* out = buf;
|
||||
write_uint8(0, out); // NAT-PMP version
|
||||
write_uint8(version_natpmp, out);
|
||||
write_uint8(0, out); // public IP address request opcode
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
log("==> get public IP address");
|
||||
|
@ -256,6 +391,7 @@ port_mapping_t natpmp::add_mapping(portmap_protocol const p, int const external_
|
|||
m_mappings.push_back(mapping_t());
|
||||
i = m_mappings.end() - 1;
|
||||
}
|
||||
aux::random_bytes(i->nonce);
|
||||
i->protocol = p;
|
||||
i->external_port = external_port;
|
||||
i->local_port = local_ep.port();
|
||||
|
@ -344,29 +480,85 @@ void natpmp::send_map_request(port_mapping_t const i)
|
|||
m_currently_mapping = i;
|
||||
mapping_t& m = m_mappings[i];
|
||||
TORRENT_ASSERT(m.act != portmap_action::none);
|
||||
char buf[12];
|
||||
char buf[60];
|
||||
char* out = buf;
|
||||
write_uint8(0, out); // NAT-PMP version
|
||||
write_uint8(m.protocol == portmap_protocol::udp ? 1 : 2, out); // map "protocol"
|
||||
write_uint16(0, out); // reserved
|
||||
write_uint16(m.local_port, out); // private port
|
||||
write_uint16(m.external_port, out); // requested public port
|
||||
int ttl = m.act == portmap_action::add ? 3600 : 0;
|
||||
write_uint32(ttl, out); // port mapping lifetime
|
||||
if (m_version == version_natpmp)
|
||||
{
|
||||
write_uint8(m_version, out);
|
||||
write_uint8(m.protocol == portmap_protocol::udp ? 1 : 2, out); // map "protocol"
|
||||
write_uint16(0, out); // reserved
|
||||
write_uint16(m.local_port, out); // private port
|
||||
write_uint16(m.external_port, out); // requested public port
|
||||
write_uint32(ttl, out); // port mapping lifetime
|
||||
}
|
||||
else if (m_version == version_pcp)
|
||||
{
|
||||
// PCP requires the use of IPv6 addresses even for IPv4 messages
|
||||
// reference asio's address_v6 class directly so that we can use it
|
||||
// even if TORRENT_USE_IPV6 is false
|
||||
using boost::asio::ip::address_v6;
|
||||
write_uint8(m_version, out);
|
||||
write_uint8(opcode_map, out);
|
||||
write_uint16(0, out); // reserved
|
||||
write_uint32(ttl, out);
|
||||
address const local_addr = m_socket.local_endpoint().address();
|
||||
auto const local_bytes = local_addr.is_v4()
|
||||
? address_v6::v4_mapped(local_addr.to_v4()).to_bytes()
|
||||
: local_addr.to_v6().to_bytes();
|
||||
out = std::copy(local_bytes.begin(), local_bytes.end(), out);
|
||||
out = std::copy(m.nonce.begin(), m.nonce.end(), out);
|
||||
// translate portmap_protocol to an IANA protocol number
|
||||
int const protocol =
|
||||
(m.protocol == portmap_protocol::tcp) ? 6
|
||||
: (m.protocol == portmap_protocol::udp) ? 17
|
||||
: 0;
|
||||
write_int8(protocol, out);
|
||||
write_uint8(0, out); // reserved
|
||||
write_uint16(0, out); // reserved
|
||||
write_uint16(m.local_port, out);
|
||||
write_uint16(m.external_port, out);
|
||||
address_v6::bytes_type external_addr;
|
||||
if (!m.external_address.is_unspecified())
|
||||
{
|
||||
external_addr = m.external_address.is_v4()
|
||||
? address_v6::v4_mapped(m.external_address.to_v4()).to_bytes()
|
||||
: m.external_address.to_v6().to_bytes();
|
||||
}
|
||||
else if (is_local(local_addr))
|
||||
{
|
||||
external_addr = local_addr.is_v4() ? address_v6::v4_mapped(address_v4()).to_bytes()
|
||||
: address_v6().to_bytes();
|
||||
}
|
||||
else if (local_addr.is_v4())
|
||||
{
|
||||
external_addr = address_v6::v4_mapped(local_addr.to_v4()).to_bytes();
|
||||
}
|
||||
else
|
||||
{
|
||||
external_addr = local_addr.to_v6().to_bytes();
|
||||
}
|
||||
out = std::copy(external_addr.begin(), external_addr.end(), out);
|
||||
}
|
||||
else
|
||||
{
|
||||
TORRENT_ASSERT_FAIL();
|
||||
}
|
||||
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
if (should_log())
|
||||
{
|
||||
log("==> port map [ mapping: %d action: %s"
|
||||
" proto: %s local: %u external: %u ttl: %u ]"
|
||||
" transport: %s proto: %s local: %u external: %u ttl: %u ]"
|
||||
, static_cast<int>(i), to_string(m.act)
|
||||
, version_to_string(m_version)
|
||||
, to_string(m.protocol)
|
||||
, m.local_port, m.external_port, ttl);
|
||||
}
|
||||
#endif
|
||||
|
||||
error_code ec;
|
||||
m_socket.send_to(boost::asio::buffer(buf, sizeof(buf)), m_nat_endpoint, 0, ec);
|
||||
m_socket.send_to(boost::asio::buffer(buf, std::size_t(out - buf)), m_nat_endpoint, 0, ec);
|
||||
m.map_sent = true;
|
||||
m.outstanding_request = true;
|
||||
if (m_abort)
|
||||
|
@ -454,7 +646,7 @@ void natpmp::on_reply(error_code const& e
|
|||
error_code ec;
|
||||
m_send_timer.cancel(ec);
|
||||
|
||||
if (bytes_transferred < 12)
|
||||
if (bytes_transferred < 4)
|
||||
{
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
log("received packet of invalid size: %d", int(bytes_transferred));
|
||||
|
@ -464,13 +656,64 @@ void natpmp::on_reply(error_code const& e
|
|||
|
||||
char* in = msg_buf;
|
||||
int const version = read_uint8(in);
|
||||
int const cmd = read_uint8(in);
|
||||
int const result = read_uint16(in);
|
||||
|
||||
if (version != version_natpmp && version != version_pcp)
|
||||
{
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
log("unexpected version: %u", version);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
int cmd = read_uint8(in);
|
||||
if (version == version_pcp)
|
||||
{
|
||||
cmd &= 0x7f;
|
||||
}
|
||||
int result;
|
||||
if (version == version_pcp)
|
||||
{
|
||||
++in; // reserved
|
||||
result = read_uint8(in);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = read_uint16(in);
|
||||
}
|
||||
|
||||
if (result == errors::pcp_unsupp_version)
|
||||
{
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
log("unsupported version");
|
||||
#endif
|
||||
if (m_version == version_pcp && !is_v6(m_socket.local_endpoint()))
|
||||
{
|
||||
m_version = version_natpmp;
|
||||
resend_request(m_currently_mapping, error_code());
|
||||
send_get_ip_address_request();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((version == version_natpmp && bytes_transferred < 12)
|
||||
|| (version == version_pcp && bytes_transferred < 24))
|
||||
{
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
log("received packet of invalid size: %d", int(bytes_transferred));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
int lifetime = 0;
|
||||
if (version == version_pcp)
|
||||
{
|
||||
lifetime = aux::numeric_cast<int>(read_uint32(in));
|
||||
}
|
||||
int const time = aux::numeric_cast<int>(read_uint32(in));
|
||||
TORRENT_UNUSED(version);
|
||||
if (version == version_pcp) in += 12; // reserved
|
||||
TORRENT_UNUSED(time);
|
||||
|
||||
if (cmd == 128)
|
||||
if (version == version_natpmp && cmd == 128)
|
||||
{
|
||||
// public IP request response
|
||||
m_external_ip = read_v4_address(in);
|
||||
|
@ -485,7 +728,8 @@ void natpmp::on_reply(error_code const& e
|
|||
|
||||
}
|
||||
|
||||
if (bytes_transferred != 16)
|
||||
if ((version == version_natpmp && bytes_transferred != 16)
|
||||
|| (version == version_pcp && bytes_transferred != 60))
|
||||
{
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
log("received packet of invalid size: %d", int(bytes_transferred));
|
||||
|
@ -493,27 +737,47 @@ void natpmp::on_reply(error_code const& e
|
|||
return;
|
||||
}
|
||||
|
||||
std::array<char, 12> nonce;
|
||||
portmap_protocol protocol = portmap_protocol::none;
|
||||
if (version == version_pcp)
|
||||
{
|
||||
std::memcpy(nonce.data(), in, nonce.size());
|
||||
in += nonce.size();
|
||||
int p = read_uint8(in);
|
||||
protocol = p == 6 ? portmap_protocol::tcp
|
||||
: portmap_protocol::udp;
|
||||
in += 3; // reserved
|
||||
}
|
||||
int const private_port = read_uint16(in);
|
||||
int const public_port = read_uint16(in);
|
||||
int const lifetime = aux::numeric_cast<int>(read_uint32(in));
|
||||
if (version == version_natpmp)
|
||||
lifetime = aux::numeric_cast<int>(read_uint32(in));
|
||||
address external_addr;
|
||||
if (version == version_pcp)
|
||||
{
|
||||
using boost::asio::ip::address_v6;
|
||||
address_v6::bytes_type addr;
|
||||
std::memcpy(addr.data(), in, addr.size());
|
||||
in += addr.size();
|
||||
external_addr = address_v6(addr);
|
||||
if (external_addr.to_v6().is_v4_mapped())
|
||||
external_addr = external_addr.to_v6().to_v4();
|
||||
}
|
||||
|
||||
portmap_protocol const protocol = (cmd - 128 == 1)
|
||||
? portmap_protocol::udp
|
||||
: portmap_protocol::tcp;
|
||||
if (version == version_natpmp)
|
||||
{
|
||||
protocol = (cmd - 128 == 1)
|
||||
? portmap_protocol::udp
|
||||
: portmap_protocol::tcp;
|
||||
}
|
||||
|
||||
#ifndef TORRENT_DISABLE_LOGGING
|
||||
char msg[200];
|
||||
int const num_chars = std::snprintf(msg, sizeof(msg), "<== port map ["
|
||||
" protocol: %s local: %u external: %u ttl: %u ]"
|
||||
, (cmd - 128 == 1 ? "udp" : "tcp")
|
||||
" transport: %s protocol: %s local: %u external: %u ttl: %u ]"
|
||||
, version_to_string(protocol_version(version))
|
||||
, (protocol == portmap_protocol::udp ? "udp" : "tcp")
|
||||
, private_port, public_port, lifetime);
|
||||
|
||||
if (version != 0)
|
||||
{
|
||||
std::snprintf(msg + num_chars, sizeof(msg) - aux::numeric_cast<std::size_t>(num_chars), "unexpected version: %u"
|
||||
, version);
|
||||
log("%s", msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
mapping_t* m = nullptr;
|
||||
|
@ -524,6 +788,7 @@ void natpmp::on_reply(error_code const& e
|
|||
if (protocol != i->protocol) continue;
|
||||
if (!i->map_sent) continue;
|
||||
if (!i->outstanding_request) continue;
|
||||
if (version == version_pcp && nonce != i->nonce) continue;
|
||||
m = &*i;
|
||||
index = port_mapping_t(static_cast<int>(i - m_mappings.begin()));
|
||||
break;
|
||||
|
@ -553,32 +818,23 @@ void natpmp::on_reply(error_code const& e
|
|||
{
|
||||
m->expires = aux::time_now() + seconds(int(lifetime * 0.7f));
|
||||
m->external_port = public_port;
|
||||
if (!external_addr.is_unspecified())
|
||||
m->external_address = external_addr;
|
||||
}
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
// TODO: 3 it would be nice to have a separate NAT-PMP error category
|
||||
static errors::error_code_enum const errors[] =
|
||||
{
|
||||
errors::unsupported_protocol_version,
|
||||
errors::natpmp_not_authorized,
|
||||
errors::network_failure,
|
||||
errors::no_resources,
|
||||
errors::unsupported_opcode
|
||||
};
|
||||
errors::error_code_enum ev = errors::no_error;
|
||||
if (result >= 1 && result <= 5) ev = errors[result - 1];
|
||||
|
||||
m->expires = aux::time_now() + hours(2);
|
||||
portmap_protocol const proto = m->protocol;
|
||||
m_callback.on_port_mapping(port_mapping_t{index}, address(), 0, proto
|
||||
, ev, portmap_transport::natpmp);
|
||||
, from_result_code(version, result), portmap_transport::natpmp);
|
||||
}
|
||||
else if (m->act == portmap_action::add)
|
||||
{
|
||||
portmap_protocol const proto = m->protocol;
|
||||
m_callback.on_port_mapping(port_mapping_t{index}, m_external_ip, m->external_port, proto
|
||||
, errors::no_error, portmap_transport::natpmp);
|
||||
address const ext_ip = version == version_pcp ? m->external_address : m_external_ip;
|
||||
m_callback.on_port_mapping(port_mapping_t{index}, ext_ip, m->external_port, proto
|
||||
, errors::pcp_success, portmap_transport::natpmp);
|
||||
}
|
||||
|
||||
if (m_abort) return;
|
||||
|
|
|
@ -1226,10 +1226,10 @@ namespace {
|
|||
}
|
||||
#endif // TORRENT_ABI_VERSION
|
||||
|
||||
port_mapping_t session_handle::add_port_mapping(portmap_protocol const t
|
||||
std::vector<port_mapping_t> session_handle::add_port_mapping(portmap_protocol const t
|
||||
, int external_port, int local_port)
|
||||
{
|
||||
return sync_call_ret<port_mapping_t>(&session_impl::add_port_mapping, t, external_port, local_port);
|
||||
return sync_call_ret<std::vector<port_mapping_t>>(&session_impl::add_port_mapping, t, external_port, local_port);
|
||||
}
|
||||
|
||||
void session_handle::delete_port_mapping(port_mapping_t handle)
|
||||
|
|
|
@ -586,7 +586,7 @@ namespace aux {
|
|||
|
||||
// apply all m_settings to this session
|
||||
run_all_updates(*this);
|
||||
reopen_listen_sockets();
|
||||
reopen_listen_sockets(false);
|
||||
reopen_outgoing_sockets();
|
||||
|
||||
#if TORRENT_USE_INVARIANT_CHECKS
|
||||
|
@ -1859,6 +1859,7 @@ namespace aux {
|
|||
#endif
|
||||
if ((*remove_iter)->sock) (*remove_iter)->sock->close(ec);
|
||||
if ((*remove_iter)->udp_sock) (*remove_iter)->udp_sock->sock.close();
|
||||
if ((*remove_iter)->natpmp_mapper) (*remove_iter)->natpmp_mapper->close();
|
||||
remove_iter = m_listen_sockets.erase(remove_iter);
|
||||
}
|
||||
|
||||
|
@ -1879,9 +1880,11 @@ namespace aux {
|
|||
|
||||
TORRENT_ASSERT((s->incoming == duplex::accept_incoming) == bool(s->sock));
|
||||
if (s->sock) async_accept(s->sock, s->ssl);
|
||||
if (m_settings.get_bool(settings_pack::enable_natpmp))
|
||||
start_natpmp(*s);
|
||||
// since this is a new socket it needs to map ports
|
||||
// even if the caller did not request re-mapping
|
||||
if (!map_ports) remap_ports(remap_natpmp_and_upnp, *s);
|
||||
if (!map_ports) remap_ports(remap_upnp, *s);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2126,10 +2129,10 @@ namespace aux {
|
|||
tcp::endpoint const tcp_ep = s.sock ? s.sock->local_endpoint() : tcp::endpoint();
|
||||
udp::endpoint const udp_ep = s.udp_sock ? s.udp_sock->sock.local_endpoint() : udp::endpoint();
|
||||
|
||||
if ((mask & remap_natpmp) && m_natpmp)
|
||||
if ((mask & remap_natpmp) && s.natpmp_mapper)
|
||||
{
|
||||
map_port(*m_natpmp, portmap_protocol::tcp, tcp_ep, s.tcp_port_mapping[0]);
|
||||
map_port(*m_natpmp, portmap_protocol::udp, to_tcp(udp_ep), s.udp_port_mapping[0]);
|
||||
map_port(*s.natpmp_mapper, portmap_protocol::tcp, tcp_ep, s.tcp_port_mapping[0]);
|
||||
map_port(*s.natpmp_mapper, portmap_protocol::udp, to_tcp(udp_ep), s.udp_port_mapping[0]);
|
||||
}
|
||||
if ((mask & remap_upnp) && m_upnp)
|
||||
{
|
||||
|
@ -5450,6 +5453,23 @@ namespace aux {
|
|||
m_alerts.emplace_alert<lsd_peer_alert>(t->get_handle(), peer);
|
||||
}
|
||||
|
||||
void session_impl::start_natpmp(aux::listen_socket_t& s)
|
||||
{
|
||||
// don't create mappings for local IPv6 addresses
|
||||
// they can't be reached from outside of the local network anyways
|
||||
if (is_v6(s.local_endpoint) && is_local(s.local_endpoint.address()))
|
||||
return;
|
||||
|
||||
if (!s.natpmp_mapper)
|
||||
{
|
||||
// the natpmp constructor may fail and call the callbacks
|
||||
// into the session_impl.
|
||||
s.natpmp_mapper = std::make_shared<natpmp>(m_io_service, *this);
|
||||
s.natpmp_mapper->start(s.local_endpoint.address(), s.device);
|
||||
}
|
||||
remap_ports(remap_natpmp, s);
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool find_tcp_port_mapping(portmap_transport const transport
|
||||
, port_mapping_t mapping, std::shared_ptr<listen_socket_t> const& ls)
|
||||
|
@ -6646,22 +6666,11 @@ namespace aux {
|
|||
m_alerts.emplace_alert<lsd_error_alert>(ec);
|
||||
}
|
||||
|
||||
natpmp* session_impl::start_natpmp()
|
||||
void session_impl::start_natpmp()
|
||||
{
|
||||
INVARIANT_CHECK;
|
||||
|
||||
if (m_natpmp) return m_natpmp.get();
|
||||
|
||||
// the natpmp constructor may fail and call the callbacks
|
||||
// into the session_impl.
|
||||
m_natpmp = std::make_shared<natpmp>(m_io_service, *this);
|
||||
m_natpmp->start();
|
||||
|
||||
for (auto& s : m_listen_sockets)
|
||||
{
|
||||
remap_ports(remap_natpmp, *s);
|
||||
}
|
||||
return m_natpmp.get();
|
||||
start_natpmp(*s);
|
||||
}
|
||||
|
||||
upnp* session_impl::start_upnp()
|
||||
|
@ -6687,22 +6696,28 @@ namespace aux {
|
|||
return m_upnp.get();
|
||||
}
|
||||
|
||||
port_mapping_t session_impl::add_port_mapping(portmap_protocol const t
|
||||
std::vector<port_mapping_t> session_impl::add_port_mapping(portmap_protocol const t
|
||||
, int const external_port
|
||||
, int const local_port)
|
||||
{
|
||||
port_mapping_t ret{-1};
|
||||
if (m_upnp) ret = m_upnp->add_mapping(t, external_port
|
||||
, tcp::endpoint({}, static_cast<std::uint16_t>(local_port)));
|
||||
if (m_natpmp) ret = m_natpmp->add_mapping(t, external_port
|
||||
, tcp::endpoint({}, static_cast<std::uint16_t>(local_port)));
|
||||
std::vector<port_mapping_t> ret;
|
||||
if (m_upnp) ret.push_back(m_upnp->add_mapping(t, external_port
|
||||
, tcp::endpoint({}, static_cast<std::uint16_t>(local_port))));
|
||||
for (auto& s : m_listen_sockets)
|
||||
{
|
||||
if (s->natpmp_mapper) ret.push_back(s->natpmp_mapper->add_mapping(t, external_port
|
||||
, tcp::endpoint({}, static_cast<std::uint16_t>(local_port))));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void session_impl::delete_port_mapping(port_mapping_t handle)
|
||||
{
|
||||
if (m_upnp) m_upnp->delete_mapping(handle);
|
||||
if (m_natpmp) m_natpmp->delete_mapping(handle);
|
||||
for (auto& s : m_listen_sockets)
|
||||
{
|
||||
if (s->natpmp_mapper) s->natpmp_mapper->delete_mapping(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void session_impl::stop_ip_notifier()
|
||||
|
@ -6722,16 +6737,14 @@ namespace aux {
|
|||
|
||||
void session_impl::stop_natpmp()
|
||||
{
|
||||
if (!m_natpmp) return;
|
||||
|
||||
m_natpmp->close();
|
||||
for (auto& s : m_listen_sockets)
|
||||
{
|
||||
s->tcp_port_mapping[0] = port_mapping_t{-1};
|
||||
s->udp_port_mapping[0] = port_mapping_t{-1};
|
||||
if (!s->natpmp_mapper) continue;
|
||||
s->natpmp_mapper->close();
|
||||
s->natpmp_mapper.reset();
|
||||
}
|
||||
|
||||
m_natpmp.reset();
|
||||
}
|
||||
|
||||
void session_impl::stop_upnp()
|
||||
|
|
|
@ -42,7 +42,7 @@ int main()
|
|||
io_service ios;
|
||||
error_code ec;
|
||||
|
||||
address def_gw = get_default_gateway(ios, ec);
|
||||
address def_gw = get_default_gateway(ios, "", false, ec);
|
||||
if (ec)
|
||||
{
|
||||
std::printf("%s\n", ec.message().c_str());
|
||||
|
@ -80,17 +80,19 @@ int main()
|
|||
return 1;
|
||||
}
|
||||
|
||||
std::printf("%-34s%-45s%-20s%-20sdescription\n", "address", "netmask", "name", "flags");
|
||||
std::printf("%-34s%-45s%-20s%-20s%-34sdescription\n", "address", "netmask", "name", "flags", "default gateway");
|
||||
|
||||
for (auto const& i : net)
|
||||
{
|
||||
std::printf("%-34s%-45s%-20s%s%s%-20s%s %s\n"
|
||||
address iface_def_gw = get_default_gateway(ios, i.name, i.interface_address.is_v6(), ec);
|
||||
std::printf("%-34s%-45s%-20s%s%s%-20s%-34s%s %s\n"
|
||||
, i.interface_address.to_string(ec).c_str()
|
||||
, i.netmask.to_string(ec).c_str()
|
||||
, i.name
|
||||
, (i.interface_address.is_multicast()?"multicast ":"")
|
||||
, (is_local(i.interface_address)?"local ":"")
|
||||
, (is_loopback(i.interface_address)?"loopback ":"")
|
||||
, iface_def_gw.to_string(ec).c_str()
|
||||
, i.friendly_name, i.description);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue