refactor: tr_globalIPv6() returns a std::optional<tr_address> (#4464)

This commit is contained in:
Charles Kerr
2022-12-25 07:55:51 -06:00
committed by GitHub
parent a3f561bcc0
commit ab9e971903
7 changed files with 84 additions and 147 deletions

View File

@@ -30,7 +30,7 @@
#include "crypto-utils.h" #include "crypto-utils.h"
#include "error.h" #include "error.h"
#include "log.h" #include "log.h"
#include "net.h" /* tr_globalIPv6() */ #include "net.h" // for tr_globalIPv6()
#include "peer-mgr.h" /* pex */ #include "peer-mgr.h" /* pex */
#include "quark.h" #include "quark.h"
#include "torrent.h" #include "torrent.h"
@@ -42,9 +42,7 @@
using namespace std::literals; using namespace std::literals;
/**** /****
*****
***** ANNOUNCE ***** ANNOUNCE
*****
****/ ****/
[[nodiscard]] static constexpr std::string_view get_event_string(tr_announce_request const& req) [[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<char, TR_ADDRSTRLEN>{}; auto buf = std::array<char, TR_ADDRSTRLEN>{};
auto display_name = addr.display_name(std::data(buf), std::size(buf)); auto display_name = addr.display_name(std::data(buf), std::size(buf));
return fmt::format("&ipv4={:s}", display_name); 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<char, INET6_ADDRSTRLEN>{};
evutil_inet_ntop(AF_INET6, &addr, std::data(readable), std::size(readable));
auto arg = "&ipv6="s; auto arg = "&ipv6="s;
tr_urlPercentEncode(std::back_inserter(arg), readable.data()); tr_urlPercentEncode(std::back_inserter(arg), addr.display_name());
return arg; return arg;
} }

View File

@@ -39,6 +39,8 @@
#include "utils.h" #include "utils.h"
#include "variant.h" #include "variant.h"
using namespace std::literals;
#ifndef IN_MULTICAST #ifndef IN_MULTICAST
#define IN_MULTICAST(a) (((a)&0xf0000000) == 0xe0000000) #define IN_MULTICAST(a) (((a)&0xf0000000) == 0xe0000000)
#endif #endif
@@ -435,153 +437,89 @@ void tr_netClose(tr_session* session, tr_socket_t sockfd)
namespace global_ipv6_helpers namespace global_ipv6_helpers
{ {
/* Get the source address used for a given destination address. Since // Get the source address used for a given destination address.
there is no official interface to get this information, we create // Since there is no official interface to get this information,
a connected UDP socket (connected UDP... hmm...) and check its source // we create a connected UDP socket (connected UDP... hmm...)
address. */ // 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) //
// Since it's a UDP socket, this doesn't actually send any packets
[[nodiscard]] std::optional<tr_address> 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; 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<sockaddr const*>(&dst_ss), dst_sslen) == 0)
{
auto src_ss = sockaddr_storage{};
auto src_sslen = socklen_t{ sizeof(src_ss) };
if (getsockname(sock, reinterpret_cast<sockaddr*>(&src_ss), &src_sslen) == 0)
{
if (auto const addrport = tr_address::from_sockaddr(reinterpret_cast<sockaddr*>(&src_ss)); addrport)
{
errno = save;
return addrport->first;
}
}
}
evutil_closesocket(sock);
}
errno = save; 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{}; // Pick some destination address to pretend to send a packet to
socklen_t sslen = sizeof(ss); static auto constexpr DstIPv4 = "91.121.74.28"sv;
auto sin = sockaddr_in{}; static auto constexpr DstIPv6 = "2001:1890:1112:1::20"sv;
auto sin6 = sockaddr_in6{}; auto const dst_addr = tr_address::from_string(af == AF_INET ? DstIPv4 : DstIPv6);
struct sockaddr const* sa = nullptr; auto const dst_port = tr_port::fromHost(6969);
socklen_t salen = 0;
switch (af) // In order for address selection to work right,
{ // this should be a native IPv6 address, not Teredo or 6to4
case AF_INET: TR_ASSERT(dst_addr.has_value());
memset(&sin, 0, sizeof(sin)); TR_ASSERT(dst_addr->is_global_unicast_address());
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;
case AF_INET6: auto src_addr = get_source_address(*dst_addr, dst_port);
memset(&sin6, 0, sizeof(sin6)); return src_addr && src_addr->is_global_unicast_address() ? *src_addr : std::optional<tr_address>{};
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<sockaddr const*>(&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;
}
} }
} // namespace global_ipv6_helpers } // namespace global_ipv6_helpers
/* Return our global IPv6 address, with caching. */ /* Return our global IPv6 address, with caching. */
std::optional<in6_addr> tr_globalIPv6(tr_session const* session) std::optional<tr_address> tr_globalIPv6(tr_session const* session)
{ {
using namespace global_ipv6_helpers; using namespace global_ipv6_helpers;
static auto ipv6 = in6_addr{}; // recheck our cached value every half hour
static time_t last_time = 0; static auto constexpr CacheSecs = 1800;
static bool have_ipv6 = false; static auto cache_val = std::optional<tr_address>{};
static auto cache_expires_at = time_t{};
/* Re-check every half hour */ if (auto const now = tr_time(); cache_expires_at <= now)
if (auto const now = tr_time(); last_time < now - 1800)
{ {
int addrlen = sizeof(ipv6); cache_expires_at = now + CacheSecs;
int const rc = global_address(AF_INET6, &ipv6, &addrlen); cache_val = global_address(AF_INET6);
have_ipv6 = rc >= 0 && addrlen == sizeof(ipv6);
last_time = now;
} }
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. return ret;
// 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;
} }
/*** ///
****
****
***/
namespace is_valid_for_peers_helpers namespace is_valid_for_peers_helpers
{ {

View File

@@ -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::string tr_net_strerror(int err);
[[nodiscard]] std::optional<in6_addr> tr_globalIPv6(tr_session const* session); [[nodiscard]] std::optional<tr_address> tr_globalIPv6(tr_session const* session = nullptr);

View File

@@ -290,7 +290,7 @@ public:
if (session->allowsDHT() && io->supports_dht()) if (session->allowsDHT() && io->supports_dht())
{ {
// only send PORT over IPv6 iff IPv6 DHT is running (BEP-32). // 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()); protocolSendPort(this, session->udpPort());
} }
@@ -878,7 +878,6 @@ static void cancelAllRequestsToClient(tr_peerMsgsImpl* msgs)
static void sendLtepHandshake(tr_peerMsgsImpl* msgs) static void sendLtepHandshake(tr_peerMsgsImpl* msgs)
{ {
auto& out = msgs->outMessages; auto& out = msgs->outMessages;
auto const ipv6 = tr_globalIPv6(msgs->session);
static tr_quark version_quark = 0; static tr_quark version_quark = 0;
if (msgs->clientSentLtepHandshake) if (msgs->clientSentLtepHandshake)
@@ -916,9 +915,10 @@ static void sendLtepHandshake(tr_peerMsgsImpl* msgs)
tr_variantInitDict(&val, 8); tr_variantInitDict(&val, 8);
tr_variantDictAddBool(&val, TR_KEY_e, msgs->session->encryptionMode() != TR_CLEAR_PREFERRED); 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 // http://bittorrent.org/beps/bep_0009.html

View File

@@ -308,7 +308,7 @@ private:
tr_socket_t udp6_socket_ = TR_BAD_SOCKET; tr_socket_t udp6_socket_ = TR_BAD_SOCKET;
libtransmission::evhelpers::event_unique_ptr udp4_event_; libtransmission::evhelpers::event_unique_ptr udp4_event_;
libtransmission::evhelpers::event_unique_ptr udp6_event_; libtransmission::evhelpers::event_unique_ptr udp6_event_;
std::optional<in6_addr> udp6_bound_; std::optional<tr_address> udp6_bound_;
void rebind_ipv6(bool); void rebind_ipv6(bool);
}; };

View File

@@ -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); auto const sock = socket(PF_INET6, SOCK_DGRAM, 0);
if (sock == TR_BAD_SOCKET) 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<char const*>(&one), sizeof(one)); (void)setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char const*>(&one), sizeof(one));
#endif #endif
auto sin6 = sockaddr_in6{}; TR_ASSERT(addr.is_ipv6());
sin6.sin6_family = AF_INET6; auto const [ss, sslen] = addr.to_sockaddr(port);
sin6.sin6_addr = sin6_addr; if (::bind(sock, reinterpret_cast<sockaddr const*>(&ss), sslen) == -1)
sin6.sin6_port = port.network();
if (::bind(sock, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)) == -1)
{ {
tr_netCloseSocket(sock); tr_netCloseSocket(sock);
return TR_BAD_SOCKET; return TR_BAD_SOCKET;
} }
tr_logAddInfo("Bound UDP IPv6 address {:s}", addr.display_name(port));
return sock; return sock;
} }
@@ -139,7 +138,7 @@ void tr_session::tr_udp_core::rebind_ipv6(bool force)
return; return;
} }
if (udp6_bound_ && memcmp(&*udp6_bound_, &*ipv6, sizeof(*ipv6)) == 0) if (udp6_bound_ && *udp6_bound_ == *ipv6) // unchanged
{ {
return; 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)); evutil_inet_ntop(AF_INET6, &*ipv6, std::data(ipv6_readable), std::size(ipv6_readable));
tr_logAddWarn(fmt::format( tr_logAddWarn(fmt::format(
_("Couldn't rebind IPv6 socket {address}: {error} ({error_code})"), _("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", tr_strerror(error_code)),
fmt::arg("error_code", error_code))); fmt::arg("error_code", error_code)));

View File

@@ -176,3 +176,9 @@ TEST_F(NetTest, isGlobalUnicastAddress)
EXPECT_EQ(expected, address->is_global_unicast_address()) << presentation; 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());
}