mirror of
https://github.com/transmission/transmission
synced 2025-02-20 21:26:53 +00:00
refactor: decouple tr_announcer_udp (#4002)
This commit is contained in:
parent
6187cfd67b
commit
d191a04228
16 changed files with 1313 additions and 701 deletions
|
@ -18,227 +18,25 @@
|
|||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "announcer.h"
|
||||
#include "interned-string.h"
|
||||
#include "net.h"
|
||||
#include "peer-mgr.h" // tr_pex
|
||||
|
||||
struct tr_url_parsed_t;
|
||||
|
||||
/***
|
||||
**** SCRAPE
|
||||
***/
|
||||
|
||||
/* pick a number small enough for common tracker software:
|
||||
* - ocelot has no upper bound
|
||||
* - opentracker has an upper bound of 64
|
||||
* - udp protocol has an upper bound of 74
|
||||
* - xbtt has no upper bound
|
||||
*
|
||||
* This is only an upper bound: if the tracker complains about
|
||||
* length, announcer will incrementally lower the batch size.
|
||||
*/
|
||||
auto inline constexpr TR_MULTISCRAPE_MAX = 60;
|
||||
|
||||
struct tr_scrape_request
|
||||
{
|
||||
/* the scrape URL */
|
||||
tr_interned_string scrape_url;
|
||||
|
||||
/* the name to use when deep logging is enabled */
|
||||
char log_name[128];
|
||||
|
||||
/* info hashes of the torrents to scrape */
|
||||
std::array<tr_sha1_digest_t, TR_MULTISCRAPE_MAX> info_hash;
|
||||
|
||||
/* how many hashes to use in the info_hash field */
|
||||
int info_hash_count = 0;
|
||||
};
|
||||
|
||||
struct tr_scrape_response_row
|
||||
{
|
||||
/* the torrent's info_hash */
|
||||
tr_sha1_digest_t info_hash;
|
||||
|
||||
/* how many peers are seeding this torrent */
|
||||
int seeders = 0;
|
||||
|
||||
/* how many peers are downloading this torrent */
|
||||
int leechers = 0;
|
||||
|
||||
/* how many times this torrent has been downloaded */
|
||||
int downloads = 0;
|
||||
|
||||
/* the number of active downloaders in the swarm.
|
||||
* this is a BEP 21 extension that some trackers won't support.
|
||||
* http://www.bittorrent.org/beps/bep_0021.html#tracker-scrapes */
|
||||
int downloaders = 0;
|
||||
};
|
||||
|
||||
struct tr_scrape_response
|
||||
{
|
||||
/* whether or not we managed to connect to the tracker */
|
||||
bool did_connect = false;
|
||||
|
||||
/* whether or not the scrape timed out */
|
||||
bool did_timeout = false;
|
||||
|
||||
/* how many info hashes are in the 'rows' field */
|
||||
int row_count;
|
||||
|
||||
/* the individual torrents' scrape results */
|
||||
std::array<tr_scrape_response_row, TR_MULTISCRAPE_MAX> rows;
|
||||
|
||||
/* the raw scrape url */
|
||||
tr_interned_string scrape_url;
|
||||
|
||||
/* human-readable error string on failure, or nullptr */
|
||||
std::string errmsg;
|
||||
|
||||
/* minimum interval (in seconds) allowed between scrapes.
|
||||
* this is an unofficial extension that some trackers won't support. */
|
||||
int min_request_interval;
|
||||
};
|
||||
|
||||
using tr_scrape_response_func = void (*)(tr_scrape_response const* response, void* user_data);
|
||||
|
||||
void tr_tracker_http_scrape(
|
||||
tr_session const* session,
|
||||
tr_scrape_request const* req,
|
||||
tr_scrape_response_func response_func,
|
||||
void* user_data);
|
||||
|
||||
void tr_tracker_udp_scrape(
|
||||
tr_session* session,
|
||||
tr_scrape_request const* req,
|
||||
tr_scrape_response_func response_func,
|
||||
void* user_data);
|
||||
|
||||
/***
|
||||
**** ANNOUNCE
|
||||
***/
|
||||
|
||||
enum tr_announce_event
|
||||
{
|
||||
/* Note: the ordering of this enum's values is important to
|
||||
* announcer.c's tr_tier.announce_event_priority. If changing
|
||||
* the enum, ensure announcer.c is compatible with the change. */
|
||||
TR_ANNOUNCE_EVENT_NONE,
|
||||
TR_ANNOUNCE_EVENT_STARTED,
|
||||
TR_ANNOUNCE_EVENT_COMPLETED,
|
||||
TR_ANNOUNCE_EVENT_STOPPED,
|
||||
};
|
||||
|
||||
std::string_view tr_announce_event_get_string(tr_announce_event);
|
||||
|
||||
struct tr_announce_request
|
||||
{
|
||||
tr_announce_event event = {};
|
||||
bool partial_seed = false;
|
||||
|
||||
/* the port we listen for incoming peers on */
|
||||
tr_port port;
|
||||
|
||||
/* per-session key */
|
||||
int key = 0;
|
||||
|
||||
/* the number of peers we'd like to get back in the response */
|
||||
int numwant = 0;
|
||||
|
||||
/* the number of bytes we uploaded since the last 'started' event */
|
||||
uint64_t up = 0;
|
||||
|
||||
/* the number of good bytes we downloaded since the last 'started' event */
|
||||
uint64_t down = 0;
|
||||
|
||||
/* the number of bad bytes we downloaded since the last 'started' event */
|
||||
uint64_t corrupt = 0;
|
||||
|
||||
/* the total size of the torrent minus the number of bytes completed */
|
||||
uint64_t leftUntilComplete = 0;
|
||||
|
||||
/* the tracker's announce URL */
|
||||
tr_interned_string announce_url;
|
||||
|
||||
/* key generated by and returned from an http tracker.
|
||||
* see tr_announce_response.tracker_id_str */
|
||||
std::string tracker_id;
|
||||
|
||||
/* the torrent's peer id.
|
||||
* this changes when a torrent is stopped -> restarted. */
|
||||
tr_peer_id_t peer_id;
|
||||
|
||||
/* the torrent's info_hash */
|
||||
tr_sha1_digest_t info_hash;
|
||||
|
||||
/* the name to use when deep logging is enabled */
|
||||
char log_name[128];
|
||||
};
|
||||
|
||||
struct tr_announce_response
|
||||
{
|
||||
/* the torrent's info hash */
|
||||
tr_sha1_digest_t info_hash = {};
|
||||
|
||||
/* whether or not we managed to connect to the tracker */
|
||||
bool did_connect = false;
|
||||
|
||||
/* whether or not the scrape timed out */
|
||||
bool did_timeout = false;
|
||||
|
||||
/* preferred interval between announces.
|
||||
* transmission treats this as the interval for periodic announces */
|
||||
int interval = 0;
|
||||
|
||||
/* minimum interval between announces. (optional)
|
||||
* transmission treats this as the min interval for manual announces */
|
||||
int min_interval = 0;
|
||||
|
||||
/* how many peers are seeding this torrent */
|
||||
int seeders = -1;
|
||||
|
||||
/* how many peers are downloading this torrent */
|
||||
int leechers = -1;
|
||||
|
||||
/* how many times this torrent has been downloaded */
|
||||
int downloads = -1;
|
||||
|
||||
/* IPv4 peers that we acquired from the tracker */
|
||||
std::vector<tr_pex> pex;
|
||||
|
||||
/* IPv6 peers that we acquired from the tracker */
|
||||
std::vector<tr_pex> pex6;
|
||||
|
||||
/* human-readable error string on failure, or nullptr */
|
||||
std::string errmsg;
|
||||
|
||||
/* human-readable warning string or nullptr */
|
||||
std::string warning;
|
||||
|
||||
/* key generated by and returned from an http tracker.
|
||||
* if this is provided, subsequent http announces must include this. */
|
||||
std::string tracker_id;
|
||||
|
||||
/* tracker extension that returns the client's public IP address.
|
||||
* https://www.bittorrent.org/beps/bep_0024.html */
|
||||
std::optional<tr_address> external_ip;
|
||||
};
|
||||
|
||||
using tr_announce_response_func = void (*)(tr_announce_response const* response, void* userdata);
|
||||
|
||||
void tr_tracker_http_announce(
|
||||
tr_session const* session,
|
||||
tr_announce_request const* req,
|
||||
tr_announce_response_func response_func,
|
||||
void* user_data);
|
||||
|
||||
void tr_tracker_udp_announce(
|
||||
tr_session* session,
|
||||
tr_announce_request const* req,
|
||||
tr_announce_response_func response_func,
|
||||
void* user_data);
|
||||
|
||||
void tr_tracker_udp_start_shutdown(tr_session* session);
|
||||
|
||||
void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::string_view benc, std::string_view log_name);
|
||||
|
||||
void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::string_view benc, std::string_view log_name);
|
||||
|
|
|
@ -12,10 +12,6 @@
|
|||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/dns.h>
|
||||
#include <event2/util.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
|
@ -23,14 +19,15 @@
|
|||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "announcer-common.h"
|
||||
#include "announcer.h"
|
||||
#include "announcer-common.h"
|
||||
#include "crypto-utils.h" /* tr_rand_buffer() */
|
||||
#include "log.h"
|
||||
#include "peer-io.h"
|
||||
#include "peer-mgr.h" /* tr_peerMgrCompactToPex() */
|
||||
#include "session.h"
|
||||
#include "tr-assert.h"
|
||||
#include "tr-buffer.h"
|
||||
#include "utils.h"
|
||||
#include "web-utils.h"
|
||||
|
||||
|
@ -40,69 +37,11 @@
|
|||
|
||||
using namespace std::literals;
|
||||
|
||||
/****
|
||||
*****
|
||||
****/
|
||||
|
||||
static void tau_sockaddr_setport(struct sockaddr* sa, tr_port port)
|
||||
{
|
||||
if (sa->sa_family == AF_INET)
|
||||
{
|
||||
reinterpret_cast<sockaddr_in*>(sa)->sin_port = port.network();
|
||||
}
|
||||
else if (sa->sa_family == AF_INET6)
|
||||
{
|
||||
reinterpret_cast<sockaddr_in6*>(sa)->sin6_port = port.network();
|
||||
}
|
||||
}
|
||||
|
||||
void tr_session::tau_sendto(struct evutil_addrinfo* ai, tr_port port, void const* buf, size_t buflen) const
|
||||
{
|
||||
tau_sockaddr_setport(ai->ai_addr, port);
|
||||
udp_core_->sendto(buf, buflen, ai->ai_addr, ai->ai_addrlen);
|
||||
}
|
||||
|
||||
static uint32_t announce_ip(tr_session const* session)
|
||||
{
|
||||
if (!session->useAnnounceIP())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Since size of IP field is only 4 bytes long we can announce
|
||||
// only IPv4 addresses.
|
||||
auto const addr = tr_address::fromString(session->announceIP());
|
||||
return addr && addr->isIPv4() ? addr->addr.addr4.s_addr : 0;
|
||||
}
|
||||
|
||||
/****
|
||||
*****
|
||||
****/
|
||||
|
||||
static uint32_t evbuffer_read_ntoh_32(struct evbuffer* buf)
|
||||
{
|
||||
auto val = uint32_t{};
|
||||
evbuffer_remove(buf, &val, sizeof(uint32_t));
|
||||
return ntohl(val);
|
||||
}
|
||||
|
||||
static uint64_t evbuffer_read_ntoh_64(struct evbuffer* buf)
|
||||
{
|
||||
auto val = uint64_t{};
|
||||
evbuffer_remove(buf, &val, sizeof(uint64_t));
|
||||
return tr_ntohll(val);
|
||||
}
|
||||
|
||||
/****
|
||||
*****
|
||||
****/
|
||||
|
||||
using tau_connection_t = uint64_t;
|
||||
using tau_transaction_t = uint32_t;
|
||||
|
||||
static auto constexpr TauConnectionTtlSecs = int{ 60 };
|
||||
|
||||
using tau_transaction_t = uint32_t;
|
||||
|
||||
static tau_transaction_t tau_transaction_new()
|
||||
{
|
||||
auto tmp = tau_transaction_t{};
|
||||
|
@ -170,7 +109,7 @@ struct tau_scrape_request
|
|||
requestFinished();
|
||||
}
|
||||
|
||||
void onResponse(tau_action_t action, evbuffer* buf)
|
||||
void onResponse(tau_action_t action, libtransmission::Buffer& buf)
|
||||
{
|
||||
response.did_connect = true;
|
||||
response.did_timeout = false;
|
||||
|
@ -179,30 +118,27 @@ struct tau_scrape_request
|
|||
{
|
||||
for (int i = 0; i < response.row_count; ++i)
|
||||
{
|
||||
if (evbuffer_get_length(buf) < sizeof(uint32_t) * 3)
|
||||
if (std::size(buf) < sizeof(uint32_t) * 3)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
auto& row = response.rows[i];
|
||||
row.seeders = evbuffer_read_ntoh_32(buf);
|
||||
row.downloads = evbuffer_read_ntoh_32(buf);
|
||||
row.leechers = evbuffer_read_ntoh_32(buf);
|
||||
row.seeders = buf.toUint32();
|
||||
row.downloads = buf.toUint32();
|
||||
row.leechers = buf.toUint32();
|
||||
}
|
||||
|
||||
requestFinished();
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t const buflen = evbuffer_get_length(buf);
|
||||
auto const errmsg = action == TAU_ACTION_ERROR && buflen > 0 ?
|
||||
std::string_view{ reinterpret_cast<char const*>(evbuffer_pullup(buf, -1)), buflen } :
|
||||
_("Unknown error");
|
||||
std::string const errmsg = action == TAU_ACTION_ERROR && !std::empty(buf) ? buf.toString() : _("Unknown error");
|
||||
fail(true, false, errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> payload;
|
||||
std::vector<std::byte> payload;
|
||||
|
||||
time_t sent_at;
|
||||
time_t created_at;
|
||||
|
@ -214,45 +150,41 @@ struct tau_scrape_request
|
|||
};
|
||||
|
||||
static tau_scrape_request make_tau_scrape_request(
|
||||
tr_scrape_request const* in,
|
||||
tr_scrape_request const& in,
|
||||
tr_scrape_response_func callback,
|
||||
void* user_data)
|
||||
{
|
||||
tau_transaction_t const transaction_id = tau_transaction_new();
|
||||
|
||||
/* build the payload */
|
||||
auto* buf = evbuffer_new();
|
||||
evbuffer_add_hton_32(buf, TAU_ACTION_SCRAPE);
|
||||
evbuffer_add_hton_32(buf, transaction_id);
|
||||
for (int i = 0; i < in->info_hash_count; ++i)
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint32(TAU_ACTION_SCRAPE);
|
||||
buf.addUint32(transaction_id);
|
||||
for (int i = 0; i < in.info_hash_count; ++i)
|
||||
{
|
||||
evbuffer_add(buf, std::data(in->info_hash[i]), std::size(in->info_hash[i]));
|
||||
buf.add(in.info_hash[i]);
|
||||
}
|
||||
auto const* const payload_begin = evbuffer_pullup(buf, -1);
|
||||
auto const* const payload_end = payload_begin + evbuffer_get_length(buf);
|
||||
|
||||
/* build the tau_scrape_request */
|
||||
|
||||
// build the tau_scrape_request
|
||||
auto req = tau_scrape_request{};
|
||||
req.callback = callback;
|
||||
req.created_at = tr_time();
|
||||
req.transaction_id = transaction_id;
|
||||
req.callback = callback;
|
||||
req.user_data = user_data;
|
||||
req.response.scrape_url = in->scrape_url;
|
||||
req.response.row_count = in->info_hash_count;
|
||||
req.payload.assign(payload_begin, payload_end);
|
||||
req.response.scrape_url = in.scrape_url;
|
||||
req.response.row_count = in.info_hash_count;
|
||||
req.payload.insert(std::end(req.payload), std::begin(buf), std::end(buf));
|
||||
|
||||
for (int i = 0; i < req.response.row_count; ++i)
|
||||
{
|
||||
req.response.rows[i].seeders = -1;
|
||||
req.response.rows[i].leechers = -1;
|
||||
req.response.rows[i].downloads = -1;
|
||||
req.response.rows[i].info_hash = in->info_hash[i];
|
||||
req.response.rows[i].info_hash = in.info_hash[i];
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
evbuffer_free(buf);
|
||||
return req;
|
||||
}
|
||||
|
||||
|
@ -280,31 +212,33 @@ struct tau_announce_request
|
|||
this->requestFinished();
|
||||
}
|
||||
|
||||
void onResponse(tau_action_t action, struct evbuffer* buf)
|
||||
void onResponse(tau_action_t action, libtransmission::Buffer& buf)
|
||||
{
|
||||
size_t const buflen = evbuffer_get_length(buf);
|
||||
auto const buflen = std::size(buf);
|
||||
|
||||
this->response.did_connect = true;
|
||||
this->response.did_timeout = false;
|
||||
|
||||
if (action == TAU_ACTION_ANNOUNCE && buflen >= 3 * sizeof(uint32_t))
|
||||
{
|
||||
response.interval = evbuffer_read_ntoh_32(buf);
|
||||
response.leechers = evbuffer_read_ntoh_32(buf);
|
||||
response.seeders = evbuffer_read_ntoh_32(buf);
|
||||
response.pex = tr_peerMgrCompactToPex(evbuffer_pullup(buf, -1), evbuffer_get_length(buf), nullptr, 0);
|
||||
response.interval = buf.toUint32();
|
||||
response.leechers = buf.toUint32();
|
||||
response.seeders = buf.toUint32();
|
||||
|
||||
auto const compact_len = std::size(buf);
|
||||
auto contiguous = std::array<uint8_t, 576>{};
|
||||
buf.toBuf(std::data(contiguous), compact_len);
|
||||
response.pex = tr_peerMgrCompactToPex(std::data(contiguous), compact_len, nullptr, 0);
|
||||
requestFinished();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const errmsg = action == TAU_ACTION_ERROR && buflen > 0 ?
|
||||
std::string_view{ reinterpret_cast<char const*>(evbuffer_pullup(buf, -1)), buflen } :
|
||||
_("Unknown error");
|
||||
std::string const errmsg = action == TAU_ACTION_ERROR && !std::empty(buf) ? buf.toString() : _("Unknown error");
|
||||
fail(true, false, errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> payload;
|
||||
std::vector<std::byte> payload;
|
||||
|
||||
time_t created_at = 0;
|
||||
time_t sent_at = 0;
|
||||
|
@ -344,29 +278,27 @@ static tau_announce_event get_tau_announce_event(tr_announce_event e)
|
|||
}
|
||||
|
||||
static tau_announce_request make_tau_announce_request(
|
||||
tr_session const* session,
|
||||
tr_announce_request const* in,
|
||||
uint32_t announce_ip,
|
||||
tr_announce_request const& in,
|
||||
tr_announce_response_func callback,
|
||||
void* user_data)
|
||||
{
|
||||
tau_transaction_t const transaction_id = tau_transaction_new();
|
||||
|
||||
/* build the payload */
|
||||
auto* buf = evbuffer_new();
|
||||
evbuffer_add_hton_32(buf, TAU_ACTION_ANNOUNCE);
|
||||
evbuffer_add_hton_32(buf, transaction_id);
|
||||
evbuffer_add(buf, std::data(in->info_hash), std::size(in->info_hash));
|
||||
evbuffer_add(buf, std::data(in->peer_id), std::size(in->peer_id));
|
||||
evbuffer_add_hton_64(buf, in->down);
|
||||
evbuffer_add_hton_64(buf, in->leftUntilComplete);
|
||||
evbuffer_add_hton_64(buf, in->up);
|
||||
evbuffer_add_hton_32(buf, get_tau_announce_event(in->event));
|
||||
evbuffer_add_hton_32(buf, announce_ip(session));
|
||||
evbuffer_add_hton_32(buf, in->key);
|
||||
evbuffer_add_hton_32(buf, in->numwant);
|
||||
evbuffer_add_hton_16(buf, in->port.host());
|
||||
auto const* const payload_begin = evbuffer_pullup(buf, -1);
|
||||
auto const* const payload_end = payload_begin + evbuffer_get_length(buf);
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint32(TAU_ACTION_ANNOUNCE);
|
||||
buf.addUint32(transaction_id);
|
||||
buf.add(in.info_hash);
|
||||
buf.add(in.peer_id);
|
||||
buf.addUint64(in.down);
|
||||
buf.addUint64(in.leftUntilComplete);
|
||||
buf.addUint64(in.up);
|
||||
buf.addUint32(get_tau_announce_event(in.event));
|
||||
buf.addUint32(announce_ip);
|
||||
buf.addUint32(in.key);
|
||||
buf.addUint32(in.numwant);
|
||||
buf.addUint16(in.port.host());
|
||||
|
||||
/* build the tau_announce_request */
|
||||
auto req = tau_announce_request();
|
||||
|
@ -374,13 +306,12 @@ static tau_announce_request make_tau_announce_request(
|
|||
req.transaction_id = transaction_id;
|
||||
req.callback = callback;
|
||||
req.user_data = user_data;
|
||||
req.payload.assign(payload_begin, payload_end);
|
||||
req.payload.insert(std::end(req.payload), std::begin(buf), std::end(buf));
|
||||
req.response.seeders = -1;
|
||||
req.response.leechers = -1;
|
||||
req.response.downloads = -1;
|
||||
req.response.info_hash = in->info_hash;
|
||||
req.response.info_hash = in.info_hash;
|
||||
|
||||
evbuffer_free(buf);
|
||||
return req;
|
||||
}
|
||||
|
||||
|
@ -392,9 +323,19 @@ static tau_announce_request make_tau_announce_request(
|
|||
|
||||
struct tau_tracker
|
||||
{
|
||||
[[nodiscard]] auto isIdle() const
|
||||
using Mediator = tr_announcer_udp::Mediator;
|
||||
|
||||
tau_tracker(Mediator& mediator, tr_interned_string key_in, tr_interned_string host_in, tr_port port_in)
|
||||
: mediator_{ mediator }
|
||||
, key{ key_in }
|
||||
, host{ host_in }
|
||||
, port{ port_in }
|
||||
{
|
||||
return std::empty(announces) && std::empty(scrapes) && dns_request == nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto isIdle() const noexcept
|
||||
{
|
||||
return std::empty(announces) && std::empty(scrapes) && (dns_request_ == 0U);
|
||||
}
|
||||
|
||||
void failAll(bool did_connect, bool did_timeout, std::string_view errmsg)
|
||||
|
@ -413,15 +354,31 @@ struct tau_tracker
|
|||
this->announces.clear();
|
||||
}
|
||||
|
||||
tr_session* const session;
|
||||
void sendto(void const* buf, size_t buflen)
|
||||
{
|
||||
auto [ss, sslen] = *addr_;
|
||||
|
||||
if (ss.ss_family == AF_INET)
|
||||
{
|
||||
reinterpret_cast<sockaddr_in*>(&ss)->sin_port = port.network();
|
||||
}
|
||||
else if (ss.ss_family == AF_INET6)
|
||||
{
|
||||
reinterpret_cast<sockaddr_in6*>(&ss)->sin6_port = port.network();
|
||||
}
|
||||
|
||||
mediator_.sendto(buf, buflen, reinterpret_cast<sockaddr*>(&ss), sslen);
|
||||
}
|
||||
|
||||
Mediator& mediator_;
|
||||
|
||||
tr_interned_string const key;
|
||||
tr_interned_string const host;
|
||||
tr_port const port;
|
||||
|
||||
evdns_getaddrinfo_request* dns_request = nullptr;
|
||||
std::shared_ptr<evutil_addrinfo> addr;
|
||||
time_t addr_expiration_time = 0;
|
||||
libtransmission::Dns::Tag dns_request_ = {};
|
||||
std::optional<std::pair<sockaddr_storage, socklen_t>> addr_;
|
||||
time_t addr_expires_at_ = 0;
|
||||
|
||||
time_t connecting_at = 0;
|
||||
time_t connection_expiration_time = 0;
|
||||
|
@ -430,53 +387,46 @@ struct tau_tracker
|
|||
|
||||
time_t close_at = 0;
|
||||
|
||||
static time_t constexpr DnsRetryIntervalSecs = 60 * 60;
|
||||
|
||||
std::list<tau_announce_request> announces;
|
||||
std::list<tau_scrape_request> scrapes;
|
||||
|
||||
tau_tracker(tr_session* session_in, tr_interned_string key_in, tr_interned_string host_in, tr_port port_in)
|
||||
: session{ session_in }
|
||||
, key{ key_in }
|
||||
, host{ host_in }
|
||||
, port{ port_in }
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static void tau_tracker_upkeep(struct tau_tracker* /*tracker*/);
|
||||
|
||||
static void tau_tracker_on_dns(int errcode, struct evutil_addrinfo* addr, void* vtracker)
|
||||
static void tau_tracker_on_dns(tau_tracker* const tracker, sockaddr const* sa, socklen_t salen, time_t expires_at)
|
||||
{
|
||||
auto* tracker = static_cast<struct tau_tracker*>(vtracker);
|
||||
tracker->dns_request_ = {};
|
||||
|
||||
tracker->dns_request = nullptr;
|
||||
tracker->addr_expiration_time = tr_time() + 60 * 60; /* one hour */
|
||||
|
||||
if (errcode != 0)
|
||||
if (sa == nullptr)
|
||||
{
|
||||
auto const errmsg = fmt::format(
|
||||
_("Couldn't find address of tracker '{host}': {error} ({error_code})"),
|
||||
fmt::arg("host", tracker->host),
|
||||
fmt::arg("error", evutil_gai_strerror(errcode)),
|
||||
fmt::arg("error_code", errcode));
|
||||
auto const errmsg = fmt::format(_("Couldn't find address of tracker '{host}'"), fmt::arg("host", tracker->host));
|
||||
logwarn(tracker->key, errmsg);
|
||||
tracker->failAll(false, false, errmsg.c_str());
|
||||
tracker->addr_expires_at_ = tr_time() + tau_tracker::DnsRetryIntervalSecs;
|
||||
}
|
||||
else
|
||||
{
|
||||
logdbg(tracker->key, "DNS lookup succeeded");
|
||||
tracker->addr.reset(addr, evutil_freeaddrinfo);
|
||||
auto ss = sockaddr_storage{};
|
||||
memcpy(&ss, sa, salen);
|
||||
tracker->addr_.emplace(ss, salen);
|
||||
tracker->addr_expires_at_ = expires_at;
|
||||
tau_tracker_upkeep(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
static void tau_tracker_send_request(struct tau_tracker* tracker, void const* payload, size_t payload_len)
|
||||
{
|
||||
struct evbuffer* buf = evbuffer_new();
|
||||
logdbg(tracker->key, fmt::format("sending request w/connection id {}", tracker->connection_id));
|
||||
evbuffer_add_hton_64(buf, tracker->connection_id);
|
||||
evbuffer_add_reference(buf, payload, payload_len, nullptr, nullptr);
|
||||
tracker->session->tau_sendto(tracker->addr.get(), tracker->port, evbuffer_pullup(buf, -1), evbuffer_get_length(buf));
|
||||
evbuffer_free(buf);
|
||||
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint64(tracker->connection_id);
|
||||
buf.add(payload, payload_len);
|
||||
|
||||
auto const contiguous = std::vector<std::byte>(std::begin(buf), std::end(buf));
|
||||
tracker->sendto(std::data(contiguous), std::size(contiguous));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
@ -511,41 +461,34 @@ static void tau_tracker_send_requests(tau_tracker* tracker, std::list<T>& reqs)
|
|||
|
||||
static void tau_tracker_send_reqs(tau_tracker* tracker)
|
||||
{
|
||||
TR_ASSERT(tracker->dns_request == nullptr);
|
||||
TR_ASSERT(!tracker->dns_request_);
|
||||
TR_ASSERT(tracker->addr_);
|
||||
TR_ASSERT(tracker->connecting_at == 0);
|
||||
TR_ASSERT(tracker->addr != nullptr);
|
||||
TR_ASSERT(tracker->connection_expiration_time > tr_time());
|
||||
|
||||
tau_tracker_send_requests(tracker, tracker->announces);
|
||||
tau_tracker_send_requests(tracker, tracker->scrapes);
|
||||
}
|
||||
|
||||
static void on_tracker_connection_response(struct tau_tracker* tracker, tau_action_t action, struct evbuffer* buf)
|
||||
static void on_tracker_connection_response(struct tau_tracker& tracker, tau_action_t action, libtransmission::Buffer& buf)
|
||||
{
|
||||
time_t const now = tr_time();
|
||||
|
||||
tracker->connecting_at = 0;
|
||||
tracker->connection_transaction_id = 0;
|
||||
tracker.connecting_at = 0;
|
||||
tracker.connection_transaction_id = 0;
|
||||
|
||||
if (action == TAU_ACTION_CONNECT)
|
||||
{
|
||||
tracker->connection_id = evbuffer_read_ntoh_64(buf);
|
||||
tracker->connection_expiration_time = now + TauConnectionTtlSecs;
|
||||
logdbg(tracker->key, fmt::format("Got a new connection ID from tracker: {}", tracker->connection_id));
|
||||
tracker.connection_id = buf.toUint64();
|
||||
tracker.connection_expiration_time = tr_time() + TauConnectionTtlSecs;
|
||||
logdbg(tracker.key, fmt::format("Got a new connection ID from tracker: {}", tracker.connection_id));
|
||||
}
|
||||
else
|
||||
else if (action == TAU_ACTION_ERROR)
|
||||
{
|
||||
size_t const buflen = buf != nullptr ? evbuffer_get_length(buf) : 0;
|
||||
|
||||
auto const errmsg = action == TAU_ACTION_ERROR && buflen > 0 ?
|
||||
std::string_view{ reinterpret_cast<char const*>(evbuffer_pullup(buf, -1)), buflen } :
|
||||
std::string_view{ _("Connection failed") };
|
||||
|
||||
logdbg(tracker->key, errmsg);
|
||||
tracker->failAll(true, false, errmsg);
|
||||
std::string const errmsg = !std::empty(buf) ? buf.toString() : _("Connection failed");
|
||||
logdbg(tracker.key, errmsg);
|
||||
tracker.failAll(true, false, errmsg);
|
||||
}
|
||||
|
||||
tau_tracker_upkeep(tracker);
|
||||
tau_tracker_upkeep(&tracker);
|
||||
}
|
||||
|
||||
static void tau_tracker_timeout_reqs(struct tau_tracker* tracker)
|
||||
|
@ -555,7 +498,8 @@ static void tau_tracker_timeout_reqs(struct tau_tracker* tracker)
|
|||
|
||||
if (tracker->connecting_at != 0 && tracker->connecting_at + TauRequestTtl < now)
|
||||
{
|
||||
on_tracker_connection_response(tracker, TAU_ACTION_ERROR, nullptr);
|
||||
auto empty_buf = libtransmission::Buffer{};
|
||||
on_tracker_connection_response(*tracker, TAU_ACTION_ERROR, empty_buf);
|
||||
}
|
||||
|
||||
if (auto& reqs = tracker->announces; !std::empty(reqs))
|
||||
|
@ -601,11 +545,11 @@ static void tau_tracker_upkeep_ex(struct tau_tracker* tracker, bool timeout_reqs
|
|||
bool const closing = tracker->close_at != 0;
|
||||
|
||||
/* if the address info is too old, expire it */
|
||||
if (tracker->addr != nullptr && (closing || tracker->addr_expiration_time <= now))
|
||||
if (tracker->addr_ && (closing || tracker->addr_expires_at_ <= now))
|
||||
{
|
||||
logtrace(tracker->host, "Expiring old DNS result");
|
||||
tracker->addr.reset();
|
||||
tracker->addr_expiration_time = 0;
|
||||
tracker->addr_.reset();
|
||||
tracker->addr_expires_at_ = 0;
|
||||
}
|
||||
|
||||
/* are there any requests pending? */
|
||||
|
@ -615,52 +559,51 @@ static void tau_tracker_upkeep_ex(struct tau_tracker* tracker, bool timeout_reqs
|
|||
}
|
||||
|
||||
// if DNS lookup *recently* failed for this host, do nothing
|
||||
if (tracker->addr == nullptr && now < tracker->addr_expiration_time)
|
||||
if (!tracker->addr_ && now < tracker->addr_expires_at_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* if we don't have an address yet, try & get one now. */
|
||||
if (!closing && tracker->addr == nullptr && tracker->dns_request == nullptr)
|
||||
if (!closing && !tracker->addr_ && (tracker->dns_request_ == 0U))
|
||||
{
|
||||
struct evutil_addrinfo hints = {};
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
auto hints = libtransmission::Dns::Hints{};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
logtrace(tracker->host, "Trying a new DNS lookup");
|
||||
tracker->dns_request = evdns_getaddrinfo(
|
||||
tracker->session->evdnsBase(),
|
||||
tr_strlower(tracker->host.sv()).c_str(),
|
||||
nullptr,
|
||||
&hints,
|
||||
tau_tracker_on_dns,
|
||||
tracker);
|
||||
tracker->dns_request_ = tracker->mediator_.dns().lookup(
|
||||
tracker->host.sv(),
|
||||
[tracker](sockaddr const* sa, socklen_t len, time_t expires_at)
|
||||
{ tau_tracker_on_dns(tracker, sa, len, expires_at); },
|
||||
hints);
|
||||
return;
|
||||
}
|
||||
|
||||
logtrace(
|
||||
tracker->key,
|
||||
fmt::format(
|
||||
"addr {} -- connected {} ({} {}) -- connecting_at {}",
|
||||
fmt::ptr(tracker->addr),
|
||||
"connected {} ({} {}) -- connecting_at {}",
|
||||
tracker->connection_expiration_time > now,
|
||||
tracker->connection_expiration_time,
|
||||
now,
|
||||
tracker->connecting_at));
|
||||
|
||||
/* also need a valid connection ID... */
|
||||
if (tracker->addr != nullptr && tracker->connection_expiration_time <= now && tracker->connecting_at == 0)
|
||||
if (tracker->addr_ && tracker->connection_expiration_time <= now && tracker->connecting_at == 0)
|
||||
{
|
||||
struct evbuffer* buf = evbuffer_new();
|
||||
tracker->connecting_at = now;
|
||||
tracker->connection_transaction_id = tau_transaction_new();
|
||||
logtrace(tracker->key, fmt::format("Trying to connect. Transaction ID is {}", tracker->connection_transaction_id));
|
||||
evbuffer_add_hton_64(buf, 0x41727101980LL);
|
||||
evbuffer_add_hton_32(buf, TAU_ACTION_CONNECT);
|
||||
evbuffer_add_hton_32(buf, tracker->connection_transaction_id);
|
||||
tracker->session->tau_sendto(tracker->addr.get(), tracker->port, evbuffer_pullup(buf, -1), evbuffer_get_length(buf));
|
||||
evbuffer_free(buf);
|
||||
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint64(0x41727101980LL);
|
||||
buf.addUint32(TAU_ACTION_CONNECT);
|
||||
buf.addUint32(tracker->connection_transaction_id);
|
||||
|
||||
auto const contiguous = std::vector<std::byte>(std::begin(buf), std::end(buf));
|
||||
tracker->sendto(std::data(contiguous), std::size(contiguous));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -669,7 +612,7 @@ static void tau_tracker_upkeep_ex(struct tau_tracker* tracker, bool timeout_reqs
|
|||
tau_tracker_timeout_reqs(tracker);
|
||||
}
|
||||
|
||||
if (tracker->addr != nullptr && tracker->connection_expiration_time > now)
|
||||
if (tracker->addr_ && tracker->connection_expiration_time > now)
|
||||
{
|
||||
tau_tracker_send_reqs(tracker);
|
||||
}
|
||||
|
@ -686,227 +629,181 @@ static void tau_tracker_upkeep(struct tau_tracker* tracker)
|
|||
*****
|
||||
****/
|
||||
|
||||
struct tr_announcer_udp
|
||||
class tr_announcer_udp_impl final : public tr_announcer_udp
|
||||
{
|
||||
explicit tr_announcer_udp(tr_session* session_in)
|
||||
: session{ session_in }
|
||||
public:
|
||||
explicit tr_announcer_udp_impl(Mediator& mediator)
|
||||
: mediator_{ mediator }
|
||||
{
|
||||
}
|
||||
|
||||
std::list<tau_tracker> trackers;
|
||||
|
||||
tr_session* const session;
|
||||
};
|
||||
|
||||
static struct tr_announcer_udp* announcer_udp_get(tr_session* session)
|
||||
{
|
||||
if (session->announcer_udp != nullptr)
|
||||
void announce(tr_announce_request const& request, tr_announce_response_func response_func, void* user_data) override
|
||||
{
|
||||
return session->announcer_udp;
|
||||
}
|
||||
|
||||
auto* const tau = new tr_announcer_udp(session);
|
||||
session->announcer_udp = tau;
|
||||
return tau;
|
||||
}
|
||||
|
||||
/* Finds the tau_tracker struct that corresponds to this url.
|
||||
If it doesn't exist yet, create one. */
|
||||
static tau_tracker* tau_session_get_tracker(tr_announcer_udp* tau, tr_interned_string announce_url)
|
||||
{
|
||||
// build a lookup key for this tracker
|
||||
auto const parsed = tr_urlParseTracker(announce_url);
|
||||
TR_ASSERT(parsed);
|
||||
if (!parsed)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// see if we already have it
|
||||
auto const key = tr_announcerGetKey(*parsed);
|
||||
for (auto& tracker : tau->trackers)
|
||||
{
|
||||
if (tracker.key == key)
|
||||
auto* const tracker = getTrackerFromUrl(request.announce_url);
|
||||
if (tracker == nullptr)
|
||||
{
|
||||
return &tracker;
|
||||
return;
|
||||
}
|
||||
|
||||
// Since size of IP field is only 4 bytes long, we can only announce IPv4 addresses
|
||||
auto const addr = mediator_.announceIP();
|
||||
uint32_t const announce_ip = addr && addr->isIPv4() ? addr->addr.addr4.s_addr : 0;
|
||||
tracker->announces.push_back(make_tau_announce_request(announce_ip, request, response_func, user_data));
|
||||
tau_tracker_upkeep_ex(tracker, false);
|
||||
}
|
||||
|
||||
// we don't have it -- build a new one
|
||||
tau->trackers.emplace_back(tau->session, key, tr_interned_string(parsed->host), tr_port::fromHost(parsed->port));
|
||||
auto* const tracker = &tau->trackers.back();
|
||||
logtrace(tracker->key, "New tau_tracker created");
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/****
|
||||
*****
|
||||
***** PUBLIC API
|
||||
*****
|
||||
****/
|
||||
|
||||
void tr_tracker_udp_upkeep(tr_session* session)
|
||||
{
|
||||
if (auto* const tau = session->announcer_udp; tau != nullptr)
|
||||
void scrape(tr_scrape_request const& request, tr_scrape_response_func response_func, void* user_data) override
|
||||
{
|
||||
for (auto& tracker : tau->trackers)
|
||||
auto* const tracker = getTrackerFromUrl(request.scrape_url);
|
||||
if (tracker == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tracker->scrapes.push_back(make_tau_scrape_request(request, response_func, user_data));
|
||||
tau_tracker_upkeep_ex(tracker, false);
|
||||
}
|
||||
|
||||
void upkeep() override
|
||||
{
|
||||
for (auto& tracker : trackers_)
|
||||
{
|
||||
tau_tracker_upkeep(&tracker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool tr_tracker_udp_is_idle(tr_session const* session)
|
||||
{
|
||||
auto const* tau = session->announcer_udp;
|
||||
|
||||
return tau == nullptr ||
|
||||
std::all_of(std::begin(tau->trackers), std::end(tau->trackers), [](auto const& tracker) { return tracker.isIdle(); });
|
||||
}
|
||||
|
||||
/* drop dead now. */
|
||||
void tr_tracker_udp_close(tr_session* session)
|
||||
{
|
||||
if (auto* const tau = session->announcer_udp; tau != nullptr)
|
||||
[[nodiscard]] bool isIdle() const noexcept override
|
||||
{
|
||||
session->announcer_udp = nullptr;
|
||||
delete tau;
|
||||
return std::all_of(std::begin(trackers_), std::end(trackers_), [](auto const& tracker) { return tracker.isIdle(); });
|
||||
}
|
||||
}
|
||||
|
||||
/* start shutting down.
|
||||
This doesn't destroy everything if there are requests,
|
||||
but sets a deadline on how much longer to wait for the remaining ones */
|
||||
void tr_tracker_udp_start_shutdown(tr_session* session)
|
||||
{
|
||||
time_t const now = time(nullptr);
|
||||
|
||||
if (auto* const tau = session->announcer_udp; tau != nullptr)
|
||||
// Start shutting down.
|
||||
// This doesn't destroy everything if there are requests,
|
||||
// but sets a deadline on how much longer to wait for the remaining ones.
|
||||
void startShutdown() override
|
||||
{
|
||||
for (auto& tracker : tau->trackers)
|
||||
auto const now = time(nullptr);
|
||||
|
||||
for (auto& tracker : trackers_)
|
||||
{
|
||||
if (tracker.dns_request != nullptr)
|
||||
// if there's a pending DNS request, cancel it
|
||||
if (tracker.dns_request_ != 0U)
|
||||
{
|
||||
evdns_getaddrinfo_cancel(tracker.dns_request);
|
||||
mediator_.dns().cancel(tracker.dns_request_);
|
||||
}
|
||||
|
||||
tracker.close_at = now + 3;
|
||||
tau_tracker_upkeep(&tracker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @brief process an incoming udp message if it's a tracker response.
|
||||
* @return true if msg was a tracker response; false otherwise */
|
||||
bool tr_session::tau_handle_message(uint8_t const* msg, size_t msglen) const
|
||||
{
|
||||
if (announcer_udp == nullptr)
|
||||
// @brief process an incoming udp message if it's a tracker response.
|
||||
// @return true if msg was a tracker response; false otherwise
|
||||
bool handleMessage(uint8_t const* msg, size_t msglen) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msglen < sizeof(uint32_t) * 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* extract the action_id and see if it makes sense */
|
||||
auto* const buf = evbuffer_new();
|
||||
evbuffer_add_reference(buf, msg, msglen, nullptr, nullptr);
|
||||
auto const action_id = tau_action_t(evbuffer_read_ntoh_32(buf));
|
||||
|
||||
if (!is_tau_response_message(action_id, msglen))
|
||||
{
|
||||
evbuffer_free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* extract the transaction_id and look for a match */
|
||||
tau_transaction_t const transaction_id = evbuffer_read_ntoh_32(buf);
|
||||
|
||||
for (auto& tracker : announcer_udp->trackers)
|
||||
{
|
||||
// is it a connection response?
|
||||
if (tracker.connecting_at != 0 && transaction_id == tracker.connection_transaction_id)
|
||||
if (msglen < sizeof(uint32_t) * 2)
|
||||
{
|
||||
logtrace(tracker.key, fmt::format("{} is my connection request!", transaction_id));
|
||||
on_tracker_connection_response(&tracker, action_id, buf);
|
||||
evbuffer_free(buf);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// is it a response to one of this tracker's announces?
|
||||
if (auto& reqs = tracker.announces; !std::empty(reqs))
|
||||
// extract the action_id and see if it makes sense
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.add(msg, msglen);
|
||||
auto const action_id = static_cast<tau_action_t>(buf.toUint32());
|
||||
|
||||
if (!is_tau_response_message(action_id, msglen))
|
||||
{
|
||||
auto it = std::find_if(
|
||||
std::begin(reqs),
|
||||
std::end(reqs),
|
||||
[&transaction_id](auto const& req) { return req.transaction_id == transaction_id; });
|
||||
if (it != std::end(reqs))
|
||||
return false;
|
||||
}
|
||||
|
||||
/* extract the transaction_id and look for a match */
|
||||
tau_transaction_t const transaction_id = buf.toUint32();
|
||||
|
||||
for (auto& tracker : trackers_)
|
||||
{
|
||||
// is it a connection response?
|
||||
if (tracker.connecting_at != 0 && transaction_id == tracker.connection_transaction_id)
|
||||
{
|
||||
logtrace(tracker.key, fmt::format("{} is an announce request!", transaction_id));
|
||||
auto req = *it;
|
||||
it = reqs.erase(it);
|
||||
req.onResponse(action_id, buf);
|
||||
evbuffer_free(buf);
|
||||
logtrace(tracker.key, fmt::format("{} is my connection request!", transaction_id));
|
||||
on_tracker_connection_response(tracker, action_id, buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
// is it a response to one of this tracker's announces?
|
||||
if (auto& reqs = tracker.announces; !std::empty(reqs))
|
||||
{
|
||||
auto it = std::find_if(
|
||||
std::begin(reqs),
|
||||
std::end(reqs),
|
||||
[&transaction_id](auto const& req) { return req.transaction_id == transaction_id; });
|
||||
if (it != std::end(reqs))
|
||||
{
|
||||
logtrace(tracker.key, fmt::format("{} is an announce request!", transaction_id));
|
||||
auto req = *it;
|
||||
it = reqs.erase(it);
|
||||
req.onResponse(action_id, buf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// is it a response to one of this tracker's scrapes?
|
||||
if (auto& reqs = tracker.scrapes; !std::empty(reqs))
|
||||
{
|
||||
auto it = std::find_if(
|
||||
std::begin(reqs),
|
||||
std::end(reqs),
|
||||
[&transaction_id](auto const& req) { return req.transaction_id == transaction_id; });
|
||||
if (it != std::end(reqs))
|
||||
{
|
||||
logtrace(tracker.key, fmt::format("{} is a scrape request!", transaction_id));
|
||||
auto req = *it;
|
||||
it = reqs.erase(it);
|
||||
req.onResponse(action_id, buf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// is it a response to one of this tracker's scrapes?
|
||||
if (auto& reqs = tracker.scrapes; !std::empty(reqs))
|
||||
/* no match... */
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
// Finds the tau_tracker struct that corresponds to this url.
|
||||
// If it doesn't exist yet, create one.
|
||||
tau_tracker* getTrackerFromUrl(tr_interned_string announce_url)
|
||||
{
|
||||
// build a lookup key for this tracker
|
||||
auto const parsed = tr_urlParseTracker(announce_url);
|
||||
TR_ASSERT(parsed);
|
||||
if (!parsed)
|
||||
{
|
||||
auto it = std::find_if(
|
||||
std::begin(reqs),
|
||||
std::end(reqs),
|
||||
[&transaction_id](auto const& req) { return req.transaction_id == transaction_id; });
|
||||
if (it != std::end(reqs))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// see if we already have it
|
||||
auto const key = tr_announcerGetKey(*parsed);
|
||||
for (auto& tracker : trackers_)
|
||||
{
|
||||
if (tracker.key == key)
|
||||
{
|
||||
logtrace(tracker.key, fmt::format("{} is a scrape request!", transaction_id));
|
||||
auto req = *it;
|
||||
it = reqs.erase(it);
|
||||
req.onResponse(action_id, buf);
|
||||
evbuffer_free(buf);
|
||||
return true;
|
||||
return &tracker;
|
||||
}
|
||||
}
|
||||
|
||||
// we don't have it -- build a new one
|
||||
trackers_.emplace_back(mediator_, key, tr_interned_string(parsed->host), tr_port::fromHost(parsed->port));
|
||||
auto* const tracker = &trackers_.back();
|
||||
logtrace(tracker->key, "New tau_tracker created");
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/* no match... */
|
||||
evbuffer_free(buf);
|
||||
return false;
|
||||
}
|
||||
std::list<tau_tracker> trackers_;
|
||||
|
||||
void tr_tracker_udp_announce(
|
||||
tr_session* session,
|
||||
tr_announce_request const* request,
|
||||
tr_announce_response_func response_func,
|
||||
void* user_data)
|
||||
Mediator& mediator_;
|
||||
};
|
||||
|
||||
std::unique_ptr<tr_announcer_udp> tr_announcer_udp::create(Mediator& mediator)
|
||||
{
|
||||
tr_announcer_udp* tau = announcer_udp_get(session);
|
||||
tau_tracker* tracker = tau_session_get_tracker(tau, request->announce_url);
|
||||
if (tracker == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tracker->announces.push_back(make_tau_announce_request(session, request, response_func, user_data));
|
||||
tau_tracker_upkeep_ex(tracker, false);
|
||||
}
|
||||
|
||||
void tr_tracker_udp_scrape(
|
||||
tr_session* session,
|
||||
tr_scrape_request const* request,
|
||||
tr_scrape_response_func response_func,
|
||||
void* user_data)
|
||||
{
|
||||
tr_announcer_udp* tau = announcer_udp_get(session);
|
||||
tau_tracker* tracker = tau_session_get_tracker(tau, request->scrape_url);
|
||||
if (tracker == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tracker->scrapes.push_back(make_tau_scrape_request(request, response_func, user_data));
|
||||
tau_tracker_upkeep_ex(tracker, false);
|
||||
return std::make_unique<tr_announcer_udp_impl>(mediator);
|
||||
}
|
||||
|
|
|
@ -215,8 +215,6 @@ void tr_announcerClose(tr_session* session)
|
|||
|
||||
flushCloseMessages(announcer);
|
||||
|
||||
tr_tracker_udp_start_shutdown(session);
|
||||
|
||||
session->announcer = nullptr;
|
||||
delete announcer;
|
||||
}
|
||||
|
@ -1197,7 +1195,7 @@ static void announce_request_delegate(
|
|||
}
|
||||
else if (tr_strvStartsWith(announce_sv, "udp://"sv))
|
||||
{
|
||||
tr_tracker_udp_announce(session, request, callback, callback_data);
|
||||
session->announcer_udp_->announce(*request, callback, callback_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1429,7 +1427,7 @@ static void scrape_request_delegate(
|
|||
}
|
||||
else if (tr_strvStartsWith(scrape_sv, "udp://"sv))
|
||||
{
|
||||
tr_tracker_udp_scrape(session, request, callback, callback_data);
|
||||
session->announcer_udp_->scrape(*request, callback, callback_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1613,7 +1611,7 @@ void tr_announcer::upkeep()
|
|||
if (this->tau_upkeep_at <= now)
|
||||
{
|
||||
this->tau_upkeep_at = now + TauUpkeepIntervalSecs;
|
||||
tr_tracker_udp_upkeep(session);
|
||||
session->announcer_udp_->upkeep();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,16 @@
|
|||
#include "transmission.h"
|
||||
|
||||
#include "interned-string.h"
|
||||
#include "net.h"
|
||||
|
||||
struct tr_announcer;
|
||||
struct tr_torrent_announcer;
|
||||
|
||||
namespace libtransmission
|
||||
{
|
||||
class Dns;
|
||||
} // namespace libtransmission
|
||||
|
||||
/**
|
||||
* *** Tracker Publish / Subscribe
|
||||
* **/
|
||||
|
@ -100,12 +106,220 @@ tr_tracker_view tr_announcerTracker(tr_torrent const* torrent, size_t nth);
|
|||
|
||||
size_t tr_announcerTrackerCount(tr_torrent const* tor);
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
/// ANNOUNCE
|
||||
|
||||
void tr_tracker_udp_upkeep(tr_session* session);
|
||||
enum tr_announce_event
|
||||
{
|
||||
/* Note: the ordering of this enum's values is important to
|
||||
* announcer.c's tr_tier.announce_event_priority. If changing
|
||||
* the enum, ensure announcer.c is compatible with the change. */
|
||||
TR_ANNOUNCE_EVENT_NONE,
|
||||
TR_ANNOUNCE_EVENT_STARTED,
|
||||
TR_ANNOUNCE_EVENT_COMPLETED,
|
||||
TR_ANNOUNCE_EVENT_STOPPED,
|
||||
};
|
||||
|
||||
void tr_tracker_udp_close(tr_session* session);
|
||||
std::string_view tr_announce_event_get_string(tr_announce_event);
|
||||
|
||||
bool tr_tracker_udp_is_idle(tr_session const* session);
|
||||
struct tr_announce_request
|
||||
{
|
||||
tr_announce_event event = {};
|
||||
bool partial_seed = false;
|
||||
|
||||
/* the port we listen for incoming peers on */
|
||||
tr_port port;
|
||||
|
||||
/* per-session key */
|
||||
int key = 0;
|
||||
|
||||
/* the number of peers we'd like to get back in the response */
|
||||
int numwant = 0;
|
||||
|
||||
/* the number of bytes we uploaded since the last 'started' event */
|
||||
uint64_t up = 0;
|
||||
|
||||
/* the number of good bytes we downloaded since the last 'started' event */
|
||||
uint64_t down = 0;
|
||||
|
||||
/* the number of bad bytes we downloaded since the last 'started' event */
|
||||
uint64_t corrupt = 0;
|
||||
|
||||
/* the total size of the torrent minus the number of bytes completed */
|
||||
uint64_t leftUntilComplete = 0;
|
||||
|
||||
/* the tracker's announce URL */
|
||||
tr_interned_string announce_url;
|
||||
|
||||
/* key generated by and returned from an http tracker.
|
||||
* see tr_announce_response.tracker_id_str */
|
||||
std::string tracker_id;
|
||||
|
||||
/* the torrent's peer id.
|
||||
* this changes when a torrent is stopped -> restarted. */
|
||||
tr_peer_id_t peer_id;
|
||||
|
||||
/* the torrent's info_hash */
|
||||
tr_sha1_digest_t info_hash;
|
||||
|
||||
/* the name to use when deep logging is enabled */
|
||||
char log_name[128];
|
||||
};
|
||||
|
||||
struct tr_announce_response
|
||||
{
|
||||
/* the torrent's info hash */
|
||||
tr_sha1_digest_t info_hash = {};
|
||||
|
||||
/* whether or not we managed to connect to the tracker */
|
||||
bool did_connect = false;
|
||||
|
||||
/* whether or not the scrape timed out */
|
||||
bool did_timeout = false;
|
||||
|
||||
/* preferred interval between announces.
|
||||
* transmission treats this as the interval for periodic announces */
|
||||
int interval = 0;
|
||||
|
||||
/* minimum interval between announces. (optional)
|
||||
* transmission treats this as the min interval for manual announces */
|
||||
int min_interval = 0;
|
||||
|
||||
/* how many peers are seeding this torrent */
|
||||
int seeders = -1;
|
||||
|
||||
/* how many peers are downloading this torrent */
|
||||
int leechers = -1;
|
||||
|
||||
/* how many times this torrent has been downloaded */
|
||||
int downloads = -1;
|
||||
|
||||
/* IPv4 peers that we acquired from the tracker */
|
||||
std::vector<tr_pex> pex;
|
||||
|
||||
/* IPv6 peers that we acquired from the tracker */
|
||||
std::vector<tr_pex> pex6;
|
||||
|
||||
/* human-readable error string on failure, or nullptr */
|
||||
std::string errmsg;
|
||||
|
||||
/* human-readable warning string or nullptr */
|
||||
std::string warning;
|
||||
|
||||
/* key generated by and returned from an http tracker.
|
||||
* if this is provided, subsequent http announces must include this. */
|
||||
std::string tracker_id;
|
||||
|
||||
/* tracker extension that returns the client's public IP address.
|
||||
* https://www.bittorrent.org/beps/bep_0024.html */
|
||||
std::optional<tr_address> external_ip;
|
||||
};
|
||||
|
||||
using tr_announce_response_func = void (*)(tr_announce_response const* response, void* userdata);
|
||||
|
||||
/// SCRAPE
|
||||
|
||||
/* pick a number small enough for common tracker software:
|
||||
* - ocelot has no upper bound
|
||||
* - opentracker has an upper bound of 64
|
||||
* - udp protocol has an upper bound of 74
|
||||
* - xbtt has no upper bound
|
||||
*
|
||||
* This is only an upper bound: if the tracker complains about
|
||||
* length, announcer will incrementally lower the batch size.
|
||||
*/
|
||||
auto inline constexpr TR_MULTISCRAPE_MAX = 60;
|
||||
|
||||
struct tr_scrape_request
|
||||
{
|
||||
/* the scrape URL */
|
||||
tr_interned_string scrape_url;
|
||||
|
||||
/* the name to use when deep logging is enabled */
|
||||
char log_name[128];
|
||||
|
||||
/* info hashes of the torrents to scrape */
|
||||
std::array<tr_sha1_digest_t, TR_MULTISCRAPE_MAX> info_hash;
|
||||
|
||||
/* how many hashes to use in the info_hash field */
|
||||
int info_hash_count = 0;
|
||||
};
|
||||
|
||||
struct tr_scrape_response_row
|
||||
{
|
||||
/* the torrent's info_hash */
|
||||
tr_sha1_digest_t info_hash;
|
||||
|
||||
/* how many peers are seeding this torrent */
|
||||
int seeders = 0;
|
||||
|
||||
/* how many peers are downloading this torrent */
|
||||
int leechers = 0;
|
||||
|
||||
/* how many times this torrent has been downloaded */
|
||||
int downloads = 0;
|
||||
|
||||
/* the number of active downloaders in the swarm.
|
||||
* this is a BEP 21 extension that some trackers won't support.
|
||||
* http://www.bittorrent.org/beps/bep_0021.html#tracker-scrapes */
|
||||
int downloaders = 0;
|
||||
};
|
||||
|
||||
struct tr_scrape_response
|
||||
{
|
||||
/* whether or not we managed to connect to the tracker */
|
||||
bool did_connect = false;
|
||||
|
||||
/* whether or not the scrape timed out */
|
||||
bool did_timeout = false;
|
||||
|
||||
/* how many info hashes are in the 'rows' field */
|
||||
int row_count;
|
||||
|
||||
/* the individual torrents' scrape results */
|
||||
std::array<tr_scrape_response_row, TR_MULTISCRAPE_MAX> rows;
|
||||
|
||||
/* the raw scrape url */
|
||||
tr_interned_string scrape_url;
|
||||
|
||||
/* human-readable error string on failure, or nullptr */
|
||||
std::string errmsg;
|
||||
|
||||
/* minimum interval (in seconds) allowed between scrapes.
|
||||
* this is an unofficial extension that some trackers won't support. */
|
||||
int min_request_interval;
|
||||
};
|
||||
|
||||
using tr_scrape_response_func = void (*)(tr_scrape_response const* response, void* user_data);
|
||||
|
||||
/// UDP ANNOUNCER
|
||||
|
||||
class tr_announcer_udp
|
||||
{
|
||||
public:
|
||||
class Mediator
|
||||
{
|
||||
public:
|
||||
virtual ~Mediator() noexcept = default;
|
||||
virtual void sendto(void const* buf, size_t buflen, sockaddr const* addr, socklen_t addrlen) = 0;
|
||||
[[nodiscard]] virtual libtransmission::Dns& dns() = 0;
|
||||
[[nodiscard]] virtual std::optional<tr_address> announceIP() const = 0;
|
||||
};
|
||||
|
||||
virtual ~tr_announcer_udp() noexcept = default;
|
||||
|
||||
[[nodiscard]] static std::unique_ptr<tr_announcer_udp> create(Mediator&);
|
||||
|
||||
[[nodiscard]] virtual bool isIdle() const noexcept = 0;
|
||||
|
||||
virtual void announce(tr_announce_request const& request, tr_announce_response_func response_func, void* user_data) = 0;
|
||||
|
||||
virtual void scrape(tr_scrape_request const& request, tr_scrape_response_func response_func, void* user_data) = 0;
|
||||
|
||||
virtual void upkeep() = 0;
|
||||
|
||||
virtual void startShutdown() = 0;
|
||||
|
||||
// @brief process an incoming udp message if it's a tracker response.
|
||||
// @return true if msg was a tracker response; false otherwise
|
||||
virtual bool handleMessage(uint8_t const* msg, size_t msglen) = 0;
|
||||
};
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <string>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#include <libutp/utp.h>
|
||||
|
@ -244,11 +243,6 @@ static void event_read_cb(evutil_socket_t fd, short /*event*/, void* vio)
|
|||
tr_error_clear(&error);
|
||||
}
|
||||
|
||||
static int tr_evbuffer_write(tr_peerIo* io, int fd, size_t howmuch)
|
||||
{
|
||||
return io->outbuf.toSocket(fd, howmuch);
|
||||
}
|
||||
|
||||
static void event_write_cb(evutil_socket_t fd, short /*event*/, void* vio)
|
||||
{
|
||||
auto* io = static_cast<tr_peerIo*>(vio);
|
||||
|
@ -273,7 +267,7 @@ static void event_write_cb(evutil_socket_t fd, short /*event*/, void* vio)
|
|||
}
|
||||
|
||||
EVUTIL_SET_SOCKET_ERROR(0);
|
||||
auto const n_written = tr_evbuffer_write(io, fd, howmuch); // -1 on err, 0 on EOF
|
||||
auto const n_written = io->outbuf.toSocket(fd, howmuch); // -1 on err, 0 on EOF
|
||||
auto const err = EVUTIL_SOCKET_ERROR();
|
||||
auto const should_retry = n_written == -1 && (err == 0 || err == EAGAIN || err == EINTR || err == EINPROGRESS);
|
||||
|
||||
|
@ -838,48 +832,6 @@ void tr_peerIo::writeBytes(void const* bytes, size_t n_bytes, bool is_piece_data
|
|||
****
|
||||
***/
|
||||
|
||||
void evbuffer_add_uint8(struct evbuffer* outbuf, uint8_t addme)
|
||||
{
|
||||
evbuffer_add(outbuf, &addme, 1);
|
||||
}
|
||||
|
||||
void evbuffer_add_uint16(struct evbuffer* outbuf, uint16_t addme_hs)
|
||||
{
|
||||
uint16_t const ns = htons(addme_hs);
|
||||
evbuffer_add(outbuf, &ns, sizeof(ns));
|
||||
}
|
||||
|
||||
void evbuffer_add_uint32(struct evbuffer* outbuf, uint32_t addme_hl)
|
||||
{
|
||||
uint32_t const nl = htonl(addme_hl);
|
||||
evbuffer_add(outbuf, &nl, sizeof(nl));
|
||||
}
|
||||
|
||||
void evbuffer_add_uint64(struct evbuffer* outbuf, uint64_t addme_hll)
|
||||
{
|
||||
uint64_t const nll = tr_htonll(addme_hll);
|
||||
evbuffer_add(outbuf, &nll, sizeof(nll));
|
||||
}
|
||||
|
||||
void evbuffer_add_hton_16(struct evbuffer* buf, uint16_t val)
|
||||
{
|
||||
evbuffer_add_uint16(buf, val);
|
||||
}
|
||||
|
||||
void evbuffer_add_hton_32(struct evbuffer* buf, uint32_t val)
|
||||
{
|
||||
evbuffer_add_uint32(buf, val);
|
||||
}
|
||||
|
||||
void evbuffer_add_hton_64(struct evbuffer* buf, uint64_t val)
|
||||
{
|
||||
evbuffer_add_uint64(buf, val);
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
void tr_peerIo::readBytes(void* bytes, size_t byte_count)
|
||||
{
|
||||
TR_ASSERT(readBufferSize() >= byte_count);
|
||||
|
@ -1020,7 +972,7 @@ static int tr_peerIoTryWrite(tr_peerIo* io, size_t howmuch)
|
|||
case TR_PEER_SOCKET_TYPE_TCP:
|
||||
{
|
||||
EVUTIL_SET_SOCKET_ERROR(0);
|
||||
n = tr_evbuffer_write(io, io->socket.handle.tcp, howmuch);
|
||||
n = io->outbuf.toSocket(io->socket.handle.tcp, howmuch);
|
||||
int const e = EVUTIL_SOCKET_ERROR();
|
||||
|
||||
if (n > 0)
|
||||
|
|
|
@ -359,12 +359,3 @@ constexpr bool tr_isPeerIo(tr_peerIo const* io)
|
|||
{
|
||||
return io != nullptr && tr_address_is_valid(&io->address());
|
||||
}
|
||||
|
||||
void evbuffer_add_uint8(struct evbuffer* outbuf, uint8_t addme);
|
||||
void evbuffer_add_uint16(struct evbuffer* outbuf, uint16_t hs);
|
||||
void evbuffer_add_uint32(struct evbuffer* outbuf, uint32_t hl);
|
||||
void evbuffer_add_uint64(struct evbuffer* outbuf, uint64_t hll);
|
||||
|
||||
void evbuffer_add_hton_16(struct evbuffer* buf, uint16_t val);
|
||||
void evbuffer_add_hton_32(struct evbuffer* buf, uint32_t val);
|
||||
void evbuffer_add_hton_64(struct evbuffer* buf, uint64_t val);
|
||||
|
|
|
@ -90,6 +90,11 @@ struct tr_pex
|
|||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(tr_pex const& that) const noexcept
|
||||
{
|
||||
return compare(that) == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator<(tr_pex const& that) const noexcept
|
||||
{
|
||||
return compare(that) < 0;
|
||||
|
|
|
@ -1787,6 +1787,7 @@ void tr_session::closeImplStart()
|
|||
lpd_.reset();
|
||||
|
||||
udp_core_->startShutdown();
|
||||
announcer_udp_->startShutdown();
|
||||
|
||||
save_timer_.reset();
|
||||
now_timer_.reset();
|
||||
|
@ -1839,9 +1840,9 @@ void tr_session::closeImplWaitForIdleUdp()
|
|||
{
|
||||
/* gotta keep udp running long enough to send out all
|
||||
the &event=stopped UDP tracker messages */
|
||||
if (!tr_tracker_udp_is_idle(this))
|
||||
if (announcer_udp_ && !announcer_udp_->isIdle())
|
||||
{
|
||||
tr_tracker_udp_upkeep(this);
|
||||
announcer_udp_->upkeep();
|
||||
save_timer_->start(100ms);
|
||||
return;
|
||||
}
|
||||
|
@ -1854,7 +1855,7 @@ void tr_session::closeImplFinish()
|
|||
save_timer_.reset();
|
||||
|
||||
/* we had to wait until UDP trackers were closed before closing these: */
|
||||
tr_tracker_udp_close(this);
|
||||
this->announcer_udp_.reset();
|
||||
this->udp_core_.reset();
|
||||
|
||||
stats().saveIfDirty();
|
||||
|
@ -1894,9 +1895,9 @@ void tr_sessionClose(tr_session* session)
|
|||
* so we need to keep the transmission thread alive
|
||||
* for a bit while they tell the router & tracker
|
||||
* that we're closing now */
|
||||
while ((session->port_forwarding_ || !session->web_->isClosed() || session->announcer != nullptr ||
|
||||
session->announcer_udp != nullptr) &&
|
||||
!deadlineReached(deadline))
|
||||
while (
|
||||
(session->port_forwarding_ || !session->web_->isClosed() || session->announcer != nullptr || session->announcer_udp_) &&
|
||||
!deadlineReached(deadline))
|
||||
{
|
||||
tr_logAddTrace(fmt::format(
|
||||
"waiting on port unmap ({}) or announcer ({})... now {} deadline {}",
|
||||
|
@ -2804,12 +2805,6 @@ tr_session::tr_session(std::string_view config_dir)
|
|||
, resume_dir_{ makeResumeDir(config_dir) }
|
||||
, torrent_dir_{ makeTorrentDir(config_dir) }
|
||||
, event_base_{ makeEventBase() }
|
||||
, evdns_base_{ evdns_base_new(eventBase(), EVDNS_BASE_INITIALIZE_NAMESERVERS),
|
||||
[](evdns_base* dns)
|
||||
{
|
||||
// if zero, active requests will be aborted
|
||||
evdns_base_free(dns, 0);
|
||||
} }
|
||||
, timer_maker_{ std::make_unique<libtransmission::EvTimerMaker>(eventBase()) }
|
||||
, dns_{ std::make_unique<libtransmission::EvDns>(eventBase(), tr_time) }
|
||||
, session_id_{ tr_time }
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "transmission.h"
|
||||
|
||||
#include "announce-list.h"
|
||||
#include "announcer.h"
|
||||
#include "bandwidth.h"
|
||||
#include "bitfield.h"
|
||||
#include "cache.h"
|
||||
|
@ -52,16 +53,14 @@ enum tr_auto_switch_state_t
|
|||
tr_peer_id_t tr_peerIdInit();
|
||||
|
||||
struct event_base;
|
||||
struct evdns_base;
|
||||
|
||||
class tr_rpc_server;
|
||||
class tr_web;
|
||||
class tr_lpd;
|
||||
class tr_port_forwarding;
|
||||
class tr_rpc_server;
|
||||
class tr_web;
|
||||
struct BlocklistFile;
|
||||
struct struct_utp_context;
|
||||
struct tr_announcer;
|
||||
struct tr_announcer_udp;
|
||||
|
||||
namespace libtransmission
|
||||
{
|
||||
|
@ -141,6 +140,40 @@ private:
|
|||
tr_socket_t socket_ = TR_BAD_SOCKET;
|
||||
};
|
||||
|
||||
class AnnouncerUdpMediator final : public tr_announcer_udp::Mediator
|
||||
{
|
||||
public:
|
||||
AnnouncerUdpMediator(tr_session& session)
|
||||
: session_{ session }
|
||||
{
|
||||
}
|
||||
|
||||
~AnnouncerUdpMediator() noexcept override = default;
|
||||
|
||||
void sendto(void const* buf, size_t buflen, sockaddr const* addr, socklen_t addrlen) override
|
||||
{
|
||||
session_.udp_core_->sendto(buf, buflen, addr, addrlen);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<tr_address> announceIP() const override
|
||||
{
|
||||
if (!session_.useAnnounceIP())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return tr_address::fromString(session_.announceIP());
|
||||
}
|
||||
|
||||
[[nodiscard]] libtransmission::Dns& dns() override
|
||||
{
|
||||
return *session_.dns_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
tr_session& session_;
|
||||
};
|
||||
|
||||
class PortForwardingMediator final : public tr_port_forwarding::Mediator
|
||||
{
|
||||
public:
|
||||
|
@ -276,11 +309,6 @@ public:
|
|||
return event_base_.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] evdns_base* evdnsBase() noexcept
|
||||
{
|
||||
return evdns_base_.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] libtransmission::TimerMaker& timerMaker() noexcept
|
||||
{
|
||||
return *timer_maker_;
|
||||
|
@ -476,7 +504,7 @@ public:
|
|||
|
||||
// announce ip
|
||||
|
||||
[[nodiscard]] constexpr auto const& announceIP() const noexcept
|
||||
[[nodiscard]] constexpr std::string const& announceIP() const noexcept
|
||||
{
|
||||
return announce_ip_;
|
||||
}
|
||||
|
@ -486,7 +514,7 @@ public:
|
|||
announce_ip_ = ip;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto useAnnounceIP() const noexcept
|
||||
[[nodiscard]] constexpr bool useAnnounceIP() const noexcept
|
||||
{
|
||||
return announce_ip_enabled_;
|
||||
}
|
||||
|
@ -920,8 +948,11 @@ private:
|
|||
std::string const torrent_dir_;
|
||||
|
||||
std::unique_ptr<event_base, void (*)(event_base*)> const event_base_;
|
||||
std::unique_ptr<evdns_base, void (*)(evdns_base*)> const evdns_base_;
|
||||
|
||||
// depends on: event_base_
|
||||
std::unique_ptr<libtransmission::TimerMaker> const timer_maker_;
|
||||
|
||||
// depends on: event_base_
|
||||
std::unique_ptr<libtransmission::Dns> const dns_;
|
||||
|
||||
/// trivial type fields
|
||||
|
@ -1070,11 +1101,17 @@ private:
|
|||
|
||||
public:
|
||||
struct tr_announcer* announcer = nullptr;
|
||||
struct tr_announcer_udp* announcer_udp = nullptr;
|
||||
|
||||
// monitors the "global pool" speeds
|
||||
tr_bandwidth top_bandwidth_;
|
||||
|
||||
private:
|
||||
// depends-on: dns_, udp_core_
|
||||
AnnouncerUdpMediator announcer_udp_mediator_{ *this };
|
||||
|
||||
public:
|
||||
std::unique_ptr<tr_announcer_udp> announcer_udp_ = tr_announcer_udp::create(announcer_udp_mediator_);
|
||||
|
||||
private:
|
||||
std::vector<std::pair<tr_interned_string, std::unique_ptr<tr_bandwidth>>> bandwidth_groups_;
|
||||
|
||||
|
@ -1097,10 +1134,6 @@ private:
|
|||
public:
|
||||
struct struct_utp_context* utp_context = nullptr;
|
||||
std::unique_ptr<libtransmission::Timer> utp_timer;
|
||||
|
||||
// These UDP announcer quirks are tightly hooked with session
|
||||
bool tau_handle_message(uint8_t const* msg, size_t msglen) const;
|
||||
void tau_sendto(struct evutil_addrinfo* ai, tr_port port, void const* buf, size_t buflen) const;
|
||||
};
|
||||
|
||||
constexpr bool tr_isPriority(tr_priority_t p)
|
||||
|
|
|
@ -56,6 +56,16 @@ public:
|
|||
return Iterator(buf_, offset_ + n_bytes);
|
||||
}
|
||||
|
||||
[[nodiscard]] Iterator operator-(int n_bytes)
|
||||
{
|
||||
return Iterator(buf_, offset_ - n_bytes);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator-(Iterator const& that) const noexcept
|
||||
{
|
||||
return offset_ - that.offset_;
|
||||
}
|
||||
|
||||
Iterator& operator++() noexcept
|
||||
{
|
||||
if (iov_.iov_len > 1)
|
||||
|
@ -71,6 +81,19 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
Iterator& operator+=(int n_bytes)
|
||||
{
|
||||
setOffset(offset_ + n_bytes);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator& operator--() noexcept
|
||||
{
|
||||
// TODO(ckerr) inefficient; calls evbuffer_ptr_peek() every time
|
||||
setOffset(offset_ - 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator==(Iterator const& that) const noexcept
|
||||
{
|
||||
return offset_ == that.offset_;
|
||||
|
@ -90,7 +113,7 @@ public:
|
|||
evbuffer_peek(buf_, std::numeric_limits<ev_ssize_t>::max(), &ptr, &iov_, 1);
|
||||
}
|
||||
|
||||
evbuffer* const buf_;
|
||||
evbuffer* buf_;
|
||||
Iovec iov_ = {};
|
||||
size_t offset_ = 0;
|
||||
};
|
||||
|
|
|
@ -215,7 +215,7 @@ static void event_callback(evutil_socket_t s, [[maybe_unused]] short type, void*
|
|||
}
|
||||
else if (rc >= 8 && buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] <= 3)
|
||||
{
|
||||
if (!session->tau_handle_message(std::data(buf), rc))
|
||||
if (!session->announcer_udp_->handleMessage(std::data(buf), rc))
|
||||
{
|
||||
tr_logAddTrace("Couldn't parse UDP tracker packet.");
|
||||
}
|
||||
|
|
|
@ -412,7 +412,7 @@ struct ParentState
|
|||
|
||||
struct JsonWalk
|
||||
{
|
||||
JsonWalk(bool do_indent)
|
||||
explicit JsonWalk(bool do_indent)
|
||||
: doIndent{ do_indent }
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
add_executable(libtransmission-test
|
||||
announce-list-test.cc
|
||||
announcer-test.cc
|
||||
announcer-udp-test.cc
|
||||
benc-test.cc
|
||||
bitfield-test.cc
|
||||
block-info-test.cc
|
||||
|
|
704
tests/libtransmission/announcer-udp-test.cc
Normal file
704
tests/libtransmission/announcer-udp-test.cc
Normal file
|
@ -0,0 +1,704 @@
|
|||
// This file Copyright (C) 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <cstring> // for std::memcpy()
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "announcer.h"
|
||||
#include "crypto-utils.h"
|
||||
#include "dns.h"
|
||||
#include "peer-mgr.h" // for tr_pex
|
||||
#include "tr-buffer.h"
|
||||
|
||||
#include "test-fixtures.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
class AnnouncerUdpTest : public ::testing::Test
|
||||
{
|
||||
private:
|
||||
void SetUp() override
|
||||
{
|
||||
::testing::Test::SetUp();
|
||||
tr_timeUpdate(time(nullptr));
|
||||
}
|
||||
|
||||
protected:
|
||||
class MockDns final : public libtransmission::Dns
|
||||
{
|
||||
public:
|
||||
~MockDns() override = default;
|
||||
|
||||
[[nodiscard]] std::optional<std::pair<struct sockaddr const*, socklen_t>> cached(
|
||||
std::string_view /*address*/,
|
||||
Hints /*hints*/ = {}) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
Tag lookup(std::string_view address, Callback&& callback, Hints /*hints*/) override
|
||||
{
|
||||
auto const addr = tr_address::fromString(address); // mock has no actual DNS, just parsing e.g. inet_pton
|
||||
auto [ss, sslen] = addr->toSockaddr(Port);
|
||||
callback(reinterpret_cast<sockaddr const*>(&ss), sslen, tr_time() + 3600); // 1hr ttl
|
||||
return {};
|
||||
}
|
||||
|
||||
void cancel(Tag /*tag*/) override
|
||||
{
|
||||
}
|
||||
|
||||
static auto constexpr Port = tr_port::fromHost(443);
|
||||
};
|
||||
|
||||
class MockMediator final : public tr_announcer_udp::Mediator
|
||||
{
|
||||
public:
|
||||
MockMediator()
|
||||
: event_base_{ event_base_new(), event_base_free }
|
||||
{
|
||||
}
|
||||
|
||||
void sendto(void const* buf, size_t buflen, sockaddr const* sa, socklen_t salen) override
|
||||
{
|
||||
auto target = tr_address::fromSockaddr(sa);
|
||||
ASSERT_TRUE(target);
|
||||
sent_.emplace_back(static_cast<char const*>(buf), buflen, sa, salen);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto* eventBase()
|
||||
{
|
||||
return event_base_.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] libtransmission::Dns& dns() override
|
||||
{
|
||||
return dns_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<tr_address> announceIP() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
struct Sent
|
||||
{
|
||||
Sent() = default;
|
||||
|
||||
Sent(char const* buf, size_t buflen, sockaddr const* sa, socklen_t salen)
|
||||
: sslen_{ salen }
|
||||
{
|
||||
buf_.insert(std::end(buf_), buf, buf + buflen);
|
||||
std::memcpy(&ss_, sa, salen);
|
||||
}
|
||||
|
||||
std::vector<char> buf_;
|
||||
sockaddr_storage ss_ = {};
|
||||
socklen_t sslen_ = {};
|
||||
};
|
||||
|
||||
std::deque<Sent> sent_;
|
||||
|
||||
std::unique_ptr<event_base, void (*)(event_base*)> const event_base_;
|
||||
|
||||
MockDns dns_;
|
||||
};
|
||||
|
||||
static void expectEqual(tr_scrape_response const& expected, tr_scrape_response const& actual)
|
||||
{
|
||||
EXPECT_EQ(expected.did_connect, actual.did_connect);
|
||||
EXPECT_EQ(expected.did_timeout, actual.did_timeout);
|
||||
EXPECT_EQ(expected.errmsg, actual.errmsg);
|
||||
EXPECT_EQ(expected.min_request_interval, actual.min_request_interval);
|
||||
EXPECT_EQ(expected.scrape_url, actual.scrape_url);
|
||||
|
||||
EXPECT_EQ(expected.row_count, actual.row_count);
|
||||
for (int i = 0; i < std::min(expected.row_count, actual.row_count); ++i)
|
||||
{
|
||||
EXPECT_EQ(expected.rows[i].info_hash, actual.rows[i].info_hash);
|
||||
EXPECT_EQ(expected.rows[i].seeders, actual.rows[i].seeders);
|
||||
EXPECT_EQ(expected.rows[i].leechers, actual.rows[i].leechers);
|
||||
EXPECT_EQ(expected.rows[i].downloads, actual.rows[i].downloads);
|
||||
EXPECT_EQ(expected.rows[i].downloaders, actual.rows[i].downloaders);
|
||||
}
|
||||
}
|
||||
|
||||
static void expectEqual(tr_scrape_request const& expected, std::vector<tr_sha1_digest_t> const& actual)
|
||||
{
|
||||
EXPECT_EQ(expected.info_hash_count, std::size(actual));
|
||||
for (size_t i = 0; i < std::min(size_t(expected.info_hash_count), std::size(actual)); ++i)
|
||||
{
|
||||
EXPECT_EQ(expected.info_hash[i], actual[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] static auto randomFilled()
|
||||
{
|
||||
auto tmp = T{};
|
||||
tr_rand_buffer(&tmp, sizeof(tmp));
|
||||
return tmp;
|
||||
}
|
||||
|
||||
[[nodiscard]] static uint32_t parseConnectionRequest(libtransmission::Buffer& buf)
|
||||
{
|
||||
EXPECT_EQ(ProtocolId, buf.toUint64());
|
||||
EXPECT_EQ(ConnectAction, buf.toUint32());
|
||||
return buf.toUint32();
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto buildScrapeRequestFromResponse(tr_scrape_response const& response)
|
||||
{
|
||||
auto request = tr_scrape_request{};
|
||||
request.scrape_url = response.scrape_url;
|
||||
request.info_hash_count = response.row_count;
|
||||
for (int i = 0; i < request.info_hash_count; ++i)
|
||||
{
|
||||
request.info_hash[i] = response.rows[i].info_hash;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto buildSimpleScrapeRequestAndResponse()
|
||||
{
|
||||
auto response = tr_scrape_response{};
|
||||
response.did_connect = true;
|
||||
response.did_timeout = false;
|
||||
response.row_count = 1;
|
||||
response.rows[0].info_hash = randomFilled<tr_sha1_digest_t>();
|
||||
response.rows[0].seeders = 1;
|
||||
response.rows[0].leechers = 2;
|
||||
response.rows[0].downloads = 3;
|
||||
response.rows[0].downloaders = 0;
|
||||
response.scrape_url = DefaultScrapeUrl;
|
||||
response.min_request_interval = 0;
|
||||
|
||||
return std::make_pair(buildScrapeRequestFromResponse(response), response);
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto parseScrapeRequest(libtransmission::Buffer& buf, uint64_t expected_connection_id)
|
||||
{
|
||||
EXPECT_EQ(expected_connection_id, buf.toUint64());
|
||||
EXPECT_EQ(ScrapeAction, buf.toUint32());
|
||||
auto const transaction_id = buf.toUint32();
|
||||
auto info_hashes = std::vector<tr_sha1_digest_t>{};
|
||||
while (!std::empty(buf))
|
||||
{
|
||||
auto tmp = tr_sha1_digest_t{};
|
||||
buf.toBuf(std::data(tmp), std::size(tmp));
|
||||
info_hashes.emplace_back(tmp);
|
||||
}
|
||||
return std::make_pair(transaction_id, info_hashes);
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto waitForAnnouncerToSendMessage(MockMediator& mediator)
|
||||
{
|
||||
EXPECT_FALSE(std::empty(mediator.sent_));
|
||||
libtransmission::test::waitFor(mediator.eventBase(), [&mediator]() { return !std::empty(mediator.sent_); });
|
||||
auto buf = libtransmission::Buffer(mediator.sent_.back().buf_);
|
||||
mediator.sent_.pop_back();
|
||||
return buf;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool sendError(tr_announcer_udp& announcer, uint32_t transaction_id, std::string_view errmsg)
|
||||
{
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint32(ErrorAction);
|
||||
buf.addUint32(transaction_id);
|
||||
buf.add(errmsg);
|
||||
|
||||
auto const response_size = std::size(buf);
|
||||
auto arr = std::array<uint8_t, 256>{};
|
||||
buf.toBuf(std::data(arr), response_size);
|
||||
|
||||
return announcer.handleMessage(std::data(arr), response_size);
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto sendConnectionResponse(tr_announcer_udp& announcer, uint32_t transaction_id)
|
||||
{
|
||||
auto const connection_id = randomFilled<uint64_t>();
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint32(ConnectAction);
|
||||
buf.addUint32(transaction_id);
|
||||
buf.addUint64(connection_id);
|
||||
|
||||
auto arr = std::array<uint8_t, 128>{};
|
||||
auto response_size = std::size(buf);
|
||||
buf.toBuf(std::data(arr), response_size);
|
||||
EXPECT_TRUE(announcer.handleMessage(std::data(arr), response_size));
|
||||
|
||||
return connection_id;
|
||||
}
|
||||
|
||||
struct UdpAnnounceReq
|
||||
{
|
||||
uint64_t connection_id = 0;
|
||||
uint32_t action = 0; // 1: announce
|
||||
uint32_t transaction_id = 0;
|
||||
tr_sha1_digest_t info_hash = {};
|
||||
tr_peer_id_t peer_id = {};
|
||||
uint64_t downloaded = 0;
|
||||
uint64_t left = 0;
|
||||
uint64_t uploaded = 0;
|
||||
uint32_t event = 0; // 0: none; 1: completed; 2: started; 3: stopped
|
||||
uint32_t ip_address = 0;
|
||||
uint32_t key;
|
||||
uint32_t num_want = static_cast<uint32_t>(-1); // default
|
||||
uint16_t port;
|
||||
};
|
||||
|
||||
static void expectEqual(tr_announce_request const& expected, UdpAnnounceReq const& actual)
|
||||
{
|
||||
EXPECT_EQ(AnnounceAction, actual.action);
|
||||
EXPECT_EQ(expected.info_hash, actual.info_hash);
|
||||
EXPECT_EQ(expected.peer_id, actual.peer_id);
|
||||
EXPECT_EQ(expected.down, actual.downloaded);
|
||||
EXPECT_EQ(expected.leftUntilComplete, actual.left);
|
||||
EXPECT_EQ(expected.up, actual.uploaded);
|
||||
// EXPECT_EQ(foo, actual.event); ; // 0: none; 1: completed; 2: started; 3: stopped // FIXME
|
||||
// EXPECT_EQ(foo, actual.ip_address); // FIXME
|
||||
EXPECT_EQ(expected.key, actual.key);
|
||||
EXPECT_EQ(expected.numwant, actual.num_want);
|
||||
EXPECT_EQ(expected.port.host(), actual.port);
|
||||
}
|
||||
|
||||
static void expectEqual(tr_announce_response const& expected, tr_announce_response const& actual)
|
||||
{
|
||||
EXPECT_EQ(actual.info_hash, expected.info_hash);
|
||||
EXPECT_EQ(actual.did_connect, expected.did_connect);
|
||||
EXPECT_EQ(actual.did_timeout, expected.did_timeout);
|
||||
EXPECT_EQ(actual.interval, expected.interval);
|
||||
EXPECT_EQ(actual.min_interval, expected.min_interval);
|
||||
EXPECT_EQ(actual.seeders, expected.seeders);
|
||||
EXPECT_EQ(actual.leechers, expected.leechers);
|
||||
EXPECT_EQ(actual.downloads, expected.downloads);
|
||||
EXPECT_EQ(actual.pex, expected.pex);
|
||||
EXPECT_EQ(actual.pex6, expected.pex6);
|
||||
EXPECT_EQ(actual.errmsg, expected.errmsg);
|
||||
EXPECT_EQ(actual.warning, expected.warning);
|
||||
EXPECT_EQ(actual.tracker_id, expected.tracker_id);
|
||||
EXPECT_EQ(actual.external_ip, expected.external_ip);
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto parseAnnounceRequest(libtransmission::Buffer& buf, uint64_t connection_id)
|
||||
{
|
||||
auto req = UdpAnnounceReq{};
|
||||
req.connection_id = buf.toUint64();
|
||||
req.action = buf.toUint32();
|
||||
req.transaction_id = buf.toUint32();
|
||||
buf.toBuf(std::data(req.info_hash), std::size(req.info_hash));
|
||||
buf.toBuf(std::data(req.peer_id), std::size(req.peer_id));
|
||||
req.downloaded = buf.toUint64();
|
||||
req.left = buf.toUint64();
|
||||
req.uploaded = buf.toUint64();
|
||||
req.event = buf.toUint32();
|
||||
req.ip_address = buf.toUint32();
|
||||
req.key = buf.toUint32();
|
||||
req.num_want = buf.toUint32();
|
||||
req.port = buf.toUint16();
|
||||
|
||||
EXPECT_EQ(AnnounceAction, req.action);
|
||||
EXPECT_EQ(connection_id, req.connection_id);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
// https://www.bittorrent.org/beps/bep_0015.html
|
||||
static auto constexpr ProtocolId = uint64_t{ 0x41727101980ULL };
|
||||
static auto constexpr ConnectAction = uint32_t{ 0 };
|
||||
static auto constexpr AnnounceAction = uint32_t{ 1 };
|
||||
static auto constexpr ScrapeAction = uint32_t{ 2 };
|
||||
static auto constexpr ErrorAction = uint32_t{ 3 };
|
||||
|
||||
static auto constexpr DefaultScrapeUrl = "https://127.0.0.1/scrape"sv;
|
||||
};
|
||||
|
||||
TEST_F(AnnouncerUdpTest, canInstantiate)
|
||||
{
|
||||
auto mediator = MockMediator{};
|
||||
auto announcer = tr_announcer_udp::create(mediator);
|
||||
EXPECT_TRUE(announcer);
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerUdpTest, canScrape)
|
||||
{
|
||||
auto mediator = MockMediator{};
|
||||
auto announcer = tr_announcer_udp::create(mediator);
|
||||
|
||||
// tell announcer to scrape
|
||||
auto [request, expected_response] = buildSimpleScrapeRequestAndResponse();
|
||||
auto response = std::optional<tr_scrape_response>{};
|
||||
announcer->scrape(
|
||||
request,
|
||||
[](tr_scrape_response const* resp, void* vresponse)
|
||||
{ *static_cast<std::optional<tr_scrape_response>*>(vresponse) = *resp; },
|
||||
&response);
|
||||
EXPECT_FALSE(announcer->isIdle());
|
||||
|
||||
// The announcer should have sent a UDP connection request.
|
||||
// Inspect that request for validity.
|
||||
auto sent = waitForAnnouncerToSendMessage(mediator);
|
||||
EXPECT_FALSE(announcer->isIdle());
|
||||
auto connect_transaction_id = parseConnectionRequest(sent);
|
||||
|
||||
// Have the tracker respond to the request
|
||||
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id);
|
||||
EXPECT_FALSE(announcer->isIdle());
|
||||
|
||||
// The announcer should have sent a UDP scrape request.
|
||||
// Inspect that request for validity.
|
||||
sent = waitForAnnouncerToSendMessage(mediator);
|
||||
EXPECT_FALSE(announcer->isIdle());
|
||||
auto [scrape_transaction_id, info_hashes] = parseScrapeRequest(sent, connection_id);
|
||||
expectEqual(request, info_hashes);
|
||||
|
||||
// Have the tracker respond to the request
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint32(ScrapeAction);
|
||||
buf.addUint32(scrape_transaction_id);
|
||||
buf.addUint32(expected_response.rows[0].seeders);
|
||||
buf.addUint32(expected_response.rows[0].downloads);
|
||||
buf.addUint32(expected_response.rows[0].leechers);
|
||||
auto response_size = std::size(buf);
|
||||
auto arr = std::array<uint8_t, 256>{};
|
||||
buf.toBuf(std::data(arr), response_size);
|
||||
EXPECT_TRUE(announcer->handleMessage(std::data(arr), response_size));
|
||||
EXPECT_TRUE(announcer->isIdle());
|
||||
|
||||
// confirm that announcer processed the response
|
||||
EXPECT_TRUE(response);
|
||||
expectEqual(expected_response, *response);
|
||||
|
||||
// Now scrape again.
|
||||
// Since the timestamp hasn't changed, the connection should be good
|
||||
// and announcer-udp should skip the `connect` step, going straight to the scrape.
|
||||
response.reset();
|
||||
announcer->scrape(
|
||||
request,
|
||||
[](tr_scrape_response const* resp, void* vresponse)
|
||||
{ *static_cast<std::optional<tr_scrape_response>*>(vresponse) = *resp; },
|
||||
&response);
|
||||
|
||||
// The announcer should have sent a UDP connection request.
|
||||
// Inspect that request for validity.
|
||||
sent = waitForAnnouncerToSendMessage(mediator);
|
||||
std::tie(scrape_transaction_id, info_hashes) = parseScrapeRequest(sent, connection_id);
|
||||
expectEqual(request, info_hashes);
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerUdpTest, canDestructCleanlyEvenWhenBusy)
|
||||
{
|
||||
auto mediator = MockMediator{};
|
||||
auto announcer = tr_announcer_udp::create(mediator);
|
||||
|
||||
// tell announcer to scrape
|
||||
auto [request, expected_response] = buildSimpleScrapeRequestAndResponse();
|
||||
auto response = std::optional<tr_scrape_response>{};
|
||||
announcer->scrape(
|
||||
request,
|
||||
[](tr_scrape_response const* resp, void* vresponse)
|
||||
{ *static_cast<std::optional<tr_scrape_response>*>(vresponse) = *resp; },
|
||||
&response);
|
||||
|
||||
// The announcer should have sent a UDP connection request.
|
||||
// Inspect that request for validity.
|
||||
auto sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto const connect_transaction_id = parseConnectionRequest(sent);
|
||||
EXPECT_NE(0, connect_transaction_id);
|
||||
|
||||
// now just end the test before responding to the request.
|
||||
// the announcer and mediator will go out-of-scope & be destroyed.
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerUdpTest, canMultiScrape)
|
||||
{
|
||||
auto mediator = MockMediator{};
|
||||
auto announcer = tr_announcer_udp::create(mediator);
|
||||
|
||||
auto expected_response = tr_scrape_response{};
|
||||
expected_response.did_connect = true;
|
||||
expected_response.did_timeout = false;
|
||||
expected_response.row_count = 2;
|
||||
expected_response.rows[0] = { randomFilled<tr_sha1_digest_t>(), 1, 2, 3, 0 };
|
||||
expected_response.rows[1] = { randomFilled<tr_sha1_digest_t>(), 4, 5, 6, 0 };
|
||||
expected_response.scrape_url = DefaultScrapeUrl;
|
||||
expected_response.min_request_interval = 0;
|
||||
|
||||
auto request = buildScrapeRequestFromResponse(expected_response);
|
||||
auto response = std::optional<tr_scrape_response>{};
|
||||
announcer->scrape(
|
||||
request,
|
||||
[](tr_scrape_response const* resp, void* vresponse)
|
||||
{ *static_cast<std::optional<tr_scrape_response>*>(vresponse) = *resp; },
|
||||
&response);
|
||||
|
||||
// Announcer will request a connection. Verify and grant the request
|
||||
auto sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto connect_transaction_id = parseConnectionRequest(sent);
|
||||
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id);
|
||||
|
||||
// The announcer should have sent a UDP scrape request.
|
||||
// Inspect that request for validity.
|
||||
sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto [scrape_transaction_id, info_hashes] = parseScrapeRequest(sent, connection_id);
|
||||
expectEqual(request, info_hashes);
|
||||
|
||||
// Have the tracker respond to the request
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint32(ScrapeAction);
|
||||
buf.addUint32(scrape_transaction_id);
|
||||
for (size_t i = 0; i < expected_response.row_count; ++i)
|
||||
{
|
||||
buf.addUint32(expected_response.rows[i].seeders);
|
||||
buf.addUint32(expected_response.rows[i].downloads);
|
||||
buf.addUint32(expected_response.rows[i].leechers);
|
||||
}
|
||||
auto response_size = std::size(buf);
|
||||
auto arr = std::array<uint8_t, 256>{};
|
||||
buf.toBuf(std::data(arr), response_size);
|
||||
EXPECT_TRUE(announcer->handleMessage(std::data(arr), response_size));
|
||||
|
||||
// Confirm that announcer processed the response
|
||||
EXPECT_TRUE(response);
|
||||
expectEqual(expected_response, *response);
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerUdpTest, canHandleScrapeError)
|
||||
{
|
||||
// build the expected reponse
|
||||
auto expected_response = tr_scrape_response{};
|
||||
expected_response.did_connect = true;
|
||||
expected_response.did_timeout = false;
|
||||
expected_response.row_count = 1;
|
||||
expected_response.rows[0].info_hash = randomFilled<tr_sha1_digest_t>();
|
||||
expected_response.rows[0].seeders = -1;
|
||||
expected_response.rows[0].leechers = -1;
|
||||
expected_response.rows[0].downloads = -1;
|
||||
expected_response.rows[0].downloaders = 0;
|
||||
expected_response.scrape_url = DefaultScrapeUrl;
|
||||
expected_response.min_request_interval = 0;
|
||||
expected_response.errmsg = "Unrecognized info-hash";
|
||||
|
||||
// build the request
|
||||
auto request = buildScrapeRequestFromResponse(expected_response);
|
||||
|
||||
// build the announcer
|
||||
auto mediator = MockMediator{};
|
||||
auto announcer = tr_announcer_udp::create(mediator);
|
||||
|
||||
// tell announcer to scrape
|
||||
auto response = std::optional<tr_scrape_response>{};
|
||||
announcer->scrape(
|
||||
request,
|
||||
[](tr_scrape_response const* resp, void* vresponse)
|
||||
{ *static_cast<std::optional<tr_scrape_response>*>(vresponse) = *resp; },
|
||||
&response);
|
||||
|
||||
// The announcer should have sent a UDP connection request.
|
||||
// Inspect that request for validity.
|
||||
auto sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto connect_transaction_id = parseConnectionRequest(sent);
|
||||
|
||||
// Have the tracker respond to the request
|
||||
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id);
|
||||
|
||||
// The announcer should have sent a UDP scrape request.
|
||||
// Inspect that request for validity.
|
||||
sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto const [scrape_transaction_id, info_hashes] = parseScrapeRequest(sent, connection_id);
|
||||
|
||||
// Have the tracker respond to the request with an "unable to scrape" error
|
||||
EXPECT_TRUE(sendError(*announcer, scrape_transaction_id, expected_response.errmsg));
|
||||
|
||||
// confirm that announcer processed the response
|
||||
EXPECT_TRUE(response);
|
||||
expectEqual(expected_response, *response);
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerUdpTest, canHandleConnectError)
|
||||
{
|
||||
// build the response we'd expect for a connect failure
|
||||
auto expected_response = tr_scrape_response{};
|
||||
expected_response.did_connect = true;
|
||||
expected_response.did_timeout = false;
|
||||
expected_response.row_count = 1;
|
||||
expected_response.rows[0].info_hash = randomFilled<tr_sha1_digest_t>();
|
||||
expected_response.rows[0].seeders = -1; // -1 here & on next lines means error
|
||||
expected_response.rows[0].leechers = -1;
|
||||
expected_response.rows[0].downloads = -1;
|
||||
expected_response.rows[0].downloaders = 0;
|
||||
expected_response.scrape_url = DefaultScrapeUrl;
|
||||
expected_response.min_request_interval = 0;
|
||||
expected_response.errmsg = "Unable to Connect";
|
||||
|
||||
// build the announcer
|
||||
auto mediator = MockMediator{};
|
||||
auto announcer = tr_announcer_udp::create(mediator);
|
||||
|
||||
// tell the announcer to scrape
|
||||
auto response = std::optional<tr_scrape_response>{};
|
||||
announcer->scrape(
|
||||
buildScrapeRequestFromResponse(expected_response),
|
||||
[](tr_scrape_response const* resp, void* vresponse)
|
||||
{ *static_cast<std::optional<tr_scrape_response>*>(vresponse) = *resp; },
|
||||
&response);
|
||||
|
||||
// The announcer should have sent a UDP connection request.
|
||||
// Inspect that request for validity.
|
||||
auto sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto transaction_id = parseConnectionRequest(sent);
|
||||
|
||||
// Have the tracker respond to the request with an "unable to connect" error
|
||||
EXPECT_TRUE(sendError(*announcer, transaction_id, expected_response.errmsg));
|
||||
|
||||
// Confirm that announcer processed the response
|
||||
EXPECT_TRUE(response);
|
||||
expectEqual(expected_response, *response);
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerUdpTest, handleMessageReturnsFalseOnInvalidMessage)
|
||||
{
|
||||
// build a simple scrape request
|
||||
auto request = tr_scrape_request{};
|
||||
request.scrape_url = DefaultScrapeUrl;
|
||||
request.info_hash_count = 1;
|
||||
request.info_hash[0] = randomFilled<tr_sha1_digest_t>();
|
||||
|
||||
// build the announcer
|
||||
auto mediator = MockMediator{};
|
||||
auto announcer = tr_announcer_udp::create(mediator);
|
||||
|
||||
// tell the announcer to scrape
|
||||
auto response = std::optional<tr_scrape_response>{};
|
||||
announcer->scrape(
|
||||
request,
|
||||
[](tr_scrape_response const* resp, void* vresponse)
|
||||
{ *static_cast<std::optional<tr_scrape_response>*>(vresponse) = *resp; },
|
||||
&response);
|
||||
|
||||
// The announcer should have sent a UDP connection request.
|
||||
// Inspect that request for validity.
|
||||
auto sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto transaction_id = parseConnectionRequest(sent);
|
||||
|
||||
// send a connection response but with an *invalid* transaction id
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint32(ConnectAction);
|
||||
buf.addUint32(transaction_id + 1);
|
||||
buf.addUint64(randomFilled<uint64_t>());
|
||||
auto response_size = std::size(buf);
|
||||
auto arr = std::array<uint8_t, 256>{};
|
||||
buf.toBuf(std::data(arr), response_size);
|
||||
EXPECT_FALSE(announcer->handleMessage(std::data(arr), response_size));
|
||||
|
||||
// send a connection response but with an *invalid* action
|
||||
buf.clear();
|
||||
buf.addUint32(ScrapeAction);
|
||||
buf.addUint32(transaction_id);
|
||||
buf.addUint64(randomFilled<uint64_t>());
|
||||
response_size = std::size(buf);
|
||||
buf.toBuf(std::data(arr), response_size);
|
||||
EXPECT_FALSE(announcer->handleMessage(std::data(arr), response_size));
|
||||
|
||||
// but after discarding invalid messages,
|
||||
// a valid connection response should still work
|
||||
auto const connection_id = sendConnectionResponse(*announcer, transaction_id);
|
||||
EXPECT_NE(0, connection_id);
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerUdpTest, canAnnounce)
|
||||
{
|
||||
static auto constexpr Interval = uint32_t{ 3600 };
|
||||
static auto constexpr Leechers = uint32_t{ 10 };
|
||||
static auto constexpr Seeders = uint32_t{ 20 };
|
||||
auto const addresses = std::array<std::pair<tr_address, tr_port>, 3>{
|
||||
std::make_pair(tr_address::fromString("10.10.10.5").value_or(tr_address{}), tr_port::fromHost(128)),
|
||||
std::make_pair(tr_address::fromString("192.168.1.2").value_or(tr_address{}), tr_port::fromHost(2021)),
|
||||
std::make_pair(tr_address::fromString("192.168.1.3").value_or(tr_address{}), tr_port::fromHost(2022)),
|
||||
};
|
||||
|
||||
auto request = tr_announce_request{};
|
||||
request.event = TR_ANNOUNCE_EVENT_STARTED;
|
||||
request.port = tr_port::fromHost(80);
|
||||
request.key = 0xCAFE;
|
||||
request.numwant = 20;
|
||||
request.up = 1;
|
||||
request.down = 2;
|
||||
request.corrupt = 3;
|
||||
request.leftUntilComplete = 100;
|
||||
request.announce_url = "https://127.0.0.1/announce";
|
||||
request.tracker_id = "fnord";
|
||||
request.peer_id = tr_peerIdInit();
|
||||
request.info_hash = randomFilled<tr_sha1_digest_t>();
|
||||
|
||||
auto expected_response = tr_announce_response{};
|
||||
expected_response.info_hash = request.info_hash;
|
||||
expected_response.did_connect = true;
|
||||
expected_response.did_timeout = false;
|
||||
expected_response.interval = Interval;
|
||||
expected_response.min_interval = 0; // not specified in UDP announce
|
||||
expected_response.seeders = Seeders;
|
||||
expected_response.leechers = Leechers;
|
||||
expected_response.downloads = -1; // not specified in UDP anounce
|
||||
expected_response.pex = std::vector<tr_pex>{ tr_pex{ addresses[0].first, addresses[0].second },
|
||||
tr_pex{ addresses[1].first, addresses[1].second },
|
||||
tr_pex{ addresses[2].first, addresses[2].second } };
|
||||
expected_response.pex6 = {};
|
||||
expected_response.errmsg = {};
|
||||
expected_response.warning = {};
|
||||
expected_response.tracker_id = {}; // not specified in UDP announce
|
||||
expected_response.external_ip = {};
|
||||
|
||||
// build the announcer
|
||||
auto mediator = MockMediator{};
|
||||
auto announcer = tr_announcer_udp::create(mediator);
|
||||
|
||||
auto response = std::optional<tr_announce_response>{};
|
||||
announcer->announce(
|
||||
request,
|
||||
[](tr_announce_response const* resp, void* vresponse)
|
||||
{ *static_cast<std::optional<tr_announce_response>*>(vresponse) = *resp; },
|
||||
&response);
|
||||
|
||||
// Announcer will request a connection. Verify and grant the request
|
||||
auto sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto connect_transaction_id = parseConnectionRequest(sent);
|
||||
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id);
|
||||
|
||||
// The announcer should have sent a UDP announce request.
|
||||
// Inspect that request for validity.
|
||||
sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto udp_ann_req = parseAnnounceRequest(sent, connection_id);
|
||||
expectEqual(request, udp_ann_req);
|
||||
|
||||
// Have the tracker respond to the request
|
||||
auto buf = libtransmission::Buffer{};
|
||||
buf.addUint32(AnnounceAction);
|
||||
buf.addUint32(udp_ann_req.transaction_id);
|
||||
buf.addUint32(expected_response.interval);
|
||||
buf.addUint32(expected_response.leechers);
|
||||
buf.addUint32(expected_response.seeders);
|
||||
for (auto const& [addr, port] : addresses)
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
|
||||
buf.add(&addr.addr.addr4.s_addr, sizeof(addr.addr.addr4.s_addr));
|
||||
buf.addUint16(port.host());
|
||||
}
|
||||
|
||||
auto response_size = std::size(buf);
|
||||
auto arr = std::array<uint8_t, 512>{};
|
||||
buf.toBuf(std::data(arr), response_size);
|
||||
EXPECT_TRUE(announcer->handleMessage(std::data(arr), response_size));
|
||||
|
||||
// Confirm that announcer processed the response
|
||||
EXPECT_TRUE(response);
|
||||
expectEqual(expected_response, *response);
|
||||
}
|
|
@ -41,30 +41,6 @@ protected:
|
|||
::testing::Test::TearDown();
|
||||
}
|
||||
|
||||
static bool waitFor(
|
||||
struct event_base* evb,
|
||||
std::function<bool()> const& test,
|
||||
std::chrono::milliseconds msec = DefaultTimeout)
|
||||
{
|
||||
auto const deadline = std::chrono::steady_clock::now() + msec;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (test())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std::chrono::steady_clock::now() > deadline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
event_base_loop(evb, EVLOOP_ONCE);
|
||||
}
|
||||
}
|
||||
|
||||
static auto constexpr DefaultTimeout = 5s;
|
||||
struct event_base* event_base_ = nullptr;
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include "crypto-utils.h" // tr_base64_decode()
|
||||
#include "error.h"
|
||||
#include "file.h" // tr_sys_file_*()
|
||||
|
@ -96,6 +98,29 @@ inline bool waitFor(std::function<bool()> const& test, int msec)
|
|||
return waitFor(test, std::chrono::milliseconds{ msec });
|
||||
}
|
||||
|
||||
inline bool waitFor(
|
||||
struct event_base* evb,
|
||||
std::function<bool()> const& test,
|
||||
std::chrono::milliseconds msec = std::chrono::seconds{ 5 })
|
||||
{
|
||||
auto const deadline = std::chrono::steady_clock::now() + msec;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (test())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std::chrono::steady_clock::now() > deadline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
event_base_loop(evb, EVLOOP_ONCE);
|
||||
}
|
||||
}
|
||||
|
||||
class Sandbox
|
||||
{
|
||||
public:
|
||||
|
|
Loading…
Reference in a new issue