-
Notifications
You must be signed in to change notification settings - Fork 288
Description
This issue proposes a mechanism for NAT traversal via UDP hole punching.
This issue borrows from ethereum/portal-network-specs#144 which in-turn borrows from https://blog.ipfs.io/2022-01-20-libp2p-hole-punching/
Participants
This mechanism involves communication between three nodes:
- Initiator: a node that is behind a NAT, trying to establish a session with the "receiver" node
- Receiver: a node that is behind a NAT
- Rendezvous: a node that is able to communicate with both "initiator" and "receiver"
Detecting whether you are behind a NAT
Borrowed from: https://twurst.com/articles/stun-without-trust.html#org92b7214
A node in the network should maintain a set E which contains all of the (ip_address, port) values for outbound packets that have been sent by this node.
When receiving a packet, a node should check whether the packet's (ip_address, port) are contained in the set E.
- If a node receives a packet such that the
(ip_address, port)are not inEthen the node is not behind a NAT - If a node does not receive any packets with
(ip_address, port)values that are not inEwithin a reasonable amount of time, then the node should assume that they are behind a NAT.
We suggest 2 minutes as a reasonable amount of time before determining that the node is behind a NAT.
For practical purposes, an LRU cache should be used to constrain the overall size of the set
E
Signalling whether you are behind a NAT
We define a new field in the ENR with the key "nat".
- If the node does not know whether it is behind a NAT, this key should be omitted from the ENR
- If the node is not behind a NAT, the value of this key should be set to
0 - If the node wishes to signal that it is behind a NAT, the value of this key should be set to
1
Traversing the NAT
We define two new message types:
- RELAYREQUEST
- RELAYRESPONSE
# RELAYREQUEST
relay_request := SSZContainer(from_node_id: uint256, to_node_id: uint256)
# RELAYRESPONSE
relay_response := SSZContainer(response: uint8)
The rendezvous protocol works as follows:
- The "initiator" node learns about the "receiver" node through a FINDNODES/FOUNDNODES interaction with the "rendezvous" node.
- The "initiator sends a RELAYREQUEST to the "rendevous" node with payload:
{from_node_enr: initiator_enr, to_node_id: receiver_node_id} - The "rendezvous" node, upon receiving the RELAYREQUEST from the "initiator" node, sends the same RELAYREQUEST message to the "receiver" node.
- The "receiver" node, upon receiving the RELAYREQUEST from the "rendezvous" node, responds with a RELAYRESPONSE with the payload
{response: 1}to signal that they have accepted this request. They may alternately respond with{response: 0}if they wish to reject the request. The "receiver" node will also send a PING message to the "initiator" node (this triggers the receiver's NAT to allow and route incoming packets from the initiator's ip/port). - The "rendezvous" node, upon receiving the RELAYRESPONSE from the "receiver" node, accepting the request, will then send the same RELAYRESPONSE message to the "initiator".
- The "initiator" node, upon receiving the RELAYRESPONSE accepting the connection, should then send a PING message to the "receiver" node. (this triggers the initiator's NAT to allow and route incoming pckets from the receiver's ip/port)
TODO: diagram message flow... define edge cases like timeouts and how nodes should behave.
TODO- finish definition of the protocol and convert this to a PR towards the spec so that people can comment on individual lines.