I’ve been thinking about this a bit and think something like this might work:
A Bitcoin node run at home might not be reachable via clearnet due to NAT, however, many home nodes now support Tor and/or I2P, which allows inbound connections via Tor. It seems possible to coordinate TCP hole punching through Tor or I2P.
A node might want to offer inbound slots via clearnet, but is not reachable. One way to work around this could be the following protocol with node A and node B both being nodes behind a EIM NAT, while also being connected to the Tor and/or I2P network.
- Node A thinks it’s unreachable (TBD how to figure this out reliably) but wants to offer clearnet inbound connections.
- It first needs to figure out if it’s behind a EIM NAT. It can do so by opening and establishing two (possibly feeler?) connections to other nodes using
SO_REUSEADDR/SO_REUSEPORTon the socket and checking if the peers return the same port in the version message. This needs to be done only once per network, assuming the NAT configuration does not change. - Node A starts to listen on a dedicated hole-punch coordination Tor or I2P endpoint only for coordinating hole-punching. Address, transaction, or block relay is not supported on this endpoint. We don’t link any other Tor or I2P endpoints that this node might have to this dedicated endpoint.
- This coordination Tor or I2P endpoint is advertised via
addrv2message on clearnet only (to not link clearnet and Tor/I2P address) with a (new) service flag: e.g.NODE_HOLEPUNCH(?). These addresses are relayed as a best effort, but not stored in addrman. This has the goal of them not being relayed after around 10 minutes and not using up space in addrman. The frequency we relay these is TBD. We don’t need to self-announce them, if we don’t want any more holepunch-inbound connections. This message could also be a new BIP-155 address type for hole punching which could include the Tor or I2P coordination endpoint and the clearnet address (coordinate onabcdef.onionto connect with203.0.113.24) the nodes wants inbound connections on. This allows other nodes to filter by e.g. netgroup / AS without needing to make a connection to the coordination endpoint. - Node B receives such an announcement via an addrv2-message. It decides it wants to try to open an outbound connection to Node A. It has previously figured out that it’s behind a EIM NAT.
- Node B connects to node A’s hole-punch coordination endpoint, and does a version handshake. As part of the connection to this hole-punch coordination endpoint, a request for a hole-punch connection is implicit.
- Node A now opens a new outbound connection (e.g. a feeler?) to a known good address with
SO_REUSEADDR/SO_REUSEPORTon the socket. The goal is to do a version handshake and learn the NATIP:portof the socket. This likely requires some rate-limiting for some external party to cause Node A to make too many outbound connections. - Node B does the same.
- Node A & B now have a EIM NAT mapped socket they can use to do the simultaneous TCP open and punch through their NATs.
A few notes on how to make this easier, but not without introducing tradeoffs:
- With EIM NATs, we might be able to predict our NAT port (it’s often the same we bind on locally, at least from my limited observations). This might allow us to skip the outbound connections Node A and B need to make while already coordinating, making this a lot easier, faster, and causing less churn on the Bitcoin network. This will fail, when our NAT has already a mapping for the same port. The tradeoff here is possibly higher failure rates.
- An alternative might be to have well-known, static, (and centralized!) Bitcoin-protocol-speaking-servers-but-not-nodes run on the P2P network that just do a version handshake returning the IP:port they see your NAT IP from and then close the connection. These might be run by community members, similar to the DNS seeds. They’d learn about who is trying to open a hole-punch connection, and a passive observer (ISP) would see you making connections to them.
- Yet another alternative is that all nodes on the network would allow a special IPv4/IPv6 NAT check connection to them where it’s implicit that the connection will be shut down right after the version handshake. No need for them to be centralized, but would basically require implementing a STUN service for the P2P network in node software (e.g. Bitcoin Core).
Compared to the approach with a coordinator, this has the benefit of having clear inbound-outbound mechanics. Node B chooses Node A as an outbound. Node B is an inbound to A. The downside is that we need connectivity over Tor/I2P/(CJDNS). However, many node-in-a-box home nodes seem to ship with Tor / I2P on by default.
