udp-holepunch - Python script for UDP holepunching using a webserver. With an example of SSH using this and sctp-echo.

git clone https://benconnors.ca/git-repos/udp-holepunch

About | Log | Files | Refs

commit 1357791f0307bb465ff8bdb4e7562c6eecbc870f
parent 6778edcce27c74631f2d48a100a7fc34be1130fb
Author: Ben Connors <benconnors@outlook.com>
Date:   Wed,  9 Sep 2026 15:25:42 -0400

Make ack timout configurable; fix README

Diffstat:
MREADME.md | 2+-
Mssh_holepunch_client.py | 7+++++++
Mssh_holepunch_server.py | 7+++++++
Mudp_holepunch.py | 4++--
4 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md @@ -68,7 +68,7 @@ The client can sign the information it puts to the webserver using an SSH key (s Signing and verifying with SSH keys can be done via `ssh-keygen` without any external tools like GnuPG, so this does not pull in any additional dependencies. ## Example: SSH without open ports -We can use holepunching and [sctp-echo](/cgit/sctp-echo) to establish a connection to an SSH server without opening/forwarding any ports on the server side. +We can use holepunching and [sctp-echo](/git/sctp-echo) to establish a connection to an SSH server without opening/forwarding any ports on the server side. The ideal simple solution is, once UDP holepunching is established as above, to use `socat` on each end: ``` diff --git a/ssh_holepunch_client.py b/ssh_holepunch_client.py @@ -40,6 +40,12 @@ if __name__ == "__main__": default=None, help="Path to the SSH key used to sign postings on the webserver (can be different from the login key" ) + parser.add_argument( + "--ack-timeout", + type=int, + default=60, + help="Timeout when waiting for holepunching to be established", + ) parser.add_argument( "server_name", @@ -64,6 +70,7 @@ if __name__ == "__main__": args.server_path, no_ack=args.skip_ack, key_path=args.signing_key, + timeout=args.ack_timeout, ) ## 2. Run the client diff --git a/ssh_holepunch_server.py b/ssh_holepunch_server.py @@ -41,6 +41,12 @@ if __name__ == "__main__": help="Skip hello packet acknowledgement to finalize holepunching (a couple of the first packets down the line may get lost)", ) parser.add_argument( + "--ack-timeout", + type=int, + default=60, + help="Timeout when waiting for holepunching to be established", + ) + parser.add_argument( "-e", "--sctp-echo", default="sctp_echo", help="Path to invoke sctp_echo" @@ -76,6 +82,7 @@ if __name__ == "__main__": args.server_path, no_ack=args.skip_ack, allowed_keys=allowed_keys, + timeout=args.ack_timeout, ) if ret is None: diff --git a/udp_holepunch.py b/udp_holepunch.py @@ -178,7 +178,7 @@ def parse_packet(data): return source_ip, int(source_port) -def udp_holepunch_client(local_port, server_name, server_port, server_path, no_ack=False, key_path=None): +def udp_holepunch_client(local_port, server_name, server_port, server_path, no_ack=False, key_path=None, timeout=60): """Serve as the UDP holepunching "client". Post a message to `server_path` on `server_name` via SSH, then listen using `tcp_dump` to find @@ -336,7 +336,7 @@ rm {server_path} ## Now we're done with the intermediate: talk directly to the real server other_addr = (other_ip, other_port) - udp_holepunch_loop(c, other_addr, no_ack=no_ack) + udp_holepunch_loop(c, other_addr, no_ack=no_ack, timeout=timeout) c.close()