====== VPN over SSH ====== * **Server**: GNU/Linux (Ubuntu 12.04) -- ''172.16.0.1'' * **Client**: MacOS/BSD (El Capitan, v10.11.5) -- ''172.16.0.2'' Create and configure a tunnelled connection between client and server, via ''tun0'' interfaces: - Go install http://tuntaposx.sourceforge.net/download.xhtml on the Mac, it is needed by ''ssh'' - SSH into the server and edit ''/etc/ssh/sshd_config'' to include PermitRootLogin yes PermitTunnel yes - Restart SSHd on the server (''service ssh reload'' does not appear to be sufficient) sudo service ssh stop; sudo service ssh start - Log out of server (need to reconnect to make use of config changes) - **As root** on the client machine, SSH into the root account on the server with tun devices enabled via ''-w'' optionssh root@server.hostname.com -w 0:0 (''0:0'' specifies that both local and remote ends will create ''tun0'' interfaces) - Within the resulting root shell on the **server**, configure the new ''tun0'' network interface: ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2 ifconfig tun0 ping 172.16.0.1 # Check tun0 has an IP address ping 172.16.0.2 # Should fail, as we've not yet configured the client's tun0 - In a root shell on the **client**, configure the new ''tun0'' network interface: ifconfig tun0 inet 172.16.0.2 172.16.0.1 ifconfig tun0 ping 172.16.0.2 # Check tun0 has an IP address ping 172.16.0.1 # Check we can communicate with remote end (server) via tun0 - Back in the root shell on the **server**, repeat ''ping 172.16.0.2'' and this time it should respond - The tunnel is now configured. It will remain so until the SSH session is closed. Configure IPv4 (ICMP+TCP+UDP) forwarding and Network Address Translation (NAT): - In the root shell on the **server** (only needs to be done one per boot): # Prepare networking stack for use by forced commands in # /root/.ssh/authorized_keys that creates a point-to-point network (via tun0) # between 172.16.0.1 (this host) and 172.16.0.2 (remote end). # We then want to enabling forwarding of IPv4 traffic, i.e. we want to act as a # router. We enable this in the kernel, and then ensure traffic originating # from the remote side of the point-to-point link is accepted, and any # responses are likewise accepted echo 1 > /proc/sys/net/ipv4/ip_forward /sbin/iptables -F /sbin/iptables -A FORWARD ! --source 172.16.0.2 --destination 172.16.0.2 \ -m state --state RELATED,ESTABLISHED -j ACCEPT /sbin/iptables -A FORWARD --source 172.16.0.2 ! --destination 172.16.0.2 -j ACCEPT # Any traffic originating from the remote side should go through Network # Address Translation (NAT), so responses from (e.g.) DNS servers are sent to # this host, so *we* can forward it to the remote end. This is the MASQUERADE # rule. /sbin/iptables -t nat -F /sbin/iptables -t nat -A POSTROUTING ! --destination 172.16.0.2 -j MASQUERADE # Monitor packets watch -n0.5 -d ifconfig tun0 - As root on the **client**: route add 10.0.0.0/8 -interface tun0 (you can undo this by repeating the command with ''delete'' in place of ''add'') - IPv4 forwarding via ''tun0'' is in effect. Note: The changes made to the server persist after the SSH session has ended. To get name resolution working, you need to configure the client to use a DNS server at the remote end, e.g. - Discover the DNS nameservers used by the **server**: cat /etc/resolv.conf - Add these to the **client** system: networksetup -setdnsservers Wi-Fi 10.1.2.24 10.1.2.23 192.168.1.253 8.8.8.8 8.8.4.4 This step must be manually undone (e.g. after closing the SSH session) by running networksetup -setdnsservers Wi-Fi Empty You may also want to add your remote system's DNS search domains, e.g.: networksetup -setsearchdomains Wi-Fi site.example.com example.com local Again, this must be manually undone after you close the VPN connection: sudo networksetup -setsearchdomains Wi-Fi Empty Most useful guides: * http://ubuntuforums.org/showthread.php?t=926435&page=3&p=7886699#post7886699 * http://kovyrin.net/2006/03/17/how-to-create-ip-ip-tunnel-between-freebsd-and-linux/ * https://help.ubuntu.com/community/SSH_VPN * https://blog.brixandersen.dk/2006/10/23/vpn-using-openssh-and-tun4-under-freebsd/ * http://osxdaily.com/2015/06/02/change-dns-command-line-mac-os-x/ * NAT: http://www.revsys.com/writings/quicktips/nat.html * SSH, including forced-commands via ''.ssh/authorized_key'': https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keys More thorough networking (Ethernet layer, instead of link layer): http://sgros.blogspot.co.uk/2011/11/ssh-vpns-bridged-connection-to-lan.html ===== Automating via SSH configuration files ===== All commands here are run as **root** on the client system - **As root** on your client system, generate a new SSH keypair to use for VPN. ssh-keygen -f ~/.ssh/id_rsa_vpn -N '' - Install new public key into remote system, and prefix with a ForeCommand which is run whenever this key is used to authenticate:( \ printf 'tunnel="0",command="fuser -k /dev/net/tun; ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2" ' ; \ cat ~/.ssh/id_rsa_test.pub \ ) | ssh root@www.robmeerman.co.uk tee -a .ssh/authorized_keys - Configure client via ''~/.ssh/config''. Add the following to the end of ''.ssh/config'' (create it if it does not exist) and replace ''$SERVER'' with your server's hostname: Host vpn Hostname $SERVER User root # Remote's .ssh/authorised_keys entry for this identity is prefixed with: # tunnel="0",command="fuser -k /dev/net/tun; ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2" ssh-rsa IdentityFile ~root/.ssh/id_rsa_vpn Tunnel yes TunnelDevice 0:0 PermitLocalCommand yes LocalCommand ~root/.ssh/vpn.sh %h %T # Disable connection sharing, otherwise closing VPN may not actually reset # network settings because vpn.sh (cf. LocalCommand) continues to wait # for the `ssh` process to exit (which it may not if another session is # active) ControlPath none # Disable use of ssh-agent, as it seems to prevent our preferred identity # (cf. IdentityFile) being applied, which in turn means we don't trigger the # ForceCommand of the remote's authorized_keys file IdentityAgent none - Create a new script on your client machine at ''~root/.ssh/vpn.sh'' which configures your Mac to route traffic headed to your server via the current gateway, and then change the default gateway (that applies to all __other__ traffic) to go via the new SSH ''tun'' device at 172.16.0.1, then wait for the ''ssh'' process to exit before returning settings to normal: #!/bin/bash # .ssh/config: LocalCommand vpn.sh %h %T REMOTE_HOST=$1 TUNNEL_DEVICE=$2 ifconfig $TUNNEL_DEVICE inet 172.16.0.2 172.16.0.1 ROUTE=$(route get $REMOTE_HOST) GATEWAY=$(sed -ne 's/^ *gateway: //p' <<<"$ROUTE") INTERFACE=$(sed -ne 's/^ *interface: //p' <<<"$ROUTE") route add $REMOTE_HOST $GATEWAY route add 10/8 $GATEWAY route change default 172.16.0.1 WAIT_PID=$PPID ( while kill -0 $WAIT_PID >/dev/null 2>&1; do sleep 0.5; done # The route gets deleted when the SSH tunnel closes gracefully and tun0 disappears route change default $GATEWAY route add default $GATEWAY route delete 10/8 $GATEWAY route delete $REMOTE_HOST $GATEWAY ) & This script is unlikely to work on other OS - Make the new script executable: chmod a+x ~root/.ssh/vpn.sh - Test it by running ssh vpn Sample session showing the output from the commands above: # ssh-keygen -f ~/.ssh/id_rsa_vpn -N '' Generating public/private rsa key pair. Your identification has been saved in /var/root/.ssh/id_rsa_vpn. Your public key has been saved in /var/root/.ssh/id_rsa_vpn.pub. The key fingerprint is: SHA256:4c8jh23lnMr7ZEmiDCCenKEEo6ROBDIku3XCmKLqqcw root@roberts-mbp The key's randomart image is: +---[RSA 2048]----+ |X+ | |OB | |Bo* o . | |** B . . . | |+.= . S . o | |. o * * o | |. = B B | |+ . = = | |oE +o. | +----[SHA256]-----+ # ( \ # printf 'tunnel="0",command="fuser -k /dev/net/tun; ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2" ' ; \ # cat ~/.ssh/id_rsa_test.pub \ # ) | ssh root@www.robmeerman.co.uk tee -a .ssh/authorized_keys tunnel="0",command="ifconfig tun0 inet 172.16.0.1 dstaddr 172.16.0.2" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+pPee+HqiExk28lwKGcjoAMnkWRVKoQsn8b+90ST3HteZq1oCKtig49YOtlXDZGma0vR/y9Xbelk26xJfZO32BR3GCPou6XYSU67qwC8wK256H0LfTUlquUufklmKd3BaKamAtXU0JwhVxQCFH0hToG6dgc0FLelqs1r8u6cPni1wTxaId6epHrYCBrKvP+fwYz0S0K3e2opcqZUTwMyPYwu280UxQr2HYvzykdoJeiJtsKgneFRxhX7gnlKCYoia0fToKHel24GfUFfqipFrJbsm8LDYuVh5KVgx1J1Hx19Fu0LM3IIqoXQESob91TjTx1bq41iIMZ0n0td5gDVj root@roberts-mbp # ssh vpn add host www.robmeerman.co.uk: gateway 10.1.36.1 add net 10: gateway 10.1.36.1 change net default: gateway 172.16.0.1 # Nothing further appears to happen. VPN is up and running! Try `traceroute # google.com` in another terminal to verify that the traffic is going via your # server and not its default route. # When all done, press ^C to kill the VPN and restore default settings. Your # prompt will return first, and *then* the clean-up code will execute and # print: ^C route: writing to routing socket: not in table change net default: gateway 10.1.36.1: not in table add net default: gateway 10.1.36.1 delete net 10: gateway 10.1.36.1 delete host www.robmeerman.co.uk: gateway 10.1.36.1 ===== systemd service ===== Configure a remote host, which lives behind a firewall, to maintain an SSH connection to my home network that provides a reverse tunnel back into `sshd` on the remote host. ''/etc/systemd/system/my-vpn.service'' [Unit] Description=SSH-based VPN After=network.target [Service] Restart=always # Disable rate-limiting, which may result in "giving up" StartLimitInterval=0 ExecStartPre=/bin/sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward" ExecStartPre=/sbin/iptables -A FORWARD ! --source 172.16.0.2 --destination 172.16.0.2 \ -m state --state RELATED,ESTABLISHED -j ACCEPT ExecStartPre=/sbin/iptables -A FORWARD --source 172.16.0.2 ! --destination 172.16.0.2 -j ACCEPT ExecStartPre=/sbin/iptables -t nat -A POSTROUTING ! --destination 172.16.0.2 -j MASQUERADE ExecStart=/usr/bin/ssh user@example.com -R10022:localhost:22 -N -oServerAliveInterval=15 -oExitOnForwardFailure=yes [Install] WantedBy=multi-user.target ===== KCP Tunnelling via Fast Reverse Proxy (frp) ===== [[https://github.com/fatedier/frp|Fast Reverse Proxy (frp)]] is a pair of standalone executables which can be used to expose services behind NAT. I like it because: * It supports [[https://github.com/skywind3000/kcp/blob/master/README.en.md|KCP protocol]]. * KCP reduces latency on lossy links by implementing error **correction** instead of just the error **detection** of TCP. This is achieved by using more bandwidth to send [[https://en.wikipedia.org/wiki/Erasure_code|erasure codes]] which can be used to recover lost packets (instead of requesting they be retransmitted which add round-trip delays). Similar to how CD-ROMs cope with scratches. * This is especially useful on cellular / mobile networks (3G, 4G, etc) * It can NAT-bust, even when **both** parties are behind NAT. * This does require a public, non-NAT, server both parties can initiate contact with Download and unpack [[https://github.com/fatedier/frp|Fast Reverse Proxy (frp)]] which contains two executables: ''frpc'' and ''frps''. I drive them with this bash script, which I call ''frp'': #!/bin/bash # # FRP: Fast Reverse Proxy (cf. https://github.com/fatedier/frp) # # XTCP: Creates a direct connection between hosts which are behind NAT gateways # by getting both to contact a public server they can both access, and then # (ab)using a UDP connection directly to one another. Because UDP is stateless, # both sides can send a packet to the other and thereby get their NAT gateway # to set up a session (temporary port forward) for any return traffic. Voila, # you now have holes in both NAT gateways and the clients (frpc) can talk to # each other directly. SERVER_PORT=29900 SERVER_ADDR=203.0.113.0:${SERVER_PORT} TOKEN=yourtokenhere SECRET_KEY=yoursecrethere case $1 in public-server) CMD=( ./frps --bind_udp_port=7001 --kcp_bind_port=${SERVER_PORT} --token=${TOKEN} );; ssh-server) CMD=( ./frpc xtcp # Public server details and auth token --server_addr=${SERVER_ADDR} --protocol=kcp --token=${TOKEN} # Proxy entry to publish for other hosts (also behind NAT) to # access, thus making us act as a server. --role=server --proxy_name=ssh_p2p --sk=${SECRET_KEY} # Service to connect incoming tunnelled connections to --local_ip=127.0.0.1 --local_port=22 );; ssh-client) CMD=( ./frpc xtcp # Public server details and auth token --server_addr=${SERVER_ADDR} --protocol=kcp --token=${TOKEN} # Proxy entry (published by another client) within the server we want to # use, and server secret key --server_name=ssh_p2p --sk=${SECRET_KEY} # As a visitor, we are trying to access something published by another # client (also behind NAT) with --role=server. "bind" parameters dictate # where to put the listening end of our P2P tunnel: apps on our network # will connect to this to be tunnelled through FRP and it's NAT hole. # # Also note that KCP protocol runs over UDP, but very few applications # support that directly, so the both ends of the KCP tunnel convert # to/from TCP. --role=visitor --bind_addr=127.0.0.1 --bind_port=29922 );; *) echo "Please provide exactly one argument: public-server, ssh-server, or ssh-client" exit 1 ;; esac cd $(dirname $0) exec "${CMD[@]}" and if you like, here's a systemd file for it, ''frp.service'': [Unit] Description=Fast Reliable Proxy Server After=network.target [Service] Type=simple User=nobody Restart=on-failure RestartSec=5s ExecStart=/opt/frp/frp public-server [Install] WantedBy=multi-user.target To install it: sudo ln -s $(readlink -f frp.service) /etc/systemd/system/ sudo systemctl daemon-reload # To notice the new symbolic link sudo systemctl enable frp.service # So it starts automatically on boot sudo systemctl start frp.service # .. one off start it now