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
This commit is contained in:
Charles Kerr 2022-09-29 09:12:33 -05:00 committed by GitHub
parent 31ce989334
commit a5ca289f41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 354 additions and 272 deletions

View File

@ -281,12 +281,12 @@ public:
tellPeerWhatWeHave(this); 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()) 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)) if (auto const dht_port = tr_port::fromNetwork(nport); !std::empty(dht_port))
{ {
msgs->dht_port = 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; break;

View File

@ -53,7 +53,6 @@
#include "timer-ev.h" #include "timer-ev.h"
#include "torrent.h" #include "torrent.h"
#include "tr-assert.h" #include "tr-assert.h"
#include "tr-dht.h" /* tr_dhtUpkeep() */
#include "tr-lpd.h" #include "tr-lpd.h"
#include "tr-strbuf.h" #include "tr-strbuf.h"
#include "tr-utp.h" #include "tr-utp.h"
@ -673,7 +672,7 @@ void tr_session::onNowTimer()
// tr_session upkeep tasks to perform once per second // tr_session upkeep tasks to perform once per second
tr_timeUpdate(time(nullptr)); tr_timeUpdate(time(nullptr));
tr_dhtUpkeep(this); udp_core_->dhtUpkeep();
if (turtle.isClockEnabled) if (turtle.isClockEnabled)
{ {
turtleCheckClock(this, &this->turtle); turtleCheckClock(this, &this->turtle);
@ -1807,7 +1806,7 @@ void tr_session::closeImplStart()
lpd_.reset(); lpd_.reset();
tr_dhtUninit(this); udp_core_->dhtUninit();
save_timer_.reset(); save_timer_.reset();
now_timer_.reset(); now_timer_.reset();

View File

@ -486,6 +486,9 @@ public:
~tr_udp_core(); ~tr_udp_core();
void dhtUninit();
void dhtUpkeep();
void set_socket_buffers(); void set_socket_buffers();
void set_socket_tos() void set_socket_tos()

View File

@ -5,17 +5,18 @@
#include <algorithm> #include <algorithm>
#include <cerrno> #include <cerrno>
#include <chrono> #include <chrono>
#include <csignal> // for sig_atomic_t
#include <cstdlib> // for abort()
#include <cstdio> #include <cstdio>
#include <cstring> // for memcpy(), memset() #include <cstdlib> // for abort()
#include <cstring> // for memcpy()
#include <ctime> #include <ctime>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <mutex>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <thread> #include <thread>
#include <tuple> // for std::tie()
#include <vector> #include <vector>
#ifdef _WIN32 #ifdef _WIN32
@ -46,7 +47,6 @@
#include "tr-assert.h" #include "tr-assert.h"
#include "tr-dht.h" #include "tr-dht.h"
#include "tr-strbuf.h" #include "tr-strbuf.h"
#include "trevent.h"
#include "variant.h" #include "variant.h"
#include "utils.h" // tr_time(), _() #include "utils.h" // tr_time(), _()
@ -56,15 +56,183 @@ static std::unique_ptr<libtransmission::Timer> dht_timer;
static std::array<unsigned char, 20> myid; static std::array<unsigned char, 20> myid;
static tr_session* my_session = nullptr; 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) 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); auto const status = getStatus(session, af, nullptr);
return status == TR_DHT_STOPPED || status >= TR_DHT_FIREWALLED; return status == Status::Stopped || isReady(status);
} }
static void nap(int roughly_sec) static void nap(int roughly_sec)
@ -74,14 +242,14 @@ static void nap(int roughly_sec)
tr_wait_msec(msec); 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; return AF_INET;
} }
if (bootstrap_done(session, AF_INET)) if (isBootstrapDone(session, AF_INET))
{ {
return AF_INET6; return AF_INET6;
} }
@ -89,7 +257,7 @@ static int bootstrap_af(tr_session* session)
return 0; 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{}; auto hints = addrinfo{};
hints.ai_socktype = SOCK_DGRAM; 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; addrinfo* infop = info;
while (infop != nullptr) while (infop != nullptr)
{ {
dht_ping_node(infop->ai_addr, infop->ai_addrlen); locked_dht::ping_node(infop->ai_addr, infop->ai_addrlen);
nap(15); nap(15);
if (bootstrap_done(my_session, af)) if (isBootstrapDone(session, af))
{ {
break; break;
} }
@ -128,9 +296,9 @@ static void bootstrap_from_name(char const* name, tr_port port, int af)
freeaddrinfo(info); 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; 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 // format is each line has address, a space char, and port number
tr_logAddTrace("Attempting manual bootstrap"); tr_logAddTrace("Attempting manual bootstrap");
auto line = std::string{}; 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 line_stream = std::istringstream{ line };
auto addrstr = std::string{}; auto addrstr = std::string{};
@ -158,22 +326,19 @@ static void dht_boostrap_from_file(tr_session* session)
} }
else 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<uint8_t> nodes, std::vector<uint8_t> nodes6) static void bootstrapStart(tr_session* const session, std::vector<uint8_t> nodes4, std::vector<uint8_t> nodes6)
{ {
if (my_session != session) TR_ASSERT(tr_dhtEnabled(session));
{
return;
}
auto const num = std::size(nodes) / 6; auto const num4 = std::size(nodes4) / 6;
if (num > 0) 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; auto const num6 = std::size(nodes6) / 18;
@ -182,26 +347,26 @@ static void dht_bootstrap(tr_session* session, std::vector<uint8_t> nodes, std::
tr_logAddDebug(fmt::format("Bootstrapping from {} IPv6 nodes", num6)); 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{}; auto addr = tr_address{};
memset(&addr, 0, sizeof(addr)); auto port = tr_port{};
addr.type = TR_AF_INET; std::tie(addr, walk4) = tr_address::fromCompact4(walk4);
memcpy(&addr.addr.addr4, &nodes[i * 6], 4); std::tie(port, walk4) = tr_port::fromCompact(walk4);
auto const [port, out] = tr_port::fromCompact(&nodes[i * 6 + 4]); tr_dhtAddNode(session, addr, port, true);
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{}; auto addr = tr_address{};
memset(&addr, 0, sizeof(addr)); auto port = tr_port{};
addr.type = TR_AF_INET6; std::tie(addr, walk6) = tr_address::fromCompact6(walk6);
memcpy(&addr.addr.addr6, &nodes6[i * 18], 16); std::tie(port, walk6) = tr_port::fromCompact(walk6);
auto const [port, out] = tr_port::fromCompact(&nodes6[i * 18 + 16]); tr_dhtAddNode(session, addr, port, true);
tr_dhtAddNode(session, &addr, port, true);
} }
/* Our DHT code is able to take up to 9 nodes in a row without /* 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<uint8_t> nodes, std::
nap(15); nap(15);
} }
if (bootstrap_done(my_session, 0)) if (isBootstrapDone(session, 0))
{ {
break; 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) for (int i = 0; i < 6; ++i)
{ {
@ -237,7 +404,7 @@ static void dht_bootstrap(tr_session* session, std::vector<uint8_t> nodes, std::
node, for example because we've just been restarted. */ node, for example because we've just been restarted. */
nap(40); nap(40);
if (bootstrap_done(session, 0)) if (isBootstrapDone(session, 0))
{ {
break; break;
} }
@ -247,14 +414,14 @@ static void dht_bootstrap(tr_session* session, std::vector<uint8_t> nodes, std::
tr_logAddDebug("Attempting bootstrap from dht.transmissionbt.com"); 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"); tr_logAddTrace("Finished bootstrapping");
} }
int tr_dhtInit(tr_session* ss) int tr_dhtInit(tr_session* session)
{ {
if (my_session != nullptr) /* already initialized */ if (my_session != nullptr) /* already initialized */
{ {
@ -269,14 +436,12 @@ int tr_dhtInit(tr_session* ss)
} }
auto benc = tr_variant{}; 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()); auto const ok = tr_variantFromFile(&benc, TR_VARIANT_PARSE_BENC, dat_file.sv());
bool have_id = false; bool have_id = false;
auto nodes = std::vector<uint8_t>{}; auto nodes = std::vector<uint8_t>{};
auto nodes6 = std::vector<uint8_t>{}; auto nodes6 = std::vector<uint8_t>{};
auto udp_socket = ss->udp_core_->udp_socket();
auto udp6_socket = ss->udp_core_->udp6_socket();
if (ok) if (ok)
{ {
@ -290,12 +455,12 @@ int tr_dhtInit(tr_session* ss)
size_t raw_len = 0U; size_t raw_len = 0U;
uint8_t const* raw = nullptr; 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); 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); 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)); 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; auto const errcode = errno;
tr_logAddDebug(fmt::format("DHT initialization failed: {} ({})", tr_strerror(errcode), errcode)); tr_logAddDebug(fmt::format("DHT initialization failed: {} ({})", tr_strerror(errcode), errcode));
@ -323,11 +488,11 @@ int tr_dhtInit(tr_session* ss)
return -1; 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; auto const random_percent = tr_rand_int_weak(1000) / 1000.0;
static auto constexpr MinInterval = 10ms; static auto constexpr MinInterval = 10ms;
static auto constexpr MaxInterval = 1s; static auto constexpr MaxInterval = 1s;
@ -339,20 +504,17 @@ int tr_dhtInit(tr_session* ss)
return 1; return 1;
} }
void tr_dhtUninit(tr_session* ss) void tr_dhtUninit(tr_session const* session)
{ {
if (my_session != ss) TR_ASSERT(tr_dhtEnabled(session));
{
return;
}
tr_logAddTrace("Uninitializing DHT"); tr_logAddTrace("Uninitializing DHT");
dht_timer.reset(); dht_timer.reset();
/* Since we only save known good nodes, avoid erasing older data if we /* Since we only save known good nodes,
don't know enough 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) if (!isReady(session, AF_INET) && !isReady(session, AF_INET6))
{ {
tr_logAddTrace("Not saving nodes, DHT not ready"); tr_logAddTrace("Not saving nodes, DHT not ready");
} }
@ -369,7 +531,7 @@ void tr_dhtUninit(tr_session* ss)
auto sins6 = std::array<struct sockaddr_in6, MaxNodes>{}; auto sins6 = std::array<struct sockaddr_in6, MaxNodes>{};
int num = MaxNodes; int num = MaxNodes;
int num6 = 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_logAddTrace(fmt::format("Saving {} ({} + {}) nodes", n, num, num6));
tr_variant benc; 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)); 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_variantToFile(&benc, TR_VARIANT_FMT_BENC, dat_file.sv());
tr_variantClear(&benc); tr_variantClear(&benc);
} }
dht_uninit(); locked_dht::uninit();
tr_logAddTrace("Done uninitializing DHT"); tr_logAddTrace("Done uninitializing DHT");
my_session = nullptr; my_session = nullptr;
} }
bool tr_dhtEnabled(tr_session const* ss) std::optional<tr_port> tr_dhtPort(tr_session const* session)
{ {
return ss != nullptr && ss == my_session; if (!tr_dhtEnabled(session))
{
return {};
} }
struct getstatus_closure return session->udp_core_->port();
{
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) bool tr_dhtAddNode(tr_session* ss, tr_address const& addr, tr_port port, bool bootstrap)
{ {
auto closure = getstatus_closure{ af, -1, -1 }; int const af = addr.isIPv4() ? AF_INET : AF_INET6;
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;
if (!tr_dhtEnabled(ss)) 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, /* Since we don't want to abuse our bootstrap nodes,
* we don't ping them if the DHT is in a good state. */ * 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; return false;
} }
if (address->isIPv4()) if (addr.isIPv4())
{ {
auto sin = sockaddr_in{}; auto sin = sockaddr_in{};
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET; sin.sin_family = AF_INET;
memcpy(&sin.sin_addr, &address->addr.addr4, 4); sin.sin_addr = addr.addr.addr4;
sin.sin_port = port.network(); sin.sin_port = port.network();
dht_ping_node((struct sockaddr*)&sin, sizeof(sin)); locked_dht::ping_node((struct sockaddr*)&sin, sizeof(sin));
return true; return true;
} }
if (address->isIPv6()) if (addr.isIPv6())
{ {
auto sin6 = sockaddr_in6{}; auto sin6 = sockaddr_in6{};
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6; sin6.sin6_family = AF_INET6;
memcpy(&sin6.sin6_addr, &address->addr.addr6, 16); sin6.sin6_addr = addr.addr.addr6;
sin6.sin6_port = port.network(); 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 true;
} }
return false; return false;
} }
char const* tr_dhtPrintableStatus(int status) static void callback(void* vsession, int event, unsigned char const* info_hash, void const* data, size_t data_len)
{
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)
{ {
auto* const session = static_cast<tr_session*>(vsession);
auto hash = tr_sha1_digest_t{}; auto hash = tr_sha1_digest_t{};
std::copy_n(reinterpret_cast<std::byte const*>(info_hash), std::size(hash), std::data(hash)); std::copy_n(reinterpret_cast<std::byte const*>(info_hash), std::size(hash), std::data(hash));
auto const lock = my_session->unique_lock(); auto const lock = session->unique_lock();
auto* const tor = my_session->torrents().get(hash); auto* const tor = session->torrents().get(hash);
if (event == DHT_EVENT_VALUES || event == DHT_EVENT_VALUES6) if (event == DHT_EVENT_VALUES || event == DHT_EVENT_VALUES6)
{ {
@ -601,36 +673,33 @@ enum class AnnounceResult
FAILED 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()) TR_ASSERT(tor->allowsDht());
{
return AnnounceResult::INVALID;
}
int numnodes = 0; int numnodes = 0;
int const status = tr_dhtStatus(tor->session, af, &numnodes); auto const status = getStatus(session, af, &numnodes);
if (status == TR_DHT_STOPPED) if (status == Status::Stopped)
{ {
// let the caller believe everything is all right. // let the caller believe everything is all right.
return AnnounceResult::OK; return AnnounceResult::OK;
} }
if (status < TR_DHT_POOR) if (status < Status::Poor)
{ {
tr_logAddTraceTor( tr_logAddTraceTor(
tor, tor,
fmt::format( fmt::format(
"{} DHT not ready ({}, {} nodes)", "{} DHT not ready ({}, {} nodes)",
af == AF_INET6 ? "IPv6" : "IPv4", af == AF_INET6 ? "IPv6" : "IPv4",
tr_dhtPrintableStatus(status), printableStatus(status),
numnodes)); numnodes));
return AnnounceResult::FAILED; return AnnounceResult::FAILED;
} }
auto const* dht_hash = reinterpret_cast<unsigned char const*>(std::data(tor->infoHash())); auto const* dht_hash = reinterpret_cast<unsigned char const*>(std::data(tor->infoHash()));
auto const hport = announce ? my_session->peerPort().host() : 0; auto const hport = announce ? session->peerPort().host() : 0;
int const rc = dht_search(dht_hash, hport, af, callback, nullptr); int const rc = locked_dht::search(dht_hash, hport, af, callback, nullptr);
if (rc < 0) if (rc < 0)
{ {
auto const error_code = errno; auto const error_code = errno;
@ -639,7 +708,7 @@ static AnnounceResult tr_dhtAnnounce(tr_torrent* tor, int af, bool announce)
fmt::format( fmt::format(
_("Unable to announce torrent in DHT with {type}: {error} ({error_code}); state is {state}"), _("Unable to announce torrent in DHT with {type}: {error} ({error_code}); state is {state}"),
fmt::arg("type", af == AF_INET6 ? "IPv6" : "IPv4"), 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_code", error_code),
fmt::arg("error", tr_strerror(error_code)))); fmt::arg("error", tr_strerror(error_code))));
return AnnounceResult::FAILED; return AnnounceResult::FAILED;
@ -650,7 +719,7 @@ static AnnounceResult tr_dhtAnnounce(tr_torrent* tor, int af, bool announce)
fmt::format( fmt::format(
"Starting {} DHT announce ({}, {} nodes)", "Starting {} DHT announce ({}, {} nodes)",
af == AF_INET6 ? "IPv6" : "IPv4", af == AF_INET6 ? "IPv6" : "IPv4",
tr_dhtPrintableStatus(status), printableStatus(status),
numnodes)); numnodes));
return AnnounceResult::OK; return AnnounceResult::OK;
@ -658,7 +727,10 @@ static AnnounceResult tr_dhtAnnounce(tr_torrent* tor, int af, bool announce)
void tr_dhtUpkeep(tr_session* session) 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()) for (auto* const tor : session->torrents())
{ {
@ -669,7 +741,7 @@ void tr_dhtUpkeep(tr_session* session)
if (tor->dhtAnnounceAt <= now) 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 + tor->dhtAnnounceAt = now +
((rc == AnnounceResult::FAILED) ? 5 + tr_rand_int_weak(5) : 25 * 60 + tr_rand_int_weak(3 * 60)); ((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) 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 + tor->dhtAnnounce6At = now +
((rc == AnnounceResult::FAILED) ? 5 + tr_rand_int_weak(5) : 25 * 60 + tr_rand_int_weak(3 * 60)); ((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); TR_ASSERT(tr_dhtEnabled(session));
if (sv != my_session)
{
return;
}
time_t tosleep = 0; 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) if (rc < 0)
{ {
@ -717,8 +784,8 @@ void tr_dhtCallback(unsigned char* buf, int buflen, struct sockaddr* from, sockl
} }
} }
/* Being slightly late is fine, // Being slightly late is fine,
and has the added benefit of adding some jitter. */ // and has the added benefit of adding some jitter.
auto const random_percent = tr_rand_int_weak(1000) / 1000.0; auto const random_percent = tr_rand_int_weak(1000) / 1000.0;
auto const min_interval = std::chrono::seconds{ tosleep }; auto const min_interval = std::chrono::seconds{ tosleep };
auto const max_interval = std::chrono::seconds{ tosleep + 1 }; auto const max_interval = std::chrono::seconds{ tosleep + 1 };
@ -726,18 +793,28 @@ void tr_dhtCallback(unsigned char* buf, int buflen, struct sockaddr* from, sockl
dht_timer->startSingleShot(std::chrono::duration_cast<std::chrono::milliseconds>(interval)); dht_timer->startSingleShot(std::chrono::duration_cast<std::chrono::milliseconds>(interval));
} }
/* This function should return true when a node is blacklisted. We do extern "C"
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. */
// 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*/) int dht_blacklisted(sockaddr const* /*sa*/, int /*salen*/)
{ {
return 0; 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) 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<std::byte*>(hash_return); auto* setme = reinterpret_cast<std::byte*>(hash_return);
std::fill_n(static_cast<char*>(hash_return), hash_size, '\0'); std::fill_n(static_cast<char*>(hash_return), hash_size, '\0');
@ -761,12 +838,7 @@ int dht_sendto(int sockfd, void const* buf, int len, int flags, struct sockaddr
} }
#if defined(_WIN32) && !defined(__MINGW32__) #if defined(_WIN32) && !defined(__MINGW32__)
int dht_gettimeofday(struct timeval* tv, [[maybe_unused]] struct timezone* tz)
/***
****
***/
extern "C" int dht_gettimeofday(struct timeval* tv, [[maybe_unused]] struct timezone* tz)
{ {
TR_ASSERT(tz == nullptr); TR_ASSERT(tz == nullptr);
@ -777,5 +849,6 @@ extern "C" int dht_gettimeofday(struct timeval* tv, [[maybe_unused]] struct time
return 0; return 0;
} }
#endif #endif
} // extern "C"

View File

@ -8,25 +8,16 @@
#error only libtransmission should #include this header. #error only libtransmission should #include this header.
#endif #endif
#include <optional>
#include "transmission.h" #include "transmission.h"
#include "net.h" // tr_port #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*); int tr_dhtInit(tr_session*);
void tr_dhtUninit(tr_session*); void tr_dhtUninit(tr_session const*);
bool tr_dhtEnabled(tr_session const*); bool tr_dhtEnabled(tr_session const*);
tr_port tr_dhtPort(tr_session const*); std::optional<tr_port> tr_dhtPort(tr_session const*);
int tr_dhtStatus(tr_session*, int af, int* setme_node_count); bool tr_dhtAddNode(tr_session*, tr_address const&, tr_port, bool bootstrap);
char const* tr_dhtPrintableStatus(int status);
bool tr_dhtAddNode(tr_session*, tr_address const*, tr_port, bool bootstrap);
void tr_dhtUpkeep(tr_session*); 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);

View File

@ -5,7 +5,7 @@
#include <array> #include <array>
#include <cerrno> #include <cerrno>
#include <cstdint> #include <cstdint>
#include <cstring> /* memcmp(), memcpy(), memset() */ #include <cstring> /* memcmp(), memset() */
#ifdef _WIN32 #ifdef _WIN32
#include <io.h> /* dup2() */ #include <io.h> /* dup2() */
@ -205,7 +205,7 @@ static void event_callback(evutil_socket_t s, [[maybe_unused]] short type, void*
if (session->allowsDHT()) if (session->allowsDHT())
{ {
buf[rc] = '\0'; /* required by the DHT code */ 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) 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; sin.sin_family = AF_INET;
if (!is_default) 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(); sin.sin_port = udp_port_.network();
@ -314,9 +314,25 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session)
} }
} }
tr_session::tr_udp_core::~tr_udp_core() 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_dhtUninit(&session_);
}
}
tr_session::tr_udp_core::~tr_udp_core()
{
dhtUninit();
if (udp_socket_ != TR_BAD_SOCKET) if (udp_socket_ != TR_BAD_SOCKET)
{ {