diff --git a/content/concepts/assets/.DS_Store b/content/concepts/assets/.DS_Store new file mode 100644 index 00000000..2c99682a Binary files /dev/null and b/content/concepts/assets/.DS_Store differ diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-1.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-1.svg new file mode 100644 index 00000000..66ae3f02 --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-1.svg @@ -0,0 +1,45 @@ +Network_ANetwork_BInternetARouter_ABRouter_B \ No newline at end of file diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-10.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-10.svg new file mode 100644 index 00000000..d98b2e83 --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-10.svg @@ -0,0 +1,30 @@ +AARouter_ARouter_ARouter_BRouter_BBBPacketAAdd 5-tupleto local state tablePacketBAdd 5-tupleto local state tablePacketAPacketB5-tuple existsin local state tablePacketB5-tuple existsin local state tablePacketA \ No newline at end of file diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-2.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-2.svg new file mode 100644 index 00000000..630d3c53 --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-2.svg @@ -0,0 +1,31 @@ +AARouter_ARouter_ARouter_BRouter_BBBA tries to connect to BPacketAPacketAChecking state tableNo 5-tuple.Dropping PacketAB tries to connect to APacketBPacketBChecking state tableNo 5-tuple.Dropping PacketB \ No newline at end of file diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-3.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-3.svg new file mode 100644 index 00000000..d98b2e83 --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-3.svg @@ -0,0 +1,30 @@ +AARouter_ARouter_ARouter_BRouter_BBBPacketAAdd 5-tupleto local state tablePacketBAdd 5-tupleto local state tablePacketAPacketB5-tuple existsin local state tablePacketB5-tuple existsin local state tablePacketA \ No newline at end of file diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-4.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-4.svg new file mode 100644 index 00000000..ce099611 --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-4.svg @@ -0,0 +1,78 @@ +Goal: Peer A establishes a direct connection to non-dialable peer B.AARelayRelayBBOther_PeersOther_PeersPhase 1 - PreparationB determining whether it is dialable via AutoNAT protocolDialRequest {supposedly_public_addresses}For each provided supposedly public addressDialalt[One of the dials succeeded.Thus B is dialable. No need for Hole Punching.Don't continue.]DialOutcome {"Ok"successful_address}[None of the dials succeeded.Thus not dialable. Hole Punching needed. Continue.]DialOutcome {"Error"}B finding closest public Relay nodes via Kademlia ProtocolFindNodes(PEER_ID_B)[ Public nodes closest to PEER_ID_B ]B listening for incoming connections via closest RelayFor each closest Relay via Circuit Relay v2 ProtocolEstablish connectionRequest reservationAccept reservationAdvertise oneself as/<RELAY_ADDR>/p2p-circuit/<PEER_ID_B>Phase 2 - Hole PunchingA establish relayed connection to BEstablish ConnectionCircuit Relay v2 ProtocolRequest connection to BRequest connection from AAccept connection requestAccept connection requestRelayed Connection establishedA and B coordinate dial via Direct Connection Upgrade through Relay (DCUtR) Protocolloop[Until direct connection established]Via relayed connectionConnect { addresses }Measure round-trip timeConnect { addresses }SyncSimultaneously establish connection- A after 1/2 RTT- B when receiving SyncAttempt direct connection / Hole Punch \ No newline at end of file diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-5.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-5.svg new file mode 100644 index 00000000..292e6a14 --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-5.svg @@ -0,0 +1,28 @@ +BBOther_PeersOther_PeersB determining whether it is dialable via AutoNAT protocolDialRequest {supposedly_public_addresses}For each provided supposedly public addressDialalt[One of the dials succeeded.Thus B is dialable. No need for Hole Punching.Don't continue.]DialOutcome {"Ok"successful_address}[None of the dials succeeded.Thus not dialable. Hole Punching needed. Continue.]DialOutcome {"Error"} \ No newline at end of file diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-6.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-6.svg new file mode 100644 index 00000000..dbf95aa9 --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-6.svg @@ -0,0 +1,23 @@ +BBOther_PeersOther_PeersB finding closest public Relay nodes via Kademlia ProtocolFindNodes(PEER_ID_B)[ Public nodes closest to PEER_ID_B ] \ No newline at end of file diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-7.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-7.svg new file mode 100644 index 00000000..ba2f3891 --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-7.svg @@ -0,0 +1,28 @@ +RelayRelayBBOther_PeersOther_PeersB listening for incoming connections via closest RelayFor each closest Relay via Circuit Relay v2 ProtocolEstablish connectionRequest reservationAccept reservationAdvertise oneself as/<RELAY_ADDR>/p2p-circuit/<PEER_ID_B> \ No newline at end of file diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-8.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-8.svg new file mode 100644 index 00000000..4f2520e6 --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-8.svg @@ -0,0 +1,33 @@ +AARelayRelayBBA establish relayed connection to BEstablish ConnectionCircuit Relay v2 ProtocolRequest connection to BRequest connection from AAccept connection requestAccept connection requestRelayed Connection established \ No newline at end of file diff --git a/content/concepts/assets/hole-punching/libp2p-hole-punching-9.svg b/content/concepts/assets/hole-punching/libp2p-hole-punching-9.svg new file mode 100644 index 00000000..71e22dca --- /dev/null +++ b/content/concepts/assets/hole-punching/libp2p-hole-punching-9.svg @@ -0,0 +1,33 @@ +AARelayRelayBBA and B coordinate dial via Direct Connection Upgrade through Relay (DCUtR) Protocolloop[Until direct connection established]Via relayed connectionConnect { addresses }Measure round-trip timeConnect { addresses }SyncSimultaneously establish connection- A after 1/2 RTT- B when receiving SyncAttempt direct connection / Hole Punch \ No newline at end of file diff --git a/content/concepts/hole-punching.md b/content/concepts/hole-punching.md new file mode 100644 index 00000000..39dfb54d --- /dev/null +++ b/content/concepts/hole-punching.md @@ -0,0 +1,212 @@ +--- +title: Hole Punching +weight: 3 +--- + +## Background + +### Types of nodes + +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. + +### How can a node become dialable despite being behind a firewall and/or NAT? + +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 the dial each other: + +1. The first packet of both nodes (e.g., in the case of TCP, an SYN) + passes through their respective routers. +2. The routers add a 5-tuple to their router's state table. + + {{% notice "info" %}} + 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. + {{% /notice %}} + +3. `PacketA` and `PacketB` "punch holes" into their respective routers' + firewalls. +4. Both packets arrive at the opposite router. +5. Once `A`'s packet arrives at `Router_B`, `Router_B` checks its state + table and finds a 5-tuple previously added through the packet sent by + node B. +6. The routers forward the packets through the "punched holes" to `B`. + The same occurs with `B`'s packet; upon arriving at `Router_A`, it matches + a 5-tuple in `Router_A`'s state table and thus forwards the packet to `A`. + +The following use case diagram illustrates the above process. + +![](../assets/hole-punching/libp2p-hole-punching-2.svg) + +{{% notice "note" %}} +This process assumes a mechanism to synchronize `A` and `B` simultaneously. +{{% /notice %}} + +## Hole punching in libp2p + +Inspired by the +[ICE protocol](https://datatracker.ietf.org/doc/html/rfc8445), +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. + +![](../assets/hole-punching/libp2p-hole-punching-4.svg) + +libp2p hole punching can be divided into two phases, a preparation phase and +a hole punching phase. + +### Phase I: Preparation + +1. [AutoNAT](/concepts/nat/#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](https://www.rfc-editor.org/rfc/rfc3489) in ICE. + + ![](../assets/hole-punching/libp2p-hole-punching-5.svg) + + - `B` reaches out to `Other_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](https://github.com/libp2p/specs/blob/master/identify/README.md). + - `Other_Peers` attempt to dial each of `B`'s addresses and report the + outcome back to `B`. + - Based on the reports, `B` can gauge whether it is publicly dialable and + determine if hole punching is needed. + + + + +1. 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): `//p2p-circuit/` + + ![](../assets/hole-punching/libp2p-hole-punching-6.svg) + + - `Other_Peers` outside `B`'s network can dial `B` indirectly through + a public relay node. In the case of [IPFS](https://ipfs.tech/), each public + node would serve as a `Relay`. `B` would either perform a lookup on the + [Kademlia DHT](https://github.com/libp2p/specs/blob/master/kad-dht/README.md) + for the closest peers to its Peer ID or choose a subset of the public nodes + it is already connected to. + +2. [Circuit Relay](/concepts/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](https://datatracker.ietf.org/doc/html/rfc5766) in ICE. + + ![](../assets/hole-punching/libp2p-hole-punching-7.svg) + + - `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 through `Relay`. + +### Phase II: Hole punching + +1. [Circuit Relay](/concepts/circuit-relay): Establish a secure relay connection + through the public relay node. Node `A` establishes a direct connection with + the relay node. Node `B` then requests a relayed connection to node `A` through + the relay node, creating a bi-directional channel and uses TLS to secure the + channel. + + ![](../assets/hole-punching/libp2p-hole-punching-8.svg) + + - `A` establishes a relayed connection to `B` via the `Relay` using the + information contained in `B`'s advertised address. + - `A` first establishes a direct connection to `Relay` and then + requests a relayed connection to `B` from `Relay`. + - `Relay` forwards said request to `B` and accepts. + - `Relay` forwards the acceptance to `A`. + - `A` and `B` can use the bi-directional channel over `Relay` to + communicate. + - `A` and `B` upgrade the relayed connection with a security protocol + like TLS. + + + +2. [DCUtR](https://github.com/libp2p/specs/blob/master/relay/DCUtR.md): Use + DCUtR as a synchronization mechanism to coordinate hole punching. + + ![](../assets/hole-punching/libp2p-hole-punching-9.svg) + + - `A` sends a `Connect` message to `B` through `Relay`. + - `Connect` contains the addresses of A. libp2p offers multiple + mechanisms to discover one's addresses, e.g., via the libp2p Identify + protocol. + - `B` receives the `Connect` message on the relayed connection and replies + with a `Connect` message containing its (non-relayed) addresses. + - `A` measures the time between sending its message and receiving `B`'s + message, thereby determining the round-trip time between `A` and `B` via `Relay`. + - Then, `A` sends a `Sync` message to `B` on the relayed connection. + - `A` waits for half the round-trip time, then directly dials `B` via the + addresses received in `B`'s `Connect`. + - As soon as `B` receives `A`'s `Sync` message, it directly dials `A` with the + addresses provided in `A`'s `Connect` message. + - Once `A` and `B` dial each other simultaneously, a hole punch occurs. + +### Resources + +- This guide is a byproduct of the + [Hole punching in libp2p - Overcoming Firewalls](https://blog.ipfs.tech/2022-01-20-libp2p-hole-punching/) + blog post by Max Inden. +- Research paper on + [decentralized hole punching by Protocol Labs Research](https://research.protocol.ai/publications/decentralized-hole-punching/) +- Keep up with the [libp2p implementations page](https://libp2p.io/implementations/) for + the state on different hole punching implementations.