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. // and check its source address.
// //
// Since it's a UDP socket, this doesn't actually send any packets // 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( [[nodiscard]] std::optional<tr_address> get_source_address(
tr_address const& dst_addr, tr_address const& dst_addr,
tr_port dst_port, 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 void tr_ip_cache::update_addr(tr_address_type type) noexcept
{ {
update_source_addr(type); update_source_addr(type);
if (global_source_addr(type)) if (source_addr(type))
{ {
update_global_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); TR_ASSERT(is_updating_[type] == is_updating_t::YES);
auto const protocol = tr_ip_protocol_to_sv(type); auto const protocol = tr_ip_protocol_to_sv(type);
auto err = 0; auto err = 0;
auto const& source_addr = get_global_source_address(bind_addr(type), err);
source_addr_checked_[type] = true; 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); set_source_addr(*source_addr);
tr_logAddDebug(fmt::format( tr_logAddDebug(fmt::format(

View File

@@ -28,10 +28,10 @@
* This class caches 3 useful info: * This class caches 3 useful info:
* 1. Whether your machine supports the IP protocol * 1. Whether your machine supports the IP protocol
* 2. Source address used for global connections * 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 * 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. * the same as the source address, then you are not behind a NAT.
*/ */
class tr_ip_cache class tr_ip_cache
@@ -70,7 +70,7 @@ public:
return global_addr_[type]; 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] }; auto const lock = std::shared_lock{ source_addr_mutex_[type] };
return source_addr_[type]; return source_addr_[type];

View File

@@ -257,6 +257,16 @@ struct tr_address
return is_ipv6() && IN6_IS_ADDR_LINKLOCAL(&addr.addr6); 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; [[nodiscard]] std::optional<tr_address> from_ipv4_mapped() const noexcept;
tr_address_type type = NUM_TR_AF_INET_TYPES; 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. // 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, if we can determine which one to use via global_source_address(ipv6) magic, use it.
// otherwise, use any_ipv6 (::). // 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 : auto const default_addr = source_addr && source_addr->is_global_unicast_address() ? *source_addr :
tr_address::any(TR_AF_INET6); tr_address::any(TR_AF_INET6);
return tr_address::from_string(settings_.bind_address_ipv6).value_or(default_addr); 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); 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)); 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 [[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; return;
} }
else if ( else if (
addrport && addrport->address().is_global_unicast_address() && addrport && !addrport->address().is_ipv4_loopback_address() && !addrport->address().is_ipv6_loopback_address() &&
!session_.global_source_address(tr_af_to_ip_protocol(to->sa_family))) !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; return;
} }
else if (::sendto(sock, static_cast<char const*>(buf), buflen, 0, to, tolen) != -1) 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_ = std::make_unique<tr_ip_cache>(mediator);
ip_cache_->update_source_addr(TR_AF_INET); 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) if (!addr)
{ {
GTEST_SKIP() << "globalSourceIPv4 did not return an address, either:\n" GTEST_SKIP() << "globalSourceIPv4 did not return an address, either:\n"
<< "1. globalSourceIPv4 is broken\n" << "1. globalSourceIPv4 is broken\n"
<< "2. Your system does not support IPv4\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()); EXPECT_TRUE(addr->is_ipv4());
} }
@@ -199,13 +199,13 @@ TEST_F(IPCacheTest, globalSourceIPv6)
ip_cache_ = std::make_unique<tr_ip_cache>(mediator); ip_cache_ = std::make_unique<tr_ip_cache>(mediator);
ip_cache_->update_source_addr(TR_AF_INET6); 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) if (!addr)
{ {
GTEST_SKIP() << "globalSourceIPv6 did not return an address, either:\n" GTEST_SKIP() << "globalSourceIPv6 did not return an address, either:\n"
<< "1. globalSourceIPv6 is broken\n" << "1. globalSourceIPv6 is broken\n"
<< "2. Your system does not support IPv6\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()); EXPECT_TRUE(addr->is_ipv6());
} }