net: don’t self advertise tor exit node ip addresses in outbound connections #35578

pull stratospher wants to merge 2 commits into bitcoin:master from stratospher:2026-05-self-adv-tor-exit changing 3 files +4 −4
  1. stratospher commented at 6:37 PM on June 21, 2026: contributor
    • During self advertisement in GetLocalAddrFromPeer - we advertise the "IP address we think we are" and sometimes replace it with what "IP address the peer thinks we are".
    • when an outbound connections is made to a peer via a proxy, the "IP address the peer thinks we are" is the proxy's ip address.
    • so the proxy's ip address can get relayed as our self advertisement
    • in normal cases the proxy's ip address is not a reachable inbound address and shouldn't be relayed on the network.

    Example: when a node running both Tor and clearnet via a SOCKS5 proxy connects to a clearnet peer (proxy=127.0.0.1:9050, listen=1, discover=1 (or) onion=127.0.0.1:9050), the "IP address the peer thinks we are" is the Tor exit node's IP address and doesn't make sense to relay it. 55-76% of all tor exit nodes were present in various addrman snapshots I checked (my node and the ones from b10c’s peer.observer) from now and few years ago. would be nice to not unnecessarily relay these tor exit IPs since they end up being stored as fresh and gossiped from multiple sources entries in our addrman.

    an advanced node runner might have a proxy whose ip address is also a reachable inbound address. I've personally not used such a proxy but 1 difference for them is that they'd need to explicitly set -externalip=<addr> to self-advertise their proxy address. i feel it’s an acceptable tradeoff and would imagine they are already using -externalip=<addr> config flag as they are explicitly managing their node's public address.

  2. net: don't self-advertise proxy address in outbound connections
    When outbound connections to clearnet peers are made via a proxy,
    
    - During self advertisement - we advertise
      "IP address we think we are" and sometimes replace it with what
      "IP address the peer thinks we are".
    - the "IP address the peer thinks we are" is the proxy's ip address.
      and can get relayed as our self advertisement
    - in normal cases the proxy's ip address is not a reachable inbound
      address and shouldn't be relayed on the network.
    
    there is a soft-set init parameter interaction proxy → discover=0
    to prevent this situation but don't think it works in practice
    because people might end up overriding it with discover=1
    in real life, 55-76% of all tor exit nodes were present in
    various addrman snapshots from now and few years ago. so would be
    nice to check when behind a proxy before self advertising.
    412aa3d1fd
  3. doc: use proxy=127.0.0.1:9050=onion in tor.md
    update tor.md after PR 34031
    ```
    [error] Unrecognized network in -proxy='127.0.0.1:9050=tor': 'tor'
    Error: Unrecognized network in -proxy='127.0.0.1:9050=tor': 'tor'
    ```
    b5d58ee149
  4. DrahtBot added the label P2P on Jun 21, 2026
  5. DrahtBot commented at 6:37 PM on June 21, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/35578.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Concept ACK 0xB10C, naiyoma

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #31260 (scripted-diff: Type-safe settings retrieval by ryanofsky)

    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.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  6. 0xB10C commented at 8:37 AM on June 26, 2026: contributor

    Concept ACK, it makes sense to me to not advertise an address we know we can't be reached on. I think this is the case when using -proxy, as we specifically say "make outbound connections through this SOCKS5" but don't expect inbound connections via this socket. There seems to be something called BIND-request for SOCKS5 proxies, but I don't think that's widely supported (and not implemented in Bitcoin Core).

    I've looked at the number of connections my nodes make to Tor exit node IPs in this post: In mid 2026, my nodes each make between 7.5 to 12.5 connection attempts per day to Tor exit node IPs.

    While nearly all of these connection attempts fail (as expected), there's about 20 IPs where I've seen some connections to Tor exit node IPs succeed over the last years. I assume these are hosts that run (or ran at some point) a Tor exit node and a Bitcoin node, which isn't too unexpected.

  7. 0xB10C commented at 9:12 AM on June 26, 2026: contributor

    proxy=127.0.0.1:9050, listen=1, discover=1

    Hm are we sure this is being used like this? With just -proxy set, we wouldn't be listening and not self-announcing.

    https://github.com/bitcoin/bitcoin/blob/93012d7ff918eef41adf453bf10ddb4da21f3df4/src/init.cpp#L812-L825

    https://github.com/bitcoin/bitcoin/blob/93012d7ff918eef41adf453bf10ddb4da21f3df4/src/net_processing.cpp#L5475-L5476

  8. stratospher commented at 9:21 AM on June 30, 2026: contributor

    proxy=127.0.0.1:9050, listen=1, discover=1

    Hm are we sure this is being used like this? With just -proxy set, we wouldn't be listening and not self-announcing.

    good point. it's either this or people using onion=127.0.0.1:9050.

    even though there is the soft-set init parameter interactions which does proxy=127.0.0.1:9050 -> listen=0 and discover=0 - I wouldn't be surprised if people end up overriding it.

    • listen=1 is overriden to allow inbound connections without having to deal with the clearnet firewall rules. I've seen/done this a lot.
    • I think discover=1 and its interaction with other parameters can be confusing to users. It makes a node's own IP discoverable to peers (the node advertises it). That IP is obtained from the node's own machine, and (less obviously) also from what peers report the node's address to be.
      • If the node wants control over what exactly it advertises, -externalip=<ip> can be used (keeping the soft-set discover=0) - the node then advertises only the address we specified in externalip, and not the peer-reported view of a node's IP.
      • if -externalip=<ip> is used with explicit discover=1 override in the bitcoin.conf then again the peer-report view of a node's ip can be advertised.

    quick GitHub search shows proxy=127.0.0.1:9050, listen=1, discover=1 use here and here. unsure if it's common/rare/how many people use it like this though.

  9. naiyoma commented at 5:46 PM on July 10, 2026: contributor

    Concept ACK It makes sense not to advertise these addresses, as they are not useful. i used this list https://check.torproject.org/torbulkexitlist to check for tor exit nodes.

    On master, I noticed that sometimes clearnet outbound peers would send me their own addresses as my address, and I would occasionally self-advertise those same addresses back to them. I think this PR fixes that only for proxy, but maybe we should also add check so that a peer does not send us their own address.

    tested 412aa3d1fde685bb6b0da7c34ae943383bd02189 with listen=1 proxy=127.0.0.1:9050 discover=1

    My logs show that even when the score flip favoured the peers reported address for an outbound clearnet (tor_proxied ) peer , we do not advertise it , instead we now always fallback to advertising our own adress that we know.

    <details> <summary>Logs</summary>

    2026-07-10T13:12:40Z New outbound-full-relay peer connected: transport: v2, version: 70016, peer=4, peeraddr=91.67.73.147:8333
    2026-07-10T13:12:40Z [net] sending sendcmpct (9 bytes) peer=4
    2026-07-10T13:12:40Z [net] sending ping (8 bytes) peer=4
    2026-07-10T13:12:40Z [net] Maybe we should self announce to this address relay peer 4, relay_enabled=1, fListen=1, next_local_addr_send=0, current_time=1783689160359904
    ...
    2026-07-10T13:12:40Z [net] GetLocalAddrForPeer peer=4: ours=[2605:59c1:4782:7c08:4936:c6a5:fdee:50d2]:8333 (routable=1) peer_reported=185.220.101.24:43126 (routable=1) | peer_good=1 own_unroutable=0 own_score=1 LOCAL_MANUAL=4 flip_bits=1 (1-in-2) won_flip=1 -> using peer's addrLocal
    2026-07-10T13:12:40Z [net] Advertising address [2605:xxx:xx:xxx:xx:xx:xx]:8333 to peer=4
    

    </details>

  10. in src/net.cpp:254 in b5d58ee149
     250 | @@ -252,7 +251,7 @@ std::optional<CService> GetLocalAddrForPeer(CNode& node)
     251 |              // For inbound connections, assume both the address and the port
     252 |              // as seen from the peer.
     253 |              addrLocal = CService{node.GetAddrLocal()};
     254 | -        } else {
     255 | +        } else if (!GetProxy(node.addr.GetNetwork()).has_value()) {
    


    naiyoma commented at 5:48 PM on July 10, 2026:

    Maybe add a comment? to explain a bit on why there's a proxy check here


    naiyoma commented at 11:28 AM on July 16, 2026:

    An alternative approach would be to check whether an outbound connection is proxied before we even check if IsPeerAddrLocalGood(), since the result isn't useful if we won't use the address anyway.

    
    std::optional<CService> GetLocalAddrForPeer(CNode& node)
     {
         CService addrLocal{GetLocalAddress(node)};
    +    // For outbound connections through a proxy, the address the peer reports
    +    // seeing us as is the proxy's exit IP, not ours, so it must never be
    +    // relayed as our self-advertised address. Skip the peer's view
    +    // entirely in that case and keep what we know ourselves to be.
    +    const bool outbound_via_proxy{!node.IsInboundConn() &&
    +                                  GetProxy(node.addr.GetNetwork()).has_value()};
         // 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.
         FastRandomContext rng;
    -    if (IsPeerAddrLocalGood(&node) && (!addrLocal.IsRoutable() ||
    +    if (!outbound_via_proxy && IsPeerAddrLocalGood(&node) && (!addrLocal.IsRoutable() ||
              rng.randbits((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3 : 1) == 0))
         {
             if (node.IsInboundConn()) {
                 // For inbound connections, assume both the address and the port
                 // as seen from the peer.
                 addrLocal = CService{node.GetAddrLocal()};
    -        } else if (!GetProxy(node.addr.GetNetwork()).has_value()) {
    +        } else {
    
    

    naiyoma commented at 1:17 PM on July 16, 2026:

    I think its possible to test this in net_tests.cpp similar to get_local_addr_for_peer_port but using SetProxy() and verifying that we dont relay what the peer told us.

    I tried this locally

    
    +BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_proxy)
    +{
    +    // when proxy is set for an outbound connection, the address
    +    // the peer sends us is the proxy's view of us, not our actual address
    +    // we ignore this this and instead announce what we think we are
    +    // With no local address know,nothing is advertised
    +    // with a routable local address known, that is advertised
    +
    +    in_addr peer_us_addr;
    +    peer_us_addr.s_addr = htonl(0x02030405);
    +
    +    const CService peer_us{peer_us_addr, 20002};
    +    std::cout << "peer_us: " << peer_us.ToStringAddr() << std::endl;
    +
    +    in_addr peer_out_in_addr;
    +    peer_out_in_addr.s_addr = htonl(0x01020304);
    +
    +    std::cout << "peer_out_in_addr: " << CService{peer_out_in_addr, 8333}.ToStringAddr() << std::endl;
    +    CNode peer_out{/*id=*/0, /*sock=*/nullptr,
    +                     /*addrIn=*/CAddress{CService{peer_out_in_addr, 8333}, NODE_NETWORK},
    +                     /*nKeyedNetGroupIn=*/0,
    +                     /*nLocalHostNonceIn=*/0,
    +                     /*addrBindIn=*/CService{},
    +                     /*addrNameIn=*/std::string{},
    +                     /*conn_type_in=*/ConnectionType::OUTBOUND_FULL_RELAY,
    +                     /*inbound_onion=*/false,
    +                     /*network_key=*/0};
    +
    +    peer_out.fSuccessfullyConnected = true;
    +    peer_out.SetAddrLocal(peer_us);
    +
    +    std::cout << "peer_out: " << peer_out.addr.ToStringAddr() << std::endl;
    +    BOOST_REQUIRE(SetProxy(NET_IPV4, Proxy{CService{peer_out_in_addr, 9050}}));
    +
    +    BOOST_REQUIRE(!GetLocalAddrForPeer(peer_out));
    +
    +    const CService our_addr{Lookup("8.8.8.8", 8333, false).value()};
    +    BOOST_REQUIRE(AddLocal(our_addr, LOCAL_MANUAL));
    +
    +    auto chosen = GetLocalAddrForPeer(peer_out);
    +    std::cout << "our_addr: " << our_addr.ToStringAddr() << std::endl;
    +    std::cout << "chosen: " << (chosen ? chosen->ToStringAddr() : "none") << std::endl;
    +
    +    BOOST_REQUIRE(chosen);
    +    BOOST_CHECK(*chosen == our_addr);
    +    ResetProxy(NET_IPV4);
    +    RemoveLocal(our_addr);
    +}
    
    

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-07-22 07:50 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me