limit tracker announces and SOCKS5 connection attempts to listen_socket_t that actually can reach the target

This commit is contained in:
arvidn 2020-01-06 10:11:21 +01:00 committed by Arvid Norberg
parent a0b0f2aec5
commit 4ceb2ea467
18 changed files with 269 additions and 64 deletions

View File

@ -169,10 +169,16 @@ class test_torrent_handle(unittest.TestCase):
# wait a bit until the endpoints list gets populated
while len(self.h.trackers()[0]['endpoints']) == 0:
time.sleep(0.1)
pickled_trackers = pickle.dumps(self.h.trackers())
trackers = self.h.trackers()
self.assertEqual(trackers[0]['url'], 'udp://tracker1.com')
# this is not necessarily 0, it could also be (EHOSTUNREACH) if the
# local machine doesn't support the address family
expect_value = trackers[0]['endpoints'][0]['last_error']['value']
pickled_trackers = pickle.dumps(trackers)
unpickled_trackers = pickle.loads(pickled_trackers)
self.assertEqual(unpickled_trackers[0]['url'], 'udp://tracker1.com')
self.assertEqual(unpickled_trackers[0]['endpoints'][0]['last_error']['value'], 0)
self.assertEqual(unpickled_trackers[0]['endpoints'][0]['last_error']['value'], expect_value)
def test_file_status(self):
self.setup()

View File

@ -58,6 +58,7 @@ namespace libtorrent { namespace aux {
address get_external_address() const;
tcp::endpoint get_local_endpoint() const;
bool can_route(address const&) const;
bool is_ssl() const;

View File

@ -154,6 +154,12 @@ namespace aux {
listen_socket_t& operator=(listen_socket_t const&) = delete;
listen_socket_t& operator=(listen_socket_t&&) = delete;
// returns true if this listen socket/interface can reach and be reached
// by the given address. This is useful to know whether it should be
// annoucned to a tracker (given the tracker's IP) or whether it should
// have a SOCKS5 UDP tunnel set up (given the IP of the socks proxy)
bool can_route(address const&) const;
// this may be empty but can be set
// to the WAN IP address of a NAT router
ip_voter external_address;
@ -161,6 +167,8 @@ namespace aux {
// this is a cached local endpoint for the listen TCP socket
tcp::endpoint local_endpoint;
address netmask;
// the name of the device the socket is bound to, may be empty
// if the socket is not bound to a device
std::string device;
@ -229,8 +237,8 @@ namespace aux {
struct TORRENT_EXTRA_EXPORT listen_endpoint_t
{
listen_endpoint_t(address const& adr, int p, std::string dev, transport s
, listen_socket_flags_t f)
: addr(adr), port(p), device(std::move(dev)), ssl(s), flags(f) {}
, listen_socket_flags_t f, address const& nmask = address{})
: addr(adr), netmask(nmask), port(p), device(std::move(dev)), ssl(s), flags(f) {}
bool operator==(listen_endpoint_t const& o) const
{
@ -238,6 +246,10 @@ namespace aux {
}
address addr;
// if this listen endpoint/interface doesn't have a gateway, we cannot
// route outside of our network, this netmask defines the range of our
// local network
address netmask;
int port;
std::string device;
transport ssl;
@ -258,6 +270,10 @@ namespace aux {
span<ip_interface const> ifs
, 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
// thread started to run the main downloader loop
struct TORRENT_EXTRA_EXPORT session_impl final

View File

@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/utp_socket_manager.hpp"
#include "libtorrent/config.hpp"
#include "libtorrent/aux_/allocating_handler.hpp"
#include "libtorrent/aux_/listen_socket_handle.hpp"
#include <boost/asio/io_service.hpp>
#include <vector>
@ -47,13 +48,14 @@ namespace aux {
struct listen_endpoint_t;
struct proxy_settings;
struct listen_socket_t;
enum class transport : std::uint8_t { plaintext, ssl };
struct session_udp_socket : utp_socket_interface
{
explicit session_udp_socket(io_service& ios)
: sock(ios) {}
explicit session_udp_socket(io_service& ios, listen_socket_handle ls)
: sock(ios, std::move(ls)) {}
udp::endpoint local_endpoint() override { return sock.local_endpoint(); }
@ -73,7 +75,7 @@ namespace aux {
struct outgoing_udp_socket final : session_udp_socket
{
outgoing_udp_socket(io_service& ios, std::string const& dev, transport ssl_)
: session_udp_socket(ios), device(dev), ssl(ssl_) {}
: session_udp_socket(ios, listen_socket_handle{}), device(dev), ssl(ssl_) {}
// the name of the device the socket is bound to, may be empty
// if the socket is not bound to a device

View File

@ -40,6 +40,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/debug.hpp"
#include "libtorrent/span.hpp"
#include "libtorrent/flags.hpp"
#include "libtorrent/aux_/listen_socket_handle.hpp"
#include <array>
#include <memory>
@ -54,7 +55,7 @@ namespace libtorrent {
class TORRENT_EXTRA_EXPORT udp_socket : single_threaded
{
public:
explicit udp_socket(io_service& ios);
explicit udp_socket(io_service& ios, aux::listen_socket_handle ls);
static constexpr udp_send_flags_t peer_connection = 0_bit;
static constexpr udp_send_flags_t tracker_connection = 1_bit;
@ -145,6 +146,7 @@ namespace libtorrent {
using receive_buffer = std::array<char, 1500>;
std::unique_ptr<receive_buffer> m_buf;
aux::listen_socket_handle m_listen_socket;
std::uint16_t m_bind_port;

View File

@ -104,7 +104,7 @@ TORRENT_TEST(dht_rate_limit)
asio::io_service dht_ios(sim, address_v4::from_string("40.30.20.10"));
// receiver (the DHT under test)
lt::udp_socket sock(dht_ios);
lt::udp_socket sock(dht_ios, lt::aux::listen_socket_handle{});
obs o;
auto ls = std::make_shared<lt::aux::listen_socket_t>();
ls->external_address.cast_vote(address_v4::from_string("40.30.20.10")
@ -230,7 +230,7 @@ TORRENT_TEST(dht_delete_socket)
sim::simulation sim(cfg);
sim::asio::io_service dht_ios(sim, lt::address_v4::from_string("40.30.20.10"));
lt::udp_socket sock(dht_ios);
lt::udp_socket sock(dht_ios, lt::aux::listen_socket_handle{});
error_code ec;
sock.bind(udp::endpoint(address_v4::from_string("40.30.20.10"), 8888), ec);

View File

@ -472,6 +472,8 @@ int _System __libsocket_sysctl(int* mib, u_int namelen, void *oldp, size_t *oldl
if (a1.is_v6())
{
if (a1.to_v6().scope_id() != a2.to_v6().scope_id()) return false;
address_v6::bytes_type b1 = a1.to_v6().to_bytes();
address_v6::bytes_type b2 = a2.to_v6().to_bytes();
address_v6::bytes_type m = mask.to_v6().to_bytes();

View File

@ -529,21 +529,9 @@ void http_connection::on_resolve(error_code const& e
// only connect to addresses of the same family
if (m_bind_addr)
{
auto new_end = std::partition(m_endpoints.begin(), m_endpoints.end()
, [this] (tcp::endpoint const& ep)
{
if (is_v4(ep) != m_bind_addr->is_v4())
return false;
if (is_v4(ep) && m_bind_addr->is_v4())
return true;
TORRENT_ASSERT(is_v6(ep) && m_bind_addr->is_v6());
// don't try to connect to a global address with a local source address
// this is mainly needed to prevent attempting to connect to a global
// address using a ULA as the source
if (!is_local(ep.address()) && is_local(*m_bind_addr))
return false;
return ep.address().to_v6().scope_id() == m_bind_addr->to_v6().scope_id();
});
auto const new_end = std::remove_if(m_endpoints.begin(), m_endpoints.end()
, [&](tcp::endpoint const& ep) { return is_v4(ep) != m_bind_addr->is_v4(); });
m_endpoints.erase(new_end, m_endpoints.end());
if (m_endpoints.empty())
{

View File

@ -272,6 +272,20 @@ namespace libtorrent {
void http_tracker_connection::on_filter(http_connection& c
, std::vector<tcp::endpoint>& endpoints)
{
// filter all endpoints we cannot reach from this listen socket, which may
// be all of them, in which case we should not announce this listen socket
// to this tracker
auto const ls = bind_socket();
endpoints.erase(std::remove_if(endpoints.begin(), endpoints.end()
, [&](tcp::endpoint const& ep) { return !ls.can_route(ep.address()); })
, endpoints.end());
if (endpoints.empty())
{
fail(error_code(boost::system::errc::host_unreachable, system_category()));
return;
}
TORRENT_UNUSED(c);
if (!tracker_req().filter) return;

View File

@ -64,4 +64,11 @@ namespace libtorrent { namespace aux {
return m_sock.lock().get();
}
bool listen_socket_handle::can_route(address const& a) const
{
auto s = m_sock.lock();
if (!s) return false;
return s->can_route(a);
}
} }

View File

@ -283,6 +283,70 @@ namespace aux {
}
}
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)
{
auto const iface = ep.device.empty()
? std::find_if(ifs.begin(), ifs.end(), [&](ip_interface const& ipface)
{
return match_addr_mask(ipface.interface_address, ep.addr, ipface.netmask);
})
: std::find_if(ifs.begin(), ifs.end(), [&](ip_interface const& ipface)
{
return ipface.name == ep.device
&& match_addr_mask(ipface.interface_address, ep.addr, ipface.netmask);
});
if (iface == ifs.end())
{
// we can't find which device this is for, just assume we can't
// reach anything on it
ep.netmask = build_netmask(0, ep.addr.is_v4() ? AF_INET : AF_INET6);
continue;
}
ep.netmask = iface->netmask;
bool const v4 = iface->interface_address.is_v4();
// also record whether the device has a gateway associated with it
// (which indicates it can be used to reach the internet)
// only gateways inside the interface's network count
bool const has_gateway = std::find_if(routes.begin(), routes.end(), [&](ip_route const& r)
{
return r.destination.is_unspecified()
&& r.destination.is_v4() == v4
&& !r.gateway.is_unspecified()
&& match_addr_mask(r.gateway, iface->interface_address, iface->netmask)
&& strcmp(r.name, iface->name) == 0;
}) != routes.end();
if (has_gateway)
ep.flags |= listen_socket_t::has_gateway;
ep.device = iface->name;
}
}
bool listen_socket_t::can_route(address const& addr) const
{
if (local_endpoint.address().is_v4() != addr.is_v4()) return false;
if (local_endpoint.address().is_v6()
&& 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;
}
void session_impl::init_peer_class_filter(bool unlimited_local)
{
// set the default peer_class_filter to use the local peer class
@ -1328,6 +1392,7 @@ namespace aux {
ret->ssl = lep.ssl;
ret->original_port = bind_ep.port();
ret->flags = lep.flags;
ret->netmask = lep.netmask;
operation_t last_op = operation_t::unknown;
socket_type_t const sock_type
= (lep.ssl == transport::ssl)
@ -1535,7 +1600,7 @@ namespace aux {
: socket_type_t::udp;
udp::endpoint udp_bind_ep(bind_ep.address(), bind_ep.port());
ret->udp_sock = std::make_shared<session_udp_socket>(m_io_service);
ret->udp_sock = std::make_shared<session_udp_socket>(m_io_service, ret);
ret->udp_sock->sock.open(udp_bind_ep.protocol(), ec);
if (ec)
{
@ -1798,6 +1863,8 @@ namespace aux {
if (!ec)
{
expand_unspecified_address(ifs, eps);
auto const routes = enum_routes(m_io_service, ec);
if (!ec) expand_devices(ifs, routes, eps);
}
// if no listen interfaces are specified, create sockets to use

View File

@ -10988,7 +10988,8 @@ bool is_downloading_state(int const st)
debug_log("*** increment tracker fail count [%d]", aep->fails);
#endif
// don't try to announce from this endpoint again
if (ec == boost::system::errc::address_family_not_supported)
if (ec == boost::system::errc::address_family_not_supported
|| ec == boost::system::errc::host_unreachable)
{
aep->enabled = false;
#ifndef TORRENT_DISABLE_LOGGING

View File

@ -69,14 +69,14 @@ std::size_t const max_header_size = 255;
// the common case cheaper by not allocating this space unconditionally
struct socks5 : std::enable_shared_from_this<socks5>
{
explicit socks5(io_service& ios, alert_manager& alerts)
explicit socks5(io_service& ios, aux::listen_socket_handle ls
, alert_manager& alerts)
: m_socks5_sock(ios)
, m_resolver(ios)
, m_timer(ios)
, m_retry_timer(ios)
, m_alerts(alerts)
, m_abort(false)
, m_active(false)
, m_listen_socket(std::move(ls))
{}
void start(aux::proxy_settings const& ps);
@ -107,6 +107,7 @@ private:
deadline_timer m_timer;
deadline_timer m_retry_timer;
alert_manager& m_alerts;
aux::listen_socket_handle m_listen_socket;
std::array<char, tmp_buffer_size> m_tmp_buf;
aux::proxy_settings m_proxy_settings;
@ -123,10 +124,10 @@ private:
udp::endpoint m_udp_proxy_addr;
// set to true when we've been asked to shut down
bool m_abort;
bool m_abort = false;
// set to true once the tunnel is established
bool m_active;
bool m_active = false;
};
#ifdef TORRENT_HAS_DONT_FRAGMENT
@ -159,9 +160,10 @@ struct set_dont_frag
{ set_dont_frag(udp::socket&, int) {} };
#endif
udp_socket::udp_socket(io_service& ios)
udp_socket::udp_socket(io_service& ios, aux::listen_socket_handle ls)
: m_socket(ios)
, m_buf(new receive_buffer())
, m_listen_socket(std::move(ls))
, m_bind_port(0)
, m_abort(true)
{}
@ -495,7 +497,8 @@ void udp_socket::set_proxy_settings(aux::proxy_settings const& ps
|| ps.type == settings_pack::socks5_pw)
{
// connect to socks5 server and open up the UDP tunnel
m_socks5_connection = std::make_shared<socks5>(lt::get_io_service(m_socket), alerts);
m_socks5_connection = std::make_shared<socks5>(lt::get_io_service(m_socket)
, m_listen_socket, alerts);
m_socks5_connection->start(ps);
}
}
@ -528,6 +531,27 @@ void socks5::on_name_lookup(error_code const& e, tcp::resolver::iterator i)
return;
}
// only set up a SOCKS5 tunnel for sockets with the same address family
// as the proxy
// this is a hack to mitigate excessive SOCKS5 tunnels, until this can get
// fixed properly.
for (;;)
{
if (i == tcp::resolver::iterator{})
{
if (m_alerts.should_post<socks5_alert>())
m_alerts.emplace_alert<socks5_alert>(tcp::endpoint()
, operation_t::hostname_lookup
, error_code(boost::system::errc::host_unreachable, generic_category()));
return;
}
// we found a match
if (m_listen_socket.can_route(i->endpoint().address()))
break;
++i;
}
m_proxy_addr = i->endpoint();
error_code ec;

View File

@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/aux_/time.hpp"
#include "libtorrent/aux_/io.hpp"
#include "libtorrent/peer.hpp"
#include "libtorrent/error_code.hpp"
#ifndef TORRENT_DISABLE_LOGGING
#include "libtorrent/socket_io.hpp"
@ -203,24 +204,20 @@ namespace libtorrent {
return;
}
auto bind_address = bind_interface();
auto const listen_socket = bind_socket();
// look for an address that has the same kind as the one
// we're listening on. To make sure the tracker get our
// correct listening address.
bool is_v4 = bind_address.is_v4();
auto scope = is_v4 ? 0 : bind_address.to_v6().scope_id();
// filter all endpoints we cannot reach from this listen socket, which may
// be all of them, in which case we should not announce this listen socket
// to this tracker
for (auto const& addr : addresses)
{
if (addr.is_v4() != is_v4) continue;
if (addr.is_v6() && addr.to_v6().scope_id() != scope)
continue;
if (!listen_socket.can_route(addr)) continue;
m_endpoints.emplace_back(addr, std::uint16_t(port));
}
if (m_endpoints.empty())
{
fail(error_code(boost::asio::error::address_family_not_supported));
fail(error_code(boost::system::errc::host_unreachable, generic_category()));
return;
}

View File

@ -76,24 +76,36 @@ TORRENT_TEST(is_any)
TORRENT_TEST(match_addr_mask)
{
error_code ec;
TEST_CHECK(match_addr_mask(
address::from_string("10.0.1.176", ec),
address::from_string("10.0.1.176", ec),
address::from_string("255.255.255.0", ec)));
TEST_CHECK(!ec);
address::from_string("10.0.1.176"),
address::from_string("10.0.1.176"),
address::from_string("255.255.255.0")));
TEST_CHECK(match_addr_mask(
address::from_string("10.0.1.3", ec),
address::from_string("10.0.3.3", ec),
address::from_string("255.255.0.0", ec)));
TEST_CHECK(!ec);
address::from_string("10.0.1.3"),
address::from_string("10.0.3.3"),
address::from_string("255.255.0.0")));
TEST_CHECK(!match_addr_mask(
address::from_string("10.0.1.3", ec),
address::from_string("10.1.3.3", ec),
address::from_string("255.255.0.0", ec)));
TEST_CHECK(!ec);
address::from_string("10.0.1.3"),
address::from_string("10.1.3.3"),
address::from_string("255.255.0.0")));
TEST_CHECK(match_addr_mask(
address::from_string("ff00:1234::"),
address::from_string("ff00:5678::"),
address::from_string("ffff::")));
TEST_CHECK(!match_addr_mask(
address::from_string("ff00:1234::"),
address::from_string("ff00:5678::"),
address::from_string("ffff:f000::")));
// different scope IDs always means a mismatch
TEST_CHECK(!match_addr_mask(
address::from_string("ff00:1234::%1"),
address::from_string("ff00:1234::%2"),
address::from_string("ffff::")));
}
TORRENT_TEST(is_ip_address)

View File

@ -57,14 +57,25 @@ namespace
TEST_EQUAL(e1.device, dev);
}
ip_interface ifc(char const* ip, char const* device)
ip_interface ifc(char const* ip, char const* device, char const* netmask = nullptr)
{
ip_interface ipi;
ipi.interface_address = address::from_string(ip);
if (netmask) ipi.netmask = address::from_string(netmask);
strncpy(ipi.name, device, sizeof(ipi.name));
return ipi;
}
ip_route rt(char const* ip, char const* device, char const* gateway)
{
ip_route ret;
ret.destination = address::from_string(ip);
ret.gateway = address::from_string(gateway);
std::strncpy(ret.name, device, sizeof(ret.name));
ret.name[sizeof(ret.name) - 1] = '\0';
return ret;
}
aux::listen_endpoint_t ep(char const* ip, int port
, tp ssl = tp::plaintext
, std::string device = {})
@ -229,6 +240,59 @@ TORRENT_TEST(partition_listen_sockets_op_ports)
TEST_EQUAL(eps.size(), 2);
}
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")
, ifc("24.172.48.90", "eth1", "255.255.255.0")
, ifc("::1", "lo", "ffff:ffff:ffff:ffff::")
, ifc("fe80::d250:99ff:fe0c:9b74", "eth0", "ffff:ffff:ffff:ffff::")
, ifc("2601:646:c600:a3:d250:99ff:fe0c:9b74", "eth0", "ffff:ffff:ffff:ffff::")
};
std::vector<aux::listen_endpoint_t> eps = {
{
address::from_string("127.0.0.1"),
6881, // port
"", // device
aux::transport::plaintext,
aux::listen_socket_flags_t{} },
{
address::from_string("192.168.1.2"),
6881, // port
"", // device
aux::transport::plaintext,
aux::listen_socket_flags_t{} }
};
expand_devices(ifs, routes, eps);
TEST_CHECK((eps == std::vector<aux::listen_endpoint_t>{
{
address::from_string("127.0.0.1"),
6881, // port
"lo", // device
aux::transport::plaintext,
aux::listen_socket_flags_t{},
address::from_string("255.0.0.0") },
{
address::from_string("192.168.1.2"),
6881, // port
"eth0", // device
aux::transport::plaintext,
aux::listen_socket_flags_t{},
address::from_string("255.255.255.0") },
}));
}
TORRENT_TEST(expand_unspecified)
{
std::vector<ip_interface> const ifs = {
@ -237,7 +301,7 @@ TORRENT_TEST(expand_unspecified)
, ifc("24.172.48.90", "eth1")
, ifc("::1", "lo")
, ifc("fe80::d250:99ff:fe0c:9b74", "eth0")
, ifc( "2601:646:c600:a3:d250:99ff:fe0c:9b74", "eth0")
, ifc("2601:646:c600:a3:d250:99ff:fe0c:9b74", "eth0")
};
auto v4_nossl = ep("0.0.0.0", 6881);

View File

@ -345,7 +345,6 @@ void test_udp_tracker(std::string const& iface, address tracker, tcp::endpoint c
settings_pack pack = settings();
pack.set_bool(settings_pack::announce_to_all_trackers, true);
pack.set_bool(settings_pack::announce_to_all_tiers, true);
pack.set_str(settings_pack::listen_interfaces, iface + ":48875");
std::unique_ptr<lt::session> s(new lt::session(pack));
@ -420,7 +419,10 @@ TORRENT_TEST(udp_tracker_v6)
{
if (supports_ipv6())
{
test_udp_tracker("[::1]", address_v6::any(), ep("::1.3.3.7", 1337));
// if the machine running the test doesn't have an actual IPv6 connection
// the test would fail with any other address than loopback (because it
// would be unreachable)
test_udp_tracker("[::1]", address_v6::any(), ep("::1", 1337));
}
}

View File

@ -138,10 +138,10 @@ struct udp_tracker
detail::write_uint32(0, ptr);
detail::write_uint32(0, ptr);
detail::write_uint32(0, ptr);
detail::write_uint8(0, ptr);
detail::write_uint8(0, ptr);
detail::write_uint8(0, ptr);
detail::write_uint8(1, ptr);
detail::write_uint8(3, ptr);
detail::write_uint8(3, ptr);
detail::write_uint8(7, ptr);
detail::write_uint16(1337, ptr);
}
else