fix: various DHT bugs (#6569)

* fix: unconditionally send DHT Port msg if supported

* chore: add "maybe" to dht add_node functions

* feat: change `bind-address-ipv*` defaults to empty string

* chore: housekeeping

* fix: initialise DHT node id with random bytes

* chore: housekeeping
This commit is contained in:
Yat Ho 2024-02-05 02:18:01 +08:00 committed by GitHub
parent 1c71ba5c83
commit ca11c33d05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 71 additions and 69 deletions

View File

@ -93,8 +93,8 @@ Here is a sample of the three basic types: respectively Boolean, Number and Stri
* **preferred-transport:** String ("utp" = Prefer µTP, "tcp" = Prefer TCP; default = "utp") Choose your preferred transport protocol (has no effect if one of them is disabled).
#### Peers
* **bind-address-ipv4:** String (default = "0.0.0.0") Where to listen for peer connections. When no valid IPv4 address is provided, Transmission will bind to "0.0.0.0".
* **bind-address-ipv6:** String (default = "::") Where to listen for peer connections. When no valid IPv6 address is provided, Transmission will try to bind to your default global IPv6 address. If that didn't work, then Transmission will bind to "::".
* **bind-address-ipv4:** String (default = "") Where to listen for peer connections. When no valid IPv4 address is provided, Transmission will bind to "0.0.0.0".
* **bind-address-ipv6:** String (default = "") Where to listen for peer connections. When no valid IPv6 address is provided, Transmission will try to bind to your default global IPv6 address. If that didn't work, then Transmission will bind to "::".
* **peer-congestion-algorithm:** String. This is documented on https://www.pps.jussieu.fr/~jch/software/bittorrent/tcp-congestion-control.html.
* **peer-limit-global:** Number (default = 240)
* **peer-limit-per-torrent:** Number (default = 60)

View File

@ -352,11 +352,7 @@ public:
if (session->allowsDHT() && io->supports_dht())
{
// only send PORT over IPv6 iff IPv6 DHT is running (BEP-32).
if (auto const addr = session->bind_address(TR_AF_INET6); !addr.is_any())
{
protocolSendPort(this, session->udpPort());
}
protocolSendPort(this, session->udpPort());
}
io->set_callbacks(canRead, didWrite, gotError, this);
@ -1485,7 +1481,7 @@ ReadResult process_peer_message(tr_peerMsgsImpl* msgs, uint8_t id, MessageReader
if (auto const dht_port = tr_port::from_host(hport); !std::empty(dht_port))
{
msgs->dht_port = dht_port;
msgs->session->addDhtNode(msgs->io->address(), msgs->dht_port);
msgs->session->maybe_add_dht_node(msgs->io->address(), msgs->dht_port);
}
}
break;

View File

@ -21,8 +21,8 @@ struct tr_variant;
#define SESSION_SETTINGS_FIELDS(V) \
V(TR_KEY_announce_ip, announce_ip, std::string, "", "") \
V(TR_KEY_announce_ip_enabled, announce_ip_enabled, bool, false, "") \
V(TR_KEY_bind_address_ipv4, bind_address_ipv4, std::string, "0.0.0.0", "") \
V(TR_KEY_bind_address_ipv6, bind_address_ipv6, std::string, "::", "") \
V(TR_KEY_bind_address_ipv4, bind_address_ipv4, std::string, "", "") \
V(TR_KEY_bind_address_ipv6, bind_address_ipv6, std::string, "", "") \
V(TR_KEY_blocklist_enabled, blocklist_enabled, bool, false, "") \
V(TR_KEY_blocklist_url, blocklist_url, std::string, "http://www.example.com/blocklist", "") \
V(TR_KEY_cache_size_mb, cache_size_mbytes, size_t, 4U, "") \

View File

@ -901,11 +901,11 @@ public:
void addTorrent(tr_torrent* tor);
void addDhtNode(tr_address const& addr, tr_port port)
void maybe_add_dht_node(tr_address const& addr, tr_port port)
{
if (dht_)
{
dht_->add_node(addr, port);
dht_->maybe_add_node(addr, port);
}
}

View File

@ -139,11 +139,9 @@ public:
{
tr_logAddDebug(fmt::format("Starting DHT on port {port}", fmt::arg("port", peer_port.host())));
// load up the bootstrap nodes
if (tr_sys_path_exists(state_filename_.c_str()))
{
std::tie(id_, bootstrap_queue_) = load_state(state_filename_);
}
// init state from scratch, or load from state file if it exists
std::tie(id_, bootstrap_queue_) = init_state(state_filename_);
get_nodes_from_bootstrap_file(tr_pathbuf{ mediator_.config_dir(), "/dht.bootstrap"sv }, bootstrap_queue_);
get_nodes_from_name("dht.transmissionbt.com", tr_port::from_host(6881), bootstrap_queue_);
bootstrap_timer_->start_single_shot(100ms);
@ -176,7 +174,7 @@ public:
tr_logAddTrace("Done uninitializing DHT");
}
void add_node(tr_address const& addr, tr_port port) override
void maybe_add_node(tr_address const& addr, tr_port port) override
{
if (addr.is_ipv4())
{
@ -207,7 +205,7 @@ public:
}
private:
[[nodiscard]] constexpr tr_socket_t udpSocket(int af) const noexcept
[[nodiscard]] constexpr tr_socket_t udp_socket(int af) const noexcept
{
switch (af)
{
@ -224,7 +222,7 @@ private:
[[nodiscard]] SwarmStatus swarm_status(int family, int* const setme_node_count = nullptr) const
{
if (udpSocket(family) == TR_BAD_SOCKET)
if (udp_socket(family) == TR_BAD_SOCKET)
{
if (setme_node_count != nullptr)
{
@ -309,7 +307,7 @@ private:
auto [address, port] = bootstrap_queue_.front();
bootstrap_queue_.pop_front();
add_node(address, port);
maybe_add_node(address, port);
++n_bootstrapped_;
bootstrap_timer_->start_single_shot(bootstrap_interval(n_bootstrapped_));
@ -413,12 +411,12 @@ private:
void save_state() const
{
auto constexpr MaxNodes = int{ 300 };
auto constexpr PortLen = size_t{ 2 };
auto constexpr CompactAddrLen = size_t{ 4 };
auto constexpr CompactLen = size_t{ CompactAddrLen + PortLen };
auto constexpr Compact6AddrLen = size_t{ 16 };
auto constexpr Compact6Len = size_t{ Compact6AddrLen + PortLen };
static auto constexpr MaxNodes = int{ 300 };
static auto constexpr PortLen = tr_port::CompactPortBytes;
static auto constexpr CompactAddrLen = tr_address::CompactAddrBytes[TR_AF_INET];
static auto constexpr CompactLen = tr_socket_address::CompactSockAddrBytes[TR_AF_INET];
static auto constexpr Compact6AddrLen = tr_address::CompactAddrBytes[TR_AF_INET6];
static auto constexpr Compact6Len = tr_socket_address::CompactSockAddrBytes[TR_AF_INET6];
auto sins4 = std::array<struct sockaddr_in, MaxNodes>{};
auto sins6 = std::array<struct sockaddr_in6, MaxNodes>{};
@ -464,55 +462,65 @@ private:
tr_variant_serde::benc().to_file(benc, state_filename_);
}
[[nodiscard]] static std::pair<Id, Nodes> load_state(std::string_view filename)
[[nodiscard]] static std::pair<Id, Nodes> init_state(std::string_view filename)
{
// Note that DHT ids need to be distributed uniformly,
// so it should be something truly random
auto id = tr_rand_obj<Id>();
if (!tr_sys_path_exists(std::data(filename)))
{
return { id, {} };
}
auto otop = tr_variant_serde::benc().parse_file(filename);
if (!otop)
{
return { id, {} };
}
static auto constexpr CompactLen = tr_socket_address::CompactSockAddrBytes[TR_AF_INET];
static auto constexpr Compact6Len = tr_socket_address::CompactSockAddrBytes[TR_AF_INET6];
auto& top = *otop;
auto nodes = Nodes{};
if (auto otop = tr_variant_serde::benc().parse_file(filename); otop)
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&top, TR_KEY_id, &sv) && std::size(sv) == std::size(id))
{
auto& top = *otop;
std::copy(std::begin(sv), std::end(sv), std::begin(id));
}
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&top, TR_KEY_id, &sv) && std::size(sv) == std::size(id))
size_t raw_len = 0U;
std::byte const* raw = nullptr;
if (tr_variantDictFindRaw(&top, TR_KEY_nodes, &raw, &raw_len) && raw_len % CompactLen == 0)
{
auto* walk = raw;
auto const* const end = raw + raw_len;
while (walk < end)
{
std::copy(std::begin(sv), std::end(sv), std::begin(id));
}
size_t raw_len = 0U;
std::byte const* raw = nullptr;
if (tr_variantDictFindRaw(&top, TR_KEY_nodes, &raw, &raw_len) && raw_len % 6 == 0)
{
auto* walk = raw;
auto const* const end = raw + raw_len;
while (walk < end)
{
auto addr = tr_address{};
auto port = tr_port{};
std::tie(addr, walk) = tr_address::from_compact_ipv4(walk);
std::tie(port, walk) = tr_port::from_compact(walk);
nodes.emplace_back(addr, port);
}
}
if (tr_variantDictFindRaw(&top, TR_KEY_nodes6, &raw, &raw_len) && raw_len % 18 == 0)
{
auto* walk = raw;
auto const* const end = raw + raw_len;
while (walk < end)
{
auto addr = tr_address{};
auto port = tr_port{};
std::tie(addr, walk) = tr_address::from_compact_ipv6(walk);
std::tie(port, walk) = tr_port::from_compact(walk);
nodes.emplace_back(addr, port);
}
auto addr = tr_address{};
auto port = tr_port{};
std::tie(addr, walk) = tr_address::from_compact_ipv4(walk);
std::tie(port, walk) = tr_port::from_compact(walk);
nodes.emplace_back(addr, port);
}
}
return std::make_pair(id, nodes);
if (tr_variantDictFindRaw(&top, TR_KEY_nodes6, &raw, &raw_len) && raw_len % Compact6Len == 0)
{
auto* walk = raw;
auto const* const end = raw + raw_len;
while (walk < end)
{
auto addr = tr_address{};
auto port = tr_port{};
std::tie(addr, walk) = tr_address::from_compact_ipv6(walk);
std::tie(port, walk) = tr_port::from_compact(walk);
nodes.emplace_back(addr, port);
}
}
return { id, std::move(nodes) };
}
///
@ -556,11 +564,9 @@ private:
hints.ai_protocol = 0;
hints.ai_flags = 0;
auto port_str = std::array<char, 16>{};
*fmt::format_to(std::data(port_str), "{:d}", port_in.host()) = '\0';
auto const port_str = fmt::format("{:d}", port_in.host());
addrinfo* info = nullptr;
if (int const rc = getaddrinfo(name, std::data(port_str), &hints, &info); rc != 0)
if (int const rc = getaddrinfo(name, port_str.c_str(), &hints, &info); rc != 0)
{
tr_logAddWarn(fmt::format(
_("Couldn't look up '{address}:{port}': {error} ({error_code})"),

View File

@ -114,6 +114,6 @@ public:
tr_socket_t udp6_socket);
virtual ~tr_dht() = default;
virtual void add_node(tr_address const& address, tr_port port) = 0;
virtual void maybe_add_node(tr_address const& address, tr_port port) = 0;
virtual void handle_message(unsigned char const* msg, size_t msglen, struct sockaddr* from, socklen_t fromlen) = 0;
};

View File

@ -619,7 +619,7 @@ TEST_F(DhtTest, pingsAddedNodes)
EXPECT_TRUE(addr.has_value());
assert(addr.has_value());
auto constexpr Port = tr_port::from_host(128);
dht->add_node(*addr, Port);
dht->maybe_add_node(*addr, Port);
ASSERT_EQ(1U, std::size(mediator.mock_dht_.pinged_));
EXPECT_EQ(addr, mediator.mock_dht_.pinged_.front().addrport.address());