mirror of
https://github.com/transmission/transmission.git
synced 2025-12-20 02:18:42 +00:00
refactor: tr_globalIPv6() returns a std::optional<tr_address> (#4464)
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user