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);
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;

View File

@ -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();

View File

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

View File

@ -5,17 +5,18 @@
#include <algorithm>
#include <cerrno>
#include <chrono>
#include <csignal> // for sig_atomic_t
#include <cstdlib> // for abort()
#include <cstdio>
#include <cstring> // for memcpy(), memset()
#include <cstdlib> // for abort()
#include <cstring> // for memcpy()
#include <ctime>
#include <fstream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <string_view>
#include <thread>
#include <tuple> // for std::tie()
#include <vector>
#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<libtransmission::Timer> dht_timer;
static std::array<unsigned char, 20> 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<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)
{
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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<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)
{
@ -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<struct sockaddr_in6, MaxNodes>{};
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_port> 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<tr_session*>(vsession);
auto hash = tr_sha1_digest_t{};
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 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<unsigned char const*>(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<std::chrono::milliseconds>(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<std::byte*>(hash_return);
std::fill_n(static_cast<char*>(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<char const*>(v1), size_t(len1) };
auto const sv2 = std::string_view{ static_cast<char const*>(v2), size_t(len2) };
auto const sv3 = std::string_view{ static_cast<char const*>(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<std::byte*>(hash_return);
std::fill_n(static_cast<char*>(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<char const*>(v1), size_t(len1) };
auto const sv2 = std::string_view{ static_cast<char const*>(v2), size_t(len2) };
auto const sv3 = std::string_view{ static_cast<char const*>(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<char const*>(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<char const*>(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<std::chrono::seconds>(d);
tv->tv_sec = s.count();
tv->tv_usec = std::chrono::duration_cast<std::chrono::microseconds>(d - s).count();
return 0;
}
auto const d = std::chrono::system_clock::now().time_since_epoch();
auto const s = std::chrono::duration_cast<std::chrono::seconds>(d);
tv->tv_sec = s.count();
tv->tv_usec = std::chrono::duration_cast<std::chrono::microseconds>(d - s).count();
return 0;
}
#endif
} // extern "C"

View File

@ -8,25 +8,16 @@
#error only libtransmission should #include this header.
#endif
#include <optional>
#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_port> 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);

View File

@ -5,7 +5,7 @@
#include <array>
#include <cerrno>
#include <cstdint>
#include <cstring> /* memcmp(), memcpy(), memset() */
#include <cstring> /* memcmp(), memset() */
#ifdef _WIN32
#include <io.h> /* 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)
{