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 "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<char, TR_ADDRSTRLEN>{};
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<char, INET6_ADDRSTRLEN>{};
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;
}

View File

@@ -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<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;
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 -1;
return addrport->first;
}
}
}
evutil_closesocket(sock);
}
errno = save;
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<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;
}
auto src_addr = get_source_address(*dst_addr, dst_port);
return src_addr && src_addr->is_global_unicast_address() ? *src_addr : std::optional<tr_address>{};
}
} // namespace global_ipv6_helpers
/* 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;
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<tr_address>{};
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
{

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::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())
{
// 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

View File

@@ -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<in6_addr> udp6_bound_;
std::optional<tr_address> udp6_bound_;
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);
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));
#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<struct sockaddr*>(&sin6), sizeof(sin6)) == -1)
TR_ASSERT(addr.is_ipv6());
auto const [ss, sslen] = addr.to_sockaddr(port);
if (::bind(sock, reinterpret_cast<sockaddr const*>(&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)));

View File

@@ -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());
}