880 lines
22 KiB
C++
880 lines
22 KiB
C++
/*
|
|
* Copyright (c) 2009-2010 by Juliusz Chroboczek
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
*
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <csignal> /* sig_atomic_t */
|
|
#include <cstdio>
|
|
#include <cstdlib> /* atoi() */
|
|
#include <cstring> /* memcpy(), memset(), memchr(), strlen() */
|
|
|
|
#ifdef _WIN32
|
|
#include <inttypes.h>
|
|
#include <ws2tcpip.h>
|
|
#undef gai_strerror
|
|
#define gai_strerror gai_strerrorA
|
|
#else
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h> /* socket(), bind() */
|
|
#include <netdb.h>
|
|
#include <netinet/in.h> /* sockaddr_in */
|
|
#endif
|
|
|
|
/* third party */
|
|
#include <event2/event.h>
|
|
#include <dht/dht.h>
|
|
|
|
/* libT */
|
|
#include "transmission.h"
|
|
#include "crypto-utils.h"
|
|
#include "file.h"
|
|
#include "log.h"
|
|
#include "net.h"
|
|
#include "peer-mgr.h" /* tr_peerMgrCompactToPex() */
|
|
#include "platform.h" /* tr_threadNew() */
|
|
#include "session.h"
|
|
#include "torrent.h" /* tr_torrentFindFromHash() */
|
|
#include "tr-assert.h"
|
|
#include "tr-dht.h"
|
|
#include "trevent.h" /* tr_runInEventThread() */
|
|
#include "utils.h"
|
|
#include "variant.h"
|
|
|
|
static struct event* dht_timer = nullptr;
|
|
static unsigned char myid[20];
|
|
static tr_session* session_ = nullptr;
|
|
|
|
static void timer_callback(evutil_socket_t s, short type, void* ignore);
|
|
|
|
struct bootstrap_closure
|
|
{
|
|
tr_session* session;
|
|
uint8_t* nodes;
|
|
uint8_t* nodes6;
|
|
size_t len;
|
|
size_t len6;
|
|
};
|
|
|
|
static bool bootstrap_done(tr_session* session, int af)
|
|
{
|
|
int status;
|
|
|
|
if (af == 0)
|
|
{
|
|
return bootstrap_done(session, AF_INET) && bootstrap_done(session, AF_INET6);
|
|
}
|
|
|
|
status = tr_dhtStatus(session, af, nullptr);
|
|
return status == TR_DHT_STOPPED || status >= TR_DHT_FIREWALLED;
|
|
}
|
|
|
|
static void nap(int roughly_sec)
|
|
{
|
|
int const roughly_msec = roughly_sec * 1000;
|
|
int const msec = roughly_msec / 2 + tr_rand_int_weak(roughly_msec);
|
|
tr_wait_msec(msec);
|
|
}
|
|
|
|
static int bootstrap_af(tr_session* session)
|
|
{
|
|
if (bootstrap_done(session, AF_INET6))
|
|
{
|
|
return AF_INET;
|
|
}
|
|
|
|
if (bootstrap_done(session, AF_INET))
|
|
{
|
|
return AF_INET6;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void bootstrap_from_name(char const* name, tr_port port, int af)
|
|
{
|
|
struct addrinfo hints;
|
|
struct addrinfo* info;
|
|
struct addrinfo* infop;
|
|
char pp[10];
|
|
int rc;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
hints.ai_family = af;
|
|
/* No, just passing p + 1 to gai won't work. */
|
|
tr_snprintf(pp, sizeof(pp), "%d", (int)port);
|
|
|
|
rc = getaddrinfo(name, pp, &hints, &info);
|
|
|
|
if (rc != 0)
|
|
{
|
|
tr_logAddNamedError("DHT", "%s:%s: %s", name, pp, gai_strerror(rc));
|
|
return;
|
|
}
|
|
|
|
infop = info;
|
|
|
|
while (infop != nullptr)
|
|
{
|
|
dht_ping_node(infop->ai_addr, infop->ai_addrlen);
|
|
|
|
nap(15);
|
|
|
|
if (bootstrap_done(session_, af))
|
|
{
|
|
break;
|
|
}
|
|
|
|
infop = infop->ai_next;
|
|
}
|
|
|
|
freeaddrinfo(info);
|
|
}
|
|
|
|
static void dht_bootstrap(void* closure)
|
|
{
|
|
auto* cl = static_cast<struct bootstrap_closure*>(closure);
|
|
int num = cl->len / 6;
|
|
int num6 = cl->len6 / 18;
|
|
|
|
if (session_ != cl->session)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cl->len > 0)
|
|
{
|
|
tr_logAddNamedInfo("DHT", "Bootstrapping from %d IPv4 nodes", num);
|
|
}
|
|
|
|
if (cl->len6 > 0)
|
|
{
|
|
tr_logAddNamedInfo("DHT", "Bootstrapping from %d IPv6 nodes", num6);
|
|
}
|
|
|
|
for (int i = 0; i < std::max(num, num6); ++i)
|
|
{
|
|
if (i < num && !bootstrap_done(cl->session, AF_INET))
|
|
{
|
|
tr_port port;
|
|
struct tr_address addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.type = TR_AF_INET;
|
|
memcpy(&addr.addr.addr4, &cl->nodes[i * 6], 4);
|
|
memcpy(&port, &cl->nodes[i * 6 + 4], 2);
|
|
port = ntohs(port);
|
|
tr_dhtAddNode(cl->session, &addr, port, 1);
|
|
}
|
|
|
|
if (i < num6 && !bootstrap_done(cl->session, AF_INET6))
|
|
{
|
|
tr_port port;
|
|
struct tr_address addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.type = TR_AF_INET6;
|
|
memcpy(&addr.addr.addr6, &cl->nodes6[i * 18], 16);
|
|
memcpy(&port, &cl->nodes6[i * 18 + 16], 2);
|
|
port = ntohs(port);
|
|
tr_dhtAddNode(cl->session, &addr, port, 1);
|
|
}
|
|
|
|
/* Our DHT code is able to take up to 9 nodes in a row without
|
|
dropping any. After that, it takes some time to split buckets.
|
|
So ping the first 8 nodes quickly, then slow down. */
|
|
if (i < 8)
|
|
{
|
|
nap(2);
|
|
}
|
|
else
|
|
{
|
|
nap(15);
|
|
}
|
|
|
|
if (bootstrap_done(session_, 0))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bootstrap_done(cl->session, 0))
|
|
{
|
|
char* bootstrap_file;
|
|
tr_sys_file_t f = TR_BAD_SYS_FILE;
|
|
|
|
bootstrap_file = tr_buildPath(cl->session->configDir, "dht.bootstrap", nullptr);
|
|
|
|
if (bootstrap_file != nullptr)
|
|
{
|
|
f = tr_sys_file_open(bootstrap_file, TR_SYS_FILE_READ, 0, nullptr);
|
|
}
|
|
|
|
if (f != TR_BAD_SYS_FILE)
|
|
{
|
|
tr_logAddNamedInfo("DHT", "Attempting manual bootstrap");
|
|
|
|
for (;;)
|
|
{
|
|
char buf[201];
|
|
if (!tr_sys_file_read_line(f, buf, 200, nullptr))
|
|
{
|
|
break;
|
|
}
|
|
|
|
auto* p = static_cast<char*>(memchr(buf, ' ', strlen(buf)));
|
|
int port = 0;
|
|
|
|
if (p != nullptr)
|
|
{
|
|
port = atoi(p + 1);
|
|
}
|
|
|
|
if (p == nullptr || port <= 0 || port >= 0x10000)
|
|
{
|
|
tr_logAddNamedError("DHT", "Couldn't parse %s", buf);
|
|
continue;
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
bootstrap_from_name(buf, port, bootstrap_af(session_));
|
|
|
|
if (bootstrap_done(cl->session, 0))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
tr_sys_file_close(f, nullptr);
|
|
}
|
|
|
|
tr_free(bootstrap_file);
|
|
}
|
|
|
|
if (!bootstrap_done(cl->session, 0))
|
|
{
|
|
for (int i = 0; i < 6; ++i)
|
|
{
|
|
/* We don't want to abuse our bootstrap nodes, so be very
|
|
slow. The initial wait is to give other nodes a chance
|
|
to contact us before we attempt to contact a bootstrap
|
|
node, for example because we've just been restarted. */
|
|
nap(40);
|
|
|
|
if (bootstrap_done(cl->session, 0))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
tr_logAddNamedInfo("DHT", "Attempting bootstrap from dht.transmissionbt.com");
|
|
}
|
|
|
|
bootstrap_from_name("dht.transmissionbt.com", 6881, bootstrap_af(session_));
|
|
}
|
|
}
|
|
|
|
if (cl->nodes != nullptr)
|
|
{
|
|
tr_free(cl->nodes);
|
|
}
|
|
|
|
if (cl->nodes6 != nullptr)
|
|
{
|
|
tr_free(cl->nodes6);
|
|
}
|
|
|
|
tr_free(closure);
|
|
tr_logAddNamedDbg("DHT", "Finished bootstrapping");
|
|
}
|
|
|
|
int tr_dhtInit(tr_session* ss)
|
|
{
|
|
tr_variant benc;
|
|
int rc;
|
|
bool have_id = false;
|
|
char* dat_file;
|
|
uint8_t* nodes = nullptr;
|
|
uint8_t* nodes6 = nullptr;
|
|
uint8_t const* raw;
|
|
size_t len = 0;
|
|
size_t len6 = 0;
|
|
struct bootstrap_closure* cl;
|
|
|
|
if (session_ != nullptr) /* already initialized */
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
tr_logAddNamedDbg("DHT", "Initializing DHT");
|
|
|
|
if (tr_env_key_exists("TR_DHT_VERBOSE"))
|
|
{
|
|
dht_debug = stderr;
|
|
}
|
|
|
|
dat_file = tr_buildPath(ss->configDir, "dht.dat", nullptr);
|
|
rc = tr_variantFromFile(&benc, TR_VARIANT_FMT_BENC, dat_file, nullptr) ? 0 : -1;
|
|
tr_free(dat_file);
|
|
|
|
if (rc == 0)
|
|
{
|
|
have_id = tr_variantDictFindRaw(&benc, TR_KEY_id, &raw, &len);
|
|
|
|
if (have_id && len == 20)
|
|
{
|
|
memcpy(myid, raw, len);
|
|
}
|
|
|
|
if (ss->udp_socket != TR_BAD_SOCKET && tr_variantDictFindRaw(&benc, TR_KEY_nodes, &raw, &len) && len % 6 == 0)
|
|
{
|
|
nodes = static_cast<uint8_t*>(tr_memdup(raw, len));
|
|
}
|
|
|
|
if (ss->udp6_socket != TR_BAD_SOCKET && tr_variantDictFindRaw(&benc, TR_KEY_nodes6, &raw, &len6) && len6 % 18 == 0)
|
|
{
|
|
nodes6 = static_cast<uint8_t*>(tr_memdup(raw, len6));
|
|
}
|
|
|
|
tr_variantFree(&benc);
|
|
}
|
|
|
|
if (nodes == nullptr)
|
|
{
|
|
len = 0;
|
|
}
|
|
|
|
if (nodes6 == nullptr)
|
|
{
|
|
len6 = 0;
|
|
}
|
|
|
|
if (have_id)
|
|
{
|
|
tr_logAddNamedInfo("DHT", "Reusing old id");
|
|
}
|
|
else
|
|
{
|
|
/* Note that DHT ids need to be distributed uniformly,
|
|
* so it should be something truly random. */
|
|
tr_logAddNamedInfo("DHT", "Generating new id");
|
|
tr_rand_buffer(myid, 20);
|
|
}
|
|
|
|
rc = dht_init(ss->udp_socket, ss->udp6_socket, myid, nullptr);
|
|
|
|
if (rc < 0)
|
|
{
|
|
goto fail;
|
|
}
|
|
|
|
session_ = ss;
|
|
|
|
cl = tr_new(struct bootstrap_closure, 1);
|
|
cl->session = session_;
|
|
cl->nodes = nodes;
|
|
cl->nodes6 = nodes6;
|
|
cl->len = len;
|
|
cl->len6 = len6;
|
|
tr_threadNew(dht_bootstrap, cl);
|
|
|
|
dht_timer = evtimer_new(session_->event_base, timer_callback, session_);
|
|
tr_timerAdd(dht_timer, 0, tr_rand_int_weak(1000000));
|
|
|
|
tr_logAddNamedDbg("DHT", "DHT initialized");
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
tr_free(nodes6);
|
|
tr_free(nodes);
|
|
|
|
tr_logAddNamedDbg("DHT", "DHT initialization failed (errno = %d)", errno);
|
|
session_ = nullptr;
|
|
return -1;
|
|
}
|
|
|
|
void tr_dhtUninit(tr_session* ss)
|
|
{
|
|
if (session_ != ss)
|
|
{
|
|
return;
|
|
}
|
|
|
|
tr_logAddNamedDbg("DHT", "Uninitializing DHT");
|
|
|
|
if (dht_timer != nullptr)
|
|
{
|
|
event_free(dht_timer);
|
|
dht_timer = nullptr;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
tr_logAddNamedInfo("DHT", "Not saving nodes, DHT not ready");
|
|
}
|
|
else
|
|
{
|
|
enum
|
|
{
|
|
MAX_NODES = 300,
|
|
PORT_LEN = 2,
|
|
COMPACT_ADDR_LEN = 4,
|
|
COMPACT_LEN = (COMPACT_ADDR_LEN + PORT_LEN),
|
|
COMPACT6_ADDR_LEN = 16,
|
|
COMPACT6_LEN = (COMPACT6_ADDR_LEN + PORT_LEN),
|
|
};
|
|
|
|
struct sockaddr_in sins[MAX_NODES];
|
|
struct sockaddr_in6 sins6[MAX_NODES];
|
|
int num = MAX_NODES;
|
|
int num6 = MAX_NODES;
|
|
int n = dht_get_nodes(sins, &num, sins6, &num6);
|
|
tr_logAddNamedInfo("DHT", "Saving %d (%d + %d) nodes", n, num, num6);
|
|
|
|
tr_variant benc;
|
|
tr_variantInitDict(&benc, 3);
|
|
tr_variantDictAddRaw(&benc, TR_KEY_id, myid, 20);
|
|
|
|
if (num > 0)
|
|
{
|
|
char compact[MAX_NODES * COMPACT_LEN];
|
|
char* out = compact;
|
|
for (struct sockaddr_in const* in = sins; in < sins + num; ++in)
|
|
{
|
|
memcpy(out, &in->sin_addr, COMPACT_ADDR_LEN);
|
|
out += COMPACT_ADDR_LEN;
|
|
memcpy(out, &in->sin_port, PORT_LEN);
|
|
out += PORT_LEN;
|
|
}
|
|
|
|
tr_variantDictAddRaw(&benc, TR_KEY_nodes, compact, out - compact);
|
|
}
|
|
|
|
if (num6 > 0)
|
|
{
|
|
char compact6[MAX_NODES * COMPACT6_LEN];
|
|
char* out6 = compact6;
|
|
for (struct sockaddr_in6 const* in = sins6; in < sins6 + num6; ++in)
|
|
{
|
|
memcpy(out6, &in->sin6_addr, COMPACT6_ADDR_LEN);
|
|
out6 += COMPACT6_ADDR_LEN;
|
|
memcpy(out6, &in->sin6_port, PORT_LEN);
|
|
out6 += PORT_LEN;
|
|
}
|
|
|
|
tr_variantDictAddRaw(&benc, TR_KEY_nodes6, compact6, out6 - compact6);
|
|
}
|
|
|
|
char* const dat_file = tr_buildPath(ss->configDir, "dht.dat", nullptr);
|
|
tr_variantToFile(&benc, TR_VARIANT_FMT_BENC, dat_file);
|
|
tr_variantFree(&benc);
|
|
tr_free(dat_file);
|
|
}
|
|
|
|
dht_uninit();
|
|
tr_logAddNamedDbg("DHT", "Done uninitializing DHT");
|
|
|
|
session_ = nullptr;
|
|
}
|
|
|
|
bool tr_dhtEnabled(tr_session const* ss)
|
|
{
|
|
return ss != nullptr && ss == session_;
|
|
}
|
|
|
|
struct getstatus_closure
|
|
{
|
|
int af;
|
|
sig_atomic_t status;
|
|
sig_atomic_t count;
|
|
};
|
|
|
|
static void getstatus(void* cl)
|
|
{
|
|
auto* closure = static_cast<struct getstatus_closure*>(cl);
|
|
int good;
|
|
int dubious;
|
|
int incoming;
|
|
|
|
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* nodes_return)
|
|
{
|
|
auto closure = getstatus_closure{ af, -1, -1 };
|
|
|
|
if (!tr_dhtEnabled(session) || (af == AF_INET && session->udp_socket == TR_BAD_SOCKET) ||
|
|
(af == AF_INET6 && session->udp6_socket == TR_BAD_SOCKET))
|
|
{
|
|
if (nodes_return != nullptr)
|
|
{
|
|
*nodes_return = 0;
|
|
}
|
|
|
|
return TR_DHT_STOPPED;
|
|
}
|
|
|
|
tr_runInEventThread(session, getstatus, &closure);
|
|
|
|
while (closure.status < 0)
|
|
{
|
|
tr_wait_msec(50 /*msec*/);
|
|
}
|
|
|
|
if (nodes_return != nullptr)
|
|
{
|
|
*nodes_return = closure.count;
|
|
}
|
|
|
|
return closure.status;
|
|
}
|
|
|
|
tr_port tr_dhtPort(tr_session* ss)
|
|
{
|
|
return tr_dhtEnabled(ss) ? ss->udp_port : 0;
|
|
}
|
|
|
|
bool tr_dhtAddNode(tr_session* ss, tr_address const* address, tr_port port, bool bootstrap)
|
|
{
|
|
int af = address->type == TR_AF_INET ? AF_INET : AF_INET6;
|
|
|
|
if (!tr_dhtEnabled(ss))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* 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))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (address->type == TR_AF_INET)
|
|
{
|
|
struct sockaddr_in sin;
|
|
memset(&sin, 0, sizeof(sin));
|
|
sin.sin_family = AF_INET;
|
|
memcpy(&sin.sin_addr, &address->addr.addr4, 4);
|
|
sin.sin_port = htons(port);
|
|
dht_ping_node((struct sockaddr*)&sin, sizeof(sin));
|
|
return true;
|
|
}
|
|
|
|
if (address->type == TR_AF_INET6)
|
|
{
|
|
struct sockaddr_in6 sin6;
|
|
memset(&sin6, 0, sizeof(sin6));
|
|
sin6.sin6_family = AF_INET6;
|
|
memcpy(&sin6.sin6_addr, &address->addr.addr6, 16);
|
|
sin6.sin6_port = htons(port);
|
|
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(
|
|
[[maybe_unused]] void* ignore,
|
|
int event,
|
|
unsigned char const* info_hash,
|
|
void const* data,
|
|
size_t data_len)
|
|
{
|
|
if (event == DHT_EVENT_VALUES || event == DHT_EVENT_VALUES6)
|
|
{
|
|
tr_torrent* tor;
|
|
tr_sessionLock(session_);
|
|
tor = tr_torrentFindFromHash(session_, info_hash);
|
|
|
|
if (tor != nullptr && tr_torrentAllowsDHT(tor))
|
|
{
|
|
size_t n;
|
|
tr_pex* pex;
|
|
|
|
if (event == DHT_EVENT_VALUES)
|
|
{
|
|
pex = tr_peerMgrCompactToPex(data, data_len, nullptr, 0, &n);
|
|
}
|
|
else
|
|
{
|
|
pex = tr_peerMgrCompact6ToPex(data, data_len, nullptr, 0, &n);
|
|
}
|
|
|
|
tr_peerMgrAddPex(tor, TR_PEER_FROM_DHT, pex, n);
|
|
|
|
tr_free(pex);
|
|
tr_logAddTorDbg(tor, "Learned %d %s peers from DHT", (int)n, event == DHT_EVENT_VALUES6 ? "IPv6" : "IPv4");
|
|
}
|
|
|
|
tr_sessionUnlock(session_);
|
|
}
|
|
else if (event == DHT_EVENT_SEARCH_DONE || event == DHT_EVENT_SEARCH_DONE6)
|
|
{
|
|
tr_torrent* tor = tr_torrentFindFromHash(session_, info_hash);
|
|
|
|
if (tor != nullptr)
|
|
{
|
|
if (event == DHT_EVENT_SEARCH_DONE)
|
|
{
|
|
tr_logAddTorInfo(tor, "%s", "IPv4 DHT announce done");
|
|
tor->dhtAnnounceInProgress = false;
|
|
}
|
|
else
|
|
{
|
|
tr_logAddTorInfo(tor, "%s", "IPv6 DHT announce done");
|
|
tor->dhtAnnounce6InProgress = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int tr_dhtAnnounce(tr_torrent* tor, int af, bool announce)
|
|
{
|
|
int rc;
|
|
int status;
|
|
int numnodes;
|
|
int ret = 0;
|
|
|
|
if (!tr_torrentAllowsDHT(tor))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
status = tr_dhtStatus(tor->session, af, &numnodes);
|
|
|
|
if (status == TR_DHT_STOPPED)
|
|
{
|
|
/* Let the caller believe everything is all right. */
|
|
return 1;
|
|
}
|
|
|
|
if (status >= TR_DHT_POOR)
|
|
{
|
|
rc = dht_search(tor->info.hash, announce ? tr_sessionGetPeerPort(session_) : 0, af, callback, nullptr);
|
|
|
|
if (rc >= 0)
|
|
{
|
|
tr_logAddTorInfo(
|
|
tor,
|
|
"Starting %s DHT announce (%s, %d nodes)",
|
|
af == AF_INET6 ? "IPv6" : "IPv4",
|
|
tr_dhtPrintableStatus(status),
|
|
numnodes);
|
|
|
|
if (af == AF_INET)
|
|
{
|
|
tor->dhtAnnounceInProgress = true;
|
|
}
|
|
else
|
|
{
|
|
tor->dhtAnnounce6InProgress = true;
|
|
}
|
|
|
|
ret = 1;
|
|
}
|
|
else
|
|
{
|
|
tr_logAddTorErr(
|
|
tor,
|
|
"%s DHT announce failed (%s, %d nodes): %s",
|
|
af == AF_INET6 ? "IPv6" : "IPv4",
|
|
tr_dhtPrintableStatus(status),
|
|
numnodes,
|
|
tr_strerror(errno));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tr_logAddTorDbg(
|
|
tor,
|
|
"%s DHT not ready (%s, %d nodes)",
|
|
af == AF_INET6 ? "IPv6" : "IPv4",
|
|
tr_dhtPrintableStatus(status),
|
|
numnodes);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void tr_dhtUpkeep(tr_session* session)
|
|
{
|
|
time_t const now = tr_time();
|
|
|
|
for (auto* tor : session->torrents)
|
|
{
|
|
if (!tor->isRunning || !tr_torrentAllowsDHT(tor))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (tor->dhtAnnounceAt <= now)
|
|
{
|
|
int const rc = tr_dhtAnnounce(tor, AF_INET, true);
|
|
|
|
tor->dhtAnnounceAt = now + ((rc == 0) ? 5 + tr_rand_int_weak(5) : 25 * 60 + tr_rand_int_weak(3 * 60));
|
|
}
|
|
|
|
if (tor->dhtAnnounce6At <= now)
|
|
{
|
|
int const rc = tr_dhtAnnounce(tor, AF_INET6, true);
|
|
|
|
tor->dhtAnnounce6At = now + ((rc == 0) ? 5 + tr_rand_int_weak(5) : 25 * 60 + tr_rand_int_weak(3 * 60));
|
|
}
|
|
}
|
|
}
|
|
|
|
void tr_dhtCallback(unsigned char* buf, int buflen, struct sockaddr* from, socklen_t fromlen, void* sv)
|
|
{
|
|
TR_ASSERT(tr_isSession(static_cast<tr_session*>(sv)));
|
|
|
|
if (sv != session_)
|
|
{
|
|
return;
|
|
}
|
|
|
|
time_t tosleep;
|
|
int rc = dht_periodic(buf, buflen, from, fromlen, &tosleep, callback, nullptr);
|
|
|
|
if (rc < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
{
|
|
tosleep = 0;
|
|
}
|
|
else
|
|
{
|
|
tr_logAddNamedError("DHT", "dht_periodic failed: %s", tr_strerror(errno));
|
|
|
|
if (errno == EINVAL || errno == EFAULT)
|
|
{
|
|
abort();
|
|
}
|
|
|
|
tosleep = 1;
|
|
}
|
|
}
|
|
|
|
/* Being slightly late is fine,
|
|
and has the added benefit of adding some jitter. */
|
|
tr_timerAdd(dht_timer, (int)tosleep, tr_rand_int_weak(1000000));
|
|
}
|
|
|
|
static void timer_callback([[maybe_unused]] evutil_socket_t s, [[maybe_unused]] short type, void* session)
|
|
{
|
|
tr_dhtCallback(nullptr, 0, nullptr, 0, session);
|
|
}
|
|
|
|
/* 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([[maybe_unused]] struct sockaddr const* sa, [[maybe_unused]] int salen)
|
|
{
|
|
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)
|
|
{
|
|
unsigned char sha1[SHA_DIGEST_LENGTH];
|
|
tr_sha1(sha1, v1, len1, v2, len2, v3, len3, nullptr);
|
|
memset(hash_return, 0, hash_size);
|
|
memcpy(hash_return, sha1, std::min(hash_size, SHA_DIGEST_LENGTH));
|
|
}
|
|
|
|
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__)
|
|
|
|
extern "C" int dht_gettimeofday(struct timeval* tv, struct timezone* tz)
|
|
{
|
|
TR_ASSERT(tz == nullptr);
|
|
|
|
return tr_gettimeofday(tv);
|
|
}
|
|
|
|
#endif
|