fix: caching a source address doesn't imply public internet connectivity (#7520)

This commit is contained in:
Yat Ho
2025-11-13 04:46:26 +08:00
committed by GitHub
parent 8d25484cdb
commit 4318a6f1ac
7 changed files with 34 additions and 17 deletions

View File

@@ -63,6 +63,15 @@ namespace global_source_ip_helpers
// and check its source address.
//
// Since it's a UDP socket, this doesn't actually send any packets
//
// N.B. Successfully obtaining a source address does not imply
// connectivity to the given destination address, since all connect()
// does is setting the default remote address for subsequent send() and
// recv() calls.
//
// Having said that, the connect() step is still needed because on Windows,
// calling getsockname() might not return what we want before calling
// connect() if we are binding to 0.0.0.0 or ::.
[[nodiscard]] std::optional<tr_address> get_source_address(
tr_address const& dst_addr,
tr_port dst_port,
@@ -221,7 +230,7 @@ bool tr_ip_cache::set_global_addr(tr_address const& addr_new) noexcept
void tr_ip_cache::update_addr(tr_address_type type) noexcept
{
update_source_addr(type);
if (global_source_addr(type))
if (source_addr(type))
{
update_global_addr(type);
}
@@ -272,11 +281,9 @@ void tr_ip_cache::update_source_addr(tr_address_type type) noexcept
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
auto const protocol = tr_ip_protocol_to_sv(type);
auto err = 0;
auto const& source_addr = get_global_source_address(bind_addr(type), err);
source_addr_checked_[type] = true;
if (source_addr)
if (auto const& source_addr = get_global_source_address(bind_addr(type), err); source_addr)
{
set_source_addr(*source_addr);
tr_logAddDebug(fmt::format(

View File

@@ -28,10 +28,10 @@
* This class caches 3 useful info:
* 1. Whether your machine supports the IP protocol
* 2. Source address used for global connections
* 3. Global address
* 3. Global address (IPv4 public address/IPv6 global unicast address)
*
* The idea is, if this class successfully cached a source address, that means
* you have connectivity to the public internet. And if the global address is
* your system is capable in that IP protocol. And if the global address is
* the same as the source address, then you are not behind a NAT.
*/
class tr_ip_cache
@@ -70,7 +70,7 @@ public:
return global_addr_[type];
}
[[nodiscard]] std::optional<tr_address> global_source_addr(tr_address_type type) const noexcept
[[nodiscard]] std::optional<tr_address> source_addr(tr_address_type type) const noexcept
{
auto const lock = std::shared_lock{ source_addr_mutex_[type] };
return source_addr_[type];

View File

@@ -257,6 +257,16 @@ struct tr_address
return is_ipv6() && IN6_IS_ADDR_LINKLOCAL(&addr.addr6);
}
[[nodiscard]] constexpr bool is_ipv4_loopback_address() const noexcept
{
return is_ipv4() && reinterpret_cast<uint8_t const*>(&addr.addr4.s_addr)[0] == 127U;
}
[[nodiscard]] constexpr bool is_ipv6_loopback_address() const noexcept
{
return is_ipv6() && IN6_IS_ADDR_LOOPBACK(&addr.addr6);
}
[[nodiscard]] std::optional<tr_address> from_ipv4_mapped() const noexcept;
tr_address_type type = NUM_TR_AF_INET_TYPES;

View File

@@ -447,7 +447,7 @@ tr_address tr_session::bind_address(tr_address_type type) const noexcept
// if user provided an address, use it.
// otherwise, if we can determine which one to use via global_source_address(ipv6) magic, use it.
// otherwise, use any_ipv6 (::).
auto const source_addr = global_source_address(type);
auto const source_addr = source_address(type);
auto const default_addr = source_addr && source_addr->is_global_unicast_address() ? *source_addr :
tr_address::any(TR_AF_INET6);
return tr_address::from_string(settings_.bind_address_ipv6).value_or(default_addr);

View File

@@ -1045,10 +1045,10 @@ public:
return ip_cache_.set_global_addr(addr);
}
[[nodiscard]] std::optional<tr_address> global_source_address(tr_address_type type) const noexcept
[[nodiscard]] std::optional<tr_address> source_address(tr_address_type type) const noexcept
{
TR_ASSERT(tr_address::is_valid(type));
return ip_cache_.global_source_addr(type);
return ip_cache_.source_addr(type);
}
[[nodiscard]] auto speed_limit(tr_direction const dir) const noexcept

View File

@@ -284,10 +284,10 @@ void tr_session::tr_udp_core::sendto(void const* buf, size_t buflen, struct sock
return;
}
else if (
addrport && addrport->address().is_global_unicast_address() &&
!session_.global_source_address(tr_af_to_ip_protocol(to->sa_family)))
addrport && !addrport->address().is_ipv4_loopback_address() && !addrport->address().is_ipv6_loopback_address() &&
!session_.source_address(tr_af_to_ip_protocol(to->sa_family)))
{
// don't try to connect to a global address if we don't have connectivity to public internet
// don't try to send if we don't have a route in this IP protocol
return;
}
else if (::sendto(sock, static_cast<char const*>(buf), buflen, 0, to, tolen) != -1)

View File

@@ -175,13 +175,13 @@ TEST_F(IPCacheTest, globalSourceIPv4)
ip_cache_ = std::make_unique<tr_ip_cache>(mediator);
ip_cache_->update_source_addr(TR_AF_INET);
auto const addr = ip_cache_->global_source_addr(TR_AF_INET);
auto const addr = ip_cache_->source_addr(TR_AF_INET);
if (!addr)
{
GTEST_SKIP() << "globalSourceIPv4 did not return an address, either:\n"
<< "1. globalSourceIPv4 is broken\n"
<< "2. Your system does not support IPv4\n"
<< "3. You don't have IPv4 connectivity to public internet";
<< "3. You don't have an IPv4 address to your interface";
}
EXPECT_TRUE(addr->is_ipv4());
}
@@ -199,13 +199,13 @@ TEST_F(IPCacheTest, globalSourceIPv6)
ip_cache_ = std::make_unique<tr_ip_cache>(mediator);
ip_cache_->update_source_addr(TR_AF_INET6);
auto const addr = ip_cache_->global_source_addr(TR_AF_INET6);
auto const addr = ip_cache_->source_addr(TR_AF_INET6);
if (!addr)
{
GTEST_SKIP() << "globalSourceIPv6 did not return an address, either:\n"
<< "1. globalSourceIPv6 is broken\n"
<< "2. Your system does not support IPv6\n"
<< "3. You don't have IPv6 connectivity to public internet";
<< "3. You don't have an IPv6 address to your interface";
}
EXPECT_TRUE(addr->is_ipv6());
}