| udp-holepunch - Python script for UDP holepunching using a webserver. With an example of SSH using this and sctp-echo. |
ssh_holepunch_server.py (3442B) - raw
1 #!/usr/bin/env python3 2 3 import argparse as ap 4 import subprocess as subp 5 import sys 6 7 from udp_holepunch import udp_holepunch_server 8 9 if __name__ == "__main__": 10 parser = ap.ArgumentParser( 11 description="Run SSH through UDP with holepunching (server)" 12 ) 13 14 parser.add_argument( 15 "-l", "--local-port", 16 type=int, 17 default=0, 18 help="Fixed internal UDP port for us to use (defaults to 0 = auto-assigned by system)" 19 ) 20 parser.add_argument( 21 "--local-ip", 22 default="0.0.0.0", 23 help="Local IP to listen on (defaults to 0.0.0.0 = all)", 24 ) 25 parser.add_argument( 26 "-p", "--server-port", 27 type=int, 28 default=0, 29 help="Fixed UDP port to use to start holepunching (defaults to 0 to use our external UDP port number)" 30 ) 31 parser.add_argument( 32 "-t", "--timeout", 33 type=int, 34 default=60, 35 help="Inactivity timeout once holepunching has been established (defaults to 60)", 36 ) 37 parser.add_argument( 38 "-s", "--skip-ack", 39 action="store_true", 40 default=False, 41 help="Skip hello packet acknowledgement to finalize holepunching (a couple of the first packets down the line may get lost)", 42 ) 43 parser.add_argument( 44 "--ack-timeout", 45 type=int, 46 default=60, 47 help="Timeout when waiting for holepunching to be established", 48 ) 49 parser.add_argument( 50 "-e", "--sctp-echo", 51 default="sctp_echo", 52 help="Path to invoke sctp_echo" 53 ) 54 parser.add_argument( 55 "--ssh-port", 56 type=int, 57 default=22, 58 help="SSH port on the local machine (defaults to 22)", 59 ) 60 parser.add_argument( 61 "-k", "--allowed-keys", 62 type=str, 63 default=None, 64 help="Comma-separated list of allowed SSH public keys to verify postings on the webserver, as stored in e.g. authorized_keys" 65 ) 66 67 parser.add_argument( 68 "server_path", 69 help="Path of file to write on the intermediate server (e.g. /var/www/inter)" 70 ) 71 72 args = parser.parse_args() 73 74 allowed_keys = None 75 if args.allowed_keys is not None: 76 allowed_keys = args.allowed_keys.split(',') 77 78 ## 1. Holepunch 79 ret = udp_holepunch_server( 80 args.local_port, 81 args.server_port, 82 args.server_path, 83 no_ack=args.skip_ack, 84 allowed_keys=allowed_keys, 85 timeout=args.ack_timeout, 86 ) 87 88 if ret is None: 89 ## Nothing on the intermediate server, just exit 90 sys.exit(0) 91 92 local_port, remote_ip, remote_port = ret 93 94 ## 2. Run the server 95 server = subp.Popen( 96 [ 97 args.sctp_echo, 98 "-s", 99 "0.0.0.0", ## Local IP 100 str(local_port), ## Local UDP 101 str(local_port), ## Local SCTP = UDP 102 remote_ip, ## Remote IP 103 str(remote_port), ## Remote SCTP port 104 ], 105 stdout=subp.PIPE, 106 stdin=subp.PIPE, 107 ) 108 109 ## 3. Use socat to make the actual connection 110 socat = subp.Popen( 111 [ 112 "socat", 113 "-T", str(args.timeout), 114 "-", 115 "TCP4:127.0.0.1:%d" % args.ssh_port, 116 ], 117 stdin=server.stdout, 118 stdout=server.stdin, 119 ) 120 121 ## Communicate with socat; this lets us bypass the fact that sctp-echo doesn't 122 ## have timeout functionality 123 socat.wait() 124 server.terminate()