Hole Punching
Nodes on a peer-to-peer network can be categorized into two groups: public and non-public. Public nodes are those nodes that have unobstructed access to the internet, whereas non-public nodes are located behind some kind of firewall. This applies to most nodes in home and in corporate network, as well as mobile phones. In most configurations, both public and non-public nodes can dial connections to other public nodes. However, it’s not possible to establish a connection from the public internet to a non-public node.
Dialing a non-public node
Here are a few methods that nodes can use to dial a non-public node:
- UPnP (Universal Plug and Play): A protocol spoken between routers and computers inside the network. It allows the computer to request that certain ports be opened and forward to that computer.
- Port forwarding: Manually configuring a port forwarding on a router.
Limitations
In many settings, UPnP is disabled by the router or a firewall. UPnP may also not work depending on the router’s firmware.
Manually opening a port requires technical expertise and does not enforce authentication or authorization.
Possible solution: hole punching
Relaying overview
Relaying is a mechanism used to send information between two ends. In the case of non-public nodes:
Node A maintains a permanent connection to a relay node, R, and when node B wants to connect to node A, it first establishes a connection to node R, where R forwards all the packets on the connection. Relaying adds additional latency and is resource intensive as node R needs to handle a lot of traffic. Using a relay node also requires technical expertise.
What if we could use node R to help facilitate a direct connection between node A and node B?
In the case where the other options aren’t sufficient, networks can use a technique called hole punching to establish connections with non-public nodes.
Each node connects to a relay node and shares its external address and port information. The server temporarily stores the node’s information and relays each node’s information to the other. Clients can use this information to establish direct connections with each other.
Take two nodes, A
and B
, that would like to dial each other:
- The first packet of both nodes (e.g., in the case of TCP, an SYN) passes through their respective routers.
- The routers add a 5-tuple to their router’s state table.A router state table (routing table) is data store within a router that lists the routes to particular network destinations. The 5-tuple structure includes the source IP address, source port, destination IP address, destination port, and transport protocol.
PacketA
andPacketB
“punch holes” into their respective routers' firewalls.- Both packets arrive at the opposite router.
- Once
A
’s packet arrives atRouter_B
,Router_B
checks its state table and finds a 5-tuple previously added through the packet sent by node B. - The routers forward the packets through the “punched holes” to
B
. The same occurs withB
’s packet; upon arriving atRouter_A
, it matches a 5-tuple inRouter_A
’s state table and thus forwards the packet toA
.
The following use case diagram illustrates the above process.
A
and B
simultaneously.Hole punching in libp2p
Inspired by the ICE protocol, libp2p includes a decentralized hole punching feature that allows for firewall and NAT traversal without the need for central coordination servers like STUN and TURN.
The following sequence diagram illustrates the whole process.
libp2p hole punching can be divided into two phases, a preparation phase and a hole punching phase.
Phase I: Preparation
AutoNAT: Determine whether a node is dialable, as in, discover if a node is behind a NAT or firewall.
This is equivalent to the STUN protocol in ICE.
B
reaches out toOther_Peers
(e.g., boot nodes) on the network it is on and asks each node to dial it on a set of addresses it suspects could be reachable. A libp2p node has multiple ways of discovering its addresses, but the most prominent is using the libp2p Identify protocol.Other_Peers
attempt to dial each ofB
’s addresses and report the outcome back toB
.- Based on the reports,
B
can gauge whether it is publicly dialable and determine if hole punching is needed.
AutoRelay: Dynamically discover and bind to relay nodes on the network.
IPFS discovers the k-closest public relay nodes using a lookup method via Kademlia DHT):
/<RELAY_ADDR>/p2p-circuit/<PEER_ID_B>
Other_Peers
outsideB
’s network can dialB
indirectly through a public relay node. In the case of IPFS, each public node would serve as aRelay
.B
would either perform a lookup on the Kademlia DHT for the closest peers to its Peer ID or choose a subset of the public nodes it is already connected to.
Circuit Relay: Connect to and request reservations with the discovered relay nodes. A node can advertise itself as being reachable through a remote relay node.
This is equivalent to the TURN protocol in ICE.
Relay
can limit the resources used to relay connections (e.g., by the number of connections, the time, and bytes) via Circuit Relay v2. In the case of IPFS, this allows every public node in the network to serve as a relay without high resource consumption.- For each discovered
Relay
,B
:- connects to the remote node and requests the Relay node to listen to connections on its behalf, known as a reservation;
- if
Relay
accepts reservation requests,B
can advertise itself as being reachable throughRelay
.
Phase II: Hole punching
Circuit Relay: Establish a secure relay connection through the public relay node. Node
A
establishes a direct connection with the relay node. NodeB
then requests a relayed connection to nodeA
through the relay node, creating a bi-directional channel and uses TLS to secure the channel.A
establishes a relayed connection toB
via theRelay
using the information contained inB
’s advertised address.A
first establishes a direct connection toRelay
and then requests a relayed connection toB
fromRelay
.Relay
forwards said request toB
and accepts.Relay
forwards the acceptance toA
.A
andB
can use the bi-directional channel overRelay
to communicate.A
andB
upgrade the relayed connection with a security protocol like TLS.
DCUtR: Use DCUtR as a synchronization mechanism to coordinate hole punching.
A
sends aConnect
message toB
throughRelay
.Connect
contains the addresses of A. libp2p offers multiple mechanisms to discover one’s addresses, e.g., via the libp2p Identify protocol.
B
receives theConnect
message on the relayed connection and replies with aConnect
message containing its (non-relayed) addresses.A
measures the time between sending its message and receivingB
’s message, thereby determining the round-trip time betweenA
andB
viaRelay
.- Then,
A
sends aSync
message toB
on the relayed connection. A
waits for half the round-trip time, then directly dialsB
via the addresses received inB
’sConnect
.- As soon as
B
receivesA
’sSync
message, it directly dialsA
with the addresses provided inA
’sConnect
message. - Once
A
andB
dial each other simultaneously, a hole punch occurs.
Resources
- This guide is a byproduct of the Hole punching in libp2p - Overcoming Firewalls blog post by Max Inden.
- Research paper on decentralized hole punching by Protocol Labs Research
- Keep up with the libp2p implementations page for the state on different hole punching implementations.