From 9e22b51878b7f488f403a65eeffec8313f0c53f5 Mon Sep 17 00:00:00 2001 From: stickz Date: Mon, 25 Dec 2023 09:24:31 -0500 Subject: [PATCH] rTorrent: Implement UDNS support This pull request implements the UDNS library into libtorrent for asynchronous DNS requests on UDP trackers. The current c-ares support is limited to TCP trackers. It resolves stability issues with the rTorrent software stack where it resolves UDP trackers synchronously by default. With a large number of torrents, the software will give out and fail to seed. It also caches the result of `sscanf` into vector object, to avoid calling it thousands of times on broken trackers. This prevents the software from crashing and increases performance. --- Dockerfile | 6 +- README.md | 3 +- .../libtorrent/libtorrent-scanf-0.13.8.patch | 216 ++++++ .../libtorrent/libtorrent-udns-0.13.8.patch | 699 ++++++++++++++++++ 4 files changed, 922 insertions(+), 2 deletions(-) create mode 100644 patches/libtorrent/libtorrent-scanf-0.13.8.patch create mode 100644 patches/libtorrent/libtorrent-udns-0.13.8.patch diff --git a/Dockerfile b/Dockerfile index 85d9da1..a64e46b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -96,6 +96,7 @@ RUN apk --update --no-cache add \ php82-pear \ tar \ tree \ + udns-dev \ xz \ zlib-dev @@ -136,7 +137,9 @@ RUN tree ${DIST_PATH} WORKDIR /usr/local/src/libtorrent COPY --from=src-libtorrent /src . COPY /patches/libtorrent . -RUN patch -p1 < throttle-fix-0.13.8.patch +RUN patch -p1 < throttle-fix-0.13.8.patch \ + && patch -p1 < libtorrent-udns-0.13.8.patch \ + && patch -p1 < libtorrent-scanf-0.13.8.patch RUN ./autogen.sh RUN ./configure --with-posix-fallocate RUN make -j$(nproc) CXXFLAGS="-w -O3 -flto" @@ -239,6 +242,7 @@ RUN apk --update --no-cache add \ sox \ tar \ tzdata \ + udns \ unzip \ util-linux \ zip \ diff --git a/README.md b/README.md index 522bb1d..40ba1ff 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,9 @@ ___ * Multi-platform image * Latest [rTorrent](https://github.com/rakshasa/rtorrent) / [libTorrent](https://github.com/rakshasa/libtorrent) release compiled from source * Includes [rTorrent patches](./patches/rtorrent) to increase software stability + * Includes [libtorrent patches](./patches/libtorrent) to increase software stability * Latest [ruTorrent](https://github.com/Novik/ruTorrent) release -* Name resolving enhancements with [c-ares](https://github.com/rakshasa/rtorrent/wiki/Performance-Tuning#rtrorrent-with-c-ares) for asynchronous DNS requests (including name resolves) +* Domain name resolving enhancements with [c-ares](https://github.com/rakshasa/rtorrent/wiki/Performance-Tuning#rtrorrent-with-c-ares) and [UDNS](https://www.corpit.ru/mjt/udns.html) for asynchronous DNS requests * Enhanced [rTorrent config](rootfs/tpls/.rtorrent.rc) and bootstraping with a [local config](rootfs/tpls/etc/rtorrent/.rtlocal.rc) * XMLRPC through nginx over SCGI socket (basic auth optional) * WebDAV on completed downloads (basic auth optional) diff --git a/patches/libtorrent/libtorrent-scanf-0.13.8.patch b/patches/libtorrent/libtorrent-scanf-0.13.8.patch new file mode 100644 index 0000000..8683203 --- /dev/null +++ b/patches/libtorrent/libtorrent-scanf-0.13.8.patch @@ -0,0 +1,216 @@ +From: stickz +Date: Mon, 17 Apr 2023 17:48:59 -0400 +Subject: [PATCH] Add udpTrackerInfo vector + +--- + rak/tracker_info.h | 67 +++++++++++++++++++++++++++++++++++++ + src/globals.cc | 1 + + src/globals.h | 3 ++ + src/torrent/tracker_list.cc | 9 +++-- + src/tracker/tracker_udp.cc | 29 ++++------------ + src/tracker/tracker_udp.h | 3 +- + 6 files changed, 86 insertions(+), 26 deletions(-) + create mode 100644 rak/tracker_info.h + +diff --git a/rak/tracker_info.h b/rak/tracker_info.h +new file mode 100644 +index 000000000..50a03d136 +--- /dev/null ++++ b/rak/tracker_info.h +@@ -0,0 +1,67 @@ ++#ifndef RAK_TRACKER_H ++#define RAK_TRACKER_H ++ ++#include ++#include ++ ++namespace rak { ++ ++class tracker_info { ++public: ++ bool equals (const std::string u) { return url.compare(u) == 0; } ++ ++ void set (const std::string u, const char* h, const int p) { ++ url = u; ++ hostname.assign(h); ++ port = p; ++ broken = false; ++ } ++ void set_broken() { broken = true; } ++ bool get_broken() { return broken; } ++ ++ std::string get_url() { return url; } ++ std::string get_hostname() { return hostname; } ++ int get_port() { return port; } ++ ++private: ++ bool broken; ++ std::string url; ++ std::string hostname; ++ int port; ++}; ++ ++class udp_tracker_vector : public std::vector { ++public: ++ tracker_info get_info(const std::string url) { ++ for (size_t i=0; i base_type; ++ typedef typename base_type::reference reference; ++ using base_type::size; ++ using base_type::at; ++ ++ tracker_info create_info(const std::string url) { ++ char hostname[1024] = {0}; ++ int port; ++ tracker_info new_info; ++ ++ if (sscanf(url.c_str(), "udp://%1023[^:]:%i/announce", &hostname, &port) == 2 && hostname[0] != '\0' && port > 0 && port < (1 << 16)) { ++ new_info.set(url, hostname, port); ++ base_type::push_back(new_info); ++ return new_info; ++ } ++ ++ new_info.set_broken(); ++ return new_info; ++ } ++}; ++} ++ ++#endif +diff --git a/src/globals.cc b/src/globals.cc +index 88130c19c..c33d395a7 100644 +--- a/src/globals.cc ++++ b/src/globals.cc +@@ -43,5 +43,6 @@ namespace torrent { + + LIBTORRENT_EXPORT rak::priority_queue_default taskScheduler; + LIBTORRENT_EXPORT rak::timer cachedTime; ++LIBTORRENT_EXPORT rak::udp_tracker_vector udpTrackerInfo; + + } +diff --git a/src/globals.h b/src/globals.h +index 564ac86df..967d0d482 100644 +--- a/src/globals.h ++++ b/src/globals.h +@@ -39,12 +39,15 @@ + + #include + #include ++#include + + namespace torrent { + + extern rak::priority_queue_default taskScheduler; + extern rak::timer cachedTime; + ++extern rak::udp_tracker_vector udpTrackerInfo; ++ + } + + #endif +diff --git a/src/torrent/tracker_list.cc b/src/torrent/tracker_list.cc +index 625055da7..ee1f2dcef 100644 +--- a/src/torrent/tracker_list.cc ++++ b/src/torrent/tracker_list.cc +@@ -197,8 +197,13 @@ TrackerList::insert_url(unsigned int group, const std::string& url, bool extra_t + std::strncmp("https://", url.c_str(), 8) == 0) { + tracker = new TrackerHttp(this, url, flags); + +- } else if (std::strncmp("udp://", url.c_str(), 6) == 0) { +- tracker = new TrackerUdp(this, url, flags); ++ } else if (std::strncmp("udp://", url.c_str(), 6) == 0) { ++ rak::tracker_info udpInfo = udpTrackerInfo.get_info(url); ++ if (udpInfo.get_broken()) { ++ LT_LOG_TRACKER(INFO, "skipped broken tracker (url:%s)", url.c_str()); ++ return; ++ } ++ tracker = new TrackerUdp(this, udpInfo, flags); + + } else if (std::strncmp("dht://", url.c_str(), 6) == 0 && TrackerDht::is_allowed()) { + tracker = new TrackerDht(this, url, flags); +diff --git a/src/tracker/tracker_udp.cc b/src/tracker/tracker_udp.cc +index 04d836f49..2ba6ea212 100644 +--- a/src/tracker/tracker_udp.cc ++++ b/src/tracker/tracker_udp.cc +@@ -65,10 +65,11 @@ + + namespace torrent { + +-TrackerUdp::TrackerUdp(TrackerList* parent, const std::string& url, int flags) : +- Tracker(parent, url, flags), ++TrackerUdp::TrackerUdp(TrackerList* parent, rak::tracker_info& info, int flags) : ++ Tracker(parent, info.get_url(), flags), + +- m_port(0), ++ m_port(info.get_port()), ++ m_hostname(info.get_hostname()), + + m_readBuffer(NULL), + m_writeBuffer(NULL) { +@@ -93,35 +94,17 @@ TrackerUdp::send_state(int state) { + close_directly(); + m_latest_event = state; + +- hostname_type hostname; +- +- if (!parse_udp_url(m_url, hostname, m_port)) +- return receive_failed("could not parse hostname or port"); +- +- LT_LOG_TRACKER(DEBUG, "hostname lookup (address:%s)", hostname.data()); ++ LT_LOG_TRACKER(DEBUG, "hostname lookup (address:%s)", m_hostname.c_str()); + + m_sendState = state; + m_resolver_query = manager->connection_manager()->async_resolver().enqueue( +- hostname.data(), ++ m_hostname.c_str(), + AF_UNSPEC, + &m_resolver_callback + ); + manager->connection_manager()->async_resolver().flush(); + } + +-bool +-TrackerUdp::parse_udp_url(const std::string& url, hostname_type& hostname, int& port) const { +- if (std::sscanf(m_url.c_str(), "udp://%1023[^:]:%i", hostname.data(), &port) == 2 && hostname[0] != '\0' && +- port > 0 && port < (1 << 16)) +- return true; +- +- if (std::sscanf(m_url.c_str(), "udp://[%1023[^]]]:%i", hostname.data(), &port) == 2 && hostname[0] != '\0' && +- port > 0 && port < (1 << 16)) +- return true; +- +- return false; +-} +- + void + TrackerUdp::start_announce(const sockaddr* sa, int err) { + m_resolver_query = NULL; +diff --git a/src/tracker/tracker_udp.h b/src/tracker/tracker_udp.h +index a5f250a77..480a1e885 100644 +--- a/src/tracker/tracker_udp.h ++++ b/src/tracker/tracker_udp.h +@@ -58,7 +58,7 @@ class TrackerUdp : public SocketDatagram, public Tracker { + + static const uint64_t magic_connection_id = 0x0000041727101980ll; + +- TrackerUdp(TrackerList* parent, const std::string& url, int flags); ++ TrackerUdp(TrackerList* parent, rak::tracker_info& info, int flags); + ~TrackerUdp(); + + const char* type_name() const { return "tracker_udp"; } +@@ -95,6 +95,7 @@ class TrackerUdp : public SocketDatagram, public Tracker { + + rak::socket_address m_connectAddress; + int m_port; ++ std::string m_hostname; + + int m_sendState; + diff --git a/patches/libtorrent/libtorrent-udns-0.13.8.patch b/patches/libtorrent/libtorrent-udns-0.13.8.patch new file mode 100644 index 0000000..d4079c4 --- /dev/null +++ b/patches/libtorrent/libtorrent-udns-0.13.8.patch @@ -0,0 +1,699 @@ +From: stickz +Date: Mon, 17 Apr 2023 13:10:00 -0400 +Subject: [PATCH] Udns 0.13.8 (#3) + +--- + configure.ac | 2 + + scripts/udns.m4 | 26 ++++ + src/torrent/connection_manager.cc | 81 +++++++++++- + src/torrent/connection_manager.h | 44 +++++-- + src/tracker/tracker_udp.cc | 49 ++----- + src/tracker/tracker_udp.h | 6 +- + src/utils/Makefile.am | 4 +- + src/utils/udnsevent.cc | 208 ++++++++++++++++++++++++++++++ + src/utils/udnsevent.h | 57 ++++++++ + 9 files changed, 423 insertions(+), 54 deletions(-) + create mode 100644 scripts/udns.m4 + create mode 100644 src/utils/udnsevent.cc + create mode 100644 src/utils/udnsevent.h + +diff --git a/configure.ac b/configure.ac +index 4ed08124b..ef4a4fab8 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -103,6 +103,8 @@ AC_ARG_ENABLE(cyrus-rc4, + ] + ) + ++TORRENT_WITH_UDNS() ++ + AC_CHECK_FUNCS(posix_memalign) + + TORRENT_CHECK_MADVISE() +diff --git a/scripts/udns.m4 b/scripts/udns.m4 +new file mode 100644 +index 000000000..fee645c9d +--- /dev/null ++++ b/scripts/udns.m4 +@@ -0,0 +1,26 @@ ++dnl function for enabling/disabling udns support ++AC_DEFUN([TORRENT_WITH_UDNS], [ ++ AC_ARG_WITH( ++ [udns], ++ AS_HELP_STRING([--without-udns], [Don't use udns, falling back to synchronous DNS resolution.]) ++ ) ++dnl neither ubuntu nor fedora ships a pkgconfig file for udns ++ AS_IF( ++ [test "x$with_udns" != "xno"], ++ [AC_CHECK_HEADERS([udns.h], [have_udns=yes], [have_udns=no])], ++ [have_udns=no] ++ ) ++ AS_IF( ++ [test "x$have_udns" = "xyes"], ++ [ ++ AC_DEFINE(USE_UDNS, 1, Define to build with udns support.) ++ LIBS="$LIBS -ludns" ++ ], ++ [ ++ AS_IF( ++ [test "x$with_udns" = "xyes"], ++ [AC_MSG_ERROR([udns requested but not found])] ++ ) ++ ] ++ ) ++]) +diff --git a/src/torrent/connection_manager.cc b/src/torrent/connection_manager.cc +index 972dcbfc3..f1a5778a6 100644 +--- a/src/torrent/connection_manager.cc ++++ b/src/torrent/connection_manager.cc +@@ -48,11 +48,79 @@ + #include "exceptions.h" + #include "manager.h" + ++#ifdef USE_UDNS ++#include "utils/udnsevent.h" ++#endif ++ + namespace torrent { + +-// Fix TrackerUdp, etc, if this is made async. +-static ConnectionManager::slot_resolver_result_type* +-resolve_host(const char* host, int family, int socktype, ConnectionManager::slot_resolver_result_type slot) { ++AsyncResolver::AsyncResolver(ConnectionManager *) {} ++ ++#ifdef USE_UDNS ++class UdnsAsyncResolver : public AsyncResolver { ++public: ++ UdnsAsyncResolver(ConnectionManager *cm) : AsyncResolver(cm) {} ++ ++ void *enqueue(const char *name, int family, resolver_callback *cbck) { ++ return m_udnsevent.enqueue_resolve(name, family, cbck); ++ } ++ ++ void flush() { ++ m_udnsevent.flush_resolves(); ++ } ++ ++ void cancel(void *query) { ++ m_udnsevent.cancel(static_cast(query)); ++ } ++ ++protected: ++ UdnsEvent m_udnsevent; ++}; ++#define ASYNC_RESOLVER_IMPL UdnsAsyncResolver ++#else ++class StubAsyncResolver : public AsyncResolver { ++public: ++ struct mock_resolve { ++ std::string hostname; ++ int family; ++ resolver_callback *callback; ++ }; ++ ++ StubAsyncResolver(ConnectionManager *cm): AsyncResolver(cm), m_connection_manager(cm) {} ++ ++ void *enqueue(const char *name, int family, resolver_callback *cbck) { ++ mock_resolve *mr = new mock_resolve {name, family, cbck}; ++ m_mock_resolve_queue.emplace_back(mr); ++ return mr; ++ } ++ ++ void flush() { ++ // dequeue all callbacks and resolve them synchronously ++ while (!m_mock_resolve_queue.empty()) { ++ std::unique_ptr mr = std::move(m_mock_resolve_queue.back()); ++ m_mock_resolve_queue.pop_back(); ++ m_connection_manager->resolver()(mr->hostname.c_str(), mr->family, 0, *(mr->callback)); ++ } ++ } ++ ++ void cancel(void *query) { ++ auto it = std::find( ++ std::begin(m_mock_resolve_queue), ++ std::end(m_mock_resolve_queue), ++ std::unique_ptr(static_cast(query)) ++ ); ++ if (it != std::end(m_mock_resolve_queue)) m_mock_resolve_queue.erase(it); ++ } ++ ++protected: ++ ConnectionManager *m_connection_manager; ++ std::vector> m_mock_resolve_queue; ++}; ++#define ASYNC_RESOLVER_IMPL StubAsyncResolver ++#endif ++ ++static void ++resolve_host(const char* host, int family, int socktype, resolver_callback slot) { + if (manager->main_thread_main()->is_current()) + thread_base::release_global_lock(); + +@@ -64,7 +132,7 @@ resolve_host(const char* host, int family, int socktype, ConnectionManager::slot + thread_base::acquire_global_lock(); + + slot(NULL, err); +- return NULL; ++ return; + } + + rak::socket_address sa; +@@ -75,7 +143,7 @@ resolve_host(const char* host, int family, int socktype, ConnectionManager::slot + thread_base::acquire_global_lock(); + + slot(sa.c_sockaddr(), 0); +- return NULL; ++ return; + } + + ConnectionManager::ConnectionManager() : +@@ -89,7 +157,8 @@ ConnectionManager::ConnectionManager() : + + m_listen(new Listen), + m_listen_port(0), +- m_listen_backlog(SOMAXCONN) { ++ m_listen_backlog(SOMAXCONN), ++ m_async_resolver(new ASYNC_RESOLVER_IMPL(this)) { + + m_bindAddress = (new rak::socket_address())->c_sockaddr(); + m_localAddress = (new rak::socket_address())->c_sockaddr(); +diff --git a/src/torrent/connection_manager.h b/src/torrent/connection_manager.h +index 2dcf2b379..5b03f110f 100644 +--- a/src/torrent/connection_manager.h ++++ b/src/torrent/connection_manager.h +@@ -39,12 +39,13 @@ + #ifndef LIBTORRENT_CONNECTION_MANAGER_H + #define LIBTORRENT_CONNECTION_MANAGER_H + +-#include + #include + #include + #include + #include + #include ++#include ++#include + #include lt_tr1_functional + #include + +@@ -54,6 +55,29 @@ namespace torrent { + // First element is upload throttle, second element is download throttle. + typedef std::pair ThrottlePair; + ++// The sockaddr argument in the result call is NULL if the resolve failed, ++// and the int holds the error code. ++typedef std::function resolver_callback; ++ ++// Encapsulates whether we do genuine async resolution or fall back to sync. ++// In a build with USE_UDNS, these do genuine asynchronous DNS resolution. ++// In a build without it, they're stubbed out to use a synchronous getaddrinfo(3) ++// call, while exposing the same API. ++class LIBTORRENT_EXPORT AsyncResolver { ++public: ++ AsyncResolver(ConnectionManager *); ++ ++ // this queues a DNS resolve but doesn't send it. it doesn't execute any callbacks ++ // and returns control immediately. the return value is an opaque identifier that ++ // can be used to cancel the query (as long as the callback hasn't been executed yet): ++ virtual void* enqueue(const char *name, int family, resolver_callback *cbck) = 0; ++ // this sends any queued resolves. it can execute arbitrary callbacks ++ // before returning control: ++ virtual void flush() = 0; ++ // this cancels a pending async query (as long as the callback hasn't executed yet): ++ virtual void cancel(void *query) = 0; ++}; ++ + class LIBTORRENT_EXPORT ConnectionManager { + public: + typedef uint32_t size_type; +@@ -100,9 +124,7 @@ class LIBTORRENT_EXPORT ConnectionManager { + typedef std::function slot_filter_type; + typedef std::function slot_throttle_type; + +- // The sockaddr argument in the result slot call is NULL if the resolve failed, and the int holds the errno. +- typedef std::function slot_resolver_result_type; +- typedef std::function slot_resolver_type; ++ typedef std::function slot_resolver_type; + + ConnectionManager(); + ~ConnectionManager(); +@@ -154,12 +176,16 @@ class LIBTORRENT_EXPORT ConnectionManager { + void set_listen_port(port_type p) { m_listen_port = p; } + void set_listen_backlog(int v); + +- // The resolver returns a pointer to its copy of the result slot +- // which the caller may set blocked to prevent the slot from being +- // called. The pointer must be NULL if the result slot was already +- // called because the resolve was synchronous. ++ void* enqueue_async_resolve(const char *name, int family, resolver_callback *cbck); ++ void flush_async_resolves(); ++ void cancel_async_resolve(void *query); ++ ++ // Legacy synchronous resolver interface. + slot_resolver_type& resolver() { return m_slot_resolver; } + ++ // Asynchronous resolver interface. ++ AsyncResolver& async_resolver() { return *m_async_resolver; } ++ + // The slot returns a ThrottlePair to use for the given address, or + // NULLs to use the default throttle. + slot_throttle_type& address_throttle() { return m_slot_address_throttle; } +@@ -190,6 +216,8 @@ class LIBTORRENT_EXPORT ConnectionManager { + slot_filter_type m_slot_filter; + slot_resolver_type m_slot_resolver; + slot_throttle_type m_slot_address_throttle; ++ ++ std::unique_ptr m_async_resolver; + }; + + } +diff --git a/src/tracker/tracker_udp.cc b/src/tracker/tracker_udp.cc +index 93493e478..04d836f49 100644 +--- a/src/tracker/tracker_udp.cc ++++ b/src/tracker/tracker_udp.cc +@@ -70,25 +70,22 @@ TrackerUdp::TrackerUdp(TrackerList* parent, const std::string& url, int flags) : + + m_port(0), + +- m_slot_resolver(NULL), + m_readBuffer(NULL), + m_writeBuffer(NULL) { + + m_taskTimeout.slot() = std::bind(&TrackerUdp::receive_timeout, this); ++ ++ m_resolver_callback = std::bind(&TrackerUdp::start_announce, this, std::placeholders::_1, std::placeholders::_2); ++ m_resolver_query = NULL; + } + + TrackerUdp::~TrackerUdp() { +- if (m_slot_resolver != NULL) { +- *m_slot_resolver = resolver_type(); +- m_slot_resolver = NULL; +- } +- + close_directly(); + } + + bool + TrackerUdp::is_busy() const { +- return get_fd().is_valid(); ++ return (m_resolver_query != NULL) || get_fd().is_valid(); + } + + void +@@ -104,15 +101,12 @@ TrackerUdp::send_state(int state) { + LT_LOG_TRACKER(DEBUG, "hostname lookup (address:%s)", hostname.data()); + + m_sendState = state; +- +- // Because we can only remember one slot, set any pending resolves blocked +- // so that if this tracker is deleted, the member function won't be called. +- if (m_slot_resolver != NULL) { +- *m_slot_resolver = resolver_type(); +- m_slot_resolver = NULL; +- } +- +- m_slot_resolver = make_resolver_slot(hostname); ++ m_resolver_query = manager->connection_manager()->async_resolver().enqueue( ++ hostname.data(), ++ AF_UNSPEC, ++ &m_resolver_callback ++ ); ++ manager->connection_manager()->async_resolver().flush(); + } + + bool +@@ -128,21 +122,9 @@ TrackerUdp::parse_udp_url(const std::string& url, hostname_type& hostname, int& + return false; + } + +-TrackerUdp::resolver_type* +-TrackerUdp::make_resolver_slot(const hostname_type& hostname) { +- return manager->connection_manager()->resolver()(hostname.data(), PF_UNSPEC, SOCK_DGRAM, +- std::bind(&TrackerUdp::start_announce, +- this, +- std::placeholders::_1, +- std::placeholders::_2)); +-} +- + void + TrackerUdp::start_announce(const sockaddr* sa, int err) { +- if (m_slot_resolver != NULL) { +- *m_slot_resolver = resolver_type(); +- m_slot_resolver = NULL; +- } ++ m_resolver_query = NULL; + + if (sa == NULL) + return receive_failed("could not resolve hostname"); +@@ -180,9 +162,6 @@ TrackerUdp::start_announce(const sockaddr* sa, int err) { + + void + TrackerUdp::close() { +- if (!get_fd().is_valid()) +- return; +- + LT_LOG_TRACKER(DEBUG, "request cancelled (state:%s url:%s)", + option_as_string(OPTION_TRACKER_EVENT, m_latest_event), m_url.c_str()); + +@@ -191,9 +170,6 @@ TrackerUdp::close() { + + void + TrackerUdp::disown() { +- if (!get_fd().is_valid()) +- return; +- + LT_LOG_TRACKER(DEBUG, "request disowned (state:%s url:%s)", + option_as_string(OPTION_TRACKER_EVENT, m_latest_event), m_url.c_str()); + +@@ -202,6 +178,9 @@ TrackerUdp::disown() { + + void + TrackerUdp::close_directly() { ++ manager->connection_manager()->async_resolver().cancel(m_resolver_query); ++ m_resolver_query = NULL; ++ + if (!get_fd().is_valid()) + return; + +diff --git a/src/tracker/tracker_udp.h b/src/tracker/tracker_udp.h +index aaa7ff671..a5f250a77 100644 +--- a/src/tracker/tracker_udp.h ++++ b/src/tracker/tracker_udp.h +@@ -56,8 +56,6 @@ class TrackerUdp : public SocketDatagram, public Tracker { + typedef ProtocolBuffer<512> ReadBuffer; + typedef ProtocolBuffer<512> WriteBuffer; + +- typedef ConnectionManager::slot_resolver_result_type resolver_type; +- + static const uint64_t magic_connection_id = 0x0000041727101980ll; + + TrackerUdp(TrackerList* parent, const std::string& url, int flags); +@@ -94,14 +92,14 @@ class TrackerUdp : public SocketDatagram, public Tracker { + bool process_error_output(); + + bool parse_udp_url(const std::string& url, hostname_type& hostname, int& port) const; +- resolver_type* make_resolver_slot(const hostname_type& hostname); + + rak::socket_address m_connectAddress; + int m_port; + + int m_sendState; + +- resolver_type* m_slot_resolver; ++ resolver_callback m_resolver_callback; ++ void* m_resolver_query; + + uint32_t m_action; + uint64_t m_connectionId; +diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am +index 27ce359b1..d148ff798 100644 +--- a/src/utils/Makefile.am ++++ b/src/utils/Makefile.am +@@ -9,6 +9,8 @@ libsub_utils_la_SOURCES = \ + sha1.h \ + sha_fast.cc \ + sha_fast.h \ +- queue_buckets.h ++ queue_buckets.h \ ++ udnsevent.cc \ ++ udnsevent.h + + AM_CPPFLAGS = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir) +diff --git a/src/utils/udnsevent.cc b/src/utils/udnsevent.cc +new file mode 100644 +index 000000000..b53d32e8a +--- /dev/null ++++ b/src/utils/udnsevent.cc +@@ -0,0 +1,208 @@ ++#include "config.h" ++#ifdef USE_UDNS ++ ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include "udnsevent.h" ++#include "globals.h" ++#include "manager.h" ++#include "torrent/poll.h" ++ ++namespace torrent { ++ ++int udnserror_to_gaierror(int udnserror) { ++ switch (udnserror) { ++ case DNS_E_TEMPFAIL: ++ return EAI_AGAIN; ++ case DNS_E_PROTOCOL: ++ // this isn't quite right ++ return EAI_FAIL; ++ case DNS_E_NXDOMAIN: ++ return EAI_NONAME; ++ case DNS_E_NODATA: ++ return EAI_ADDRFAMILY; ++ case DNS_E_NOMEM: ++ return EAI_MEMORY; ++ case DNS_E_BADQUERY: ++ return EAI_NONAME; ++ default: ++ return EAI_ADDRFAMILY; ++ } ++} ++ ++// Compatibility layers so udns can call std::function callbacks. ++ ++void a4_callback_wrapper(struct ::dns_ctx *ctx, ::dns_rr_a4 *result, void *data) { ++ struct sockaddr_in sa; ++ udns_query *query = static_cast(data); ++ // udns will free the a4_query after this callback exits ++ query->a4_query = NULL; ++ ++ if (result == NULL || result->dnsa4_nrr == 0) { ++ if (query->a6_query == NULL) { ++ // nothing more to do: call the callback with a failure status ++ (*(query->callback))(NULL, udnserror_to_gaierror(::dns_status(ctx))); ++ delete query; ++ } ++ // else: return and wait to see if we get an a6 response ++ } else { ++ sa.sin_family = AF_INET; ++ sa.sin_port = 0; ++ sa.sin_addr = result->dnsa4_addr[0]; ++ if (query->a6_query != NULL) { ++ ::dns_cancel(ctx, query->a6_query); ++ } ++ (*query->callback)(reinterpret_cast(&sa), 0); ++ delete query; ++ } ++} ++ ++void a6_callback_wrapper(struct ::dns_ctx *ctx, ::dns_rr_a6 *result, void *data) { ++ struct sockaddr_in6 sa; ++ udns_query *query = static_cast(data); ++ // udns will free the a6_query after this callback exits ++ query->a6_query = NULL; ++ ++ if (result == NULL || result->dnsa6_nrr == 0) { ++ if (query->a4_query == NULL) { ++ // nothing more to do: call the callback with a failure status ++ (*(query->callback))(NULL, udnserror_to_gaierror(::dns_status(ctx))); ++ delete query; ++ } ++ // else: return and wait to see if we get an a6 response ++ } else { ++ sa.sin6_family = AF_INET6; ++ sa.sin6_port = 0; ++ sa.sin6_addr = result->dnsa6_addr[0]; ++ if (query->a4_query != NULL) { ++ ::dns_cancel(ctx, query->a4_query); ++ } ++ (*query->callback)(reinterpret_cast(&sa), 0); ++ delete query; ++ } ++} ++ ++ ++UdnsEvent::UdnsEvent() { ++ // reinitialize the default context, no-op ++ // TODO don't do this here --- do it once in the manager, or in rtorrent ++ ::dns_init(NULL, 0); ++ // thread-safe context isolated to this object: ++ m_ctx = ::dns_new(NULL); ++ m_fileDesc = ::dns_open(m_ctx); ++ if (m_fileDesc == -1) throw internal_error("dns_init failed"); ++ ++ m_taskTimeout.slot() = std::bind(&UdnsEvent::process_timeouts, this); ++} ++ ++UdnsEvent::~UdnsEvent() { ++ priority_queue_erase(&taskScheduler, &m_taskTimeout); ++ ::dns_close(m_ctx); ++ ::dns_free(m_ctx); ++ m_fileDesc = -1; ++ ++ for (auto it = std::begin(m_malformed_queries); it != std::end(m_malformed_queries); ++it) { ++ delete *it; ++ } ++} ++ ++void UdnsEvent::event_read() { ++ ::dns_ioevent(m_ctx, 0); ++} ++ ++void UdnsEvent::event_write() { ++} ++ ++void UdnsEvent::event_error() { ++} ++ ++struct udns_query *UdnsEvent::enqueue_resolve(const char *name, int family, resolver_callback *callback) { ++ struct udns_query *query = new udns_query { NULL, NULL, callback, 0 }; ++ ++ if (family == AF_INET || family == AF_UNSPEC) { ++ query->a4_query = ::dns_submit_a4(m_ctx, name, 0, a4_callback_wrapper, query); ++ if (query->a4_query == NULL) { ++ // XXX udns does query parsing up front and will fail immediately ++ // during submission of malformed domain names, e.g., `..`. In order to ++ // maintain a clean interface, keep track of this query internally ++ // so we can call the callback later with a failure code ++ if (::dns_status(m_ctx) == DNS_E_BADQUERY) { ++ // this is what getaddrinfo(3) would return: ++ query->error = EAI_NONAME; ++ m_malformed_queries.push_back(query); ++ return query; ++ } else { ++ // unrecoverable errors, like ENOMEM ++ throw new internal_error("dns_submit_a4 failed"); ++ } ++ } ++ } ++ ++ if (family == AF_INET6) { ++ query->a6_query = ::dns_submit_a6(m_ctx, name, 0, a6_callback_wrapper, query); ++ if (query->a6_query == NULL) { ++ // it should be impossible for dns_submit_a6 to fail if dns_submit_a4 ++ // succeeded, but just in case, make it a hard failure: ++ if (::dns_status(m_ctx) == DNS_E_BADQUERY && query->a4_query == NULL) { ++ query->error = EAI_NONAME; ++ m_malformed_queries.push_back(query); ++ return query; ++ } else { ++ throw new internal_error("dns_submit_a6 failed"); ++ } ++ } ++ } ++ ++ return query; ++} ++ ++void UdnsEvent::flush_resolves() { ++ // first process any queries that were malformed ++ while (!m_malformed_queries.empty()) { ++ udns_query *query = m_malformed_queries.back(); ++ m_malformed_queries.pop_back(); ++ (*(query->callback))(NULL, query->error); ++ delete query; ++ } ++ process_timeouts(); ++} ++ ++void UdnsEvent::process_timeouts() { ++ int timeout = ::dns_timeouts(m_ctx, -1, 0); ++ if (timeout == -1) { ++ // no pending queries ++ manager->poll()->remove_read(this); ++ manager->poll()->remove_error(this); ++ } else { ++ manager->poll()->insert_read(this); ++ manager->poll()->insert_error(this); ++ priority_queue_erase(&taskScheduler, &m_taskTimeout); ++ priority_queue_insert(&taskScheduler, &m_taskTimeout, (cachedTime + rak::timer::from_seconds(timeout)).round_seconds()); ++ } ++} ++ ++void UdnsEvent::cancel(struct udns_query *query) { ++ if (query == NULL) return; ++ ++ if (query->a4_query != NULL) ::dns_cancel(m_ctx, query->a4_query); ++ ++ if (query->a6_query != NULL) ::dns_cancel(m_ctx, query->a6_query); ++ ++ auto it = std::find(std::begin(m_malformed_queries), std::end(m_malformed_queries), query); ++ if (it != std::end(m_malformed_queries)) m_malformed_queries.erase(it); ++ ++ delete query; ++} ++ ++const char *UdnsEvent::type_name() { ++ return "UdnsEvent"; ++} ++ ++} ++ ++#endif +diff --git a/src/utils/udnsevent.h b/src/utils/udnsevent.h +new file mode 100644 +index 000000000..f214814e2 +--- /dev/null ++++ b/src/utils/udnsevent.h +@@ -0,0 +1,57 @@ ++#ifndef LIBTORRENT_NET_UDNSEVENT_H ++#define LIBTORRENT_NET_UDNSEVENT_H ++ ++#include lt_tr1_functional ++ ++#include ++#include ++ ++#include ++#include "torrent/event.h" ++#include "torrent/connection_manager.h" ++ ++struct dns_ctx; ++struct dns_query; ++ ++namespace torrent { ++ ++struct udns_query { ++ ::dns_query *a4_query; ++ ::dns_query *a6_query; ++ resolver_callback *callback; ++ int error; ++}; ++ ++class UdnsEvent : public Event { ++public: ++ ++ typedef std::vector query_list_type; ++ ++ UdnsEvent(); ++ ~UdnsEvent(); ++ ++ virtual void event_read(); ++ virtual void event_write(); ++ virtual void event_error(); ++ virtual const char* type_name(); ++ ++ // wraps udns's dns_submit_a[46] functions. they and it return control immediately, ++ // without either sending outgoing UDP packets or executing callbacks: ++ udns_query* enqueue_resolve(const char *name, int family, resolver_callback *callback); ++ // wraps the dns_timeouts function. it sends packets and can execute arbitrary ++ // callbacks: ++ void flush_resolves(); ++ // wraps the dns_cancel function: ++ void cancel(udns_query *query); ++ ++protected: ++ void process_timeouts(); ++ ++ ::dns_ctx* m_ctx; ++ rak::priority_item m_taskTimeout; ++ query_list_type m_malformed_queries; ++}; ++ ++} ++ ++#endif