fix: accept either one of udp announce response (#7583)

* fix: accept either one of udp announce response

* fix: udp announcer tests should read packets in-order

* test: housekeeping

* test: new tests for the new logic
This commit is contained in:
Yat Ho
2025-10-28 01:08:59 +08:00
committed by GitHub
parent f26eca5fd5
commit 41dd2cfd53
3 changed files with 579 additions and 104 deletions

View File

@@ -25,6 +25,7 @@
#include "libtransmission/net.h" #include "libtransmission/net.h"
#include "libtransmission/peer-mgr.h" // tr_pex #include "libtransmission/peer-mgr.h" // tr_pex
#include "libtransmission/tr-macros.h" // tr_peer_id_t #include "libtransmission/tr-macros.h" // tr_peer_id_t
#include "libtransmission/utils.h"
struct tr_url_parsed_t; struct tr_url_parsed_t;
@@ -145,6 +146,30 @@ struct tr_announce_response
/* tracker extension that returns the client's public IP address. /* tracker extension that returns the client's public IP address.
* https://www.bittorrent.org/beps/bep_0024.html */ * https://www.bittorrent.org/beps/bep_0024.html */
std::optional<tr_address> external_ip; std::optional<tr_address> external_ip;
static constexpr struct
{
static constexpr int compare(tr_announce_response const& lhs, tr_announce_response const& rhs)
{
if (auto val = tr_compare_3way(lhs.did_connect, rhs.did_connect); val != 0)
{
return val;
}
if (auto val = tr_compare_3way(lhs.did_timeout, rhs.did_timeout); val != 0)
{
return -val;
}
// Non-empty error message most likely means we reached the tracker
return -tr_compare_3way(std::empty(lhs.errmsg), std::empty(rhs.errmsg));
}
constexpr bool operator()(tr_announce_response const& lhs, tr_announce_response const& rhs) const noexcept
{
return compare(lhs, rhs) > 0;
}
} CompareFailed{};
}; };
// --- SCRAPE // --- SCRAPE

View File

@@ -171,15 +171,67 @@ private:
// --- ANNOUNCE // --- ANNOUNCE
class tau_announce_data
{
public:
explicit tau_announce_data(tr_announce_response_func&& on_response)
: on_response_{ std::move(on_response) }
{
}
constexpr void inc_request_sent_count() noexcept
{
++requests_sent_count_;
}
void on_response(tr_announce_response&& response, bool is_success)
{
TR_ASSERT(on_response_);
if (!on_response_)
{
return;
}
auto const got_all_responses = ++requests_answered_count_ == requests_sent_count_;
if (is_success)
{
on_response_(response);
succeeded_ = true;
}
else if (!succeeded_)
{
failed_responses_.emplace_back(std::move(response));
if (got_all_responses)
{
auto const begin = std::begin(failed_responses_);
std::partial_sort(begin, std::next(begin), std::end(failed_responses_), tr_announce_response::CompareFailed);
on_response_(failed_responses_.front());
}
}
}
private:
bool succeeded_ = false;
std::vector<tr_announce_response> failed_responses_;
tr_announce_response_func on_response_;
uint8_t requests_sent_count_ = {};
uint8_t requests_answered_count_ = {};
};
struct tau_announce_request struct tau_announce_request
{ {
tau_announce_request( tau_announce_request(
tr_address_type ip_protocol_in, tr_address_type ip_protocol_in,
std::optional<tr_address> announce_ip, std::optional<tr_address> announce_ip,
tr_announce_request const& in, tr_announce_request const& in,
tr_announce_response_func on_response) std::shared_ptr<tau_announce_data> data)
: ip_protocol{ ip_protocol_in } : ip_protocol{ ip_protocol_in }
, on_response_{ std::move(on_response) } , data_{ std::move(data) }
{ {
// https://www.bittorrent.org/beps/bep_0015.html sets key size at 32 bits // https://www.bittorrent.org/beps/bep_0015.html sets key size at 32 bits
static_assert(sizeof(tr_announce_request::key) == sizeof(uint32_t)); static_assert(sizeof(tr_announce_request::key) == sizeof(uint32_t));
@@ -207,19 +259,13 @@ struct tau_announce_request
payload.add_uint32(in.key); payload.add_uint32(in.key);
payload.add_uint32(in.numwant); payload.add_uint32(in.numwant);
payload.add_port(in.port); payload.add_port(in.port);
data_->inc_request_sent_count();
} }
[[nodiscard]] auto has_callback() const noexcept [[nodiscard]] auto has_callback() const noexcept
{ {
return !!on_response_; return !!data_;
}
void request_finished() const
{
if (on_response_)
{
on_response_(response);
}
} }
void fail(bool did_connect, bool did_timeout, std::string_view errmsg) void fail(bool did_connect, bool did_timeout, std::string_view errmsg)
@@ -227,7 +273,7 @@ struct tau_announce_request
response.did_connect = did_connect; response.did_connect = did_connect;
response.did_timeout = did_timeout; response.did_timeout = did_timeout;
response.errmsg = errmsg; response.errmsg = errmsg;
request_finished(); data_->on_response(std::move(response), false);
} }
void on_response(tr_address_type ip_protocol_resp, tau_action_t action, InBuf& buf) void on_response(tr_address_type ip_protocol_resp, tau_action_t action, InBuf& buf)
@@ -254,7 +300,7 @@ struct tau_announce_request
default: default:
break; break;
} }
request_finished(); data_->on_response(std::move(response), true);
} }
else else
{ {
@@ -307,7 +353,7 @@ private:
time_t const created_at_ = tr_time(); time_t const created_at_ = tr_time();
tr_announce_response_func on_response_; std::shared_ptr<tau_announce_data> data_;
}; };
// --- TRACKER // --- TRACKER
@@ -681,9 +727,10 @@ public:
return; return;
} }
auto const data = std::make_shared<tau_announce_data>(std::move(on_response));
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp) for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{ {
tracker->announces.emplace_back(static_cast<tr_address_type>(ipp), mediator_.announce_ip(), request, on_response); tracker->announces.emplace_back(static_cast<tr_address_type>(ipp), mediator_.announce_ip(), request, data);
} }
tracker->upkeep(false); tracker->upkeep(false);
} }

View File

@@ -5,7 +5,6 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cassert>
#include <cstddef> // std::byte #include <cstddef> // std::byte
#include <cstdint> // uint32_t, uint64_t #include <cstdint> // uint32_t, uint64_t
#include <cstring> // for std::memcpy() #include <cstring> // for std::memcpy()
@@ -189,12 +188,24 @@ protected:
return std::make_pair(transaction_id, info_hashes); return std::make_pair(transaction_id, info_hashes);
} }
[[nodiscard]] static auto waitForAnnouncerToSendMessage(MockMediator& mediator) [[nodiscard]] static auto waitForAnnouncerToSendMessage(
MockMediator& mediator,
sockaddr* const from = nullptr,
socklen_t* const fromlen = nullptr)
{ {
EXPECT_TRUE( EXPECT_TRUE(
libtransmission::test::waitFor(mediator.eventBase(), [&mediator]() { return !std::empty(mediator.sent_); })); libtransmission::test::waitFor(mediator.eventBase(), [&mediator]() { return !std::empty(mediator.sent_); }));
auto buf = std::move(mediator.sent_.back().buf_); auto& sent = mediator.sent_.front();
mediator.sent_.pop_back(); auto const buf = std::move(sent.buf_);
if (from != nullptr)
{
std::memcpy(from, &sent.ss_, sent.sslen_);
}
if (fromlen != nullptr)
{
*fromlen = sent.sslen_;
}
mediator.sent_.pop_front();
return buf; return buf;
} }
@@ -321,16 +332,6 @@ protected:
return timer; return timer;
} }
static auto sockaddrFromUrl(std::string_view tracker_url)
{
auto parsed_url = tr_urlParse(tracker_url);
EXPECT_TRUE(parsed_url);
auto addr = tr_address::from_string(parsed_url->host);
EXPECT_TRUE(addr);
return tr_socket_address{ *addr, tr_port::from_host(parsed_url->port) }.to_sockaddr();
}
// https://www.bittorrent.org/beps/bep_0015.html // https://www.bittorrent.org/beps/bep_0015.html
static auto constexpr ProtocolId = uint64_t{ 0x41727101980ULL }; static auto constexpr ProtocolId = uint64_t{ 0x41727101980ULL };
static auto constexpr ConnectAction = uint32_t{ 0 }; static auto constexpr ConnectAction = uint32_t{ 0 };
@@ -359,20 +360,22 @@ TEST_F(AnnouncerUdpTest, canScrape)
auto response = std::optional<tr_scrape_response>{}; auto response = std::optional<tr_scrape_response>{};
announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; }); announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; });
// Obtain the source socket address from tracker url auto from = sockaddr_storage{};
auto [from, fromlen] = sockaddrFromUrl(request.scrape_url); auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from); auto fromlen = socklen_t{};
// The announcer should have sent a UDP connection request. // The announcer should have sent a UDP connection request.
// Inspect that request for validity. // Inspect that request for validity.
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
// Have the tracker respond to the request // Have the tracker respond to the request
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen); auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP scrape request. // The announcer should have sent a UDP scrape request.
// Inspect that request for validity. // Inspect that request for validity.
auto [scrape_transaction_id, info_hashes] = parseScrapeRequest(waitForAnnouncerToSendMessage(mediator), connection_id); auto [scrape_transaction_id, info_hashes] = parseScrapeRequest(
waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen),
connection_id);
expectEqual(request, info_hashes); expectEqual(request, info_hashes);
// Have the tracker respond to the request // Have the tracker respond to the request
@@ -388,8 +391,7 @@ TEST_F(AnnouncerUdpTest, canScrape)
EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen)); EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// confirm that announcer processed the response // confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); ASSERT_TRUE(response.has_value());
assert(response.has_value());
expectEqual(expected_response, *response); expectEqual(expected_response, *response);
// Now scrape again. // Now scrape again.
@@ -439,21 +441,23 @@ TEST_F(AnnouncerUdpTest, canMultiScrape)
expected_response.scrape_url = DefaultScrapeUrl; expected_response.scrape_url = DefaultScrapeUrl;
expected_response.min_request_interval = 0; expected_response.min_request_interval = 0;
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(expected_response.scrape_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto request = buildScrapeRequestFromResponse(expected_response); auto request = buildScrapeRequestFromResponse(expected_response);
auto response = std::optional<tr_scrape_response>{}; auto response = std::optional<tr_scrape_response>{};
announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; }); announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
// Announcer will request a connection. Verify and grant the request // Announcer will request a connection. Verify and grant the request
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen); auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP scrape request. // The announcer should have sent a UDP scrape request.
// Inspect that request for validity. // Inspect that request for validity.
auto [scrape_transaction_id, info_hashes] = parseScrapeRequest(waitForAnnouncerToSendMessage(mediator), connection_id); auto [scrape_transaction_id, info_hashes] = parseScrapeRequest(
waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen),
connection_id);
expectEqual(request, info_hashes); expectEqual(request, info_hashes);
// Have the tracker respond to the request // Have the tracker respond to the request
@@ -472,8 +476,7 @@ TEST_F(AnnouncerUdpTest, canMultiScrape)
EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen)); EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// Confirm that announcer processed the response // Confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); ASSERT_TRUE(response.has_value());
assert(response.has_value());
expectEqual(expected_response, *response); expectEqual(expected_response, *response);
} }
@@ -491,11 +494,7 @@ TEST_F(AnnouncerUdpTest, canHandleScrapeError)
expected_response.rows[0].downloaders = std::nullopt; expected_response.rows[0].downloaders = std::nullopt;
expected_response.scrape_url = DefaultScrapeUrl; expected_response.scrape_url = DefaultScrapeUrl;
expected_response.min_request_interval = 0; expected_response.min_request_interval = 0;
expected_response.errmsg = "Unrecognized info-hash"; expected_response.errmsg = "Unrecognized info-hash"s;
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(expected_response.scrape_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
// build the request // build the request
auto request = buildScrapeRequestFromResponse(expected_response); auto request = buildScrapeRequestFromResponse(expected_response);
@@ -509,9 +508,13 @@ TEST_F(AnnouncerUdpTest, canHandleScrapeError)
auto response = std::optional<tr_scrape_response>{}; auto response = std::optional<tr_scrape_response>{};
announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; }); announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
// The announcer should have sent a UDP connection request. // The announcer should have sent a UDP connection request.
// Inspect that request for validity. // Inspect that request for validity.
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
// Have the tracker respond to the request // Have the tracker respond to the request
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen); auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
@@ -519,15 +522,14 @@ TEST_F(AnnouncerUdpTest, canHandleScrapeError)
// The announcer should have sent a UDP scrape request. // The announcer should have sent a UDP scrape request.
// Inspect that request for validity. // Inspect that request for validity.
auto const [scrape_transaction_id, info_hashes] = parseScrapeRequest( auto const [scrape_transaction_id, info_hashes] = parseScrapeRequest(
waitForAnnouncerToSendMessage(mediator), waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen),
connection_id); connection_id);
// Have the tracker respond to the request with an "unable to scrape" error // Have the tracker respond to the request with an "unable to scrape" error
EXPECT_TRUE(sendError(*announcer, scrape_transaction_id, expected_response.errmsg, from_ptr, fromlen)); EXPECT_TRUE(sendError(*announcer, scrape_transaction_id, expected_response.errmsg, from_ptr, fromlen));
// confirm that announcer processed the response // confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); ASSERT_TRUE(response.has_value());
assert(response.has_value());
expectEqual(expected_response, *response); expectEqual(expected_response, *response);
} }
@@ -545,11 +547,7 @@ TEST_F(AnnouncerUdpTest, canHandleConnectError)
expected_response.rows[0].downloaders = std::nullopt; expected_response.rows[0].downloaders = std::nullopt;
expected_response.scrape_url = DefaultScrapeUrl; expected_response.scrape_url = DefaultScrapeUrl;
expected_response.min_request_interval = 0; expected_response.min_request_interval = 0;
expected_response.errmsg = "Unable to Connect"; expected_response.errmsg = "Unable to Connect"s;
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(expected_response.scrape_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
// build the announcer // build the announcer
auto mediator = MockMediator{}; auto mediator = MockMediator{};
@@ -562,16 +560,19 @@ TEST_F(AnnouncerUdpTest, canHandleConnectError)
buildScrapeRequestFromResponse(expected_response), buildScrapeRequestFromResponse(expected_response),
[&response](tr_scrape_response const& resp) { response = resp; }); [&response](tr_scrape_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
// The announcer should have sent a UDP connection request. // The announcer should have sent a UDP connection request.
// Inspect that request for validity. // Inspect that request for validity.
auto transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
// Have the tracker respond to the request with an "unable to connect" error // Have the tracker respond to the request with an "unable to connect" error
EXPECT_TRUE(sendError(*announcer, transaction_id, expected_response.errmsg, from_ptr, fromlen)); EXPECT_TRUE(sendError(*announcer, transaction_id, expected_response.errmsg, from_ptr, fromlen));
// Confirm that announcer processed the response // Confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); ASSERT_TRUE(response.has_value());
assert(response.has_value());
expectEqual(expected_response, *response); expectEqual(expected_response, *response);
} }
@@ -583,10 +584,6 @@ TEST_F(AnnouncerUdpTest, handleMessageReturnsFalseOnInvalidMessage)
request.info_hash_count = 1U; request.info_hash_count = 1U;
request.info_hash[0] = tr_rand_obj<tr_sha1_digest_t>(); request.info_hash[0] = tr_rand_obj<tr_sha1_digest_t>();
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(request.scrape_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
// build the announcer // build the announcer
auto mediator = MockMediator{}; auto mediator = MockMediator{};
auto announcer = tr_announcer_udp::create(mediator); auto announcer = tr_announcer_udp::create(mediator);
@@ -596,9 +593,13 @@ TEST_F(AnnouncerUdpTest, handleMessageReturnsFalseOnInvalidMessage)
auto response = std::optional<tr_scrape_response>{}; auto response = std::optional<tr_scrape_response>{};
announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; }); announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
// The announcer should have sent a UDP connection request. // The announcer should have sent a UDP connection request.
// Inspect that request for validity. // Inspect that request for validity.
auto transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
// send a connection response but with an *invalid* transaction id // send a connection response but with an *invalid* transaction id
auto buf = MessageBuffer{}; auto buf = MessageBuffer{};
@@ -631,9 +632,9 @@ TEST_F(AnnouncerUdpTest, canAnnounceIPv4)
static auto constexpr Leechers = uint32_t{ 10 }; static auto constexpr Leechers = uint32_t{ 10 };
static auto constexpr Seeders = uint32_t{ 20 }; static auto constexpr Seeders = uint32_t{ 20 };
auto const addresses = std::array<tr_socket_address, 3>{ { auto const addresses = std::array<tr_socket_address, 3>{ {
{ tr_address::from_string("10.10.10.5").value_or(tr_address{}), tr_port::from_host(128) }, { tr_address::from_string("10.10.10.5"sv).value_or(tr_address{}), tr_port::from_host(128) },
{ tr_address::from_string("192.168.1.2").value_or(tr_address{}), tr_port::from_host(2021) }, { tr_address::from_string("192.168.1.2"sv).value_or(tr_address{}), tr_port::from_host(2021) },
{ tr_address::from_string("192.168.1.3").value_or(tr_address{}), tr_port::from_host(2022) }, { tr_address::from_string("192.168.1.3"sv).value_or(tr_address{}), tr_port::from_host(2022) },
} }; } };
auto request = tr_announce_request{}; auto request = tr_announce_request{};
@@ -645,15 +646,11 @@ TEST_F(AnnouncerUdpTest, canAnnounceIPv4)
request.down = 2; request.down = 2;
request.corrupt = 3; request.corrupt = 3;
request.leftUntilComplete = 100; request.leftUntilComplete = 100;
request.announce_url = "https://127.0.0.1/announce"; request.announce_url = "https://127.0.0.1/announce"sv;
request.tracker_id = "fnord"; request.tracker_id = "fnord"s;
request.peer_id = tr_peerIdInit(); request.peer_id = tr_peerIdInit();
request.info_hash = tr_rand_obj<tr_sha1_digest_t>(); request.info_hash = tr_rand_obj<tr_sha1_digest_t>();
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(request.announce_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto expected_response = tr_announce_response{}; auto expected_response = tr_announce_response{};
expected_response.info_hash = request.info_hash; expected_response.info_hash = request.info_hash;
expected_response.did_connect = true; expected_response.did_connect = true;
@@ -673,18 +670,22 @@ TEST_F(AnnouncerUdpTest, canAnnounceIPv4)
// build the announcer // build the announcer
auto mediator = MockMediator{}; auto mediator = MockMediator{};
auto announcer = tr_announcer_udp::create(mediator); auto announcer = tr_announcer_udp::create(mediator);
auto upkeep_timer = createUpkeepTimer(mediator, announcer); auto const upkeep_timer = createUpkeepTimer(mediator, announcer);
auto response = std::optional<tr_announce_response>{}; auto response = std::optional<tr_announce_response>{};
announcer->announce(request, [&response](tr_announce_response const& resp) { response = resp; }); announcer->announce(request, [&response](tr_announce_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
// Announcer will request a connection. Verify and grant the request // Announcer will request a connection. Verify and grant the request
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto const connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen); auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP announce request. // The announcer should have sent a UDP announce request.
// Inspect that request for validity. // Inspect that request for validity.
auto udp_ann_req = parseAnnounceRequest(waitForAnnouncerToSendMessage(mediator), connection_id); auto const udp_ann_req = parseAnnounceRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen), connection_id);
expectEqual(request, udp_ann_req); expectEqual(request, udp_ann_req);
// Have the tracker respond to the request // Have the tracker respond to the request
@@ -701,15 +702,10 @@ TEST_F(AnnouncerUdpTest, canAnnounceIPv4)
buf.add_uint16(port.host()); buf.add_uint16(port.host());
} }
auto response_size = std::size(buf); EXPECT_TRUE(announcer->handle_message(reinterpret_cast<uint8_t const*>(std::data(buf)), std::size(buf), from_ptr, fromlen));
auto arr = std::array<uint8_t, 512>{};
buf.to_buf(std::data(arr), response_size);
EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// Confirm that announcer processed the response // Confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); ASSERT_TRUE(response.has_value());
assert(response.has_value());
expectEqual(expected_response, *response); expectEqual(expected_response, *response);
} }
@@ -719,9 +715,9 @@ TEST_F(AnnouncerUdpTest, canAnnounceIPv6)
static auto constexpr Leechers = uint32_t{ 10 }; static auto constexpr Leechers = uint32_t{ 10 };
static auto constexpr Seeders = uint32_t{ 20 }; static auto constexpr Seeders = uint32_t{ 20 };
auto const addresses = std::array<tr_socket_address, 3>{ { auto const addresses = std::array<tr_socket_address, 3>{ {
{ tr_address::from_string("fd12:3456:789a:1::1").value_or(tr_address{}), tr_port::from_host(128) }, { tr_address::from_string("fd12:3456:789a:1::1"sv).value_or(tr_address{}), tr_port::from_host(128) },
{ tr_address::from_string("fd12:3456:789a:1::2").value_or(tr_address{}), tr_port::from_host(2021) }, { tr_address::from_string("fd12:3456:789a:1::2"sv).value_or(tr_address{}), tr_port::from_host(2021) },
{ tr_address::from_string("fd12:3456:789a:1::3").value_or(tr_address{}), tr_port::from_host(2022) }, { tr_address::from_string("fd12:3456:789a:1::3"sv).value_or(tr_address{}), tr_port::from_host(2022) },
} }; } };
auto request = tr_announce_request{}; auto request = tr_announce_request{};
@@ -733,15 +729,11 @@ TEST_F(AnnouncerUdpTest, canAnnounceIPv6)
request.down = 2; request.down = 2;
request.corrupt = 3; request.corrupt = 3;
request.leftUntilComplete = 100; request.leftUntilComplete = 100;
request.announce_url = "https://[::1]/announce"; request.announce_url = "https://[::1]/announce"sv;
request.tracker_id = "fnord"; request.tracker_id = "fnord"s;
request.peer_id = tr_peerIdInit(); request.peer_id = tr_peerIdInit();
request.info_hash = tr_rand_obj<tr_sha1_digest_t>(); request.info_hash = tr_rand_obj<tr_sha1_digest_t>();
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(request.announce_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto expected_response = tr_announce_response{}; auto expected_response = tr_announce_response{};
expected_response.info_hash = request.info_hash; expected_response.info_hash = request.info_hash;
expected_response.did_connect = true; expected_response.did_connect = true;
@@ -761,18 +753,22 @@ TEST_F(AnnouncerUdpTest, canAnnounceIPv6)
// build the announcer // build the announcer
auto mediator = MockMediator{}; auto mediator = MockMediator{};
auto announcer = tr_announcer_udp::create(mediator); auto announcer = tr_announcer_udp::create(mediator);
auto upkeep_timer = createUpkeepTimer(mediator, announcer); auto const upkeep_timer = createUpkeepTimer(mediator, announcer);
auto response = std::optional<tr_announce_response>{}; auto response = std::optional<tr_announce_response>{};
announcer->announce(request, [&response](tr_announce_response const& resp) { response = resp; }); announcer->announce(request, [&response](tr_announce_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
// Announcer will request a connection. Verify and grant the request // Announcer will request a connection. Verify and grant the request
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto const connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen); auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP announce request. // The announcer should have sent a UDP announce request.
// Inspect that request for validity. // Inspect that request for validity.
auto udp_ann_req = parseAnnounceRequest(waitForAnnouncerToSendMessage(mediator), connection_id); auto const udp_ann_req = parseAnnounceRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen), connection_id);
expectEqual(request, udp_ann_req); expectEqual(request, udp_ann_req);
// Have the tracker respond to the request // Have the tracker respond to the request
@@ -789,14 +785,421 @@ TEST_F(AnnouncerUdpTest, canAnnounceIPv6)
buf.add_uint16(port.host()); buf.add_uint16(port.host());
} }
auto response_size = std::size(buf); EXPECT_TRUE(announcer->handle_message(reinterpret_cast<uint8_t const*>(std::data(buf)), std::size(buf), from_ptr, fromlen));
auto arr = std::array<uint8_t, 512>{};
buf.to_buf(std::data(arr), response_size);
EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// Confirm that announcer processed the response // Confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); ASSERT_TRUE(response.has_value());
assert(response.has_value());
expectEqual(expected_response, *response); expectEqual(expected_response, *response);
} }
TEST_F(AnnouncerUdpTest, canAnnounceDualStack)
{
static auto constexpr Interval = time_t{ 3600 };
static auto constexpr Leechers = uint32_t{ 10 };
static auto constexpr Seeders = uint32_t{ 20 };
auto const addresses = std::array{
std::array<tr_socket_address, 3>{ {
{ tr_address::from_string("10.10.10.5"sv).value_or(tr_address{}), tr_port::from_host(128) },
{ tr_address::from_string("192.168.1.2"sv).value_or(tr_address{}), tr_port::from_host(2021) },
{ tr_address::from_string("192.168.1.3"sv).value_or(tr_address{}), tr_port::from_host(2022) },
} },
std::array<tr_socket_address, 3>{ {
{ tr_address::from_string("fd12:3456:789a:1::1"sv).value_or(tr_address{}), tr_port::from_host(128) },
{ tr_address::from_string("fd12:3456:789a:1::2"sv).value_or(tr_address{}), tr_port::from_host(2021) },
{ tr_address::from_string("fd12:3456:789a:1::3"sv).value_or(tr_address{}), tr_port::from_host(2022) },
} },
};
auto request = tr_announce_request{};
request.event = TR_ANNOUNCE_EVENT_STARTED;
request.port = tr_port::from_host(80);
request.key = 0xCAFE;
request.numwant = 20;
request.up = 1;
request.down = 2;
request.corrupt = 3;
request.leftUntilComplete = 100;
request.announce_url = "https://localhost/announce"sv;
request.tracker_id = "fnord"s;
request.peer_id = tr_peerIdInit();
request.info_hash = tr_rand_obj<tr_sha1_digest_t>();
auto expected_responses = std::array<tr_announce_response, NUM_TR_AF_INET_TYPES>{};
for (auto& expected_response : expected_responses)
{
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 = std::nullopt; // not specified in UDP announce
expected_response.errmsg = {};
expected_response.warning = {};
expected_response.tracker_id = {}; // not specified in UDP announce
expected_response.external_ip = {};
}
expected_responses[TR_AF_INET].pex = std::vector<tr_pex>{
tr_pex{ addresses[TR_AF_INET][0] },
tr_pex{ addresses[TR_AF_INET][1] },
tr_pex{ addresses[TR_AF_INET][2] },
};
expected_responses[TR_AF_INET6].pex6 = std::vector<tr_pex>{
tr_pex{ addresses[TR_AF_INET6][0] },
tr_pex{ addresses[TR_AF_INET6][1] },
tr_pex{ addresses[TR_AF_INET6][2] },
};
// build the announcer
auto mediator = MockMediator{};
auto announcer = tr_announcer_udp::create(mediator);
auto const upkeep_timer = createUpkeepTimer(mediator, announcer);
auto response = std::optional<tr_announce_response>{};
announcer->announce(request, [&response](tr_announce_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
auto connection_ids = std::array<tau_connection_t, NUM_TR_AF_INET_TYPES>{};
for (uint8_t i = 0U; i < NUM_TR_AF_INET_TYPES; ++i)
{
// Announcer will request a connection. Verify and grant the request
auto const connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
auto const ipp = tr_af_to_ip_protocol(from_ptr->sa_family);
connection_ids[ipp] = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
}
for (uint8_t i = 0U; i < NUM_TR_AF_INET_TYPES; ++i)
{
response.reset();
// The announcer should have sent a UDP announce request.
// Inspect that request for validity.
auto const data = waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen);
auto const ipp = tr_af_to_ip_protocol(from_ptr->sa_family);
auto const udp_ann_req = parseAnnounceRequest(data, connection_ids[ipp]);
expectEqual(request, udp_ann_req);
// Have the tracker respond to the request
auto const& expected_response = expected_responses[ipp];
auto buf = MessageBuffer{};
buf.add_uint32(AnnounceAction);
buf.add_uint32(udp_ann_req.transaction_id);
buf.add_uint32(expected_response.interval);
buf.add_uint32(expected_response.leechers.value_or(-1));
buf.add_uint32(expected_response.seeders.value_or(-1));
for (auto const& [addr, port] : addresses[ipp])
{
if (ipp == TR_AF_INET)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
buf.add(&addr.addr.addr4.s_addr, sizeof(addr.addr.addr4.s_addr));
}
else
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
buf.add(&addr.addr.addr6.s6_addr, sizeof(addr.addr.addr6.s6_addr));
}
buf.add_uint16(port.host());
}
EXPECT_TRUE(
announcer->handle_message(reinterpret_cast<uint8_t const*>(std::data(buf)), std::size(buf), from_ptr, fromlen));
// Confirm that announcer processed the response
ASSERT_TRUE(response.has_value());
expectEqual(expected_response, *response);
}
}
TEST_F(AnnouncerUdpTest, announceDualStackOnlyIPv4Successful)
{
static auto constexpr Interval = time_t{ 3600 };
static auto constexpr Leechers = uint32_t{ 10 };
static auto constexpr Seeders = uint32_t{ 20 };
auto const addresses = std::array<tr_socket_address, 3>{ {
{ tr_address::from_string("10.10.10.5"sv).value_or(tr_address{}), tr_port::from_host(128) },
{ tr_address::from_string("192.168.1.2"sv).value_or(tr_address{}), tr_port::from_host(2021) },
{ tr_address::from_string("192.168.1.3"sv).value_or(tr_address{}), tr_port::from_host(2022) },
} };
auto request = tr_announce_request{};
request.event = TR_ANNOUNCE_EVENT_STARTED;
request.port = tr_port::from_host(80);
request.key = 0xCAFE;
request.numwant = 20;
request.up = 1;
request.down = 2;
request.corrupt = 3;
request.leftUntilComplete = 100;
request.announce_url = "https://localhost/announce"sv;
request.tracker_id = "fnord"s;
request.peer_id = tr_peerIdInit();
request.info_hash = tr_rand_obj<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 = std::nullopt; // not specified in UDP announce
expected_response.pex = std::vector<tr_pex>{ tr_pex{ addresses[0] }, tr_pex{ addresses[1] }, tr_pex{ addresses[2] } };
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 const upkeep_timer = createUpkeepTimer(mediator, announcer);
auto response = std::optional<tr_announce_response>{};
announcer->announce(request, [&response](tr_announce_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
auto connection_ids = std::array<tau_connection_t, NUM_TR_AF_INET_TYPES>{};
for (uint8_t i = 0U; i < NUM_TR_AF_INET_TYPES; ++i)
{
// Announcer will request a connection. Verify and grant the request
auto const connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
auto const ipp = tr_af_to_ip_protocol(from_ptr->sa_family);
connection_ids[ipp] = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
}
for (uint8_t i = 0U; i < NUM_TR_AF_INET_TYPES; ++i)
{
response.reset();
// The announcer should have sent a UDP announce request.
// Inspect that request for validity.
auto const data = waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen);
auto const ipp = tr_af_to_ip_protocol(from_ptr->sa_family);
auto const udp_ann_req = parseAnnounceRequest(data, connection_ids[ipp]);
expectEqual(request, udp_ann_req);
// Have the tracker respond to the request
if (ipp == TR_AF_INET)
{
auto buf = MessageBuffer{};
buf.add_uint32(AnnounceAction);
buf.add_uint32(udp_ann_req.transaction_id);
buf.add_uint32(expected_response.interval);
buf.add_uint32(expected_response.leechers.value_or(-1));
buf.add_uint32(expected_response.seeders.value_or(-1));
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.add_uint16(port.host());
}
EXPECT_TRUE(
announcer->handle_message(reinterpret_cast<uint8_t const*>(std::data(buf)), std::size(buf), from_ptr, fromlen));
}
else
{
EXPECT_TRUE(sendError(*announcer, udp_ann_req.transaction_id, "Failed"sv, from_ptr, fromlen));
}
// Failed responses won't be processed if one of the other announce requests succeeded
EXPECT_TRUE(ipp == TR_AF_INET6 ? !response.has_value() : response.has_value());
if (response)
{
expectEqual(expected_response, *response);
}
}
}
TEST_F(AnnouncerUdpTest, announceDualStackOnlyIPv6Successful)
{
static auto constexpr Interval = time_t{ 3600 };
static auto constexpr Leechers = uint32_t{ 10 };
static auto constexpr Seeders = uint32_t{ 20 };
auto const addresses = std::array<tr_socket_address, 3>{ {
{ tr_address::from_string("fd12:3456:789a:1::1"sv).value_or(tr_address{}), tr_port::from_host(128) },
{ tr_address::from_string("fd12:3456:789a:1::2"sv).value_or(tr_address{}), tr_port::from_host(2021) },
{ tr_address::from_string("fd12:3456:789a:1::3"sv).value_or(tr_address{}), tr_port::from_host(2022) },
} };
auto request = tr_announce_request{};
request.event = TR_ANNOUNCE_EVENT_STARTED;
request.port = tr_port::from_host(80);
request.key = 0xCAFE;
request.numwant = 20;
request.up = 1;
request.down = 2;
request.corrupt = 3;
request.leftUntilComplete = 100;
request.announce_url = "https://localhost/announce"sv;
request.tracker_id = "fnord"s;
request.peer_id = tr_peerIdInit();
request.info_hash = tr_rand_obj<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 = std::nullopt; // not specified in UDP announce
expected_response.pex = {};
expected_response.pex6 = std::vector<tr_pex>{ tr_pex{ addresses[0] }, tr_pex{ addresses[1] }, tr_pex{ addresses[2] } };
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 const upkeep_timer = createUpkeepTimer(mediator, announcer);
auto response = std::optional<tr_announce_response>{};
announcer->announce(request, [&response](tr_announce_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
auto connection_ids = std::array<tau_connection_t, NUM_TR_AF_INET_TYPES>{};
for (uint8_t i = 0U; i < NUM_TR_AF_INET_TYPES; ++i)
{
// Announcer will request a connection. Verify and grant the request
auto const connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
auto const ipp = tr_af_to_ip_protocol(from_ptr->sa_family);
connection_ids[ipp] = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
}
for (uint8_t i = 0U; i < NUM_TR_AF_INET_TYPES; ++i)
{
response.reset();
// The announcer should have sent a UDP announce request.
// Inspect that request for validity.
auto const data = waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen);
auto const ipp = tr_af_to_ip_protocol(from_ptr->sa_family);
auto const udp_ann_req = parseAnnounceRequest(data, connection_ids[ipp]);
expectEqual(request, udp_ann_req);
// Have the tracker respond to the request
if (ipp == TR_AF_INET6)
{
auto buf = MessageBuffer{};
buf.add_uint32(AnnounceAction);
buf.add_uint32(udp_ann_req.transaction_id);
buf.add_uint32(expected_response.interval);
buf.add_uint32(expected_response.leechers.value_or(-1));
buf.add_uint32(expected_response.seeders.value_or(-1));
for (auto const& [addr, port] : addresses)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
buf.add(&addr.addr.addr6.s6_addr, sizeof(addr.addr.addr6.s6_addr));
buf.add_uint16(port.host());
}
EXPECT_TRUE(
announcer->handle_message(reinterpret_cast<uint8_t const*>(std::data(buf)), std::size(buf), from_ptr, fromlen));
}
else
{
EXPECT_TRUE(sendError(*announcer, udp_ann_req.transaction_id, "Failed"sv, from_ptr, fromlen));
}
// Failed responses won't be processed if one of the other announce requests succeeded
EXPECT_TRUE(ipp == TR_AF_INET ? !response.has_value() : response.has_value());
if (response)
{
expectEqual(expected_response, *response);
}
}
}
TEST_F(AnnouncerUdpTest, announceDualStackNoneSuccessful)
{
auto request = tr_announce_request{};
request.event = TR_ANNOUNCE_EVENT_STARTED;
request.port = tr_port::from_host(80);
request.key = 0xCAFE;
request.numwant = 20;
request.up = 1;
request.down = 2;
request.corrupt = 3;
request.leftUntilComplete = 100;
request.announce_url = "https://localhost/announce"sv;
request.tracker_id = "fnord"s;
request.peer_id = tr_peerIdInit();
request.info_hash = tr_rand_obj<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 = {};
expected_response.min_interval = 0; // not specified in UDP announce
expected_response.seeders = {};
expected_response.leechers = {};
expected_response.downloads = std::nullopt; // not specified in UDP announce
expected_response.pex = {};
expected_response.pex6 = {};
expected_response.errmsg = "Failed"s;
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 const upkeep_timer = createUpkeepTimer(mediator, announcer);
auto response = std::optional<tr_announce_response>{};
announcer->announce(request, [&response](tr_announce_response const& resp) { response = resp; });
auto from = sockaddr_storage{};
auto* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto fromlen = socklen_t{};
auto connection_ids = std::array<tau_connection_t, NUM_TR_AF_INET_TYPES>{};
for (uint8_t i = 0U; i < NUM_TR_AF_INET_TYPES; ++i)
{
// Announcer will request a connection. Verify and grant the request
auto const connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen));
auto const ipp = tr_af_to_ip_protocol(from_ptr->sa_family);
connection_ids[ipp] = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
}
for (uint8_t i = 0U; i < NUM_TR_AF_INET_TYPES; ++i)
{
auto const received_response = response.has_value();
// The announcer should have sent a UDP announce request.
// Inspect that request for validity.
auto const data = waitForAnnouncerToSendMessage(mediator, from_ptr, &fromlen);
auto const ipp = tr_af_to_ip_protocol(from_ptr->sa_family);
auto const udp_ann_req = parseAnnounceRequest(data, connection_ids[ipp]);
expectEqual(request, udp_ann_req);
// Have the tracker respond to the request
EXPECT_TRUE(sendError(*announcer, udp_ann_req.transaction_id, expected_response.errmsg, from_ptr, fromlen));
// Failed responses will only be processed if none of the announce requests are successful
// Or in other words, at most one failed response will be processed for each announce event
EXPECT_FALSE(received_response && response.has_value());
if (response)
{
expectEqual(expected_response, *response);
}
}
}