diff --git a/libtransmission/announcer-common.h b/libtransmission/announcer-common.h index 38aa71a5b..1df6eb716 100644 --- a/libtransmission/announcer-common.h +++ b/libtransmission/announcer-common.h @@ -214,6 +214,10 @@ struct tr_announce_response /* key generated by and returned from an http tracker. * if this is provided, subsequent http announces must include this. */ std::string tracker_id; + + /* tracker extension that returns the client's public IP address. + * https://www.bittorrent.org/beps/bep_0024.html */ + std::optional external_ip; }; using tr_announce_response_func = void (*)(tr_announce_response const* response, void* userdata); diff --git a/libtransmission/announcer-http.cc b/libtransmission/announcer-http.cc index 4494a86fb..228b8c489 100644 --- a/libtransmission/announcer-http.cc +++ b/libtransmission/announcer-http.cc @@ -253,6 +253,10 @@ void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std:: { // unused } + else if (key == "external ip"sv && std::size(value) == 4) + { + response_.external_ip = tr_address::from_4byte_ipv4(value); + } else if (!tr_error_is_set(context.error)) { tr_error_set(context.error, EINVAL, tr_strvJoin("unexpected str: key["sv, key, "] value["sv, value, "]"sv)); @@ -402,6 +406,14 @@ void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::stri { response_.rows[*row_].leechers = value; } + else if (row_ && key == "downloaders"sv) + { + response_.rows[*row_].downloaders = value; + } + else if (key == "min_request_interval"sv) + { + response_.min_request_interval = value; + } else if (!tr_error_is_set(context.error)) { auto const errmsg = tr_strvJoin("unexpected int: key["sv, key, "] value["sv, std::to_string(value), "]"sv); diff --git a/libtransmission/announcer.cc b/libtransmission/announcer.cc index 73b74c154..69aa96c0a 100644 --- a/libtransmission/announcer.cc +++ b/libtransmission/announcer.cc @@ -995,6 +995,11 @@ static void on_announce_done(tr_announce_response const* response, void* vdata) tier->isAnnouncing = false; tier->manualAnnounceAllowedAt = now + tier->announceMinIntervalSec; + if (response->external_ip) + { + data->session->setExternalIP(*response->external_ip); + } + if (!response->did_connect) { on_announce_error(tier, _("Could not connect to tracker"), event); diff --git a/libtransmission/net.cc b/libtransmission/net.cc index b3b2e220b..e20c47b88 100644 --- a/libtransmission/net.cc +++ b/libtransmission/net.cc @@ -115,7 +115,7 @@ bool tr_address_from_string(tr_address* dst, std::string_view src) { // inet_pton() requires zero-terminated strings, // so make a zero-terminated copy here on the stack. - auto buf = std::array{}; + auto buf = std::array{}; if (std::size(src) >= std::size(buf)) { // shouldn't ever be that large; malformed address @@ -127,6 +127,28 @@ bool tr_address_from_string(tr_address* dst, std::string_view src) return tr_address_from_string(dst, std::data(buf)); } +std::optional tr_address::from_string(std::string_view str) +{ + auto addr = tr_address{}; + + if (!tr_address_from_string(&addr, str)) + { + return {}; + } + + return addr; +} + +tr_address tr_address::from_4byte_ipv4(std::string_view in) +{ + TR_ASSERT(std::size(in) == 4); + + auto addr = tr_address{}; + addr.type = TR_AF_INET; + std::copy_n(std::begin(in), 4, reinterpret_cast(&addr.addr)); + return addr; +} + /* * Compare two tr_address structures. * Returns: diff --git a/libtransmission/net.h b/libtransmission/net.h index 9328a6dd5..0359ade28 100644 --- a/libtransmission/net.h +++ b/libtransmission/net.h @@ -68,14 +68,37 @@ enum tr_address_type NUM_TR_AF_INET_TYPES }; +struct tr_address; + +int tr_address_compare(tr_address const* a, tr_address const* b); + struct tr_address { + static tr_address from_4byte_ipv4(std::string_view in); + + static std::optional from_string(std::string_view str); + tr_address_type type; union { struct in6_addr addr6; struct in_addr addr4; } addr; + + bool operator==(tr_address const& that) const + { + return tr_address_compare(this, &that) == 0; + } + + bool operator<(tr_address const& that) const + { + return tr_address_compare(this, &that) < 0; + } + + bool operator>(tr_address const& that) const + { + return tr_address_compare(this, &that) > 0; + } }; extern tr_address const tr_inaddr_any; @@ -93,8 +116,6 @@ bool tr_address_from_string(tr_address* dst, std::string_view src); bool tr_address_from_sockaddr_storage(tr_address* setme, tr_port* port, struct sockaddr_storage const* src); -int tr_address_compare(tr_address const* a, tr_address const* b); - bool tr_address_is_valid_for_peers(tr_address const* addr, tr_port port); constexpr bool tr_address_is_valid(tr_address const* a) diff --git a/libtransmission/session.h b/libtransmission/session.h index f59561d4e..db3919360 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -239,6 +239,16 @@ public: bool useRpcWhitelist() const; + auto externalIP() const + { + return external_ip_; + } + + void setExternalIP(tr_address external_ip) + { + external_ip_ = external_ip; + } + // peer networking std::string const& peerCongestionAlgorithm() const @@ -419,6 +429,7 @@ private: std::string default_trackers_str_; std::string incomplete_dir_; std::string peer_congestion_algorithm_; + std::optional external_ip_; std::array scripts_enabled_; bool blocklist_enabled_ = false; diff --git a/tests/libtransmission/announcer-test.cc b/tests/libtransmission/announcer-test.cc index fd17162a8..ec660db74 100644 --- a/tests/libtransmission/announcer-test.cc +++ b/tests/libtransmission/announcer-test.cc @@ -32,6 +32,7 @@ TEST_F(AnnouncerTest, parseHttpAnnounceResponseNoPeers) "8:interval" "i1803e" "12:min interval" "i1800e" "5:peers" "0:" + "11:external ip" "4:\x01\x02\x03\x04" "e"sv; // clang-format on @@ -42,6 +43,7 @@ TEST_F(AnnouncerTest, parseHttpAnnounceResponseNoPeers) EXPECT_EQ(3, response.seeders); EXPECT_EQ(0, response.leechers); EXPECT_EQ(2, response.downloads); + EXPECT_EQ(*tr_address::from_string("1.2.3.4"), response.external_ip); EXPECT_EQ(0, std::size(response.pex)); EXPECT_EQ(0, std::size(response.pex6)); EXPECT_EQ(""sv, response.errmsg);