mirror of
https://github.com/transmission/transmission
synced 2024-12-22 07:42:37 +00:00
parent
befeafbcfe
commit
d2125ee965
13 changed files with 578 additions and 82 deletions
|
@ -11,6 +11,8 @@
|
|||
0A6169A80FE5C9A200C66CE6 /* bitfield.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A6169A60FE5C9A200C66CE6 /* bitfield.h */; };
|
||||
0A89346B736DBCF81F3A4850 /* torrent-metainfo.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0A89346B736DBCF81F3A4851 /* torrent-metainfo.cc */; };
|
||||
0A89346B736DBCF81F3A4852 /* torrent-metainfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A89346B736DBCF81F3A4853 /* torrent-metainfo.h */; };
|
||||
11524394C75E57E52CD9ADF0 /* dns.h in Headers */ = {isa = PBXBuildFile; fileRef = 11524394C75E57E52CD9ADF1 /* dns.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
11524394C75E57E52CD9ADF2 /* dns-ev.h in Headers */ = {isa = PBXBuildFile; fileRef = 11524394C75E57E52CD9ADF3 /* dns-ev.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
1BB44E07B1B52E28291B4E32 /* file-piece-map.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BB44E07B1B52E28291B4E30 /* file-piece-map.cc */; };
|
||||
1BB44E07B1B52E28291B4E33 /* file-piece-map.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BB44E07B1B52E28291B4E31 /* file-piece-map.h */; };
|
||||
2856E0656A49F2665D69E760 /* benc.h in Headers */ = {isa = PBXBuildFile; fileRef = 2856E0656A49F2665D69E761 /* benc.h */; };
|
||||
|
@ -602,6 +604,8 @@
|
|||
0A89346B736DBCF81F3A4851 /* torrent-metainfo.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "torrent-metainfo.cc"; sourceTree = "<group>"; };
|
||||
0A89346B736DBCF81F3A4853 /* torrent-metainfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "torrent-metainfo.h"; sourceTree = "<group>"; };
|
||||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
11524394C75E57E52CD9ADF1 /* dns.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = dns.h; sourceTree = "<group>"; };
|
||||
11524394C75E57E52CD9ADF3 /* dns-ev.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "dns-ev.h"; sourceTree = "<group>"; };
|
||||
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
|
||||
1BB44E07B1B52E28291B4E30 /* file-piece-map.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "file-piece-map.cc"; sourceTree = "<group>"; };
|
||||
1BB44E07B1B52E28291B4E31 /* file-piece-map.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "file-piece-map.h"; sourceTree = "<group>"; };
|
||||
|
@ -1707,6 +1711,8 @@
|
|||
C11DEA151FCD31C0009E22B9 /* subprocess.h */,
|
||||
E975121263DD973CAF4AEBA5 /* timer-ev.cc */,
|
||||
E975121263DD973CAF4AEBA3 /* timer-ev.h */,
|
||||
11524394C75E57E52CD9ADF1 /* dns.h */,
|
||||
11524394C75E57E52CD9ADF3 /* dns-ev.h */,
|
||||
E975121263DD973CAF4AEBA1 /* timer.h */,
|
||||
A20152790D1C26EB0081714F /* torrent-ctor.cc */,
|
||||
A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */,
|
||||
|
@ -2223,6 +2229,8 @@
|
|||
2856E0656A49F2665D69E760 /* benc.h in Headers */,
|
||||
E975121263DD973CAF4AEBA0 /* timer.h in Headers */,
|
||||
E975121263DD973CAF4AEBA2 /* timer-ev.h in Headers */,
|
||||
11524394C75E57E52CD9ADF0 /* dns.h in Headers */,
|
||||
11524394C75E57E52CD9ADF2 /* dns-ev.h in Headers */,
|
||||
C1077A4F183EB29600634C22 /* error.h in Headers */,
|
||||
A2679295130E00A000CB7464 /* tr-utp.h in Headers */,
|
||||
A263C6B1F6718E2486DB20E0 /* tr-buffer.h in Headers */,
|
||||
|
|
|
@ -136,6 +136,8 @@ endif()
|
|||
|
||||
set(${PROJECT_NAME}_PUBLIC_HEADERS
|
||||
${PROJECT_BINARY_DIR}/version.h
|
||||
dns-ev.h
|
||||
dns.h
|
||||
error-types.h
|
||||
error.h
|
||||
file.h
|
||||
|
|
|
@ -5,17 +5,13 @@
|
|||
|
||||
#include <algorithm> // for std::find_if()
|
||||
#include <cerrno> // for errno, EAFNOSUPPORT
|
||||
#include <cstring> // for memcpy()
|
||||
#include <cstring> // for memset()
|
||||
#include <ctime>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
|
@ -27,7 +23,6 @@
|
|||
#include "announcer-common.h"
|
||||
#include "crypto-utils.h" /* tr_rand_buffer() */
|
||||
#include "log.h"
|
||||
#include "error.h"
|
||||
#include "peer-io.h"
|
||||
#include "peer-mgr.h" // for tr_pex::fromCompact4()
|
||||
#include "session.h"
|
||||
|
@ -36,11 +31,6 @@
|
|||
#include "utils.h"
|
||||
#include "web-utils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef gai_strerror
|
||||
#define gai_strerror gai_strerrorA
|
||||
#endif
|
||||
|
||||
#define logwarn(interned, msg) tr_logAddWarn(msg, (interned).sv())
|
||||
#define logdbg(interned, msg) tr_logAddDebug(msg, (interned).sv())
|
||||
#define logtrace(interned, msg) tr_logAddTrace(msg, (interned).sv())
|
||||
|
@ -341,19 +331,9 @@ struct tau_tracker
|
|||
{
|
||||
}
|
||||
|
||||
tau_tracker(tau_tracker&&) = delete;
|
||||
tau_tracker(tau_tracker const&) = delete;
|
||||
tau_tracker& operator=(tau_tracker&&) = delete;
|
||||
tau_tracker& operator=(tau_tracker const&) = delete;
|
||||
|
||||
~tau_tracker()
|
||||
{
|
||||
tr_error_clear(&addr_error_);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto isIdle() const noexcept
|
||||
{
|
||||
return std::empty(announces) && std::empty(scrapes);
|
||||
return std::empty(announces) && std::empty(scrapes) && (dns_request_ == 0U);
|
||||
}
|
||||
|
||||
void failAll(bool did_connect, bool did_timeout, std::string_view errmsg)
|
||||
|
@ -400,7 +380,7 @@ struct tau_tracker
|
|||
tr_interned_string const host;
|
||||
tr_port const port;
|
||||
|
||||
tr_error* addr_error_ = nullptr;
|
||||
libtransmission::Dns::Tag dns_request_ = {};
|
||||
std::optional<std::pair<sockaddr_storage, socklen_t>> addr_;
|
||||
time_t addr_expires_at_ = 0;
|
||||
|
||||
|
@ -417,43 +397,30 @@ struct tau_tracker
|
|||
std::list<tau_scrape_request> scrapes;
|
||||
};
|
||||
|
||||
static std::optional<std::pair<sockaddr_storage, socklen_t>> host2sockaddr(
|
||||
std::string_view host,
|
||||
tr_port port,
|
||||
tr_error** error)
|
||||
{
|
||||
auto const szhost = tr_urlbuf{ host };
|
||||
|
||||
auto szport = std::array<char, 16>{};
|
||||
*fmt::format_to(std::data(szport), FMT_STRING("{:d}"), port.host()) = '\0';
|
||||
|
||||
auto hints = addrinfo{};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
addrinfo* info = nullptr;
|
||||
if (int const rc = getaddrinfo(szhost, std::data(szport), &hints, &info); rc != 0)
|
||||
{
|
||||
tr_logAddWarn(fmt::format(
|
||||
_("Couldn't look up '{address}:{port}': {error} ({error_code})"),
|
||||
fmt::arg("address", host),
|
||||
fmt::arg("port", port.host()),
|
||||
fmt::arg("error", gai_strerror(rc)),
|
||||
fmt::arg("error_code", rc)));
|
||||
tr_error_set(error, rc, gai_strerror(rc));
|
||||
return {};
|
||||
}
|
||||
|
||||
auto ss = sockaddr_storage{};
|
||||
auto const len = info->ai_addrlen;
|
||||
memcpy(&ss, info->ai_addr, len);
|
||||
freeaddrinfo(info);
|
||||
return std::make_pair(ss, len);
|
||||
}
|
||||
|
||||
static void tau_tracker_upkeep(struct tau_tracker* /*tracker*/);
|
||||
|
||||
static void tau_tracker_on_dns(tau_tracker* const tracker, sockaddr const* sa, socklen_t salen, time_t expires_at)
|
||||
{
|
||||
tracker->dns_request_ = {};
|
||||
|
||||
if (sa == nullptr)
|
||||
{
|
||||
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");
|
||||
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)
|
||||
{
|
||||
logdbg(tracker->key, fmt::format("sending request w/connection id {}", tracker->connection_id));
|
||||
|
@ -498,6 +465,7 @@ 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_);
|
||||
TR_ASSERT(tracker->addr_);
|
||||
TR_ASSERT(tracker->connecting_at == 0);
|
||||
TR_ASSERT(tracker->connection_expiration_time > tr_time());
|
||||
|
@ -600,20 +568,20 @@ static void tau_tracker_upkeep_ex(struct tau_tracker* tracker, bool timeout_reqs
|
|||
return;
|
||||
}
|
||||
|
||||
// if we don't have an address yet, try & get one now
|
||||
if (!closing && !tracker->addr_)
|
||||
/* if we don't have an address yet, try & get one now. */
|
||||
if (!closing && !tracker->addr_ && (tracker->dns_request_ == 0U))
|
||||
{
|
||||
if (tracker->addr_expires_at_ <= now)
|
||||
{
|
||||
tr_error_clear(&tracker->addr_error_);
|
||||
tracker->addr_ = host2sockaddr(tracker->host, tracker->port, &tracker->addr_error_);
|
||||
tracker->addr_expires_at_ = now + tau_tracker::DnsRetryIntervalSecs;
|
||||
}
|
||||
if (tracker->addr_error_ != nullptr)
|
||||
{
|
||||
tracker->failAll(false, false, tracker->addr_error_->message);
|
||||
return;
|
||||
}
|
||||
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_ = 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(
|
||||
|
@ -722,6 +690,12 @@ public:
|
|||
|
||||
for (auto& tracker : trackers_)
|
||||
{
|
||||
// if there's a pending DNS request, cancel it
|
||||
if (tracker.dns_request_ != 0U)
|
||||
{
|
||||
mediator_.dns().cancel(tracker.dns_request_);
|
||||
}
|
||||
|
||||
tracker.close_at = now + 3;
|
||||
tau_tracker_upkeep(&tracker);
|
||||
}
|
||||
|
|
|
@ -301,6 +301,7 @@ public:
|
|||
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;
|
||||
};
|
||||
|
||||
|
|
210
libtransmission/dns-ev.h
Normal file
210
libtransmission/dns-ev.h
Normal file
|
@ -0,0 +1,210 @@
|
|||
// This file Copyright 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __TRANSMISSION__
|
||||
#error only libtransmission should #include this header.
|
||||
#endif
|
||||
|
||||
#include <cstring> // for std::memcpy()
|
||||
#include <ctime>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <event2/dns.h>
|
||||
#include <event2/event.h>
|
||||
|
||||
#include "dns.h"
|
||||
#include "utils.h" // for tr_strlower()
|
||||
|
||||
namespace libtransmission
|
||||
{
|
||||
|
||||
class EvDns final : public Dns
|
||||
{
|
||||
private:
|
||||
using Key = std::pair<std::string, Hints>;
|
||||
|
||||
struct CacheEntry
|
||||
{
|
||||
sockaddr_storage ss_ = {};
|
||||
socklen_t sslen_ = {};
|
||||
time_t expires_at_ = {};
|
||||
};
|
||||
|
||||
struct CallbackArg
|
||||
{
|
||||
Key key;
|
||||
EvDns* self;
|
||||
};
|
||||
|
||||
struct Request
|
||||
{
|
||||
evdns_getaddrinfo_request* request;
|
||||
|
||||
struct CallbackInfo
|
||||
{
|
||||
CallbackInfo(Tag tag, Callback callback)
|
||||
: tag_{ tag }
|
||||
, callback_{ std::move(callback) }
|
||||
{
|
||||
}
|
||||
|
||||
Tag tag_;
|
||||
Callback callback_;
|
||||
};
|
||||
|
||||
std::list<CallbackInfo> callbacks;
|
||||
};
|
||||
|
||||
public:
|
||||
using TimeFunc = time_t (*)();
|
||||
|
||||
EvDns(struct event_base* event_base, TimeFunc time_func)
|
||||
: time_func_{ time_func }
|
||||
, evdns_base_{ evdns_base_new(event_base, EVDNS_BASE_INITIALIZE_NAMESERVERS),
|
||||
[](evdns_base* dns)
|
||||
{
|
||||
// if zero, active requests will be aborted
|
||||
evdns_base_free(dns, 0);
|
||||
} }
|
||||
{
|
||||
}
|
||||
|
||||
~EvDns() override
|
||||
{
|
||||
for (auto& [key, request] : requests_)
|
||||
{
|
||||
evdns_getaddrinfo_cancel(request.request);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::pair<sockaddr const*, socklen_t>> cached(std::string_view address, Hints hints = {}) const override
|
||||
{
|
||||
if (auto const* entry = cached(makeKey(address, hints)); entry != nullptr)
|
||||
{
|
||||
return std::make_pair(reinterpret_cast<sockaddr const*>(&entry->ss_), entry->sslen_);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Tag lookup(std::string_view address, Callback&& callback, Hints hints = {}) override
|
||||
{
|
||||
auto const key = makeKey(address, hints);
|
||||
|
||||
if (auto const* entry = cached(key); entry)
|
||||
{
|
||||
callback(reinterpret_cast<sockaddr const*>(&entry->ss_), entry->sslen_, entry->expires_at_);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& request = requests_[key];
|
||||
auto const tag = next_tag_;
|
||||
++next_tag_;
|
||||
request.callbacks.emplace_back(tag, std::move(callback));
|
||||
if (request.request == nullptr)
|
||||
{
|
||||
auto evhints = evutil_addrinfo{};
|
||||
evhints.ai_family = hints.ai_family;
|
||||
evhints.ai_socktype = hints.ai_socktype;
|
||||
evhints.ai_protocol = hints.ai_protocol;
|
||||
void* const arg = new CallbackArg{ key, this };
|
||||
request.request = evdns_getaddrinfo(evdns_base_.get(), key.first.c_str(), nullptr, &evhints, evcallback, arg);
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
void cancel(Tag tag) override
|
||||
{
|
||||
for (auto& [key, request] : requests_)
|
||||
{
|
||||
for (auto iter = std::begin(request.callbacks), end = std::end(request.callbacks); iter != end; ++iter)
|
||||
{
|
||||
if (iter->tag_ != tag)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
iter->callback_(nullptr, 0, 0);
|
||||
|
||||
request.callbacks.erase(iter);
|
||||
|
||||
// if this was the last pending request for `key`, cancel the evdns request
|
||||
if (std::empty(request.callbacks))
|
||||
{
|
||||
evdns_getaddrinfo_cancel(request.request);
|
||||
requests_.erase(key);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] static Key makeKey(std::string_view address, Hints hints)
|
||||
{
|
||||
return Key{ tr_strlower(address), hints };
|
||||
}
|
||||
|
||||
[[nodiscard]] CacheEntry const* cached(Key const& key) const
|
||||
{
|
||||
if (auto iter = cache_.find(key); iter != std::end(cache_))
|
||||
{
|
||||
auto const& entry = iter->second;
|
||||
|
||||
if (auto const now = time_func_(); entry.expires_at_ > now)
|
||||
{
|
||||
return &entry;
|
||||
}
|
||||
|
||||
cache_.erase(iter); // expired
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void evcallback(int /*result*/, struct evutil_addrinfo* res, void* varg)
|
||||
{
|
||||
auto* const arg = static_cast<CallbackArg*>(varg);
|
||||
auto [key, self] = *arg;
|
||||
delete arg;
|
||||
|
||||
auto& cache_entry = self->cache_[key];
|
||||
|
||||
if (res != nullptr)
|
||||
{
|
||||
cache_entry.expires_at_ = self->time_func_() + CacheTtlSecs;
|
||||
cache_entry.sslen_ = res->ai_addrlen;
|
||||
std::memcpy(&cache_entry.ss_, res->ai_addr, res->ai_addrlen);
|
||||
evutil_freeaddrinfo(res);
|
||||
}
|
||||
|
||||
if (auto request_entry = self->requests_.extract(key); request_entry)
|
||||
{
|
||||
for (auto& callback : request_entry.mapped().callbacks)
|
||||
{
|
||||
callback.callback_(
|
||||
reinterpret_cast<sockaddr const*>(&cache_entry.ss_),
|
||||
cache_entry.sslen_,
|
||||
cache_entry.expires_at_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeFunc const time_func_;
|
||||
static time_t constexpr CacheTtlSecs = 3600U;
|
||||
std::unique_ptr<evdns_base, void (*)(evdns_base*)> const evdns_base_;
|
||||
mutable std::map<Key, CacheEntry> cache_;
|
||||
std::map<Key, Request> requests_;
|
||||
unsigned int next_tag_ = 1;
|
||||
};
|
||||
|
||||
} // namespace libtransmission
|
72
libtransmission/dns.h
Normal file
72
libtransmission/dns.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
// This file Copyright 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "net.h"
|
||||
|
||||
namespace libtransmission
|
||||
{
|
||||
|
||||
class Dns
|
||||
{
|
||||
public:
|
||||
virtual ~Dns() = default;
|
||||
|
||||
using Callback = std::function<void(struct sockaddr const*, socklen_t salen, time_t expires_at)>;
|
||||
using Tag = unsigned int;
|
||||
|
||||
class Hints
|
||||
{
|
||||
public:
|
||||
Hints()
|
||||
{
|
||||
}
|
||||
|
||||
int ai_family = AF_UNSPEC;
|
||||
int ai_socktype = SOCK_DGRAM;
|
||||
int ai_protocol = IPPROTO_UDP;
|
||||
|
||||
[[nodiscard]] constexpr int compare(Hints const& that) const noexcept // <=>
|
||||
{
|
||||
if (ai_family != that.ai_family)
|
||||
{
|
||||
return ai_family < that.ai_family ? -1 : 1;
|
||||
}
|
||||
|
||||
if (ai_socktype != that.ai_socktype)
|
||||
{
|
||||
return ai_socktype < that.ai_socktype ? -1 : 1;
|
||||
}
|
||||
|
||||
if (ai_protocol != that.ai_protocol)
|
||||
{
|
||||
return ai_protocol < that.ai_protocol ? -1 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator<(Hints const& that) const noexcept
|
||||
{
|
||||
return compare(that) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] virtual std::optional<std::pair<struct sockaddr const*, socklen_t>> cached(
|
||||
std::string_view address,
|
||||
Hints hints = {}) const = 0;
|
||||
|
||||
virtual Tag lookup(std::string_view address, Callback&& callback, Hints hints = {}) = 0;
|
||||
|
||||
virtual void cancel(Tag) = 0;
|
||||
};
|
||||
|
||||
} // namespace libtransmission
|
|
@ -26,6 +26,7 @@
|
|||
#include <sys/stat.h> /* umask() */
|
||||
#endif
|
||||
|
||||
#include <event2/dns.h>
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
@ -39,6 +40,7 @@
|
|||
#include "blocklist.h"
|
||||
#include "cache.h"
|
||||
#include "crypto-utils.h"
|
||||
#include "dns-ev.h"
|
||||
#include "error-types.h"
|
||||
#include "error.h"
|
||||
#include "file.h"
|
||||
|
@ -2173,6 +2175,7 @@ tr_session::tr_session(std::string_view config_dir, tr_variant* settings_dict)
|
|||
, blocklist_dir_{ makeBlocklistDir(config_dir) }
|
||||
, session_thread_{ tr_session_thread::create() }
|
||||
, timer_maker_{ std::make_unique<libtransmission::EvTimerMaker>(eventBase()) }
|
||||
, dns_{ std::make_unique<libtransmission::EvDns>(eventBase(), tr_time) }
|
||||
, settings_{ settings_dict }
|
||||
, session_id_{ tr_time }
|
||||
, peer_mgr_{ tr_peerMgrNew(this), tr_peerMgrFree }
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "bandwidth.h"
|
||||
#include "bitfield.h"
|
||||
#include "cache.h"
|
||||
#include "dns.h"
|
||||
#include "interned-string.h"
|
||||
#include "net.h" // tr_socket_t
|
||||
#include "open-files.h"
|
||||
|
@ -147,6 +148,11 @@ private:
|
|||
return tr_address::fromString(session_.announceIP());
|
||||
}
|
||||
|
||||
[[nodiscard]] libtransmission::Dns& dns() override
|
||||
{
|
||||
return *session_.dns_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
tr_session& session_;
|
||||
};
|
||||
|
@ -1034,6 +1040,9 @@ private:
|
|||
// depends-on: session_thread_
|
||||
std::unique_ptr<libtransmission::TimerMaker> const timer_maker_;
|
||||
|
||||
// depends-on: event_base_
|
||||
std::unique_ptr<libtransmission::Dns> const dns_;
|
||||
|
||||
/// trivial type fields
|
||||
|
||||
tr_session_settings settings_;
|
||||
|
@ -1140,7 +1149,7 @@ private:
|
|||
// depends-on: lpd_mediator_
|
||||
std::unique_ptr<tr_lpd> lpd_;
|
||||
|
||||
// depends-on: udp_core_
|
||||
// depends-on: dns_, udp_core_
|
||||
AnnouncerUdpMediator announcer_udp_mediator_{ *this };
|
||||
|
||||
// depends-on: timer_maker_, torrents_, peer_mgr_
|
||||
|
|
|
@ -2331,7 +2331,7 @@ void tr_torrentSetQueuePosition(tr_torrent* tor, size_t queue_position)
|
|||
size_t current = 0;
|
||||
auto const old_pos = tor->queuePosition;
|
||||
|
||||
tor->queuePosition = static_cast<size_t>(-1);
|
||||
tor->queuePosition = -1;
|
||||
|
||||
for (auto* const walk : tor->session->torrents())
|
||||
{
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
|
||||
#include "announcer.h"
|
||||
#include "crypto-utils.h"
|
||||
#include "dns.h"
|
||||
#include "peer-mgr.h" // for tr_pex
|
||||
#include "tr-buffer.h"
|
||||
#include "utils.h" // for tr_net_init()
|
||||
|
||||
#include "test-fixtures.h"
|
||||
|
||||
|
@ -28,15 +28,43 @@ private:
|
|||
void SetUp() override
|
||||
{
|
||||
::testing::Test::SetUp();
|
||||
|
||||
tr_net_init();
|
||||
tr_timeUpdate(time(nullptr));
|
||||
}
|
||||
|
||||
protected:
|
||||
class MockDns final : public libtransmission::Dns
|
||||
{
|
||||
public:
|
||||
[[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);
|
||||
|
@ -44,6 +72,16 @@ protected:
|
|||
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 {};
|
||||
|
@ -66,6 +104,10 @@ protected:
|
|||
};
|
||||
|
||||
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)
|
||||
|
@ -158,7 +200,7 @@ protected:
|
|||
[[nodiscard]] static auto waitForAnnouncerToSendMessage(MockMediator& mediator)
|
||||
{
|
||||
EXPECT_FALSE(std::empty(mediator.sent_));
|
||||
libtransmission::test::waitFor([&mediator]() { return !std::empty(mediator.sent_); }, 5s);
|
||||
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;
|
||||
|
@ -221,8 +263,8 @@ protected:
|
|||
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, static_cast<decltype(expected.key)>(actual.key));
|
||||
EXPECT_EQ(expected.numwant, static_cast<decltype(expected.numwant)>(actual.num_want));
|
||||
EXPECT_EQ(expected.key, actual.key);
|
||||
EXPECT_EQ(expected.numwant, actual.num_want);
|
||||
EXPECT_EQ(expected.port.host(), actual.port);
|
||||
}
|
||||
|
||||
|
@ -368,7 +410,7 @@ TEST_F(AnnouncerUdpTest, canDestructCleanlyEvenWhenBusy)
|
|||
// Inspect that request for validity.
|
||||
auto sent = waitForAnnouncerToSendMessage(mediator);
|
||||
auto const connect_transaction_id = parseConnectionRequest(sent);
|
||||
EXPECT_NE(0U, connect_transaction_id);
|
||||
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.
|
||||
|
|
175
tests/libtransmission/dns-test.cc
Normal file
175
tests/libtransmission/dns-test.cc
Normal file
|
@ -0,0 +1,175 @@
|
|||
// 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 <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "dns-ev.h"
|
||||
#include "dns.h"
|
||||
#include "trevent.h" // for tr_evthread_init();
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test-fixtures.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace libtransmission::test
|
||||
{
|
||||
|
||||
class EvDnsTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
::testing::Test::SetUp();
|
||||
|
||||
tr_evthread_init();
|
||||
event_base_ = event_base_new();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
event_base_free(event_base_);
|
||||
event_base_ = nullptr;
|
||||
|
||||
::testing::Test::TearDown();
|
||||
}
|
||||
|
||||
struct event_base* event_base_ = nullptr;
|
||||
};
|
||||
|
||||
TEST_F(EvDnsTest, canLookup)
|
||||
{
|
||||
auto dns = EvDns{ event_base_, tr_time };
|
||||
auto done = false;
|
||||
|
||||
dns.lookup(
|
||||
"example.com",
|
||||
[&done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
|
||||
{
|
||||
EXPECT_NE(nullptr, ai);
|
||||
EXPECT_GT(ailen, 0);
|
||||
EXPECT_GT(expires_at, tr_time());
|
||||
done = true;
|
||||
});
|
||||
|
||||
waitFor(event_base_, [&done]() { return done; });
|
||||
EXPECT_TRUE(done);
|
||||
}
|
||||
|
||||
TEST_F(EvDnsTest, canRequestWhilePending)
|
||||
{
|
||||
auto dns = EvDns{ event_base_, tr_time };
|
||||
auto n_done = size_t{ 0 };
|
||||
|
||||
dns.lookup(
|
||||
"example.com",
|
||||
[&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
|
||||
{
|
||||
EXPECT_NE(nullptr, ai);
|
||||
EXPECT_GT(ailen, 0);
|
||||
EXPECT_GT(expires_at, tr_time());
|
||||
++n_done;
|
||||
});
|
||||
|
||||
dns.lookup(
|
||||
"example.com",
|
||||
[&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
|
||||
{
|
||||
EXPECT_NE(nullptr, ai);
|
||||
EXPECT_GT(ailen, 0);
|
||||
EXPECT_GT(expires_at, tr_time());
|
||||
++n_done;
|
||||
});
|
||||
|
||||
// wait for both callbacks to be called
|
||||
waitFor(event_base_, [&n_done]() { return n_done >= 2U; });
|
||||
EXPECT_EQ(2U, n_done);
|
||||
}
|
||||
|
||||
TEST_F(EvDnsTest, canCancel)
|
||||
{
|
||||
auto dns = EvDns{ event_base_, tr_time };
|
||||
auto n_done = size_t{ 0 };
|
||||
static auto constexpr Name = "example.com"sv;
|
||||
|
||||
auto tag = dns.lookup(
|
||||
Name,
|
||||
[&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
|
||||
{
|
||||
++n_done;
|
||||
// we cancelled this req, so `ai` and `ailen` should be zeroed out
|
||||
EXPECT_EQ(nullptr, ai);
|
||||
EXPECT_EQ(0, ailen);
|
||||
EXPECT_EQ(0, expires_at);
|
||||
});
|
||||
|
||||
dns.lookup(
|
||||
Name,
|
||||
[&n_done](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
|
||||
{
|
||||
++n_done;
|
||||
|
||||
// this one did _not_ get cancelled so it should be OK
|
||||
EXPECT_NE(nullptr, ai);
|
||||
EXPECT_GT(ailen, 0);
|
||||
EXPECT_GT(expires_at, tr_time());
|
||||
});
|
||||
|
||||
dns.cancel(tag);
|
||||
|
||||
// wait for both callbacks to be called
|
||||
waitFor(event_base_, [&n_done]() { return n_done >= 2U; });
|
||||
EXPECT_EQ(2U, n_done);
|
||||
}
|
||||
|
||||
TEST_F(EvDnsTest, doesCacheEntries)
|
||||
{
|
||||
auto dns = EvDns{ event_base_, tr_time };
|
||||
static auto constexpr Name = "example.com"sv;
|
||||
|
||||
struct sockaddr const* ai_addr = nullptr;
|
||||
|
||||
dns.lookup(
|
||||
Name,
|
||||
[&ai_addr](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
|
||||
{
|
||||
EXPECT_NE(nullptr, ai);
|
||||
EXPECT_GT(ailen, 0);
|
||||
EXPECT_GT(expires_at, tr_time());
|
||||
ai_addr = ai;
|
||||
});
|
||||
|
||||
// wait for the lookup
|
||||
waitFor(event_base_, [&ai_addr]() { return ai_addr != nullptr; });
|
||||
ASSERT_NE(nullptr, ai_addr);
|
||||
|
||||
auto second_callback_called = false;
|
||||
dns.lookup(
|
||||
Name,
|
||||
[&ai_addr, &second_callback_called](struct sockaddr const* ai, socklen_t ailen, time_t expires_at)
|
||||
{
|
||||
EXPECT_NE(nullptr, ai);
|
||||
EXPECT_GT(ailen, 0);
|
||||
EXPECT_EQ(ai_addr, ai);
|
||||
EXPECT_GT(expires_at, tr_time());
|
||||
second_callback_called = true;
|
||||
});
|
||||
// since it's cached, the callback should have been invoked
|
||||
// without waiting for the event loop
|
||||
EXPECT_TRUE(second_callback_called);
|
||||
|
||||
// confirm that `cached()` returns the cached value immediately
|
||||
auto res = dns.cached(Name);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(ai_addr, res->first);
|
||||
EXPECT_GT(res->second, 0);
|
||||
}
|
||||
|
||||
} // namespace libtransmission::test
|
|
@ -61,7 +61,7 @@ TEST_F(NetTest, compact4)
|
|||
auto compact4 = std::array<std::byte, 6>{};
|
||||
auto out = std::data(compact4);
|
||||
out = addr.toCompact4(out, port);
|
||||
EXPECT_EQ(std::size(Compact4), static_cast<size_t>(out - std::data(compact4)));
|
||||
EXPECT_EQ(std::size(Compact4), out - std::data(compact4));
|
||||
EXPECT_EQ(Compact4, compact4);
|
||||
|
||||
/// sockaddr --> compact
|
||||
|
|
|
@ -327,7 +327,7 @@ TEST_F(SettingsTest, canSaveSizeT)
|
|||
settings.save(&dict);
|
||||
auto val = int64_t{};
|
||||
EXPECT_TRUE(tr_variantDictFindInt(&dict, Key, &val));
|
||||
EXPECT_EQ(expected_value, static_cast<size_t>(val));
|
||||
EXPECT_EQ(expected_value, val);
|
||||
tr_variantClear(&dict);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue