refactor: mock udp announcer DNS resolver for tests (#8232)

* refactor: move udp tracker dns lookup to mediator

* test: mock udp announcer DNS resolver
This commit is contained in:
Yat Ho
2026-01-28 02:38:24 +08:00
committed by GitHub
parent c738b9fe4d
commit cbc4e9dc3a
3 changed files with 112 additions and 50 deletions

View File

@@ -439,7 +439,11 @@ struct tau_tracker
// do we have a DNS request that's ready? // do we have a DNS request that's ready?
if (auto& dns = addr_pending_dns_[ipp]; dns && dns->wait_for(0ms) == std::future_status::ready) if (auto& dns = addr_pending_dns_[ipp]; dns && dns->wait_for(0ms) == std::future_status::ready)
{ {
addr_[ipp] = dns->get(); // TODO(C++23): use std::optional::transform() instead
if (auto const& addr = dns->get(); addr.has_value())
{
addr_[ipp] = addr->to_sockaddr();
}
dns.reset(); dns.reset();
addr_expires_at_[ipp] = now + DnsRetryIntervalSecs; addr_expires_at_[ipp] = now + DnsRetryIntervalSecs;
} }
@@ -453,17 +457,17 @@ struct tau_tracker
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp) for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{ {
auto const ipp_enum = static_cast<tr_address_type>(ipp);
// update the addr if our lookup is past its shelf date // update the addr if our lookup is past its shelf date
if (auto& dns = addr_pending_dns_[ipp]; !dns && addr_expires_at_[ipp] <= now) if (auto& dns = addr_pending_dns_[ipp]; !dns && addr_expires_at_[ipp] <= now)
{ {
addr_[ipp].reset(); addr_[ipp].reset();
dns = std::async( dns = std::async(
std::launch::async, std::launch::async,
[this](tr_address_type ip_protocol) { return lookup(ip_protocol); }, [this, ipp_enum] { return mediator_.dns_lookup(ipp_enum, host_lookup, port.host(), log_name()); });
static_cast<tr_address_type>(ipp));
} }
auto const ipp_enum = static_cast<tr_address_type>(ipp);
auto& conn_at = connecting_at[ipp]; auto& conn_at = connecting_at[ipp];
logtrace( logtrace(
log_name(), log_name(),
@@ -523,51 +527,6 @@ private:
return std::ranges::any_of(addr_, [](auto const& o) { return !!o; }); return std::ranges::any_of(addr_, [](auto const& o) { return !!o; });
} }
[[nodiscard]] MaybeSockaddr lookup(tr_address_type ip_protocol)
{
auto szport = std::array<char, 16>{};
*fmt::format_to(std::data(szport), "{:d}", port.host()) = '\0';
auto hints = addrinfo{};
hints.ai_family = tr_ip_protocol_to_af(ip_protocol);
hints.ai_protocol = IPPROTO_UDP;
hints.ai_socktype = SOCK_DGRAM;
addrinfo* info = nullptr;
auto const szhost = tr_urlbuf{ host_lookup };
if (int const rc = getaddrinfo(szhost.c_str(), std::data(szport), &hints, &info); rc != 0)
{
logwarn(
log_name(),
fmt::format(
fmt::runtime(_("Couldn't look up '{address}:{port}' in {ip_protocol}: {error} ({error_code})")),
fmt::arg("address", host),
fmt::arg("port", port.host()),
fmt::arg("ip_protocol", tr_ip_protocol_to_sv(ip_protocol)),
fmt::arg("error", gai_strerror(rc)),
fmt::arg("error_code", static_cast<int>(rc))));
return {};
}
auto const info_uniq = std::unique_ptr<addrinfo, decltype(&freeaddrinfo)>{ info, freeaddrinfo };
// N.B. getaddrinfo() will return IPv4-mapped addresses by default on macOS
auto socket_address = tr_socket_address::from_sockaddr(info->ai_addr);
if (!socket_address || socket_address->address().is_ipv6_ipv4_mapped())
{
logdbg(
log_name(),
fmt::format(
"Couldn't look up '{address}:{port}' in {ip_protocol}: got invalid address",
fmt::arg("address", host),
fmt::arg("port", port.host()),
fmt::arg("ip_protocol", tr_ip_protocol_to_sv(ip_protocol))));
return {};
}
logdbg(log_name(), fmt::format("{} DNS lookup succeeded", tr_ip_protocol_to_sv(ip_protocol)));
return socket_address->to_sockaddr();
}
void fail_all(bool did_connect, bool did_timeout, std::string_view errmsg) void fail_all(bool did_connect, bool did_timeout, std::string_view errmsg)
{ {
for (auto& req : scrapes) for (auto& req : scrapes)
@@ -702,7 +661,7 @@ public:
private: private:
Mediator& mediator_; Mediator& mediator_;
std::array<std::optional<std::future<MaybeSockaddr>>, NUM_TR_AF_INET_TYPES> addr_pending_dns_; std::array<std::optional<std::future<std::optional<tr_socket_address>>>, NUM_TR_AF_INET_TYPES> addr_pending_dns_;
std::array<MaybeSockaddr, NUM_TR_AF_INET_TYPES> addr_ = {}; std::array<MaybeSockaddr, NUM_TR_AF_INET_TYPES> addr_ = {};
std::array<time_t, NUM_TR_AF_INET_TYPES> addr_expires_at_ = {}; std::array<time_t, NUM_TR_AF_INET_TYPES> addr_expires_at_ = {};
@@ -902,3 +861,51 @@ std::unique_ptr<tr_announcer_udp> tr_announcer_udp::create(Mediator& mediator)
{ {
return std::make_unique<tr_announcer_udp_impl>(mediator); return std::make_unique<tr_announcer_udp_impl>(mediator);
} }
std::optional<tr_socket_address> tr_announcer_udp::Mediator::dns_lookup(
tr_address_type const ip_protocol,
std::string_view const name,
uint16_t const service,
std::string_view const log_name) const
{
auto hints = addrinfo{};
hints.ai_family = tr_ip_protocol_to_af(ip_protocol);
hints.ai_protocol = IPPROTO_UDP;
hints.ai_socktype = SOCK_DGRAM;
addrinfo* info = nullptr;
auto const szname = tr_urlbuf{ name };
auto szservice = std::array<char, 16>{};
*fmt::format_to(std::data(szservice), "{:d}", service) = '\0';
if (int const rc = getaddrinfo(szname.c_str(), std::data(szservice), &hints, &info); rc != 0)
{
logwarn(
log_name,
fmt::format(
fmt::runtime(_("Couldn't look up '{address}:{port}' in {ip_protocol}: {error} ({error_code})")),
fmt::arg("address", name),
fmt::arg("port", service),
fmt::arg("ip_protocol", tr_ip_protocol_to_sv(ip_protocol)),
fmt::arg("error", gai_strerror(rc)),
fmt::arg("error_code", static_cast<int>(rc))));
return {};
}
auto const info_uniq = std::unique_ptr<addrinfo, decltype(&freeaddrinfo)>{ info, freeaddrinfo };
// N.B. getaddrinfo() will return IPv4-mapped addresses by default on macOS
auto socket_address = tr_socket_address::from_sockaddr(info->ai_addr);
if (!socket_address || socket_address->address().is_ipv6_ipv4_mapped())
{
logdbg(
log_name,
fmt::format(
"Couldn't look up '{address}:{port}' in {ip_protocol}: got invalid address",
fmt::arg("address", name),
fmt::arg("port", service),
fmt::arg("ip_protocol", tr_ip_protocol_to_sv(ip_protocol))));
return {};
}
logdbg(log_name, fmt::format("{} DNS lookup succeeded", tr_ip_protocol_to_sv(ip_protocol)));
return socket_address;
}

View File

@@ -141,6 +141,11 @@ public:
virtual ~Mediator() noexcept = default; virtual ~Mediator() noexcept = default;
virtual void sendto(void const* buf, size_t buflen, sockaddr const* addr, socklen_t addrlen) = 0; virtual void sendto(void const* buf, size_t buflen, sockaddr const* addr, socklen_t addrlen) = 0;
[[nodiscard]] virtual std::optional<tr_address> announce_ip() const = 0; [[nodiscard]] virtual std::optional<tr_address> announce_ip() const = 0;
[[nodiscard]] virtual std::optional<tr_socket_address> dns_lookup(
tr_address_type ip_protocol,
std::string_view name,
uint16_t service,
std::string_view log_name) const;
}; };
virtual ~tr_announcer_udp() noexcept = default; virtual ~tr_announcer_udp() noexcept = default;

View File

@@ -84,6 +84,56 @@ protected:
return {}; return {};
} }
// Mock DNS lookup that only resolves localhost
[[nodiscard]] std::optional<tr_socket_address> dns_lookup(
tr_address_type const ip_protocol,
std::string_view const name,
uint16_t const service,
std::string_view /*log_name*/) const override
{
auto const is_localhost = name == "localhost"sv;
auto const port = tr_port::from_host(service);
switch (ip_protocol)
{
case TR_AF_INET:
if (is_localhost)
{
auto const addr = tr_address::from_string("127.0.0.1"sv);
EXPECT_TRUE(addr);
EXPECT_TRUE(addr->is_ipv4_loopback());
return tr_socket_address{ *addr, port };
}
if (auto const addr = tr_address::from_string(name); addr && addr->is_ipv4_loopback())
{
return tr_socket_address{ *addr, port };
}
break;
case TR_AF_INET6:
if (is_localhost)
{
auto const addr = tr_address::from_string("::1");
EXPECT_TRUE(addr);
EXPECT_TRUE(addr->is_ipv6_loopback());
return tr_socket_address{ *addr, port };
}
if (auto const addr = tr_address::from_string(name); addr && addr->is_ipv6_loopback())
{
return tr_socket_address{ *addr, port };
}
break;
default:
break;
}
return {};
}
struct Sent struct Sent
{ {
Sent() = default; Sent() = default;