diff --git a/libtransmission/announcer-http.cc b/libtransmission/announcer-http.cc index d779dddd6..81e162804 100644 --- a/libtransmission/announcer-http.cc +++ b/libtransmission/announcer-http.cc @@ -30,7 +30,7 @@ #include "crypto-utils.h" #include "error.h" #include "log.h" -#include "net.h" /* tr_globalIPv6() */ +#include "net.h" // for tr_globalIPv6() #include "peer-mgr.h" /* pex */ #include "quark.h" #include "torrent.h" @@ -42,9 +42,7 @@ using namespace std::literals; /**** -***** ***** ANNOUNCE -***** ****/ [[nodiscard]] static constexpr std::string_view get_event_string(tr_announce_request const& req) @@ -381,21 +379,17 @@ void announce_url_new(tr_urlbuf& url, tr_session const* session, tr_announce_req } } -[[nodiscard]] std::string format_ipv4_url_arg(tr_address const& addr) +[[nodiscard]] auto format_ipv4_url_arg(tr_address const& addr) { auto buf = std::array{}; auto display_name = addr.display_name(std::data(buf), std::size(buf)); return fmt::format("&ipv4={:s}", display_name); } -[[nodiscard]] std::string format_ipv6_url_arg(in6_addr const addr) +[[nodiscard]] auto format_ipv6_url_arg(tr_address const& addr) { - auto readable = std::array{}; - evutil_inet_ntop(AF_INET6, &addr, std::data(readable), std::size(readable)); - auto arg = "&ipv6="s; - tr_urlPercentEncode(std::back_inserter(arg), readable.data()); - + tr_urlPercentEncode(std::back_inserter(arg), addr.display_name()); return arg; } diff --git a/libtransmission/net.cc b/libtransmission/net.cc index 1a536b386..ce2531391 100644 --- a/libtransmission/net.cc +++ b/libtransmission/net.cc @@ -39,6 +39,8 @@ #include "utils.h" #include "variant.h" +using namespace std::literals; + #ifndef IN_MULTICAST #define IN_MULTICAST(a) (((a)&0xf0000000) == 0xe0000000) #endif @@ -435,153 +437,89 @@ void tr_netClose(tr_session* session, tr_socket_t sockfd) namespace global_ipv6_helpers { -/* Get the source address used for a given destination address. Since - there is no official interface to get this information, we create - a connected UDP socket (connected UDP... hmm...) and check its source - address. */ -[[nodiscard]] int get_source_address(struct sockaddr const* dst, socklen_t dst_len, struct sockaddr* src, socklen_t* src_len) +// Get the source address used for a given destination address. +// Since there is no official interface to get this information, +// we create a connected UDP socket (connected UDP... hmm...) +// and check its source address. +// +// Since it's a UDP socket, this doesn't actually send any packets +[[nodiscard]] std::optional get_source_address(tr_address const& dst_addr, tr_port dst_port) { - tr_socket_t const s = socket(dst->sa_family, SOCK_DGRAM, 0); - if (s == TR_BAD_SOCKET) - { - return -1; - } - - // since it's a UDP socket, this doesn't actually send any packets - if (connect(s, dst, dst_len) == 0 && getsockname(s, src, src_len) == 0) - { - evutil_closesocket(s); - return 0; - } - auto const save = errno; - evutil_closesocket(s); + + auto const [dst_ss, dst_sslen] = dst_addr.to_sockaddr(dst_port); + if (auto const sock = socket(dst_ss.ss_family, SOCK_DGRAM, 0); sock != TR_BAD_SOCKET) + { + if (connect(sock, reinterpret_cast(&dst_ss), dst_sslen) == 0) + { + auto src_ss = sockaddr_storage{}; + auto src_sslen = socklen_t{ sizeof(src_ss) }; + if (getsockname(sock, reinterpret_cast(&src_ss), &src_sslen) == 0) + { + if (auto const addrport = tr_address::from_sockaddr(reinterpret_cast(&src_ss)); addrport) + { + errno = save; + return addrport->first; + } + } + } + + evutil_closesocket(sock); + } + errno = save; - return -1; + return {}; } -[[nodiscard]] int global_address(int af, void* addr, int* addr_len) +[[nodiscard]] auto global_address(int af) { - auto ss = sockaddr_storage{}; - socklen_t sslen = sizeof(ss); - auto sin = sockaddr_in{}; - auto sin6 = sockaddr_in6{}; - struct sockaddr const* sa = nullptr; - socklen_t salen = 0; + // Pick some destination address to pretend to send a packet to + static auto constexpr DstIPv4 = "91.121.74.28"sv; + static auto constexpr DstIPv6 = "2001:1890:1112:1::20"sv; + auto const dst_addr = tr_address::from_string(af == AF_INET ? DstIPv4 : DstIPv6); + auto const dst_port = tr_port::fromHost(6969); - switch (af) - { - case AF_INET: - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - evutil_inet_pton(AF_INET, "91.121.74.28", &sin.sin_addr); - sin.sin_port = htons(6969); - sa = (struct sockaddr const*)&sin; - salen = sizeof(sin); - break; + // In order for address selection to work right, + // this should be a native IPv6 address, not Teredo or 6to4 + TR_ASSERT(dst_addr.has_value()); + TR_ASSERT(dst_addr->is_global_unicast_address()); - case AF_INET6: - memset(&sin6, 0, sizeof(sin6)); - sin6.sin6_family = AF_INET6; - /* In order for address selection to work right, this should be - a native IPv6 address, not Teredo or 6to4. */ - evutil_inet_pton(AF_INET6, "2001:1890:1112:1::20", &sin6.sin6_addr); - sin6.sin6_port = htons(6969); - sa = (struct sockaddr const*)&sin6; - salen = sizeof(sin6); - break; - - default: - return -1; - } - - if (int const rc = get_source_address(sa, salen, (struct sockaddr*)&ss, &sslen); rc < 0) - { - return -1; - } - - // We all hate NATs. - if (auto const tmp = tr_address::from_sockaddr(reinterpret_cast(&ss)); - !tmp || !tmp->first.is_global_unicast_address()) - { - return -1; - } - - switch (af) - { - case AF_INET: - if (*addr_len < 4) - { - return -1; - } - - memcpy(addr, &((struct sockaddr_in*)&ss)->sin_addr, 4); - *addr_len = 4; - return 1; - - case AF_INET6: - if (*addr_len < 16) - { - return -1; - } - - memcpy(addr, &((struct sockaddr_in6*)&ss)->sin6_addr, 16); - *addr_len = 16; - return 1; - - default: - return -1; - } + auto src_addr = get_source_address(*dst_addr, dst_port); + return src_addr && src_addr->is_global_unicast_address() ? *src_addr : std::optional{}; } } // namespace global_ipv6_helpers /* Return our global IPv6 address, with caching. */ -std::optional tr_globalIPv6(tr_session const* session) +std::optional tr_globalIPv6(tr_session const* session) { using namespace global_ipv6_helpers; - static auto ipv6 = in6_addr{}; - static time_t last_time = 0; - static bool have_ipv6 = false; - - /* Re-check every half hour */ - if (auto const now = tr_time(); last_time < now - 1800) + // recheck our cached value every half hour + static auto constexpr CacheSecs = 1800; + static auto cache_val = std::optional{}; + static auto cache_expires_at = time_t{}; + if (auto const now = tr_time(); cache_expires_at <= now) { - int addrlen = sizeof(ipv6); - int const rc = global_address(AF_INET6, &ipv6, &addrlen); - have_ipv6 = rc >= 0 && addrlen == sizeof(ipv6); - last_time = now; + cache_expires_at = now + CacheSecs; + cache_val = global_address(AF_INET6); } - if (!have_ipv6) + auto ret = cache_val; + + // check to see if the session is overriding this address + if (session != nullptr) { - return {}; // no IPv6 address at all + if (auto const [ipv6_bindaddr, is_default] = session->publicAddress(TR_AF_INET6); !is_default) + { + ret = ipv6_bindaddr; + } } - // Return the default address. - // This is useful for checking for connectivity in general. - if (session == nullptr) - { - return ipv6; - } - - // We have some sort of address. - // Now make sure that we return our bound address if non-default. - auto const [ipv6_bindaddr, is_default] = session->publicAddress(TR_AF_INET6); - if (!is_default) - { - // return this explicitly-bound address - ipv6 = ipv6_bindaddr.addr.addr6; - } - - return ipv6; + return ret; } -/*** -**** -**** -***/ +/// namespace is_valid_for_peers_helpers { diff --git a/libtransmission/net.h b/libtransmission/net.h index ae9976160..4a9cef9d2 100644 --- a/libtransmission/net.h +++ b/libtransmission/net.h @@ -368,4 +368,4 @@ void tr_netSetTOS(tr_socket_t sock, int tos, tr_address_type type); */ [[nodiscard]] std::string tr_net_strerror(int err); -[[nodiscard]] std::optional tr_globalIPv6(tr_session const* session); +[[nodiscard]] std::optional tr_globalIPv6(tr_session const* session = nullptr); diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index 0a9027c20..3e0910939 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -290,7 +290,7 @@ public: if (session->allowsDHT() && io->supports_dht()) { // only send PORT over IPv6 iff IPv6 DHT is running (BEP-32). - if (io->address().is_ipv4() || tr_globalIPv6(nullptr).has_value()) + if (io->address().is_ipv4() || tr_globalIPv6().has_value()) { protocolSendPort(this, session->udpPort()); } @@ -878,7 +878,6 @@ static void cancelAllRequestsToClient(tr_peerMsgsImpl* msgs) static void sendLtepHandshake(tr_peerMsgsImpl* msgs) { auto& out = msgs->outMessages; - auto const ipv6 = tr_globalIPv6(msgs->session); static tr_quark version_quark = 0; if (msgs->clientSentLtepHandshake) @@ -916,9 +915,10 @@ static void sendLtepHandshake(tr_peerMsgsImpl* msgs) tr_variantInitDict(&val, 8); tr_variantDictAddBool(&val, TR_KEY_e, msgs->session->encryptionMode() != TR_CLEAR_PREFERRED); - if (ipv6.has_value()) + if (auto const ipv6 = tr_globalIPv6(msgs->session); ipv6.has_value()) { - tr_variantDictAddRaw(&val, TR_KEY_ipv6, &*ipv6, sizeof(*ipv6)); + TR_ASSERT(ipv6->is_ipv6()); + tr_variantDictAddRaw(&val, TR_KEY_ipv6, &ipv6->addr.addr6, sizeof(ipv6->addr.addr6)); } // http://bittorrent.org/beps/bep_0009.html diff --git a/libtransmission/session.h b/libtransmission/session.h index a2b508546..fbd82067e 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -308,7 +308,7 @@ private: tr_socket_t udp6_socket_ = TR_BAD_SOCKET; libtransmission::evhelpers::event_unique_ptr udp4_event_; libtransmission::evhelpers::event_unique_ptr udp6_event_; - std::optional udp6_bound_; + std::optional udp6_bound_; void rebind_ipv6(bool); }; diff --git a/libtransmission/tr-udp.cc b/libtransmission/tr-udp.cc index 2ed24e451..0c2fbb652 100644 --- a/libtransmission/tr-udp.cc +++ b/libtransmission/tr-udp.cc @@ -97,7 +97,7 @@ void tr_session::tr_udp_core::set_socket_buffers() } } -static tr_socket_t rebind_ipv6_impl(in6_addr sin6_addr, tr_port port) +static tr_socket_t rebind_ipv6_impl(tr_address const& addr, tr_port port) { auto const sock = socket(PF_INET6, SOCK_DGRAM, 0); if (sock == TR_BAD_SOCKET) @@ -112,16 +112,15 @@ static tr_socket_t rebind_ipv6_impl(in6_addr sin6_addr, tr_port port) (void)setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&one), sizeof(one)); #endif - auto sin6 = sockaddr_in6{}; - sin6.sin6_family = AF_INET6; - sin6.sin6_addr = sin6_addr; - sin6.sin6_port = port.network(); - if (::bind(sock, reinterpret_cast(&sin6), sizeof(sin6)) == -1) + TR_ASSERT(addr.is_ipv6()); + auto const [ss, sslen] = addr.to_sockaddr(port); + if (::bind(sock, reinterpret_cast(&ss), sslen) == -1) { tr_netCloseSocket(sock); return TR_BAD_SOCKET; } + tr_logAddInfo("Bound UDP IPv6 address {:s}", addr.display_name(port)); return sock; } @@ -139,7 +138,7 @@ void tr_session::tr_udp_core::rebind_ipv6(bool force) return; } - if (udp6_bound_ && memcmp(&*udp6_bound_, &*ipv6, sizeof(*ipv6)) == 0) + if (udp6_bound_ && *udp6_bound_ == *ipv6) // unchanged { return; } @@ -154,7 +153,7 @@ void tr_session::tr_udp_core::rebind_ipv6(bool force) evutil_inet_ntop(AF_INET6, &*ipv6, std::data(ipv6_readable), std::size(ipv6_readable)); tr_logAddWarn(fmt::format( _("Couldn't rebind IPv6 socket {address}: {error} ({error_code})"), - fmt::arg("address", std::data(ipv6_readable)), + fmt::arg("address", ipv6->display_name()), fmt::arg("error", tr_strerror(error_code)), fmt::arg("error_code", error_code))); diff --git a/tests/libtransmission/net-test.cc b/tests/libtransmission/net-test.cc index cd6eb967f..d9b1c465e 100644 --- a/tests/libtransmission/net-test.cc +++ b/tests/libtransmission/net-test.cc @@ -176,3 +176,9 @@ TEST_F(NetTest, isGlobalUnicastAddress) EXPECT_EQ(expected, address->is_global_unicast_address()) << presentation; } } + +TEST_F(NetTest, globalIPv6) +{ + auto const addr = tr_globalIPv6(); + EXPECT_TRUE(!addr || addr->is_global_unicast_address()); +}