From a5ca289f416965f5898710940b873ca7f1726597 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 29 Sep 2022 09:12:33 -0500 Subject: [PATCH] fix: #3847 crash on session close (#3849) * chore: use typesafe sin_addr assignment instead of memcpy() * refactor: make tr_dhtPrintableStatus private * refactor: make tr_dhtStatus() private * refactor: make tr_dht status enum private * refactor: add safety mutex wrapper around libdht API calls * refactor: make tr_dht::Status an enum class * refactor: rename private functions * refactor: const correctness * refactor: use typesafe assignment instead of memcpy * refactor: tr_session does not directly call tr_dht * refactor: tr_dhtPort() returns std::optional * refactor: use tr_address::fromCompact4 in tr-dht.cc --- libtransmission/peer-msgs.cc | 8 +- libtransmission/session.cc | 5 +- libtransmission/session.h | 3 + libtransmission/tr-dht.cc | 565 ++++++++++++++++++++--------------- libtransmission/tr-dht.h | 21 +- libtransmission/tr-udp.cc | 24 +- 6 files changed, 354 insertions(+), 272 deletions(-) diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index 119f79f15..2276b5ff3 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -281,12 +281,12 @@ public: tellPeerWhatWeHave(this); - if (tr_dhtEnabled(torrent->session) && io->supportsDHT()) + if (auto const port = tr_dhtPort(torrent->session); io->supportsDHT() && port.has_value()) { - /* Only send PORT over IPv6 when the IPv6 DHT is running (BEP-32). */ + // only send PORT over IPv6 iff IPv6 DHT is running (BEP-32). if (io->address().isIPv4() || tr_globalIPv6(nullptr).has_value()) { - protocolSendPort(this, tr_dhtPort(torrent->session)); + protocolSendPort(this, *port); } } @@ -1692,7 +1692,7 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, size_t inlen) if (auto const dht_port = tr_port::fromNetwork(nport); !std::empty(dht_port)) { msgs->dht_port = dht_port; - tr_dhtAddNode(msgs->session, &msgs->io->address(), msgs->dht_port, false); + tr_dhtAddNode(msgs->session, msgs->io->address(), msgs->dht_port, false); } } break; diff --git a/libtransmission/session.cc b/libtransmission/session.cc index 63d7882d7..184e6efbe 100644 --- a/libtransmission/session.cc +++ b/libtransmission/session.cc @@ -53,7 +53,6 @@ #include "timer-ev.h" #include "torrent.h" #include "tr-assert.h" -#include "tr-dht.h" /* tr_dhtUpkeep() */ #include "tr-lpd.h" #include "tr-strbuf.h" #include "tr-utp.h" @@ -673,7 +672,7 @@ void tr_session::onNowTimer() // tr_session upkeep tasks to perform once per second tr_timeUpdate(time(nullptr)); - tr_dhtUpkeep(this); + udp_core_->dhtUpkeep(); if (turtle.isClockEnabled) { turtleCheckClock(this, &this->turtle); @@ -1807,7 +1806,7 @@ void tr_session::closeImplStart() lpd_.reset(); - tr_dhtUninit(this); + udp_core_->dhtUninit(); save_timer_.reset(); now_timer_.reset(); diff --git a/libtransmission/session.h b/libtransmission/session.h index b0e90e7e5..15b768252 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -486,6 +486,9 @@ public: ~tr_udp_core(); + void dhtUninit(); + void dhtUpkeep(); + void set_socket_buffers(); void set_socket_tos() diff --git a/libtransmission/tr-dht.cc b/libtransmission/tr-dht.cc index 01e4e3ee9..6606b7431 100644 --- a/libtransmission/tr-dht.cc +++ b/libtransmission/tr-dht.cc @@ -5,17 +5,18 @@ #include #include #include -#include // for sig_atomic_t -#include // for abort() #include -#include // for memcpy(), memset() +#include // for abort() +#include // for memcpy() #include #include #include +#include #include #include #include #include +#include // for std::tie() #include #ifdef _WIN32 @@ -46,7 +47,6 @@ #include "tr-assert.h" #include "tr-dht.h" #include "tr-strbuf.h" -#include "trevent.h" #include "variant.h" #include "utils.h" // tr_time(), _() @@ -56,15 +56,183 @@ static std::unique_ptr dht_timer; static std::array myid; static tr_session* my_session = nullptr; -static bool bootstrap_done(tr_session* session, int af) +// mutex-locked wrapper around libdht's API +namespace locked_dht +{ +namespace +{ + +[[nodiscard]] auto unique_lock() +{ + static std::recursive_mutex dht_mutex; + return std::unique_lock(dht_mutex); +} + +} // namespace + +auto getNodes(struct sockaddr_in* sin, int* num, struct sockaddr_in6* sin6, int* num6) +{ + auto lock = unique_lock(); + return dht_get_nodes(sin, num, sin6, num6); +} + +auto init(int s, int s6, unsigned char const* id, unsigned char const* v) +{ + auto lock = unique_lock(); + return dht_init(s, s6, id, v); +} + +auto nodes(int af, int* good_return, int* dubious_return, int* cached_return, int* incoming_return) +{ + auto lock = unique_lock(); + return dht_nodes(af, good_return, dubious_return, cached_return, incoming_return); +} + +auto periodic( + void const* buf, + size_t buflen, + struct sockaddr const* from, + int fromlen, + time_t* tosleep, + dht_callback_t* callback, + void* closure) +{ + auto lock = unique_lock(); + return dht_periodic(buf, buflen, from, fromlen, tosleep, callback, closure); +} + +auto ping_node(struct sockaddr const* sa, int salen) +{ + auto lock = unique_lock(); + return dht_ping_node(sa, salen); +} + +auto search(unsigned char const* id, int port, int af, dht_callback_t* callback, void* closure) +{ + auto lock = unique_lock(); + return dht_search(id, port, af, callback, closure); +} + +auto uninit() +{ + auto lock = unique_lock(); + return dht_uninit(); +} + +} // namespace locked_dht + +enum class Status +{ + Stopped, + Broken, + Poor, + Firewalled, + Good +}; + +static constexpr std::string_view printableStatus(Status status) +{ + switch (status) + { + case Status::Stopped: + return "stopped"sv; + + case Status::Broken: + return "broken"sv; + + case Status::Poor: + return "poor"sv; + + case Status::Firewalled: + return "firewalled"sv; + + case Status::Good: + return "good"sv; + + default: + return "???"sv; + } +} + +bool tr_dhtEnabled(tr_session const* session) +{ + return session != nullptr && session == my_session; +} + +static auto getUdpSocket(tr_session const* const session, int af) +{ + switch (af) + { + case AF_INET: + return session->udp_core_->udp_socket(); + + case AF_INET6: + return session->udp_core_->udp6_socket(); + + default: + return TR_BAD_SOCKET; + } +} + +static auto getStatus(tr_session const* const session, int af, int* const setme_node_count = nullptr) +{ + if (!tr_dhtEnabled(session) || (getUdpSocket(session, af) == TR_BAD_SOCKET)) + { + if (setme_node_count != nullptr) + { + *setme_node_count = 0; + } + + return Status::Stopped; + } + + int good = 0; + int dubious = 0; + int incoming = 0; + locked_dht::nodes(af, &good, &dubious, nullptr, &incoming); + + if (setme_node_count != nullptr) + { + *setme_node_count = good + dubious; + } + + if (good < 4 || good + dubious <= 8) + { + return Status::Broken; + } + + if (good < 40) + { + return Status::Poor; + } + + if (incoming < 8) + { + return Status::Firewalled; + } + + return Status::Good; +} + +static constexpr auto isReady(Status const status) +{ + return status >= Status::Firewalled; +} + +static auto isReady(tr_session const* const session, int af) +{ + return isReady(getStatus(session, af)); +} + +static bool isBootstrapDone(tr_session const* const session, int af) { if (af == 0) { - return bootstrap_done(session, AF_INET) && bootstrap_done(session, AF_INET6); + return isBootstrapDone(session, AF_INET) && isBootstrapDone(session, AF_INET6); } - int const status = tr_dhtStatus(session, af, nullptr); - return status == TR_DHT_STOPPED || status >= TR_DHT_FIREWALLED; + auto const status = getStatus(session, af, nullptr); + return status == Status::Stopped || isReady(status); } static void nap(int roughly_sec) @@ -74,14 +242,14 @@ static void nap(int roughly_sec) tr_wait_msec(msec); } -static int bootstrap_af(tr_session* session) +static int getBootstrappedAF(tr_session const* const session) { - if (bootstrap_done(session, AF_INET6)) + if (isBootstrapDone(session, AF_INET6)) { return AF_INET; } - if (bootstrap_done(session, AF_INET)) + if (isBootstrapDone(session, AF_INET)) { return AF_INET6; } @@ -89,7 +257,7 @@ static int bootstrap_af(tr_session* session) return 0; } -static void bootstrap_from_name(char const* name, tr_port port, int af) +static void bootstrapFromName(tr_session const* const session, char const* name, tr_port port, int af) { auto hints = addrinfo{}; hints.ai_socktype = SOCK_DGRAM; @@ -113,11 +281,11 @@ static void bootstrap_from_name(char const* name, tr_port port, int af) addrinfo* infop = info; while (infop != nullptr) { - dht_ping_node(infop->ai_addr, infop->ai_addrlen); + locked_dht::ping_node(infop->ai_addr, infop->ai_addrlen); nap(15); - if (bootstrap_done(my_session, af)) + if (isBootstrapDone(session, af)) { break; } @@ -128,9 +296,9 @@ static void bootstrap_from_name(char const* name, tr_port port, int af) freeaddrinfo(info); } -static void dht_boostrap_from_file(tr_session* session) +static void bootstrapFromFile(tr_session const* const session) { - if (bootstrap_done(session, 0)) + if (isBootstrapDone(session, 0)) { return; } @@ -145,7 +313,7 @@ static void dht_boostrap_from_file(tr_session* session) // format is each line has address, a space char, and port number tr_logAddTrace("Attempting manual bootstrap"); auto line = std::string{}; - while (!bootstrap_done(session, 0) && std::getline(in, line)) + while (!isBootstrapDone(session, 0) && std::getline(in, line)) { auto line_stream = std::istringstream{ line }; auto addrstr = std::string{}; @@ -158,22 +326,19 @@ static void dht_boostrap_from_file(tr_session* session) } else { - bootstrap_from_name(addrstr.c_str(), tr_port::fromHost(hport), bootstrap_af(my_session)); + bootstrapFromName(session, addrstr.c_str(), tr_port::fromHost(hport), getBootstrappedAF(session)); } } } -static void dht_bootstrap(tr_session* session, std::vector nodes, std::vector nodes6) +static void bootstrapStart(tr_session* const session, std::vector nodes4, std::vector nodes6) { - if (my_session != session) - { - return; - } + TR_ASSERT(tr_dhtEnabled(session)); - auto const num = std::size(nodes) / 6; - if (num > 0) + auto const num4 = std::size(nodes4) / 6; + if (num4 > 0) { - tr_logAddDebug(fmt::format("Bootstrapping from {} IPv4 nodes", num)); + tr_logAddDebug(fmt::format("Bootstrapping from {} IPv4 nodes", num4)); } auto const num6 = std::size(nodes6) / 18; @@ -182,26 +347,26 @@ static void dht_bootstrap(tr_session* session, std::vector nodes, std:: tr_logAddDebug(fmt::format("Bootstrapping from {} IPv6 nodes", num6)); } - for (size_t i = 0; i < std::max(num, num6); ++i) + auto const* walk4 = std::data(nodes4); + auto const* walk6 = std::data(nodes6); + for (size_t i = 0; i < std::max(num4, num6); ++i) { - if (i < num && !bootstrap_done(session, AF_INET)) + if (i < num4 && !isBootstrapDone(session, AF_INET)) { auto addr = tr_address{}; - memset(&addr, 0, sizeof(addr)); - addr.type = TR_AF_INET; - memcpy(&addr.addr.addr4, &nodes[i * 6], 4); - auto const [port, out] = tr_port::fromCompact(&nodes[i * 6 + 4]); - tr_dhtAddNode(session, &addr, port, true); + auto port = tr_port{}; + std::tie(addr, walk4) = tr_address::fromCompact4(walk4); + std::tie(port, walk4) = tr_port::fromCompact(walk4); + tr_dhtAddNode(session, addr, port, true); } - if (i < num6 && !bootstrap_done(session, AF_INET6)) + if (i < num6 && !isBootstrapDone(session, AF_INET6)) { auto addr = tr_address{}; - memset(&addr, 0, sizeof(addr)); - addr.type = TR_AF_INET6; - memcpy(&addr.addr.addr6, &nodes6[i * 18], 16); - auto const [port, out] = tr_port::fromCompact(&nodes6[i * 18 + 16]); - tr_dhtAddNode(session, &addr, port, true); + auto port = tr_port{}; + std::tie(addr, walk6) = tr_address::fromCompact6(walk6); + std::tie(port, walk6) = tr_port::fromCompact(walk6); + tr_dhtAddNode(session, addr, port, true); } /* Our DHT code is able to take up to 9 nodes in a row without @@ -216,18 +381,20 @@ static void dht_bootstrap(tr_session* session, std::vector nodes, std:: nap(15); } - if (bootstrap_done(my_session, 0)) + if (isBootstrapDone(session, 0)) { break; } } + TR_ASSERT(walk4 = std::data(nodes4) + std::size(nodes4)); + TR_ASSERT(walk6 = std::data(nodes6) + std::size(nodes6)); - if (!bootstrap_done(session, 0)) + if (!isBootstrapDone(session, 0)) { - dht_boostrap_from_file(session); + bootstrapFromFile(session); } - if (!bootstrap_done(session, 0)) + if (!isBootstrapDone(session, 0)) { for (int i = 0; i < 6; ++i) { @@ -237,7 +404,7 @@ static void dht_bootstrap(tr_session* session, std::vector nodes, std:: node, for example because we've just been restarted. */ nap(40); - if (bootstrap_done(session, 0)) + if (isBootstrapDone(session, 0)) { break; } @@ -247,14 +414,14 @@ static void dht_bootstrap(tr_session* session, std::vector nodes, std:: tr_logAddDebug("Attempting bootstrap from dht.transmissionbt.com"); } - bootstrap_from_name("dht.transmissionbt.com", tr_port::fromHost(6881), bootstrap_af(my_session)); + bootstrapFromName(session, "dht.transmissionbt.com", tr_port::fromHost(6881), getBootstrappedAF(session)); } } tr_logAddTrace("Finished bootstrapping"); } -int tr_dhtInit(tr_session* ss) +int tr_dhtInit(tr_session* session) { if (my_session != nullptr) /* already initialized */ { @@ -269,14 +436,12 @@ int tr_dhtInit(tr_session* ss) } auto benc = tr_variant{}; - auto const dat_file = tr_pathbuf{ ss->configDir(), "/dht.dat"sv }; + auto const dat_file = tr_pathbuf{ session->configDir(), "/dht.dat"sv }; auto const ok = tr_variantFromFile(&benc, TR_VARIANT_PARSE_BENC, dat_file.sv()); bool have_id = false; auto nodes = std::vector{}; auto nodes6 = std::vector{}; - auto udp_socket = ss->udp_core_->udp_socket(); - auto udp6_socket = ss->udp_core_->udp6_socket(); if (ok) { @@ -290,12 +455,12 @@ int tr_dhtInit(tr_session* ss) size_t raw_len = 0U; uint8_t const* raw = nullptr; - if (udp_socket != TR_BAD_SOCKET && tr_variantDictFindRaw(&benc, TR_KEY_nodes, &raw, &raw_len) && raw_len % 6 == 0) + if (tr_variantDictFindRaw(&benc, TR_KEY_nodes, &raw, &raw_len) && raw_len % 6 == 0) { nodes.assign(raw, raw + raw_len); } - if (udp6_socket != TR_BAD_SOCKET && tr_variantDictFindRaw(&benc, TR_KEY_nodes6, &raw, &raw_len) && raw_len % 18 == 0) + if (tr_variantDictFindRaw(&benc, TR_KEY_nodes6, &raw, &raw_len) && raw_len % 18 == 0) { nodes6.assign(raw, raw + raw_len); } @@ -315,7 +480,7 @@ int tr_dhtInit(tr_session* ss) tr_rand_buffer(std::data(myid), std::size(myid)); } - if (int const rc = dht_init(udp_socket, udp6_socket, std::data(myid), nullptr); rc < 0) + if (locked_dht::init(getUdpSocket(session, AF_INET), getUdpSocket(session, AF_INET6), std::data(myid), nullptr) < 0) { auto const errcode = errno; tr_logAddDebug(fmt::format("DHT initialization failed: {} ({})", tr_strerror(errcode), errcode)); @@ -323,11 +488,11 @@ int tr_dhtInit(tr_session* ss) return -1; } - my_session = ss; + my_session = session; - std::thread(dht_bootstrap, my_session, nodes, nodes6).detach(); + std::thread(bootstrapStart, session, nodes, nodes6).detach(); - dht_timer = my_session->timerMaker().create([]() { tr_dhtCallback(nullptr, 0, nullptr, 0, my_session); }); + dht_timer = session->timerMaker().create([session]() { tr_dhtCallback(session, nullptr, 0, nullptr, 0); }); auto const random_percent = tr_rand_int_weak(1000) / 1000.0; static auto constexpr MinInterval = 10ms; static auto constexpr MaxInterval = 1s; @@ -339,20 +504,17 @@ int tr_dhtInit(tr_session* ss) return 1; } -void tr_dhtUninit(tr_session* ss) +void tr_dhtUninit(tr_session const* session) { - if (my_session != ss) - { - return; - } + TR_ASSERT(tr_dhtEnabled(session)); tr_logAddTrace("Uninitializing DHT"); dht_timer.reset(); - /* Since we only save known good nodes, avoid erasing older data if we - don't know enough nodes. */ - if (tr_dhtStatus(ss, AF_INET, nullptr) < TR_DHT_FIREWALLED && tr_dhtStatus(ss, AF_INET6, nullptr) < TR_DHT_FIREWALLED) + /* Since we only save known good nodes, + * avoid erasing older data if we don't know enough nodes. */ + if (!isReady(session, AF_INET) && !isReady(session, AF_INET6)) { tr_logAddTrace("Not saving nodes, DHT not ready"); } @@ -369,7 +531,7 @@ void tr_dhtUninit(tr_session* ss) auto sins6 = std::array{}; int num = MaxNodes; int num6 = MaxNodes; - int const n = dht_get_nodes(std::data(sins), &num, std::data(sins6), &num6); + int const n = locked_dht::getNodes(std::data(sins), &num, std::data(sins6), &num6); tr_logAddTrace(fmt::format("Saving {} ({} + {}) nodes", n, num, num6)); tr_variant benc; @@ -406,96 +568,31 @@ void tr_dhtUninit(tr_session* ss) tr_variantDictAddRaw(&benc, TR_KEY_nodes6, std::data(compact6), out6 - std::data(compact6)); } - auto const dat_file = tr_pathbuf{ ss->configDir(), "/dht.dat"sv }; + auto const dat_file = tr_pathbuf{ session->configDir(), "/dht.dat"sv }; tr_variantToFile(&benc, TR_VARIANT_FMT_BENC, dat_file.sv()); tr_variantClear(&benc); } - dht_uninit(); + locked_dht::uninit(); + tr_logAddTrace("Done uninitializing DHT"); my_session = nullptr; } -bool tr_dhtEnabled(tr_session const* ss) +std::optional tr_dhtPort(tr_session const* session) { - return ss != nullptr && ss == my_session; + if (!tr_dhtEnabled(session)) + { + return {}; + } + + return session->udp_core_->port(); } -struct getstatus_closure +bool tr_dhtAddNode(tr_session* ss, tr_address const& addr, tr_port port, bool bootstrap) { - int af; - sig_atomic_t status; - sig_atomic_t count; -}; - -static void getstatus(getstatus_closure* const closure) -{ - int good = 0; - int dubious = 0; - int incoming = 0; - dht_nodes(closure->af, &good, &dubious, nullptr, &incoming); - - closure->count = good + dubious; - - if (good < 4 || good + dubious <= 8) - { - closure->status = TR_DHT_BROKEN; - } - else if (good < 40) - { - closure->status = TR_DHT_POOR; - } - else if (incoming < 8) - { - closure->status = TR_DHT_FIREWALLED; - } - else - { - closure->status = TR_DHT_GOOD; - } -} - -int tr_dhtStatus(tr_session* session, int af, int* setme_node_count) -{ - auto closure = getstatus_closure{ af, -1, -1 }; - auto udp_socket = session->udp_core_->udp_socket(); - auto udp6_socket = session->udp_core_->udp6_socket(); - - if (!tr_dhtEnabled(session) || (af == AF_INET && udp_socket == TR_BAD_SOCKET) || - (af == AF_INET6 && udp6_socket == TR_BAD_SOCKET)) - { - if (setme_node_count != nullptr) - { - *setme_node_count = 0; - } - - return TR_DHT_STOPPED; - } - - tr_runInEventThread(session, getstatus, &closure); - - while (closure.status < 0) - { - tr_wait_msec(50 /*msec*/); - } - - if (setme_node_count != nullptr) - { - *setme_node_count = closure.count; - } - - return closure.status; -} - -tr_port tr_dhtPort(tr_session const* ss) -{ - return tr_dhtEnabled(ss) ? ss->udp_core_->port() : tr_port{}; -} - -bool tr_dhtAddNode(tr_session* ss, tr_address const* address, tr_port port, bool bootstrap) -{ - int const af = address->isIPv4() ? AF_INET : AF_INET6; + int const af = addr.isIPv4() ? AF_INET : AF_INET6; if (!tr_dhtEnabled(ss)) { @@ -505,66 +602,41 @@ bool tr_dhtAddNode(tr_session* ss, tr_address const* address, tr_port port, bool /* Since we don't want to abuse our bootstrap nodes, * we don't ping them if the DHT is in a good state. */ - if (bootstrap && (tr_dhtStatus(ss, af, nullptr) >= TR_DHT_FIREWALLED)) + if (bootstrap && isReady(ss, af)) { return false; } - if (address->isIPv4()) + if (addr.isIPv4()) { auto sin = sockaddr_in{}; - memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; - memcpy(&sin.sin_addr, &address->addr.addr4, 4); + sin.sin_addr = addr.addr.addr4; sin.sin_port = port.network(); - dht_ping_node((struct sockaddr*)&sin, sizeof(sin)); + locked_dht::ping_node((struct sockaddr*)&sin, sizeof(sin)); return true; } - if (address->isIPv6()) + if (addr.isIPv6()) { auto sin6 = sockaddr_in6{}; - memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; - memcpy(&sin6.sin6_addr, &address->addr.addr6, 16); + sin6.sin6_addr = addr.addr.addr6; sin6.sin6_port = port.network(); - dht_ping_node((struct sockaddr*)&sin6, sizeof(sin6)); + locked_dht::ping_node((struct sockaddr*)&sin6, sizeof(sin6)); return true; } return false; } -char const* tr_dhtPrintableStatus(int status) -{ - switch (status) - { - case TR_DHT_STOPPED: - return "stopped"; - - case TR_DHT_BROKEN: - return "broken"; - - case TR_DHT_POOR: - return "poor"; - - case TR_DHT_FIREWALLED: - return "firewalled"; - - case TR_DHT_GOOD: - return "good"; - - default: - return "???"; - } -} - -static void callback(void* /*ignore*/, int event, unsigned char const* info_hash, void const* data, size_t data_len) +static void callback(void* vsession, int event, unsigned char const* info_hash, void const* data, size_t data_len) { + auto* const session = static_cast(vsession); auto hash = tr_sha1_digest_t{}; std::copy_n(reinterpret_cast(info_hash), std::size(hash), std::data(hash)); - auto const lock = my_session->unique_lock(); - auto* const tor = my_session->torrents().get(hash); + auto const lock = session->unique_lock(); + auto* const tor = session->torrents().get(hash); if (event == DHT_EVENT_VALUES || event == DHT_EVENT_VALUES6) { @@ -601,36 +673,33 @@ enum class AnnounceResult FAILED }; -static AnnounceResult tr_dhtAnnounce(tr_torrent* tor, int af, bool announce) +static AnnounceResult announceTorrent(tr_session const* const session, tr_torrent const* const tor, int af, bool announce) { - if (!tor->allowsDht()) - { - return AnnounceResult::INVALID; - } + TR_ASSERT(tor->allowsDht()); int numnodes = 0; - int const status = tr_dhtStatus(tor->session, af, &numnodes); - if (status == TR_DHT_STOPPED) + auto const status = getStatus(session, af, &numnodes); + if (status == Status::Stopped) { // let the caller believe everything is all right. return AnnounceResult::OK; } - if (status < TR_DHT_POOR) + if (status < Status::Poor) { tr_logAddTraceTor( tor, fmt::format( "{} DHT not ready ({}, {} nodes)", af == AF_INET6 ? "IPv6" : "IPv4", - tr_dhtPrintableStatus(status), + printableStatus(status), numnodes)); return AnnounceResult::FAILED; } auto const* dht_hash = reinterpret_cast(std::data(tor->infoHash())); - auto const hport = announce ? my_session->peerPort().host() : 0; - int const rc = dht_search(dht_hash, hport, af, callback, nullptr); + auto const hport = announce ? session->peerPort().host() : 0; + int const rc = locked_dht::search(dht_hash, hport, af, callback, nullptr); if (rc < 0) { auto const error_code = errno; @@ -639,7 +708,7 @@ static AnnounceResult tr_dhtAnnounce(tr_torrent* tor, int af, bool announce) fmt::format( _("Unable to announce torrent in DHT with {type}: {error} ({error_code}); state is {state}"), fmt::arg("type", af == AF_INET6 ? "IPv6" : "IPv4"), - fmt::arg("state", tr_dhtPrintableStatus(status)), + fmt::arg("state", printableStatus(status)), fmt::arg("error_code", error_code), fmt::arg("error", tr_strerror(error_code)))); return AnnounceResult::FAILED; @@ -650,7 +719,7 @@ static AnnounceResult tr_dhtAnnounce(tr_torrent* tor, int af, bool announce) fmt::format( "Starting {} DHT announce ({}, {} nodes)", af == AF_INET6 ? "IPv6" : "IPv4", - tr_dhtPrintableStatus(status), + printableStatus(status), numnodes)); return AnnounceResult::OK; @@ -658,7 +727,10 @@ static AnnounceResult tr_dhtAnnounce(tr_torrent* tor, int af, bool announce) void tr_dhtUpkeep(tr_session* session) { - time_t const now = tr_time(); + TR_ASSERT(tr_dhtEnabled(session)); + + auto lock = session->unique_lock(); + auto const now = tr_time(); for (auto* const tor : session->torrents()) { @@ -669,7 +741,7 @@ void tr_dhtUpkeep(tr_session* session) if (tor->dhtAnnounceAt <= now) { - auto const rc = tr_dhtAnnounce(tor, AF_INET, true); + auto const rc = announceTorrent(session, tor, AF_INET, true); tor->dhtAnnounceAt = now + ((rc == AnnounceResult::FAILED) ? 5 + tr_rand_int_weak(5) : 25 * 60 + tr_rand_int_weak(3 * 60)); @@ -677,7 +749,7 @@ void tr_dhtUpkeep(tr_session* session) if (tor->dhtAnnounce6At <= now) { - auto const rc = tr_dhtAnnounce(tor, AF_INET6, true); + auto const rc = announceTorrent(session, tor, AF_INET6, true); tor->dhtAnnounce6At = now + ((rc == AnnounceResult::FAILED) ? 5 + tr_rand_int_weak(5) : 25 * 60 + tr_rand_int_weak(3 * 60)); @@ -685,17 +757,12 @@ void tr_dhtUpkeep(tr_session* session) } } -void tr_dhtCallback(unsigned char* buf, int buflen, struct sockaddr* from, socklen_t fromlen, void* sv) +void tr_dhtCallback(tr_session* session, unsigned char* buf, int buflen, struct sockaddr* from, socklen_t fromlen) { - TR_ASSERT(sv != nullptr); - - if (sv != my_session) - { - return; - } + TR_ASSERT(tr_dhtEnabled(session)); time_t tosleep = 0; - int const rc = dht_periodic(buf, buflen, from, fromlen, &tosleep, callback, nullptr); + int const rc = locked_dht::periodic(buf, buflen, from, fromlen, &tosleep, callback, nullptr); if (rc < 0) { @@ -717,8 +784,8 @@ void tr_dhtCallback(unsigned char* buf, int buflen, struct sockaddr* from, sockl } } - /* Being slightly late is fine, - and has the added benefit of adding some jitter. */ + // Being slightly late is fine, + // and has the added benefit of adding some jitter. auto const random_percent = tr_rand_int_weak(1000) / 1000.0; auto const min_interval = std::chrono::seconds{ tosleep }; auto const max_interval = std::chrono::seconds{ tosleep + 1 }; @@ -726,56 +793,62 @@ void tr_dhtCallback(unsigned char* buf, int buflen, struct sockaddr* from, sockl dht_timer->startSingleShot(std::chrono::duration_cast(interval)); } -/* This function should return true when a node is blacklisted. We do - not support using a blacklist with the DHT in Transmission, since - massive (ab)use of this feature could harm the DHT. However, feel - free to add support to your private copy as long as you don't - redistribute it. */ - -int dht_blacklisted(sockaddr const* /*sa*/, int /*salen*/) +extern "C" { - return 0; -} -void dht_hash(void* hash_return, int hash_size, void const* v1, int len1, void const* v2, int len2, void const* v3, int len3) -{ - auto* setme = reinterpret_cast(hash_return); - std::fill_n(static_cast(hash_return), hash_size, '\0'); + // This function should return true when a node is blacklisted. + // We don't support using a blacklist with the DHT in Transmission, + // since massive (ab)use of this feature could harm the DHT. However, + // feel free to add support to your private copy as long as you don't + // redistribute it. + int dht_blacklisted(sockaddr const* /*sa*/, int /*salen*/) + { + return 0; + } - auto const sv1 = std::string_view{ static_cast(v1), size_t(len1) }; - auto const sv2 = std::string_view{ static_cast(v2), size_t(len2) }; - auto const sv3 = std::string_view{ static_cast(v3), size_t(len3) }; - auto const digest = tr_sha1::digest(sv1, sv2, sv3); - std::copy_n(std::data(digest), std::min(size_t(hash_size), std::size(digest)), setme); -} + void dht_hash( + void* hash_return, + int hash_size, + void const* v1, + int len1, + void const* v2, + int len2, + void const* v3, + int len3) + { + auto* setme = reinterpret_cast(hash_return); + std::fill_n(static_cast(hash_return), hash_size, '\0'); -int dht_random_bytes(void* buf, size_t size) -{ - tr_rand_buffer(buf, size); - return size; -} + auto const sv1 = std::string_view{ static_cast(v1), size_t(len1) }; + auto const sv2 = std::string_view{ static_cast(v2), size_t(len2) }; + auto const sv3 = std::string_view{ static_cast(v3), size_t(len3) }; + auto const digest = tr_sha1::digest(sv1, sv2, sv3); + std::copy_n(std::data(digest), std::min(size_t(hash_size), std::size(digest)), setme); + } -int dht_sendto(int sockfd, void const* buf, int len, int flags, struct sockaddr const* to, int tolen) -{ - return sendto(sockfd, static_cast(buf), len, flags, to, tolen); -} + int dht_random_bytes(void* buf, size_t size) + { + tr_rand_buffer(buf, size); + return size; + } + + int dht_sendto(int sockfd, void const* buf, int len, int flags, struct sockaddr const* to, int tolen) + { + return sendto(sockfd, static_cast(buf), len, flags, to, tolen); + } #if defined(_WIN32) && !defined(__MINGW32__) + int dht_gettimeofday(struct timeval* tv, [[maybe_unused]] struct timezone* tz) + { + TR_ASSERT(tz == nullptr); -/*** -**** -***/ - -extern "C" int dht_gettimeofday(struct timeval* tv, [[maybe_unused]] struct timezone* tz) -{ - TR_ASSERT(tz == nullptr); - - auto const d = std::chrono::system_clock::now().time_since_epoch(); - auto const s = std::chrono::duration_cast(d); - tv->tv_sec = s.count(); - tv->tv_usec = std::chrono::duration_cast(d - s).count(); - - return 0; -} + auto const d = std::chrono::system_clock::now().time_since_epoch(); + auto const s = std::chrono::duration_cast(d); + tv->tv_sec = s.count(); + tv->tv_usec = std::chrono::duration_cast(d - s).count(); + return 0; + } #endif + +} // extern "C" diff --git a/libtransmission/tr-dht.h b/libtransmission/tr-dht.h index 5535020dc..163252458 100644 --- a/libtransmission/tr-dht.h +++ b/libtransmission/tr-dht.h @@ -8,25 +8,16 @@ #error only libtransmission should #include this header. #endif +#include + #include "transmission.h" #include "net.h" // tr_port -enum -{ - TR_DHT_STOPPED = 0, - TR_DHT_BROKEN = 1, - TR_DHT_POOR = 2, - TR_DHT_FIREWALLED = 3, - TR_DHT_GOOD = 4 -}; - int tr_dhtInit(tr_session*); -void tr_dhtUninit(tr_session*); +void tr_dhtUninit(tr_session const*); bool tr_dhtEnabled(tr_session const*); -tr_port tr_dhtPort(tr_session const*); -int tr_dhtStatus(tr_session*, int af, int* setme_node_count); -char const* tr_dhtPrintableStatus(int status); -bool tr_dhtAddNode(tr_session*, tr_address const*, tr_port, bool bootstrap); +std::optional tr_dhtPort(tr_session const*); +bool tr_dhtAddNode(tr_session*, tr_address const&, tr_port, bool bootstrap); void tr_dhtUpkeep(tr_session*); -void tr_dhtCallback(unsigned char* buf, int buflen, struct sockaddr* from, socklen_t fromlen, void* sv); +void tr_dhtCallback(tr_session*, unsigned char* buf, int buflen, struct sockaddr* from, socklen_t fromlen); diff --git a/libtransmission/tr-udp.cc b/libtransmission/tr-udp.cc index 135188a25..14c51cbfc 100644 --- a/libtransmission/tr-udp.cc +++ b/libtransmission/tr-udp.cc @@ -5,7 +5,7 @@ #include #include #include -#include /* memcmp(), memcpy(), memset() */ +#include /* memcmp(), memset() */ #ifdef _WIN32 #include /* dup2() */ @@ -205,7 +205,7 @@ static void event_callback(evutil_socket_t s, [[maybe_unused]] short type, void* if (session->allowsDHT()) { buf[rc] = '\0'; /* required by the DHT code */ - tr_dhtCallback(std::data(buf), rc, (struct sockaddr*)&from, fromlen, vsession); + tr_dhtCallback(session, std::data(buf), rc, (struct sockaddr*)&from, fromlen); } } else if (rc >= 8 && buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] <= 3) @@ -251,7 +251,7 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session) sin.sin_family = AF_INET; if (!is_default) { - memcpy(&sin.sin_addr, &public_addr.addr.addr4, sizeof(struct in_addr)); + sin.sin_addr = public_addr.addr.addr4; } sin.sin_port = udp_port_.network(); @@ -314,9 +314,25 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session) } } +void tr_session::tr_udp_core::dhtUpkeep() +{ + if (tr_dhtEnabled(&session_)) + { + tr_dhtUpkeep(&session_); + } +} + +void tr_session::tr_udp_core::dhtUninit() +{ + if (tr_dhtEnabled(&session_)) + { + tr_dhtUninit(&session_); + } +} + tr_session::tr_udp_core::~tr_udp_core() { - tr_dhtUninit(&session_); + dhtUninit(); if (udp_socket_ != TR_BAD_SOCKET) {