Skip to content

Commit 05ad4de

Browse files
committed
Merge bitcoin/bitcoin#27411: p2p: Restrict self-advertisements with privacy networks to avoid fingerprinting
e7cf865 test: add unit test for local address advertising (Martin Zumsande) f4754b9 net: restrict self-advertisements with privacy networks (Martin Zumsande) e4d541c net, refactor: pass reference for peer address in GetReachabilityFrom (Martin Zumsande) 62d73f5 net, refactor: pass CNode instead of CNetAddr to GetLocalAddress (Martin Zumsande) Pull request description: The current logic for self-advertisements works such that we detect as many local addresses as we can, and then, using the scoring matrix from `CNetAddr::GetReachabilityFrom()`, self-advertise with the address that fits best to our peer. It is in general not hard for our peers to distinguish our self-advertisements from other addrs we send them, because we self-advertise every ~24h and because the first addr we send over a connection is likely our self-advertisement. `GetReachabilityFrom()` currently only takes into account actual reachability, but not whether we'd _want_ to announce our identity for one network to peers from other networks, which is not straightforward in connection with privacy networks. While the general approach is to prefer self-advertising with the address for the network our peer is on, there are several special situations in which we don't have one, and as a result could allow self-advertise other local addresses, for example: A) We run i2p and clearnet, use `-i2pacceptincoming=0` (so we have no local i2p address), and we have a local ipv4 address. In this case, we'd advertise the ipv4 address to our outbound i2p peers. B) Our `-discover` logic cannot detect any local clearnet addresses in our network environment, but we are actually reachable over clearnet. If we ran bitcoind clearnet-only, we'd always advertise the address our peer sees us with instead, and could get inbound peers this way. Now, if we also have an onion service running (but aren't using tor as a proxy for clearnet connections), we could advertise our onion address to clearnet peers, so that they would be able to connect our clearnet and onion identities. This PR tries to avoid these situations by 1.) never advertising our local Tor or I2P address to peers from other networks. 2.) never advertising local addresses from non-anonymity networks to peers from Tor or I2P Note that this affects only our own self-advertisements, the rules to forward other people's addrs are not changed. [Edit] after Initial [discussion](bitcoin/bitcoin#27411 (comment)): CJDNS is not being treated like Tor and I2P at least for now, because it has different privacy properties and for the practical reason that it has still very few bitcoin nodes. ACKs for top commit: achow101: ACK e7cf865 vasild: ACK e7cf865 luke-jr: utACK e7cf865 Tree-SHA512: 3db8415dea6f82223d11a23bd6cbb3b8cf68831321280e926034a1f110cbe22562570013925f6fa20d8f08e41d0202fd69c733d9f16217318a660d2a1a21b795
2 parents b479474 + e7cf865 commit 05ad4de

File tree

6 files changed

+130
-19
lines changed

6 files changed

+130
-19
lines changed

src/net.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ uint16_t GetListenPort()
146146
}
147147

148148
// find 'best' local address for a particular peer
149-
bool GetLocal(CService& addr, const CNetAddr *paddrPeer)
149+
bool GetLocal(CService& addr, const CNode& peer)
150150
{
151151
if (!fListen)
152152
return false;
@@ -157,8 +157,18 @@ bool GetLocal(CService& addr, const CNetAddr *paddrPeer)
157157
LOCK(g_maplocalhost_mutex);
158158
for (const auto& entry : mapLocalHost)
159159
{
160+
// For privacy reasons, don't advertise our privacy-network address
161+
// to other networks and don't advertise our other-network address
162+
// to privacy networks.
163+
const Network our_net{entry.first.GetNetwork()};
164+
const Network peers_net{peer.ConnectedThroughNetwork()};
165+
if (our_net != peers_net &&
166+
(our_net == NET_ONION || our_net == NET_I2P ||
167+
peers_net == NET_ONION || peers_net == NET_I2P)) {
168+
continue;
169+
}
160170
int nScore = entry.second.nScore;
161-
int nReachability = entry.first.GetReachabilityFrom(paddrPeer);
171+
int nReachability = entry.first.GetReachabilityFrom(peer.addr);
162172
if (nReachability > nBestReachability || (nReachability == nBestReachability && nScore > nBestScore))
163173
{
164174
addr = CService(entry.first, entry.second.nPort);
@@ -196,10 +206,10 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn)
196206
// Otherwise, return the unroutable 0.0.0.0 but filled in with
197207
// the normal parameters, since the IP may be changed to a useful
198208
// one by discovery.
199-
CService GetLocalAddress(const CNetAddr& addrPeer)
209+
CService GetLocalAddress(const CNode& peer)
200210
{
201211
CService addr;
202-
if (GetLocal(addr, &addrPeer)) {
212+
if (GetLocal(addr, peer)) {
203213
return addr;
204214
}
205215
return CService{CNetAddr(), GetListenPort()};
@@ -222,7 +232,7 @@ bool IsPeerAddrLocalGood(CNode *pnode)
222232

223233
std::optional<CService> GetLocalAddrForPeer(CNode& node)
224234
{
225-
CService addrLocal{GetLocalAddress(node.addr)};
235+
CService addrLocal{GetLocalAddress(node)};
226236
if (gArgs.GetBoolArg("-addrmantest", false)) {
227237
// use IPv4 loopback during addrmantest
228238
addrLocal = CService(LookupNumeric("127.0.0.1", GetListenPort()));

src/net.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE);
164164
void RemoveLocal(const CService& addr);
165165
bool SeenLocal(const CService& addr);
166166
bool IsLocal(const CService& addr);
167-
bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr);
168-
CService GetLocalAddress(const CNetAddr& addrPeer);
167+
bool GetLocal(CService& addr, const CNode& peer);
168+
CService GetLocalAddress(const CNode& peer);
169169
CService MaybeFlipIPv6toCJDNS(const CService& service);
170170

171171

src/netaddress.cpp

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -723,19 +723,16 @@ std::vector<unsigned char> CNetAddr::GetAddrBytes() const
723723

724724
// private extensions to enum Network, only returned by GetExtNetwork,
725725
// and only used in GetReachabilityFrom
726-
static const int NET_UNKNOWN = NET_MAX + 0;
727-
static const int NET_TEREDO = NET_MAX + 1;
728-
int static GetExtNetwork(const CNetAddr *addr)
726+
static const int NET_TEREDO = NET_MAX;
727+
int static GetExtNetwork(const CNetAddr& addr)
729728
{
730-
if (addr == nullptr)
731-
return NET_UNKNOWN;
732-
if (addr->IsRFC4380())
729+
if (addr.IsRFC4380())
733730
return NET_TEREDO;
734-
return addr->GetNetwork();
731+
return addr.GetNetwork();
735732
}
736733

737734
/** Calculates a metric for how reachable (*this) is from a given partner */
738-
int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
735+
int CNetAddr::GetReachabilityFrom(const CNetAddr& paddrPartner) const
739736
{
740737
enum Reachability {
741738
REACH_UNREACHABLE,
@@ -750,7 +747,7 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
750747
if (!IsRoutable() || IsInternal())
751748
return REACH_UNREACHABLE;
752749

753-
int ourNet = GetExtNetwork(this);
750+
int ourNet = GetExtNetwork(*this);
754751
int theirNet = GetExtNetwork(paddrPartner);
755752
bool fTunnel = IsRFC3964() || IsRFC6052() || IsRFC6145();
756753

@@ -790,7 +787,6 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
790787
case NET_IPV6: return REACH_IPV6_WEAK;
791788
case NET_IPV4: return REACH_IPV4;
792789
}
793-
case NET_UNKNOWN:
794790
case NET_UNROUTABLE:
795791
default:
796792
switch(ourNet) {

src/netaddress.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ class CNetAddr
203203
bool HasLinkedIPv4() const;
204204

205205
std::vector<unsigned char> GetAddrBytes() const;
206-
int GetReachabilityFrom(const CNetAddr* paddrPartner = nullptr) const;
206+
int GetReachabilityFrom(const CNetAddr& paddrPartner) const;
207207

208208
explicit CNetAddr(const struct in6_addr& pipv6Addr, const uint32_t scope = 0);
209209
bool GetIn6Addr(struct in6_addr* pipv6Addr) const;

src/test/fuzz/netaddress.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ FUZZ_TARGET(netaddress)
8484
(void)CServiceHash(0, 0)(service);
8585

8686
const CNetAddr other_net_addr = ConsumeNetAddr(fuzzed_data_provider);
87-
(void)net_addr.GetReachabilityFrom(&other_net_addr);
87+
(void)net_addr.GetReachabilityFrom(other_net_addr);
8888
(void)sub_net.Match(other_net_addr);
8989

9090
const CService other_service{net_addr, fuzzed_data_provider.ConsumeIntegral<uint16_t>()};

src/test/net_tests.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,4 +904,109 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
904904
TestOnlyResetTimeData();
905905
}
906906

907+
908+
BOOST_AUTO_TEST_CASE(advertise_local_address)
909+
{
910+
auto CreatePeer = [](const CAddress& addr) {
911+
return std::make_unique<CNode>(/*id=*/0,
912+
/*sock=*/nullptr,
913+
addr,
914+
/*nKeyedNetGroupIn=*/0,
915+
/*nLocalHostNonceIn=*/0,
916+
CAddress{},
917+
/*pszDest=*/std::string{},
918+
ConnectionType::OUTBOUND_FULL_RELAY,
919+
/*inbound_onion=*/false);
920+
};
921+
SetReachable(NET_CJDNS, true);
922+
923+
CAddress addr_ipv4{Lookup("1.2.3.4", 8333, false).value(), NODE_NONE};
924+
BOOST_REQUIRE(addr_ipv4.IsValid());
925+
BOOST_REQUIRE(addr_ipv4.IsIPv4());
926+
927+
CAddress addr_ipv6{Lookup("1122:3344:5566:7788:9900:aabb:ccdd:eeff", 8333, false).value(), NODE_NONE};
928+
BOOST_REQUIRE(addr_ipv6.IsValid());
929+
BOOST_REQUIRE(addr_ipv6.IsIPv6());
930+
931+
CAddress addr_ipv6_tunnel{Lookup("2002:3344:5566:7788:9900:aabb:ccdd:eeff", 8333, false).value(), NODE_NONE};
932+
BOOST_REQUIRE(addr_ipv6_tunnel.IsValid());
933+
BOOST_REQUIRE(addr_ipv6_tunnel.IsIPv6());
934+
BOOST_REQUIRE(addr_ipv6_tunnel.IsRFC3964());
935+
936+
CAddress addr_teredo{Lookup("2001:0000:5566:7788:9900:aabb:ccdd:eeff", 8333, false).value(), NODE_NONE};
937+
BOOST_REQUIRE(addr_teredo.IsValid());
938+
BOOST_REQUIRE(addr_teredo.IsIPv6());
939+
BOOST_REQUIRE(addr_teredo.IsRFC4380());
940+
941+
CAddress addr_onion;
942+
BOOST_REQUIRE(addr_onion.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"));
943+
BOOST_REQUIRE(addr_onion.IsValid());
944+
BOOST_REQUIRE(addr_onion.IsTor());
945+
946+
CAddress addr_i2p;
947+
BOOST_REQUIRE(addr_i2p.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p"));
948+
BOOST_REQUIRE(addr_i2p.IsValid());
949+
BOOST_REQUIRE(addr_i2p.IsI2P());
950+
951+
CService service_cjdns{Lookup("fc00:3344:5566:7788:9900:aabb:ccdd:eeff", 8333, false).value(), NODE_NONE};
952+
CAddress addr_cjdns{MaybeFlipIPv6toCJDNS(service_cjdns), NODE_NONE};
953+
BOOST_REQUIRE(addr_cjdns.IsValid());
954+
BOOST_REQUIRE(addr_cjdns.IsCJDNS());
955+
956+
const auto peer_ipv4{CreatePeer(addr_ipv4)};
957+
const auto peer_ipv6{CreatePeer(addr_ipv6)};
958+
const auto peer_ipv6_tunnel{CreatePeer(addr_ipv6_tunnel)};
959+
const auto peer_teredo{CreatePeer(addr_teredo)};
960+
const auto peer_onion{CreatePeer(addr_onion)};
961+
const auto peer_i2p{CreatePeer(addr_i2p)};
962+
const auto peer_cjdns{CreatePeer(addr_cjdns)};
963+
964+
// one local clearnet address - advertise to all but privacy peers
965+
AddLocal(addr_ipv4);
966+
BOOST_CHECK(GetLocalAddress(*peer_ipv4) == addr_ipv4);
967+
BOOST_CHECK(GetLocalAddress(*peer_ipv6) == addr_ipv4);
968+
BOOST_CHECK(GetLocalAddress(*peer_ipv6_tunnel) == addr_ipv4);
969+
BOOST_CHECK(GetLocalAddress(*peer_teredo) == addr_ipv4);
970+
BOOST_CHECK(GetLocalAddress(*peer_cjdns) == addr_ipv4);
971+
BOOST_CHECK(!GetLocalAddress(*peer_onion).IsValid());
972+
BOOST_CHECK(!GetLocalAddress(*peer_i2p).IsValid());
973+
RemoveLocal(addr_ipv4);
974+
975+
// local privacy addresses - don't advertise to clearnet peers
976+
AddLocal(addr_onion);
977+
AddLocal(addr_i2p);
978+
BOOST_CHECK(!GetLocalAddress(*peer_ipv4).IsValid());
979+
BOOST_CHECK(!GetLocalAddress(*peer_ipv6).IsValid());
980+
BOOST_CHECK(!GetLocalAddress(*peer_ipv6_tunnel).IsValid());
981+
BOOST_CHECK(!GetLocalAddress(*peer_teredo).IsValid());
982+
BOOST_CHECK(!GetLocalAddress(*peer_cjdns).IsValid());
983+
BOOST_CHECK(GetLocalAddress(*peer_onion) == addr_onion);
984+
BOOST_CHECK(GetLocalAddress(*peer_i2p) == addr_i2p);
985+
RemoveLocal(addr_onion);
986+
RemoveLocal(addr_i2p);
987+
988+
// local addresses from all networks
989+
AddLocal(addr_ipv4);
990+
AddLocal(addr_ipv6);
991+
AddLocal(addr_ipv6_tunnel);
992+
AddLocal(addr_teredo);
993+
AddLocal(addr_onion);
994+
AddLocal(addr_i2p);
995+
AddLocal(addr_cjdns);
996+
BOOST_CHECK(GetLocalAddress(*peer_ipv4) == addr_ipv4);
997+
BOOST_CHECK(GetLocalAddress(*peer_ipv6) == addr_ipv6);
998+
BOOST_CHECK(GetLocalAddress(*peer_ipv6_tunnel) == addr_ipv6);
999+
BOOST_CHECK(GetLocalAddress(*peer_teredo) == addr_ipv4);
1000+
BOOST_CHECK(GetLocalAddress(*peer_onion) == addr_onion);
1001+
BOOST_CHECK(GetLocalAddress(*peer_i2p) == addr_i2p);
1002+
BOOST_CHECK(GetLocalAddress(*peer_cjdns) == addr_cjdns);
1003+
RemoveLocal(addr_ipv4);
1004+
RemoveLocal(addr_ipv6);
1005+
RemoveLocal(addr_ipv6_tunnel);
1006+
RemoveLocal(addr_teredo);
1007+
RemoveLocal(addr_onion);
1008+
RemoveLocal(addr_i2p);
1009+
RemoveLocal(addr_cjdns);
1010+
}
1011+
9071012
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)