Module libp2p::tutorials::hole_punching
source · Expand description
Hole Punching Tutorial
This tutorial shows hands-on how to overcome firewalls and NATs with libp2p’s hole punching mechanism. Before we get started, please read the blog post to familiarize yourself with libp2p’s hole punching mechanism on a conceptual level.
We will be using the Circuit Relay v2 and the Direct Connection Upgrade through Relay (DCUtR) protocol.
You will need 3 machines for this tutorial:
- A relay server:
- Any public server will do, e.g. a cloud provider VM.
- A listening client:
- Any computer connected to the internet, but not reachable from outside its own network, works.
- This can e.g. be your friends laptop behind their router (firewall + NAT).
- This can e.g. be some cloud provider VM, shielded from incoming connections e.g. via Linux’s UFW on the same machine.
- Don’t use a machine that is in the same network as the dialing client. (This would require NAT hairpinning.)
- A dialing client:
- Like the above, any computer connected to the internet, but not reachable from the outside.
- Your local machine will likely fulfill these requirements.
Setting up the relay server
Hole punching requires a public relay node for the two private nodes to coordinate their hole punch via. For that we need a public server somewhere in the Internet. In case you don’t have one already, any cloud provider VM will do.
Either on the server directly, or on your local machine, compile the example relay server:
## Inside the rust-libp2p repository.
cargo build --example relay_v2 -p libp2p-relay
You can find the binary at target/debug/examples/relay_v2
. In case you built it locally, copy
it to your server.
On your server, start the relay server binary:
./relay_v2 --port 4001 --secret-key-seed 0
Now let’s make sure that the server is public, in other words let’s make sure one can reach it
through the Internet. First, either manually replace $RELAY_SERVER_IP
in the following
commands or export RELAY_SERVER_IP=ipaddr
with the appropriate relay server ipaddr
in
the dailing client and listening client.
Now, from the dialing client:
-
Test that you can connect on Layer 3 (IP).
ping $RELAY_SERVER_IP
-
Test that you can connect on Layer 4 (TCP).
telnet $RELAY_SERVER_IP 4001
-
Test that you can connect via libp2p using
libp2p-lookup
.## For IPv4 libp2p-lookup direct --address /ip4/$RELAY_SERVER_IP/tcp/4001 ## For IPv6 libp2p-lookup direct --address /ip6/$RELAY_SERVER_IP/tcp/4001
The libp2p-lookup output should look something like:
$ libp2p-lookup direct --address /ip4/111.11.111.111/tcp/4001
Lookup for peer with id PeerId("12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN") succeeded.
Protocol version: "/TODO/0.0.1"
Agent version: "rust-libp2p/0.36.0"
Observed address: "/ip4/22.222.222.222/tcp/39212"
Listen addresses:
- "/ip4/127.0.0.1/tcp/4001"
- "/ip4/111.11.111.111/tcp/4001"
- "/ip4/10.48.0.5/tcp/4001"
- "/ip4/10.124.0.2/tcp/4001"
Protocols:
- "/libp2p/circuit/relay/0.2.0/hop"
- "/ipfs/ping/1.0.0"
- "/ipfs/id/1.0.0"
- "/ipfs/id/push/1.0.0"
Setting up the listening client
Either on the listening client machine directly, or on your local machine, compile the example DCUtR client:
## Inside the rust-libp2p repository.
cargo build --example client -p libp2p-dcutr
You can find the binary at target/debug/examples/client
. In case you built it locally, copy
it to your listening client machine.
On the listening client machine:
RUST_LOG=info ./client --secret-key-seed 1 --mode listen --relay-address /ip4/$RELAY_SERVER_IP/tcp/4001/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN
[2022-05-11T10:38:52Z INFO client] Local peer id: PeerId("XXX")
[2022-05-11T10:38:52Z INFO client] Listening on "/ip4/127.0.0.1/tcp/44703"
[2022-05-11T10:38:52Z INFO client] Listening on "/ip4/XXX/tcp/44703"
[2022-05-11T10:38:54Z INFO client] Relay told us our public address: "/ip4/XXX/tcp/53160"
[2022-05-11T10:38:54Z INFO client] Told relay its public address.
[2022-05-11T10:38:54Z INFO client] Relay accepted our reservation request.
[2022-05-11T10:38:54Z INFO client] Listening on "/ip4/$RELAY_SERVER_IP/tcp/4001/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/XXX"
Now let’s make sure that the listening client is not public, in other words let’s make sure one can not reach it directly through the Internet. From the dialing client test that you can not connect on Layer 4 (TCP):
telnet $LISTENING_CLIENT_IP_OBSERVED_BY_RELAY 53160
Connecting to the listening client from the dialing client
RUST_LOG=info ./client --secret-key-seed 2 --mode dial --relay-address /ip4/$RELAY_SERVER_IP/tcp/4001/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN --remote-peer-id 12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X
You should see the following logs appear:
-
The dialing client establishing a relayed connection to the listening client via the relay server. Note the
/p2p-circuit
protocol in theMultiaddr
.ⓘ[2022-01-30T12:54:10Z INFO client] Established connection to PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") via Dialer { address: "/ip4/$RELAY_PEER_ID/tcp/4001/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X", role_override: Dialer }
-
The listening client initiating a direct connection upgrade for the new relayed connection. Reported by
dcutr
throughEvent::RemoteInitiatedDirectConnectionUpgrade
.ⓘ[2022-01-30T12:54:11Z INFO client] RemoteInitiatedDirectConnectionUpgrade { remote_peer_id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"), remote_relayed_addr: "/ip4/$RELAY_PEER_ID/tcp/4001/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X" }
-
The direct connection upgrade, also known as hole punch, succeeding. Reported by
dcutr
throughEvent::RemoteInitiatedDirectConnectionUpgrade
.ⓘ[2022-01-30T12:54:11Z INFO client] DirectConnectionUpgradeSucceeded { remote_peer_id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") }