-
Notifications
You must be signed in to change notification settings - Fork 37.7k
p2p: Restrict self-advertisements with privacy networks to avoid fingerprinting #27411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
Concept ACK Personally, I have given up a little bit on fighting fingerprinting (at least I've de-prioritized it) because at this point there are so many fingerprinting techniques that we should probably either give up or figure out a way to avoid them ~entirely by design.
But this is such an obvious fingerprint that we should address it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
On the approach - this PR expands CNetAddr::GetReachabilityFrom()
with some logic whether we want to advertise or not. Such a logic is already in GetLocal()
and CNetAddr::GetReachabilityFrom()
is simply about reachability. It would be good to keep this separation.
Is it not possible to achieve the same with something simpler, like the following?
--- i/src/net.cpp
+++ w/src/net.cpp
@@ -164,12 +164,22 @@ bool GetLocal(CService& addr, const CNetAddr *paddrPeer)
int nBestScore = -1;
int nBestReachability = -1;
{
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 auto& local_addr = entry.first;
+ if ((local_addr.IsTor() && !paddrPeer->IsTor()) ||
+ (!local_addr.IsTor() && paddrPeer->IsTor()) ||
+ (local_addr.IsI2P() && !paddrPeer->IsI2P()) ||
+ (!local_addr.IsI2P() && paddrPeer->IsI2P())) {
+ continue;
+ }
int nScore = entry.second.nScore;
Or why not even expand this logic to all networks and delete |
concept ACK. good catch! |
We'd need one more But yes, I will try this alternative out.
I think that would be too drastic: I don't see a reason to never advertise IPv4 addresses to IPv6 peers, and vice versa - for clearnet, better propagation seems more important than fingerprinting concerns. What do you think about CJDNS? |
Right, incoming Tor need special handling. That is already what // 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{node.ConnectedThroughNetwork()};
if (our_net != peers_net &&
(our_net == NET_ONION || our_net == NET_I2P ||
peers_net == NET_ONION || peers_net == NET_I2P)) {
continue;
} (and pass I am not sure about CJDNS. Normally I would say treat it like Tor and I2P (but I even had the heretic idea to do that with IPv4 and IPv6 too, so maybe I lean a bit to the paranoid side, don't listen to me too much). However there are very few CJDNS addresses right now on mainnet and if we only advertise our CJDNS address to CJDNS peers that could impede its adoption. Ultimately, the perfect solution would be to broadcast our address exactly like we broadcast foreign addresses. So that a spying peer cannot distinguish which one is it. Right now we are more aggressive in broadcasting our address than broadcasting foreign addresses. To make it the same, either:
either one would be a serious change and would need some simulation to be justified. Just thinking aloud. |
Makes sense to me - if bitcoin over CJDNS gets more popular we could revisit this later.
Yes, this would probably require an overhaul of the entire address relay mechanism, and I'm not sure if the benefits would be that great - after all, we don't really want to keep our address private, just not fingerprint ourselves in the most obvious way.
I think I will switch to this approach, keeping reachability and privacy concerns separate might be good. Will also try to add some unit test coverage for this area of code (there seems to be none so far), and then push an update in the next days. |
a98feda
to
6be8b6d
Compare
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 6be8b6d
src/netaddress.cpp
Outdated
@@ -725,17 +725,15 @@ std::vector<unsigned char> CNetAddr::GetAddrBytes() const | |||
// and only used in GetReachabilityFrom | |||
static const int NET_UNKNOWN = NET_MAX + 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may expand commit b326138 net, refactor: pass reference for peer address in GetReachabilityFrom
to remove NET_UNKNOWN
since it is unused after that commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, removed NET_UNKNOWN and decreased NET_TEREDO by 1.
eda6e87
to
3f93ea3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 3f93ea3
Access to CNode will be needed in the following commits.
The address of the peer always exists (because addr is a member of CNode), so it was not possible to pass a nullptr before. Also remove NET_UNKNOWN, which is unused now.
Stop advertising 1) our i2p/onion address to peers from other networks 2) Local addresses of non-privacy networks to i2p/onion peers Doing so could lead to fingerprinting ourselves. Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
3f93ea3
to
e7cf865
Compare
yes. Rebased due to silent conflict with #26261. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK e7cf865
Yes, we could still forward it: When we receive a gossipped address we choose 1-2 random peers (that stay the same for 24 hours, see RelayAddress) and forward it to them if we don't think they already know it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK e7cf865
Approach: Unsure about the refactoring, but ok
ACK e7cf865 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Post-merge ACK
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)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -164,8 +164,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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While touching this, the GetLocal()
getter helper is unused outside the class and could be
- moved from the header to the implementation
- made static and nodiscard
- converted from a bool with an out-param to a std::optional without an out-param
Edit: done in #28078
Should this be added to the release notes? |
Access to CNode will be needed in the following commits. Github-Pull: bitcoin#27411 Rebased-From: 62d73f5
The address of the peer always exists (because addr is a member of CNode), so it was not possible to pass a nullptr before. Also remove NET_UNKNOWN, which is unused now. Github-Pull: bitcoin#27411 Rebased-From: e4d541c
Stop advertising 1) our i2p/onion address to peers from other networks 2) Local addresses of non-privacy networks to i2p/onion peers Doing so could lead to fingerprinting ourselves. Co-authored-by: Vasil Dimov <vd@FreeBSD.org> Github-Pull: bitcoin#27411 Rebased-From: f4754b9
Github-Pull: bitcoin#27411 Rebased-From: 3f93ea3
Not sure. I think the impact on users isn't that strong (it fixes behavior in rather rare constellations), so I wouldn't have added it. But happy to add it, if others would like it included! |
…use helpers over low-level code, use optional 4ecfd3e Inline short, often-called, rarely-changed basic CNetAddr getters (Jon Atack) 5316ae5 Convert GetLocal() to std::optional and remove out-param (Jon Atack) f1304db Use higher-level CNetAddr and CNode helpers in net.cpp (Jon Atack) 07f5891 Add CNetAddr::IsPrivacyNet() and CNode::IsConnectedThroughPrivacyNet() (Jon Atack) df48856 GetLocal() type-safety, naming, const, and formatting cleanups (stickies-v) fb42657 Add and use CNetAddr::HasCJDNSPrefix() helper (Jon Atack) 5ba73cd Move GetLocal() declaration from header to implementation (Jon Atack) 11426f6 Move CaptureMessageToFile() declaration from header to implementation (Jon Atack) deccf1c Move IsPeerAddrLocalGood() declaration from header to implementation (Jon Atack) Pull request description: and other improvements noticed while reviewing #27411. Addresses bitcoin/bitcoin#27411 (comment) and bitcoin/bitcoin#27411 (comment). See commit messages for details. ACKs for top commit: achow101: ACK 4ecfd3e vasild: ACK 4ecfd3e stickies-v: ACK 4ecfd3e Tree-SHA512: d792bb2cb24690aeae9bedf97e92b64fb6fd080c39385a4bdb8ed05f37f3134d85bda99da025490829c03017fd56382afe7951cdd039aede1c08ba98fb1aa7f9
…ks to avoid fingerprinting
…ks to avoid fingerprinting
…ks to avoid fingerprinting
…ks to avoid fingerprinting
…ks 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>
…ks 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>
…ks 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>
, bitcoin#25962, bitcoin#26888, bitcoin#27264, bitcoin#27257, bitcoin#27324, bitcoin#27374, bitcoin#27467, bitcoin#27411, partial bitcoin#25472 (networking backports: part 8) 8320e0c merge bitcoin#27411: Restrict self-advertisements with privacy networks to avoid fingerprinting (Kittywhiskers Van Gogh) 1376289 merge bitcoin#27467: skip netgroup diversity follow-up (Kittywhiskers Van Gogh) a52b3a3 merge bitcoin#27374: skip netgroup diversity of new connections for tor/i2p/cjdns (Kittywhiskers Van Gogh) ab11e0f merge bitcoin#27324: bitcoin#27257 follow-ups (Kittywhiskers Van Gogh) 9023dd2 merge bitcoin#27257: End friendship of CNode, CConnman and ConnmanTestMsg (Kittywhiskers Van Gogh) 3465df2 merge bitcoin#27264: Improve diversification of new connections (Kittywhiskers Van Gogh) d3f5b38 merge bitcoin#26888: simplify the call to vProcessMsg.splice() (Kittywhiskers Van Gogh) d9e56f3 merge bitcoin#25962: Add CNodeOptions and increase constness (Kittywhiskers Van Gogh) 79e67fd merge bitcoin#25814: simplify GetLocalAddress() (Kittywhiskers Van Gogh) 6d49454 partial bitcoin#25472: Increase MS Visual Studio minimum version (Kittywhiskers Van Gogh) 54bb3a4 merge bitcoin#25500: Move inbound eviction logic to its own translation unit (Kittywhiskers Van Gogh) b50febc merge bitcoin#24531: Use designated initializers (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * Dependent on #6254 * When backporting [bitcoin#27411](bitcoin#27411), the `CNetAddr*` variant of `GetLocal()` was not removed (upstream it was replaced by the `CNode&` variant with additional checks that rely on fields in `CNode`) as `CActiveMasternodeManager` relies on `GetLocal()` to detect a valid external address. * While it can also rely on other nodes to determine that, removing code that tests against a well-known public address would increase the number of reported failures esp. if the checks are run _before_ the node has a chance to connect to any peers. ## Breaking Changes None observed. ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas **(note: N/A)** - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: UdjinM6: utACK 8320e0c PastaPastaPasta: utACK 8320e0c Tree-SHA512: 1d02bc33c8d62c392960d4dd044edf3de08515a5e8c8794d95cd95e9654da91b20e7290436cf9c79b0ea8dbd42b27dcc61c8eb17e573902574d7b281b8874584
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: 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.