User Tools

Site Tools



This shows you the differences between two versions of the page.

Link to this comparison view

vpn [2020/08/13 13:23] (current)
robm created
Line 1: Line 1:
 +====== VPN over SSH ======
 +  * **Server**: GNU/Linux (Ubuntu 12.04) -- ''​''​
 +  * **Client**: MacOS/BSD (El Capitan, v10.11.5) -- ''​''​
 +Create and configure a tunnelled connection between client and server, via ''​tun0''​ interfaces:
 +  - Go install http://​​download.xhtml on the Mac, it is needed by ''​ssh''​
 +  - SSH into the server and edit ''/​etc/​ssh/​sshd_config''​ to include <​code>​
 +PermitRootLogin yes
 +PermitTunnel yes
 +  - Restart SSHd on the server (''​service ssh reload''​ does not appear to be sufficient) <​code>​sudo service ssh stop; sudo service ssh start</​code>​
 +  - 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''​ option<​code>​ssh -w 0:​0</​code>​ (''​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: <​code>​
 +ifconfig tun0 inet dstaddr
 +ifconfig tun0
 +ping ​ # Check tun0 has an IP address
 +ping ​ # 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: <​code>​
 +ifconfig tun0 inet
 +ifconfig tun0
 +ping ​ # Check tun0 has an IP address
 +ping ​ # Check we can communicate with remote end (server) via tun0
 +   - Back in the root shell on the **server**, repeat ''​ping''​ 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): <​code>​
 +# Prepare networking stack for use by forced commands in
 +# /​root/​.ssh/​authorized_keys that creates a point-to-point network (via tun0)
 +# between (this host) and (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 --destination \
 +        -m state --state RELATED,​ESTABLISHED -j ACCEPT
 +/​sbin/​iptables -A FORWARD --source ! --destination -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 ​ -j MASQUERADE
 +# Monitor packets
 +watch -n0.5 -d ifconfig tun0
 +   - As root on the **client**: <​code>​
 +route add -interface tun0
 +</​code>​ (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**: <​code>​cat /​etc/​resolv.conf</​code>​
 +   - Add these to the **client** system: <​code>​networksetup -setdnsservers Wi-Fi</​code>​ This step must be manually undone (e.g. after closing the SSH session) by running <​code>​ networksetup -setdnsservers Wi-Fi Empty</​code>​
 +You may also want to add your remote system'​s DNS search domains, e.g.: <​code>​networksetup -setsearchdomains Wi-Fi local</​code>​ Again, this must be manually undone after you close the VPN connection: <​code>​sudo networksetup -setsearchdomains Wi-Fi Empty</​code>​
 +Most useful guides:
 +   * http://​​showthread.php?​t=926435&​page=3&​p=7886699#​post7886699
 +   * http://​​2006/​03/​17/​how-to-create-ip-ip-tunnel-between-freebsd-and-linux/​
 +   * https://​​community/​SSH_VPN
 +   * https://​​2006/​10/​23/​vpn-using-openssh-and-tun4-under-freebsd/​
 +   * http://​​2015/​06/​02/​change-dns-command-line-mac-os-x/​
 +   * NAT: http://​​writings/​quicktips/​nat.html
 +   * SSH, including forced-commands via ''​.ssh/​authorized_key'':​ https://​​community/​tutorials/​ssh-essentials-working-with-ssh-servers-clients-and-keys
 +More thorough networking (Ethernet layer, instead of link layer): http://​​2011/​11/​ssh-vpns-bridged-connection-to-lan.html
 +===== Automating via SSH configuration files =====
 +<note important>​All commands here are run as **root** on the client system</​note>​
 +  - **As root** on your client system, generate a new SSH keypair to use for VPN. <​code>​ssh-keygen -f ~/​.ssh/​id_rsa_vpn -N ''</​code>​
 +  - Install new public key into remote system, and prefix with a ForeCommand which is run whenever this key is used to authenticate:<​code>​( \
 +  printf '​tunnel="​0",​command="​ifconfig tun0 inet dstaddr"​ ' ; \
 +  cat ~/​.ssh/​ \
 +) | ssh tee -a .ssh/​authorized_keys</​code>​
 +  - 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: <​code>​Host vpn
 +  Hostname $SERVER
 +  User root
 +  # Remote'​s .ssh/​authorised_keys entry for this identity is prefixed with:
 +  # tunnel="​0",​command="​ifconfig tun0 inet dstaddr"​ ssh-rsa
 +  IdentityFile ~root/​.ssh/​id_rsa_vpn
 +  Tunnel yes
 +  TunnelDevice 0:0
 +  PermitLocalCommand yes
 +  LocalCommand ~root/​.ssh/​ %h %T
 +  # Disable connection sharing, otherwise closing VPN may not actually reset
 +  # network settings because (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</​code>​
 +  - Create a new script on your client machine at ''​~root/​.ssh/​''​ 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, then wait for the ''​ssh''​ process to exit before returning settings to normal: <​code>#​!/​bin/​bash
 +# .ssh/​config:​ LocalCommand %h %T
 +ifconfig $TUNNEL_DEVICE inet
 +ROUTE=$(route get $REMOTE_HOST)
 +GATEWAY=$(sed -ne 's/^ *gateway: //p' <<<"​$ROUTE"​)
 +INTERFACE=$(sed -ne 's/^ *interface: //p' <<<"​$ROUTE"​)
 +route add 10/8 $GATEWAY
 +route change default
 +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
 +) &</​code>​ This script is unlikely to work on other OS
 +  - Make the new script executable: <​code>​chmod a+x ~root/​.ssh/​</​code>​
 +  - Test it by running <​code>​ssh vpn</​code>​
 +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/​
 +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.    |
 +# ( \
 +#   ​printf '​tunnel="​0",​command="​ifconfig tun0 inet dstaddr"​ ' ; \
 +#   cat ~/​.ssh/​ \
 +# ) | ssh tee -a .ssh/​authorized_keys
 +tunnel="​0",​command="​ifconfig tun0 inet dstaddr"​ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+pPee+HqiExk28lwKGcjoAMnkWRVKoQsn8b+90ST3HteZq1oCKtig49YOtlXDZGma0vR/​y9Xbelk26xJfZO32BR3GCPou6XYSU67qwC8wK256H0LfTUlquUufklmKd3BaKamAtXU0JwhVxQCFH0hToG6dgc0FLelqs1r8u6cPni1wTxaId6epHrYCBrKvP+fwYz0S0K3e2opcqZUTwMyPYwu280UxQr2HYvzykdoJeiJtsKgneFRxhX7gnlKCYoia0fToKHel24GfUFfqipFrJbsm8LDYuVh5KVgx1J1Hx19Fu0LM3IIqoXQESob91TjTx1bq41iIMZ0n0td5gDVj root@roberts-mbp
 +# ssh vpn
 +add host​ gateway
 +add net 10: gateway
 +change net default: gateway
 +# Nothing further appears to happen. VPN is up and running! Try `traceroute
 +#` 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:
 +route: writing to routing socket: not in table
 +change net default: gateway not in table
 +add net default: gateway
 +delete net 10: gateway
 +delete host​ gateway
 +===== 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.
 +Description=SSH-based VPN
 +# Disable rate-limiting,​ which may result in "​giving up"
 +ExecStartPre=/​bin/​sh -c "echo 1 > /​proc/​sys/​net/​ipv4/​ip_forward"​
 +ExecStartPre=/​sbin/​iptables -A FORWARD ! --source --destination \
 +        -m state --state RELATED,​ESTABLISHED -j ACCEPT
 +ExecStartPre=/​sbin/​iptables -A FORWARD --source ! --destination -j ACCEPT
 +ExecStartPre=/​sbin/​iptables -t nat -A POSTROUTING ! --destination ​ -j MASQUERADE
 +ExecStart=/​usr/​bin/​ssh -R10022:​localhost:​22 -N -oServerAliveInterval=15 -oExitOnForwardFailure=yes
vpn.txt · Last modified: 2020/08/13 13:23 by robm