Go to file
Christian Ulrich 383260c95e
first implementation of UdpPuncher (untested)
2020-10-25 21:57:09 +01:00
examples first implementation of UdpPuncher (untested) 2020-10-25 21:57:09 +01:00
README.md adapt punchd messages to README 2020-10-22 00:22:11 +02:00
asyncutils.nim need to unregister an close all events (fixes "too many open files" error) 2020-10-06 10:04:21 +02:00
ip_packet.nim use random ID in IP header 2020-10-24 19:15:46 +02:00
message.nim Revert "allow empty seqs" 2020-10-14 20:32:44 +02:00
network_interface.nim rename fromIpAddress -> getNetworkInterface 2020-10-10 12:31:18 +02:00
port_prediction.nim move port prediction into dedicated module 2020-10-14 18:40:58 +02:00
punchd.nim first implementation of UdpPuncher (untested) 2020-10-25 21:57:09 +01:00
puncher.nim store transport protocol in an Attempt; consider protocol when comparing attempts; puncher.getProtocol not needed anymore 2020-10-25 10:46:29 +01:00
raw_socket.nim move injectTcpPacket to raw_socket module 2020-10-22 17:04:20 +02:00
tcp_nutss.nim store transport protocol in an Attempt; consider protocol when comparing attempts; puncher.getProtocol not needed anymore 2020-10-25 10:46:29 +01:00
tcp_syni.nim store transport protocol in an Attempt; consider protocol when comparing attempts; puncher.getProtocol not needed anymore 2020-10-25 10:46:29 +01:00
udp.nim first implementation of UdpPuncher (untested) 2020-10-25 21:57:09 +01:00
utils.nim close socket if exception occurs too 2020-10-12 21:32:43 +02:00

README.md

Introduction

punchd (short for "hole punching daemon") provides TCP (and soon UDP) hole punching as a service to applications. This allows two applications on different hosts, both behind NAT and / or firewalls, to establish a direct communication channel. The motivation is to help peer-to-peer applications improve their connectivity.

How does it work?

The idea is to try out a sequence of well known hole punching techniques until one of them succeeds. punchd implements the following techniques:

  • SYNI (TCP hole punching based on SYN injection), DOI: 10.1109/NCA.2011.66
  • NUTSS (TCP hole punching, the non-spoofing approach described in section 4.2.2 of the paper), DOI: 10.1145/1016707.1016715

The assumption of those techniques is that there is a side-channel (i.e. a rendezvous server) between the two hosts. A peer that wants to be available for hole punching needs to constantly be connected to the rendezvous server so it can be notified by other peers about new hole punching attempts. In addition it needs to provide an endpoint (public IP address and port) to other peers before those can initiate the hole punching. See the example applications for a naive rendezvous server implementation.

How can I use punchd?

Applications can communicate with punchd through a unix domain socket. An application can call one of punchd's API functions to either start a hole punching attempt (initiate) or react to an attempt started by another peer (respond). After calling an API function punchd will report back a status (either ok, progress or error). After a successful hole punching attempt, punchd will pass a socket to the application which can be used immediately to communicate with the other peer. The full punchd API is described in section The punchd API.

The figure below shows the workflow of a successful hole punching attempt between two peers A (initiator) and B (responder).

                   +--------------------------+
                   |  Rendezvous server (RS)  |
                   +--------------------------+
                        Λ                |
                        |                4 
                        3                |
                        |                V
 +----------------------------+    +----------------------------+
 | Application at peer A (AA) |    | Application at peer B (AB) |
 +----------------------------+    +----------------------------+
            |  Λ  Λ                             |  Λ
            1  |  |                             5  |
            |  2  6                             |  7
            V  |  |                             V  |
 +-------------------------+          +-------------------------+
 |  punchd at peer A (PA)  |          |  punchd at peer B (PB)  |
 +-------------------------+          +-------------------------+
  1. initiate: AA asks PA to initiate the hole punching to AB
  2. progress: PA reports progress
  3. AA sends notification about the hole punching attempt to RS
  4. RS forwards notification to AB
  5. respond: AB asks PB to respond to the hole punching attempt
  6. ok: PA reports success and passes a connected socket to AA
  7. ok: PB reports success and passes a connected socket to AB

Installation

No packages exist yet, so installation has to be done manually to the desired location after running nimble install. Starting punchd is as simple as

$ sudo ./punchd

The punchd API

Applications can exchange messages with punchd through a unix domain socket. By default this is /tmp/punchd.socket. The message format is described in the next section. After that an example message exchange is given. Finally we need to understand how to receive a successfully connected socket from punchd.

The message format

A punchd message is a string with a maximum length of 500 bytes followed by a line feed character ('\n'). The string consists of fields (positional arguments) separated by '|'.

An application can send two types of messages (requests) to punchd: "initiate" messages (ask punchd to start a new hole punching attempt) or "respond" messages (ask punchd to react to the hole punching attempt started by another peer). These messages have the following formats:

initiate|ID|TECHNIQUE|IP_FROM|PORTS_FROM|IP_TO|PORTS_TO
respond|ID|TECHNIQUE|IP_FROM|PORTS_FROM|IP_TO|PORTS_TO|EXTRA_ARGS

The fields have the following semantics:

  • ID: the message ID (random string)
  • TECHNIQUE: the hole punching technique, one out of "tcp-syni" and "tcp-nutss"
  • IP_FROM: the public IP address of the initiating peer
  • PORTS_FROM: a list of previously used source ports of the initiating peer
  • IP_TO: the public IP address of the responding peer
  • PORTS_TO: a list of previously used source ports of the responding peer
  • EXTRA_ARGS: technique-specific arguments

For each request (identified by a message ID) punchd will report back a status (containing the same message ID). The status can be "ok" (hole punching was successful), "progress" (hole punching is in progress, the other peer has to be notified) or "error" (the hole punching failed). These status messages have the following formats:

ok|ID
progress|ID|TECHNIQUE|IP_FROM|PORTS_FROM|IP_TO|PORTS_TO|EXTRA_ARGS
error|ID|ERROR_MSG

The fields in the "progress" message are the same that can be found in the "respond" message described above. This is because the application is expected to forward them to the other peer through the rendezvous server so the other peer can call "respond" on its punchd instance.

Example

An example session between two applications AA and AB on two different peers (AA has the endpoint 1.2.3.4:1234; AB has the endpoint 5.6.7.8:5678) and their respective punchd instances (PA and PB) might look like this:

AA -> PA: initiate|645|tcp-syni|1.2.3.4|1234|5.6.7.8|5678,5679,5680
AA <- PA: progress|645|tcp-syni|1.2.3.4|1234|5.6.7.8|5678,5679,5680|344567,644456,345365
--- AA sends notification to AB ---
AB -> PB: respond|538|tcp-syni|1.2.3.4|1234|5.6.7.8|5678,5679,5680|344567,644456,345365
AB <- PB: ok|538
AA <- PA: ok|645

Receiving sockets using sendmsg/recvmsg

When punchd successfully has punched a hole, i.e. has a socket that is connected to the other peer, it will report back status "ok" to the application. But how can the application now use the socket? The answer is: punchd has to pass the corresponding file descriptor to the application's process. On Unix systems that is possible using the system calls sendmsg (on punchd's side) and recvmsg (on the application's side). This allows punchd passing a control message, a.k.a. ancillary message, of type SCM_RIGHTS along with the actual contents of the status message. This control message will contain the file descriptor.

The mechanism of passing file descriptors to other processes is described in the man pages unix(7), cmsg(3) and recv(2). The example applications can be used as a reference too. After receiving the file descriptor the application can immediately use it to send data to the other peer.

FAQ

  • Q: Why does punchd only offer a weird socket API and not REST? A: punchd needs to pass a file descriptor to the application. On Unix systems that is only possible using the sendmsg system call with an ancillary message of type SCM_RIGHTS on a unix domain socket. See the unix(7) man page.

  • Q: Why does punchd need root permissions? A: Most hole punching techniques require elevated privileges because they need raw sockets (e.g. for capturing TCP sequence numbers) or they need to create firewall rules (some techniques require sending out low-TTL packets; the resulting ICMP time-exceeded responses have to be filtered out or else TCP connections will fail). On linux punchd can run as a non-root user though if punchd is started with the CAP_NET_RAW and CAP_NET_ADMIN capabilities.

  • Q: Which one is the best hole punching technique? A: No hole punching technique will work for all environments. In fact there are environments where no hole punching will be possible at all. The goal of this project is to find a good sequence of hole punching techniques to be tried one after the other. A lot of research still has to be done. See the example applications for some proposed sequences.