Skip to content

discv5: NAT traversal via Rendezvous protocol [WIP] #207

@pipermerriam

Description

@pipermerriam

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 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions