punchbuggy
Lightweight, zero-dependency toolkit for UDP hole punching.
Overview
punchbuggy
allows users, presumably on NAT-ed home or office networks, to send UDP datagrams directly to each other.
The user runs a client on their local machine, which establishes a secure connection (TLS) to a rendezvous server. The server responds with the client's contact information (i.e. public IPv4 address and port) and a peer's contact information (if requested). The client and its peer send UDP datagrams to each other and notify the rendezvous server once they receive a datagram from the other. Finally, the rendezvous server tells boths clients that they received datagrams from the other (e.g. the "hole punching" was successful). Now, the users can send datagrams directly to each other!
Install
$ npm i punchbuggy
Usage
You can run the client and server from the command line.
Server
First, you'll need to generate a private key and self-signed TLS certificate:
$ npm run cert:generate
This is used to encrypt traffic between clients and the server.
Once the credentials are generated, you can start the server:
$ [ADDRESS=<ipv4_address>] [PORT=<port>] punchbuggy listen
ADDRESS
and PORT
specify the local IP address and port to listen on, respectively.
ADDRESS
defaults to "0.0.0.0" and PORT
defaults to 12435.
Docker
You can also create a Docker image for the server and run it in a container.
Build
Build the Docker image for the server:
npm run server:build
Start
Run a container with the image you built previously:
npm run server:start
Stop
Stop and remove the server container:
npm run server:stop
Logs
Tail the server container logs:
npm run server:logs
Client
Dial a peer
Perform a synchronized, hole-punching procedure to send UDP datagrams directly to a peer:
$ [ENABLE_CHAT=true] [SERVER_ADDRESS=<ipv4_address>] [SERVER_PORT=<port>] punchbuggy dial
SERVER_ADDRESS
and SERVER_PORT
indicate the remote server address and port to connect to, respectively.
SERVER_ADDRESS
defaults to "127.0.0.1" and SERVER_PORT
defaults to 12435.
You'll be prompted to enter a peer's session ID. You must ask the user for their session ID out-of-band. Similarly, you must share your session ID since they run the same command at roughly the same time. If the dial procedure isn't completed on both ends within a matter of minutes, it will time out.
If the command completes successfully and ENABLE_CHAT
is "true", users can send plaintext messages to each other from the command line.
Otherwise, the UDP socket is closed, assuming other applications facilitate further peer communication. punchbuggy
specifies the mapping of local port to remote address + port so the user can tell other applications where to bind UDP sockets and where to send datagrams.
Here's a simple example with netcat:
Alice
Alice runs a netcat listener on her local port:
...
[CLIENT] Connection to peer established
[CLIENT] Closed socket
[CLIENT] You may continue communication over UDP
[CLIENT] Mapping: 0.0.0.0:47691 -> 1.2.3.4:37867
$ nc -ul 47691
Bob
Bob binds a socket to his local port and sends a message to Alice:
...
[CLIENT] Connection to peer established
[CLIENT] Closed socket
[CLIENT] You may continue communication over UDP
[CLIENT] Mapping: 0.0.0.0:37867 -> 6.7.8.9:47691
$ echo hello | nc -up 37867 6.7.8.9 47691
Alice should see the greeting in her terminal!
Note: if applications/users don't regularly send messages, the NAT port mappings won't be perserved and the holes will become "unpunched".
Test network support for UDP hole punching
Some NAT routers don't support UDP hole punching.
punchbuggy
has a test
command that indicates whether dialing a peer should work on your current network:
$ [SERVER1_ADDRESS=<ipv4_address>] [SERVER1_PORT=<port>] [SERVER2_ADDRESS=<ipv4_address>] [SERVER2_PORT=<port>] punchbuggy test
You must specify 2 different IPv4 addresses to determine whether your public port (from the server's perspective) is consistent and thus conducive to UDP hole punching.
Tests
Run the unit and integration tests:
$ npm test
Linting
npm run lint
Contributing
punchbuggy
being buggy? Or want a feature added?
Feel free to open an issue and/or create a pull request.