discv5: NAT traversal via Rendezvous protocol [WIP]
- Dominant language
- JavaScript
- Stars
- 1.1k
- Forks
- 297
- Avg merge
- 5h 50m
- Merged PRs (30d)
- 1
Description
This issue proposes a mechanism for NAT traversal via UDP hole punching.
This issue borrows from https://github.com/ethereum/portal-network-specs/issues/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 in `E` then the node is **not** behind a NAT
- If a node does not receive any packets with `(ip_address, port)` values that are not in `E` within 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:
1. The "initiator" node learns about the "receiver" node through a FINDNODES/FOUNDNODES interaction with the "rendezvous" node.
2. The "initiator sends a RELAYREQUEST to the "rendevous" node with payload: `{from_node_enr: initiator_enr, to_node_id: receiver_node_id}`
3. The "rendezvous" node, upon receiving the RELAYREQUEST from the "initiator" node, sends the same RELAYREQUEST message to the "receiver" node.
4. 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).
5. The "rendezvous" node, upon receiving the RELAYRESPONSE from the "receiver" node, accepting the request, will then send the same RELAYRESPONSE message to the "initiator".
6. 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.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.