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

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()