172.16.0.1
172.16.0.2
Create and configure a tunnelled connection between client and server, via tun0
interfaces:
ssh
/etc/ssh/sshd_config
to include PermitRootLogin yes PermitTunnel yes
service ssh reload
does not appear to be sufficient) sudo service ssh stop; sudo service ssh start
-w
optionssh root@server.hostname.com -w 0:0
(0:0
specifies that both local and remote ends will create tun0
interfaces)
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
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
ping 172.16.0.2
and this time it should respondConfigure IPv4 (ICMP+TCP+UDP) forwarding and Network Address Translation (NAT):
# 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
route add 10.0.0.0/8 -interface tun0
(you can undo this by repeating the command with delete
in place of add
)
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.
cat /etc/resolv.conf
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:
.ssh/authorized_key
: https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keysMore thorough networking (Ethernet layer, instead of link layer): http://sgros.blogspot.co.uk/2011/11/ssh-vpns-bridged-connection-to-lan.html
ssh-keygen -f ~/.ssh/id_rsa_vpn -N ''
( \ 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
~/.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
~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
chmod a+x ~root/.ssh/vpn.sh
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
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
Fast Reverse Proxy (frp) is a pair of standalone executables which can be used to expose services behind NAT. I like it because:
Download and unpack 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