merge bitcoin#27411: Restrict self-advertisements with privacy networks to avoid fingerprinting

The old `GetLocal()` has been moved to `masternode/node.cpp` due to its
use in determining a node's external address. We don't want the old
variant to be used otherwise so we'll move it out of `net.{cpp,h}` for
good measure.

Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
This commit is contained in:
Kittywhiskers Van Gogh 2024-09-13 08:46:28 +00:00
parent 1376289b11
commit 8320e0ca8e
No known key found for this signature in database
GPG Key ID: 30CD0C065E5C4AAD
8 changed files with 168 additions and 22 deletions

View File

@ -15,6 +15,42 @@
#include <validation.h>
#include <warnings.h>
namespace {
bool GetLocal(CService& addr, const CNetAddr* paddrPeer)
{
if (!fListen)
return false;
int nBestScore = -1;
{
LOCK(g_maplocalhost_mutex);
int nBestReachability = -1;
for (const auto& entry : mapLocalHost)
{
// For privacy reasons, don't advertise our privacy-network address
// to other networks and don't advertise our other-network address
// to privacy networks.
const Network our_net{entry.first.GetNetwork()};
const Network peers_net{paddrPeer->GetNetwork()};
if (our_net != peers_net &&
(our_net == NET_ONION || our_net == NET_I2P ||
peers_net == NET_ONION || peers_net == NET_I2P)) {
continue;
}
int nScore = entry.second.nScore;
int nReachability = entry.first.GetReachabilityFrom(*paddrPeer);
if (nReachability > nBestReachability || (nReachability == nBestReachability && nScore > nBestScore))
{
addr = CService(entry.first, entry.second.nPort);
nBestReachability = nReachability;
nBestScore = nScore;
}
}
}
return nBestScore >= 0;
}
} // anonymous namespace
CActiveMasternodeManager::CActiveMasternodeManager(const CBLSSecretKey& sk, CConnman& connman, const std::unique_ptr<CDeterministicMNManager>& dmnman) :
m_info(sk, sk.GetPublicKey()),
m_connman{connman},
@ -204,8 +240,7 @@ bool CActiveMasternodeManager::GetLocalAddress(CService& addrRet)
auto service = m_info.service;
m_connman.ForEachNodeContinueIf(CConnman::AllNodes, [&](CNode* pnode) {
empty = false;
if (pnode->addr.IsIPv4())
fFoundLocal = GetLocal(service, &pnode->addr) && IsValidNetAddr(service);
if (pnode->addr.IsIPv4()) fFoundLocal = GetLocal(service, *pnode) && IsValidNetAddr(service);
return !fFoundLocal;
});
// nothing and no live connections, can't do anything for now

View File

@ -186,7 +186,7 @@ uint16_t GetListenPort()
}
// find 'best' local address for a particular peer
bool GetLocal(CService& addr, const CNetAddr *paddrPeer)
bool GetLocal(CService& addr, const CNode& peer)
{
if (!fListen)
return false;
@ -197,8 +197,18 @@ bool GetLocal(CService& addr, const CNetAddr *paddrPeer)
LOCK(g_maplocalhost_mutex);
for (const auto& entry : mapLocalHost)
{
// For privacy reasons, don't advertise our privacy-network address
// to other networks and don't advertise our other-network address
// to privacy networks.
const Network our_net{entry.first.GetNetwork()};
const Network peers_net{peer.ConnectedThroughNetwork()};
if (our_net != peers_net &&
(our_net == NET_ONION || our_net == NET_I2P ||
peers_net == NET_ONION || peers_net == NET_I2P)) {
continue;
}
int nScore = entry.second.nScore;
int nReachability = entry.first.GetReachabilityFrom(paddrPeer);
int nReachability = entry.first.GetReachabilityFrom(peer.addr);
if (nReachability > nBestReachability || (nReachability == nBestReachability && nScore > nBestScore))
{
addr = CService(entry.first, entry.second.nPort);
@ -236,10 +246,10 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn)
// Otherwise, return the unroutable 0.0.0.0 but filled in with
// the normal parameters, since the IP may be changed to a useful
// one by discovery.
CService GetLocalAddress(const CNetAddr& addrPeer)
CService GetLocalAddress(const CNode& peer)
{
CService addr;
if (GetLocal(addr, &addrPeer)) {
if (GetLocal(addr, peer)) {
return addr;
}
return CService{CNetAddr(), GetListenPort()};
@ -262,7 +272,7 @@ bool IsPeerAddrLocalGood(CNode *pnode)
std::optional<CService> GetLocalAddrForPeer(CNode& node)
{
CService addrLocal{GetLocalAddress(node.addr)};
CService addrLocal{GetLocalAddress(node)};
// If discovery is enabled, sometimes give our peer the address it
// tells us that it sees us as in case it has a better idea of our
// address than we do.

View File

@ -199,8 +199,8 @@ bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE);
void RemoveLocal(const CService& addr);
bool SeenLocal(const CService& addr);
bool IsLocal(const CService& addr);
bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr);
CService GetLocalAddress(const CNetAddr& addrPeer);
bool GetLocal(CService& addr, const CNode& peer);
CService GetLocalAddress(const CNode& peer);
extern bool fDiscover;

View File

@ -3460,7 +3460,7 @@ void PeerManagerImpl::ProcessMessage(
// indicate to the peer that we will participate in addr relay.
if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload())
{
CAddress addr{GetLocalAddress(pfrom.addr), peer->m_our_services, (uint32_t)GetAdjustedTime()};
CAddress addr{GetLocalAddress(pfrom), peer->m_our_services, (uint32_t)GetAdjustedTime()};
FastRandomContext insecure_rand;
if (addr.IsRoutable())
{

View File

@ -742,19 +742,16 @@ uint64_t CNetAddr::GetHash() const
// private extensions to enum Network, only returned by GetExtNetwork,
// and only used in GetReachabilityFrom
static const int NET_UNKNOWN = NET_MAX + 0;
static const int NET_TEREDO = NET_MAX + 1;
int static GetExtNetwork(const CNetAddr *addr)
static const int NET_TEREDO = NET_MAX;
int static GetExtNetwork(const CNetAddr& addr)
{
if (addr == nullptr)
return NET_UNKNOWN;
if (addr->IsRFC4380())
if (addr.IsRFC4380())
return NET_TEREDO;
return addr->GetNetwork();
return addr.GetNetwork();
}
/** Calculates a metric for how reachable (*this) is from a given partner */
int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
int CNetAddr::GetReachabilityFrom(const CNetAddr& paddrPartner) const
{
enum Reachability {
REACH_UNREACHABLE,
@ -769,7 +766,7 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
if (!IsRoutable() || IsInternal())
return REACH_UNREACHABLE;
int ourNet = GetExtNetwork(this);
int ourNet = GetExtNetwork(*this);
int theirNet = GetExtNetwork(paddrPartner);
bool fTunnel = IsRFC3964() || IsRFC6052() || IsRFC6145();
@ -809,7 +806,6 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
case NET_IPV6: return REACH_IPV6_WEAK;
case NET_IPV4: return REACH_IPV4;
}
case NET_UNKNOWN:
case NET_UNROUTABLE:
default:
switch(ourNet) {

View File

@ -211,7 +211,7 @@ public:
bool HasLinkedIPv4() const;
std::vector<unsigned char> GetAddrBytes() const;
int GetReachabilityFrom(const CNetAddr *paddrPartner = nullptr) const;
int GetReachabilityFrom(const CNetAddr& paddrPartner) const;
explicit CNetAddr(const struct in6_addr& pipv6Addr, const uint32_t scope = 0);
bool GetIn6Addr(struct in6_addr* pipv6Addr) const;

View File

@ -83,7 +83,7 @@ FUZZ_TARGET(netaddress)
(void)service.ToStringAddrPort();
const CNetAddr other_net_addr = ConsumeNetAddr(fuzzed_data_provider);
(void)net_addr.GetReachabilityFrom(&other_net_addr);
(void)net_addr.GetReachabilityFrom(other_net_addr);
(void)sub_net.Match(other_net_addr);
const CService other_service{net_addr, fuzzed_data_provider.ConsumeIntegral<uint16_t>()};

View File

@ -908,4 +908,109 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
TestOnlyResetTimeData();
}
BOOST_AUTO_TEST_CASE(advertise_local_address)
{
auto CreatePeer = [](const CAddress& addr) {
return std::make_unique<CNode>(/*id=*/0,
/*sock=*/nullptr,
addr,
/*nKeyedNetGroupIn=*/0,
/*nLocalHostNonceIn=*/0,
CAddress{},
/*pszDest=*/std::string{},
ConnectionType::OUTBOUND_FULL_RELAY,
/*inbound_onion=*/false);
};
SetReachable(NET_CJDNS, true);
CAddress addr_ipv4{Lookup("1.2.3.4", 8333, false).value(), NODE_NONE};
BOOST_REQUIRE(addr_ipv4.IsValid());
BOOST_REQUIRE(addr_ipv4.IsIPv4());
CAddress addr_ipv6{Lookup("1122:3344:5566:7788:9900:aabb:ccdd:eeff", 8333, false).value(), NODE_NONE};
BOOST_REQUIRE(addr_ipv6.IsValid());
BOOST_REQUIRE(addr_ipv6.IsIPv6());
CAddress addr_ipv6_tunnel{Lookup("2002:3344:5566:7788:9900:aabb:ccdd:eeff", 8333, false).value(), NODE_NONE};
BOOST_REQUIRE(addr_ipv6_tunnel.IsValid());
BOOST_REQUIRE(addr_ipv6_tunnel.IsIPv6());
BOOST_REQUIRE(addr_ipv6_tunnel.IsRFC3964());
CAddress addr_teredo{Lookup("2001:0000:5566:7788:9900:aabb:ccdd:eeff", 8333, false).value(), NODE_NONE};
BOOST_REQUIRE(addr_teredo.IsValid());
BOOST_REQUIRE(addr_teredo.IsIPv6());
BOOST_REQUIRE(addr_teredo.IsRFC4380());
CAddress addr_onion;
BOOST_REQUIRE(addr_onion.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"));
BOOST_REQUIRE(addr_onion.IsValid());
BOOST_REQUIRE(addr_onion.IsTor());
CAddress addr_i2p;
BOOST_REQUIRE(addr_i2p.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p"));
BOOST_REQUIRE(addr_i2p.IsValid());
BOOST_REQUIRE(addr_i2p.IsI2P());
CService service_cjdns{Lookup("fc00:3344:5566:7788:9900:aabb:ccdd:eeff", 8333, false).value(), NODE_NONE};
CAddress addr_cjdns{MaybeFlipIPv6toCJDNS(service_cjdns), NODE_NONE};
BOOST_REQUIRE(addr_cjdns.IsValid());
BOOST_REQUIRE(addr_cjdns.IsCJDNS());
const auto peer_ipv4{CreatePeer(addr_ipv4)};
const auto peer_ipv6{CreatePeer(addr_ipv6)};
const auto peer_ipv6_tunnel{CreatePeer(addr_ipv6_tunnel)};
const auto peer_teredo{CreatePeer(addr_teredo)};
const auto peer_onion{CreatePeer(addr_onion)};
const auto peer_i2p{CreatePeer(addr_i2p)};
const auto peer_cjdns{CreatePeer(addr_cjdns)};
// one local clearnet address - advertise to all but privacy peers
AddLocal(addr_ipv4);
BOOST_CHECK(GetLocalAddress(*peer_ipv4) == addr_ipv4);
BOOST_CHECK(GetLocalAddress(*peer_ipv6) == addr_ipv4);
BOOST_CHECK(GetLocalAddress(*peer_ipv6_tunnel) == addr_ipv4);
BOOST_CHECK(GetLocalAddress(*peer_teredo) == addr_ipv4);
BOOST_CHECK(GetLocalAddress(*peer_cjdns) == addr_ipv4);
BOOST_CHECK(!GetLocalAddress(*peer_onion).IsValid());
BOOST_CHECK(!GetLocalAddress(*peer_i2p).IsValid());
RemoveLocal(addr_ipv4);
// local privacy addresses - don't advertise to clearnet peers
AddLocal(addr_onion);
AddLocal(addr_i2p);
BOOST_CHECK(!GetLocalAddress(*peer_ipv4).IsValid());
BOOST_CHECK(!GetLocalAddress(*peer_ipv6).IsValid());
BOOST_CHECK(!GetLocalAddress(*peer_ipv6_tunnel).IsValid());
BOOST_CHECK(!GetLocalAddress(*peer_teredo).IsValid());
BOOST_CHECK(!GetLocalAddress(*peer_cjdns).IsValid());
BOOST_CHECK(GetLocalAddress(*peer_onion) == addr_onion);
BOOST_CHECK(GetLocalAddress(*peer_i2p) == addr_i2p);
RemoveLocal(addr_onion);
RemoveLocal(addr_i2p);
// local addresses from all networks
AddLocal(addr_ipv4);
AddLocal(addr_ipv6);
AddLocal(addr_ipv6_tunnel);
AddLocal(addr_teredo);
AddLocal(addr_onion);
AddLocal(addr_i2p);
AddLocal(addr_cjdns);
BOOST_CHECK(GetLocalAddress(*peer_ipv4) == addr_ipv4);
BOOST_CHECK(GetLocalAddress(*peer_ipv6) == addr_ipv6);
BOOST_CHECK(GetLocalAddress(*peer_ipv6_tunnel) == addr_ipv6);
BOOST_CHECK(GetLocalAddress(*peer_teredo) == addr_ipv4);
BOOST_CHECK(GetLocalAddress(*peer_onion) == addr_onion);
BOOST_CHECK(GetLocalAddress(*peer_i2p) == addr_i2p);
BOOST_CHECK(GetLocalAddress(*peer_cjdns) == addr_cjdns);
RemoveLocal(addr_ipv4);
RemoveLocal(addr_ipv6);
RemoveLocal(addr_ipv6_tunnel);
RemoveLocal(addr_teredo);
RemoveLocal(addr_onion);
RemoveLocal(addr_i2p);
RemoveLocal(addr_cjdns);
}
BOOST_AUTO_TEST_SUITE_END()