make tracker announces happen even if there are no open listen sockets (#2529)

support announcing to IPv6 trackers and running an IPv6 DHT when not listening for incoming connections
This commit is contained in:
Arvid Norberg 2017-11-20 23:42:22 +01:00 committed by GitHub
parent 30a7b52855
commit c734f42ac3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 233 additions and 52 deletions

View File

@ -128,6 +128,12 @@ namespace aux {
struct tracker_logger;
#endif
enum class duplex : std::uint8_t
{
accept_incoming,
only_outgoing
};
struct listen_socket_t
{
listen_socket_t()
@ -181,6 +187,8 @@ namespace aux {
// indicates whether this is an SSL listen socket or not
transport ssl = transport::plaintext;
duplex incoming = duplex::accept_incoming;
// the actual sockets (TCP listen socket and UDP socket)
// An entry does not necessarily have a UDP or TCP socket. One of these
// pointers may be nullptr!
@ -194,8 +202,9 @@ namespace aux {
struct TORRENT_EXTRA_EXPORT listen_endpoint_t
{
listen_endpoint_t(address adr, int p, std::string dev, transport s)
: addr(adr), port(p), device(dev), ssl(s) {}
listen_endpoint_t(address adr, int p, std::string dev, transport s
, duplex d = duplex::accept_incoming)
: addr(adr), port(p), device(dev), ssl(s), incoming(d) {}
bool operator==(listen_endpoint_t const& o) const
{
@ -206,6 +215,7 @@ namespace aux {
int port;
std::string device;
transport ssl;
duplex incoming;
};
// partitions sockets based on whether they match one of the given endpoints
@ -675,6 +685,7 @@ namespace aux {
// implements session_interface
tcp::endpoint bind_outgoing_socket(socket_type& s, address
const& remote_address, error_code& ec) const override;
bool verify_incoming_interface(address const& addr);
bool verify_bound_address(address const& addr, bool utp
, error_code& ec) override;
@ -761,8 +772,8 @@ namespace aux {
void set_external_address(std::shared_ptr<listen_socket_t> const& sock, address const& ip
, ip_source_t const source_type, address const& source);
void interface_to_endpoints(std::string const& device, int const port
, bool const ssl, std::vector<listen_endpoint_t>& eps);
void interface_to_endpoints(std::string const& device, int port
, transport ssl, duplex incoming, std::vector<listen_endpoint_t>& eps);
// the settings for the client
aux::session_settings m_settings;
@ -937,8 +948,8 @@ namespace aux {
// round-robin index into m_outgoing_interfaces
mutable std::uint8_t m_interface_index = 0;
std::shared_ptr<listen_socket_t> setup_listener(std::string const& device
, tcp::endpoint bind_ep, transport ssl, error_code& ec);
std::shared_ptr<listen_socket_t> setup_listener(
listen_endpoint_t const& lep, error_code& ec);
#ifndef TORRENT_DISABLE_DHT
dht::dht_state m_dht_state;

View File

@ -155,6 +155,7 @@ namespace libtorrent {
#endif
sha1_hash info_hash;
peer_id pid;
aux::listen_socket_handle outgoing_socket;
// set to true if the .torrent file this tracker announce is for is marked

View File

@ -97,7 +97,7 @@ namespace libtorrent {
void fail(error_code const& ec, int code = -1
, char const* msg = ""
, seconds32 interval = seconds32(0)
, seconds32 min_interval = seconds32(0));
, seconds32 min_interval = seconds32(30));
void send_udp_connect();
void send_udp_announce();

View File

@ -406,6 +406,104 @@ void test_ipv6_support(char const* listen_interfaces
TEST_EQUAL(v6_announces, expect_v6);
}
void test_udpv6_support(char const* listen_interfaces
, int const expect_v4, int const expect_v6)
{
using sim::asio::ip::address_v4;
sim_config network_cfg;
sim::simulation sim{network_cfg};
sim::asio::io_service web_server_v4(sim, address_v4::from_string("10.0.0.2"));
sim::asio::io_service web_server_v6(sim, address_v6::from_string("ff::dead:beef"));
int v4_announces = 0;
int v6_announces = 0;
{
lt::session_proxy zombie;
std::vector<asio::ip::address> ips;
for (int i = 0; i < num_interfaces; i++)
{
char ep[30];
std::snprintf(ep, sizeof(ep), "10.0.0.%d", i + 1);
ips.push_back(address::from_string(ep));
std::snprintf(ep, sizeof(ep), "ffff::1337:%d", i + 1);
ips.push_back(address::from_string(ep));
}
asio::io_service ios(sim, ips);
lt::settings_pack sett = settings();
if (listen_interfaces)
{
sett.set_str(settings_pack::listen_interfaces, listen_interfaces);
}
std::unique_ptr<lt::session> ses(new lt::session(sett, ios));
// since we don't have a udp tracker to run in the sim, looking for the
// alerts is the closest proxy
ses->set_alert_notify([&]{
ses->get_io_service().post([&] {
std::vector<lt::alert*> alerts;
ses->pop_alerts(&alerts);
for (lt::alert* a : alerts)
{
lt::time_duration d = a->timestamp().time_since_epoch();
std::uint32_t const millis = std::uint32_t(
lt::duration_cast<lt::milliseconds>(d).count());
std::printf("%4d.%03d: %s\n", millis / 1000, millis % 1000,
a->message().c_str());
if (auto tr = alert_cast<tracker_announce_alert>(a))
{
if (tr->local_endpoint.address().is_v4())
++v4_announces;
else
++v6_announces;
}
else if (alert_cast<tracker_error_alert>(a))
{
TEST_CHECK(false && "unexpected tracker error");
}
}
});
});
lt::add_torrent_params p;
p.name = "test-torrent";
p.save_path = ".";
p.info_hash.assign("abababababababababab");
p.trackers.push_back("udp://tracker.com:8080/announce");
ses->async_add_torrent(p);
// stop the torrent 5 seconds in
sim::timer t1(sim, lt::seconds(5)
, [&ses](boost::system::error_code const&)
{
std::vector<lt::torrent_handle> torrents = ses->get_torrents();
for (auto const& t : torrents)
{
t.pause();
}
});
// then shut down 10 seconds in
sim::timer t2(sim, lt::seconds(10)
, [&ses,&zombie](boost::system::error_code const&)
{
zombie = ses->abort();
ses.reset();
});
sim.run();
}
TEST_EQUAL(v4_announces, expect_v4);
TEST_EQUAL(v6_announces, expect_v6);
}
// this test makes sure that a tracker whose host name resolves to both IPv6 and
// IPv4 addresses will be announced to twice, once for each address family
TORRENT_TEST(ipv6_support)
@ -414,6 +512,20 @@ TORRENT_TEST(ipv6_support)
test_ipv6_support(nullptr, 2, num_interfaces * 2);
}
TORRENT_TEST(announce_no_listen)
{
// if we don't listen on any sockets at all (but only make outgoing peer
// connections) we still need to make sure we announce to trackers
test_ipv6_support("", 2, 2);
}
TORRENT_TEST(announce_udp_no_listen)
{
// since there's no actual udp tracker in this test, we will only try to
// announce once, and fail. We won't announce the event=stopped
test_udpv6_support("", 1, 1);
}
TORRENT_TEST(ipv6_support_bind_v4_v6_any)
{
// 2 because there's one announce on startup and one when shutting down

View File

@ -1348,7 +1348,10 @@ namespace {
!= m_settings.get_int(settings_pack::ssl_listen))
||
#endif
(pack.has_val(settings_pack::listen_interfaces)
(pack.has_val(settings_pack::force_proxy)
&& !pack.get_bool(settings_pack::force_proxy)
&& m_settings.get_bool(settings_pack::force_proxy))
|| (pack.has_val(settings_pack::listen_interfaces)
&& pack.get_str(settings_pack::listen_interfaces)
!= m_settings.get_str(settings_pack::listen_interfaces));
@ -1381,25 +1384,27 @@ namespace {
reopen_outgoing_sockets();
}
std::shared_ptr<listen_socket_t> session_impl::setup_listener(std::string const& device
, tcp::endpoint bind_ep, transport const ssl, error_code& ec)
std::shared_ptr<listen_socket_t> session_impl::setup_listener(
listen_endpoint_t const& lep, error_code& ec)
{
int retries = m_settings.get_int(settings_pack::max_retry_port_bind);
tcp::endpoint bind_ep(lep.addr, std::uint16_t(lep.port));
#ifndef TORRENT_DISABLE_LOGGING
if (should_log())
{
session_log("attempting to open listen socket to: %s on device: %s ssl: %x"
, print_endpoint(bind_ep).c_str(), device.c_str(), static_cast<int>(ssl));
, print_endpoint(bind_ep).c_str(), lep.device.c_str(), static_cast<int>(lep.ssl));
}
#endif
auto ret = std::make_shared<listen_socket_t>();
ret->ssl = ssl;
ret->ssl = lep.ssl;
ret->original_port = bind_ep.port();
ret->incoming = lep.incoming;
operation_t last_op = operation_t::unknown;
socket_type_t const sock_type
= (ssl == transport::ssl)
= (lep.ssl == transport::ssl)
? socket_type_t::tcp_ssl
: socket_type_t::tcp;
@ -1407,7 +1412,7 @@ namespace {
// accept connections on our local machine in this case.
// TODO: 3 the logic in this if-block should be factored out into a
// separate function. At least most of it
if (!m_settings.get_bool(settings_pack::force_proxy))
if (ret->incoming == duplex::accept_incoming)
{
ret->sock = std::make_shared<tcp::acceptor>(m_io_service);
ret->sock->open(bind_ep.protocol(), ec);
@ -1423,7 +1428,7 @@ namespace {
#endif
if (m_alerts.should_post<listen_failed_alert>())
m_alerts.emplace_alert<listen_failed_alert>(device, bind_ep, last_op
m_alerts.emplace_alert<listen_failed_alert>(lep.device, bind_ep, last_op
, ec, sock_type);
return ret;
}
@ -1484,26 +1489,26 @@ namespace {
}
#endif // TORRENT_USE_IPV6
if (!device.empty())
if (!lep.device.empty())
{
// we have an actual device we're interested in listening on, if we
// have SO_BINDTODEVICE functionality, use it now.
#if TORRENT_HAS_BINDTODEVICE
ret->sock->set_option(bind_to_device(device.c_str()), ec);
ret->sock->set_option(bind_to_device(lep.device.c_str()), ec);
if (ec)
{
#ifndef TORRENT_DISABLE_LOGGING
if (should_log())
{
session_log("bind to device failed (device: %s): %s"
, device.c_str(), ec.message().c_str());
, lep.device.c_str(), ec.message().c_str());
}
#endif // TORRENT_DISABLE_LOGGING
last_op = operation_t::sock_bind_to_device;
if (m_alerts.should_post<listen_failed_alert>())
{
m_alerts.emplace_alert<listen_failed_alert>(device, bind_ep
m_alerts.emplace_alert<listen_failed_alert>(lep.device, bind_ep
, last_op, ec, sock_type);
}
return ret;
@ -1523,7 +1528,7 @@ namespace {
session_log("failed to bind listen socket to: %s on device: %s :"
" [%s] (%d) %s (retries: %d)"
, print_endpoint(bind_ep).c_str()
, device.c_str()
, lep.device.c_str()
, ec.category().name(), ec.value(), ec.message().c_str()
, retries);
}
@ -1555,20 +1560,20 @@ namespace {
session_log("failed to bind listen socket to: %s on device: %s :"
" [%s] (%d) %s (giving up)"
, print_endpoint(bind_ep).c_str()
, device.c_str()
, lep.device.c_str()
, ec.category().name(), ec.value(), ec.message().c_str());
}
#endif
if (m_alerts.should_post<listen_failed_alert>())
{
m_alerts.emplace_alert<listen_failed_alert>(device, bind_ep
m_alerts.emplace_alert<listen_failed_alert>(lep.device, bind_ep
, last_op, ec, sock_type);
}
ret->sock.reset();
return ret;
}
ret->local_endpoint = ret->sock->local_endpoint(ec);
ret->device = device;
ret->device = lep.device;
last_op = operation_t::getname;
if (ec)
{
@ -1581,7 +1586,7 @@ namespace {
#endif
if (m_alerts.should_post<listen_failed_alert>())
{
m_alerts.emplace_alert<listen_failed_alert>(device, bind_ep
m_alerts.emplace_alert<listen_failed_alert>(lep.device, bind_ep
, last_op, ec, sock_type);
}
return ret;
@ -1599,20 +1604,20 @@ namespace {
if (should_log())
{
session_log("cannot listen on interface \"%s\": %s"
, device.c_str(), ec.message().c_str());
, lep.device.c_str(), ec.message().c_str());
}
#endif
if (m_alerts.should_post<listen_failed_alert>())
{
m_alerts.emplace_alert<listen_failed_alert>(device, bind_ep
m_alerts.emplace_alert<listen_failed_alert>(lep.device, bind_ep
, last_op, ec, sock_type);
}
return ret;
}
} // force-proxy mode
} // accept incoming
socket_type_t const udp_sock_type
= (ssl == transport::ssl)
= (lep.ssl == transport::ssl)
? socket_type_t::utp_ssl
: socket_type_t::udp;
udp::endpoint const udp_bind_ep(bind_ep.address(), bind_ep.port());
@ -1625,36 +1630,36 @@ namespace {
if (should_log())
{
session_log("failed to open UDP socket: %s: %s"
, device.c_str(), ec.message().c_str());
, lep.device.c_str(), ec.message().c_str());
}
#endif
last_op = operation_t::sock_open;
if (m_alerts.should_post<listen_failed_alert>())
m_alerts.emplace_alert<listen_failed_alert>(device
m_alerts.emplace_alert<listen_failed_alert>(lep.device
, bind_ep, last_op, ec, udp_sock_type);
return ret;
}
#if TORRENT_HAS_BINDTODEVICE
if (!device.empty())
if (!lep.device.empty())
{
ret->udp_sock->sock.set_option(bind_to_device(device.c_str()), ec);
ret->udp_sock->sock.set_option(bind_to_device(lep.device.c_str()), ec);
if (ec)
{
#ifndef TORRENT_DISABLE_LOGGING
if (should_log())
{
session_log("bind to device failed (device: %s): %s"
, device.c_str(), ec.message().c_str());
, lep.device.c_str(), ec.message().c_str());
}
#endif // TORRENT_DISABLE_LOGGING
last_op = operation_t::sock_bind_to_device;
if (m_alerts.should_post<listen_failed_alert>())
{
m_alerts.emplace_alert<listen_failed_alert>(device, bind_ep
m_alerts.emplace_alert<listen_failed_alert>(lep.device, bind_ep
, last_op, ec, udp_sock_type);
}
return ret;
@ -1670,18 +1675,26 @@ namespace {
if (should_log())
{
session_log("failed to bind UDP socket: %s: %s"
, device.c_str(), ec.message().c_str());
, lep.device.c_str(), ec.message().c_str());
}
#endif
if (m_alerts.should_post<listen_failed_alert>())
m_alerts.emplace_alert<listen_failed_alert>(device
m_alerts.emplace_alert<listen_failed_alert>(lep.device
, bind_ep, last_op, ec, udp_sock_type);
return ret;
}
ret->udp_external_port = ret->udp_sock->sock.local_port();
// if we did not open a TCP listen socket, ret->local_endpoint was never
// initialized, so do that now, based on the UDP socket
if (ret->incoming != duplex::accept_incoming)
{
auto const udp_ep = ret->udp_sock->local_endpoint();
ret->local_endpoint = tcp::endpoint(udp_ep.address(), udp_ep.port());
}
error_code err;
set_socket_buffer_size(ret->udp_sock->sock, m_settings, err);
if (err)
@ -1747,7 +1760,7 @@ namespace {
}
void session_impl::interface_to_endpoints(std::string const& device, int const port
, bool const ssl, std::vector<listen_endpoint_t>& eps)
, transport const ssl, duplex const incoming, std::vector<listen_endpoint_t>& eps)
{
// First, check to see if it's an IP address
error_code err;
@ -1757,8 +1770,7 @@ namespace {
#if !TORRENT_USE_IPV6
if (adr.is_v4())
#endif
eps.emplace_back(adr, port, std::string()
, ssl ? transport::ssl : transport::plaintext);
eps.emplace_back(adr, port, std::string(), ssl, incoming);
}
else
{
@ -1791,8 +1803,7 @@ namespace {
// (which must be of the same family as the address we're
// connecting to)
if (device != ipface.name) continue;
eps.emplace_back(ipface.interface_address, port, device
, ssl ? transport::ssl : transport::plaintext);
eps.emplace_back(ipface.interface_address, port, device, ssl, incoming);
}
}
}
@ -1817,14 +1828,18 @@ namespace {
// of a new socket failing to bind due to a conflict with a stale socket
std::vector<listen_endpoint_t> eps;
duplex const incoming = m_settings.get_bool(settings_pack::force_proxy)
? duplex::only_outgoing
: duplex::accept_incoming;
for (auto const& iface : m_listen_interfaces)
{
std::string const& device = iface.device;
int const port = iface.port;
bool const ssl = iface.ssl;
transport const ssl = iface.ssl ? transport::ssl : transport::plaintext;
#ifndef TORRENT_USE_OPENSSL
if (ssl)
if (ssl == transport::ssl)
{
#ifndef TORRENT_DISABLE_LOGGING
session_log("attempted to listen ssl with no library support on device: \"%s\""
@ -1845,7 +1860,7 @@ namespace {
// IP address or a device name. In case it's a device name, we want to
// (potentially) end up binding a socket for each IP address associated
// with that device.
interface_to_endpoints(device, port, ssl, eps);
interface_to_endpoints(device, port, ssl, incoming, eps);
}
#if TORRENT_USE_IPV6
@ -1856,6 +1871,18 @@ namespace {
}
#endif
// if no listen interfaces are specified, create sockets to use
// any interface
if (eps.empty())
{
eps.emplace_back(address_v4(), 0, "", transport::plaintext
, duplex::only_outgoing);
#if TORRENT_USE_IPV6
eps.emplace_back(address_v6(), 0, "", transport::plaintext
, duplex::only_outgoing);
#endif
}
auto remove_iter = partition_listen_sockets(eps, m_listen_sockets);
while (remove_iter != m_listen_sockets.end())
@ -1882,8 +1909,7 @@ namespace {
// an existing socket
for (auto const& ep : eps)
{
std::shared_ptr<listen_socket_t> s = setup_listener(ep.device
, tcp::endpoint(ep.addr, std::uint16_t(ep.port)), ep.ssl, ec);
std::shared_ptr<listen_socket_t> s = setup_listener(ep, ec);
if (!ec && (s->sock || s->udp_sock))
{
@ -1954,6 +1980,7 @@ namespace {
// initiate accepting on the listen sockets
for (auto& s : m_listen_sockets)
{
TORRENT_ASSERT((s->incoming == duplex::accept_incoming) == bool(s->sock));
if (s->sock) async_accept(s->sock, s->ssl);
if (map_ports) remap_ports(remap_natpmp_and_upnp, *s);
}
@ -1972,9 +1999,9 @@ namespace {
for (auto const& iface : m_outgoing_interfaces)
{
interface_to_endpoints(iface, 0, false, eps);
interface_to_endpoints(iface, 0, transport::plaintext, duplex::accept_incoming, eps);
#ifdef TORRENT_USE_OPENSSL
interface_to_endpoints(iface, 0, true, eps);
interface_to_endpoints(iface, 0, transport::ssl, duplex::accept_incoming, eps);
#endif
}
@ -2411,7 +2438,7 @@ namespace {
span<char const> const buf = packet.data;
// give the uTP socket manager first dis on the packet. Presumably
// give the uTP socket manager first dibs on the packet. Presumably
// the majority of packets are uTP packets.
if (!mgr.incoming_packet(socket, packet.from, buf))
{
@ -2746,7 +2773,7 @@ namespace {
}
// if there are outgoing interfaces specified, verify this
// peer is correctly bound to on of them
// peer is correctly bound to one of them
if (!m_settings.get_str(settings_pack::outgoing_interfaces).empty())
{
tcp::endpoint local = s->local_endpoint(ec);
@ -2761,6 +2788,22 @@ namespace {
#endif
return;
}
if (!verify_incoming_interface(local.address()))
{
#ifndef TORRENT_DISABLE_LOGGING
if (should_log())
{
error_code err;
session_log(" rejected connection, local interface has incoming connections disabled: %s"
, local.address().to_string(err).c_str());
}
#endif
if (m_alerts.should_post<peer_blocked_alert>())
m_alerts.emplace_alert<peer_blocked_alert>(torrent_handle()
, endp, peer_blocked_alert::invalid_local_interface);
return;
}
if (!verify_bound_address(local.address()
, is_utp(*s), ec))
{
@ -5022,6 +5065,19 @@ namespace {
return bind_ep;
}
// verify that the interface ``addr`` belongs to, allows incoming connections
bool session_impl::verify_incoming_interface(address const& addr)
{
for (auto const& s : m_listen_sockets)
{
if (s->local_endpoint.address() == addr)
{
return s->incoming == duplex::accept_incoming;
}
}
return false;
}
// verify that the given local address satisfies the requirements of
// the outgoing interfaces. i.e. that one of the allowed outgoing
// interfaces has this address. For uTP sockets, which are all backed
@ -5042,7 +5098,7 @@ namespace {
for (auto const& s : m_outgoing_interfaces)
{
error_code err;
address ip = address::from_string(s.c_str(), err);
address const ip = address::from_string(s.c_str(), err);
if (err) continue;
if (ip == addr) return true;
}

View File

@ -2771,7 +2771,7 @@ namespace libtorrent {
{
// update the endpoint list by adding entries for new listen sockets
// and removing entries for non-existent ones
std::vector<announce_endpoint>::size_type valid_endpoints = 0;
std::size_t valid_endpoints = 0;
m_ses.for_each_listen_socket([&](aux::listen_socket_handle const& s) {
if (s.is_ssl() != is_ssl_torrent())
return;
@ -10930,6 +10930,7 @@ namespace {
, retry_interval);
aep->last_error = ec;
aep->message = msg;
fails = aep->fails;
#ifndef TORRENT_DISABLE_LOGGING
debug_log("*** increment tracker fail count [%d]", aep->fails);
#endif