Raspberry Pi server and VPN-routing wifi hotspot

Overall Network

intro

I’ve been a Netflix user for quite a while, and as a Canadian geek, one of the first things I did was to set up a US-based VPN to get access to an extended library of movies and shows. A little while ago, I bought a Chromecast, which makes watching web streams on my TV much easier; no more mucking about with HDMI cables and wireless keyboards and mice.

However, you can’t set a Chromecast to connect to a VPN, and it needs to be on the same wifi network as the device that’s controlling it. Already having a Raspberry Pi running as a personal server, and a spare usb wifi dongle lying around, I decided to try setting up an alternative access point that routes all traffic over the VPN.

This project had a number of interesting challenges, but the biggest was the fact that this is not a single-purpose Raspberry Pi. I also use it as a home server for incoming email, file syncronisation with syncthing, and a few other random tasks. This means that the pi must remain accessible to my LAN, and through it, my public IP, while at the same time NATting traffic from its WAN over the VPN. This turns out to be not entirely straightforward.

Here is a diagram of my network Overall Network

As you can see, the pi exists on three networks: the home LAN, the VPN, and its own wifi network, SSID dietrich, which routes all traffic through the VPN to the host dietrich (I name my servers after theologians…)

There are a number of pieces working together here:

We’ll go through them one at a time.

OpenVPN server

My OpenVPN server is running on a VPS with the excellent RamNode (if you’re looking for a VPS, I can’t recommend them highly enough), running Debian Jesse.

Start out by installing OpenVPN, apt-get install openvpn.

You can set up OpenVPN by following their howto. It can take a bit of fighting, but I did it years ago so unfortunately I can’t transcribe all of the steps here. Follow the docs to set up your keys, and configure the server like this in /etc/openvpn/server.conf:

# note, I've edited out the comments from the default config, 
# but you should keep them; they're helpful
port 1194
proto udp
dev tun
ca keys/ca.crt
cert keys/server.crt
key keys/server.key  # This file should be kept secret
dh keys/dh1024.pem
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
comp-lzo

The server line sets the daemon in server mode and specifies our subnet. This gives the server’s VPN interface an IP of 10.8.0.1. the first push directive makes the server tell the client to set the VPN link as its default gateway, thus routing all its traffic over the VPN link. This is useful for securing my laptop connection over an open public WiFi connection, so I leave it in, but it will actually work against us for our routing. We’ll disable it in the client.conf on pi. the other push directives tell the client what DNS server to use; these two are Google’s publicly available servers.

NAT/Masquerade

Since we’ll be masquerading through the public network on this host, we need to set up ip forwarding and NAT, like this:

echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o venet0 -j MASQUERADE

venet0 is the public interface for the VPS; on a bare-metal server it would probably be eth0. The first line enables ip forwarding in the kernel, the second uses iptables to actually configure it.

Just running the commands sets this up until a reboot, but we need to make it into an init script so it always starts on system boot. See the Nat/Masquerade section below to see how we do it on pi; the process is the same on both hosts.

OpenVPN Client

I’m running Raspbian on my pi, so again, we’ll install the openvpn package with apt-get.

On the server, you’ll have generated your keys and certificates; I called the client cert for this host client-pi.crt and the client key client-pi.key. Transfer these, as well as ca.crt to pi, in /etc/openvpn/keys.

Set up /etc/openvpn/client.conf like this:

client
route-nopull
proto udp
remote dietrich 1194 # substitute your own server here
resolv-retry infinite
nobind
persist-key
persist-tun
ca /etc/openvpn/keys/ca.crt
cert /etc/openvpn/keys/client-pi.crt
key /etc/openvpn/keys/client-pi.key
ns-cert-type server
comp-lzo # this has to match the server
script-security 2
#up /etc/openvpn/routing.sh

Key here are the route-nopull and up directives:

route-nopull prevents the client making the VPN server its default gateway, as I mentioned in the server config. If the only thing the pi were donig was acting as our routed hotspot, that would be fine, but remember, I have mine set up running local services as well. As you’re setting up the VPN link, you might want to leave it commented out to test the link; you can then traceroute an external public host to make sure traffic is passing over the VPN correctly. But if you do this, do not close your ssh session to the pi, or you won’t be able to reconnect to it as long as openvpn is running. So remember to uncomment it and restart openvpn before moving on.

up tells the server to run a script after the link is established. This is what will set up our routing once everything is done. Comment it out until we get there, though.

WiFi Access Point

This is adapted from elinux.org’s page on a pi hotspot, but I’ve made some changes, particularly using dnsmasq rather than udhcpd to do some fun stuff with DNS.

First up, we need to set up the wlan0 interface. In /etc/network/interfaces add:

iface wlan0 inet static
    address 192.168.1.1
    netmask 255.255.255.0

This brings up wlan0 with a static address. Make sure the subnet you pick is different from the wired one; my home network is 192.168.0.0/24, so I picked 192.168.1.0/24 for pi’s wireless. If you have allow-hotplug and wpa-roam directives in this file, comment them out, they’re there assuming the pi will be connecting to a wlan, not hosting one.

The daemons we’ll use to operate as a wifi hotspot are hostapd and dnsmasq. We also need iptables:

apt-get install hostapd dnsmasq iptables

hostapd

To set up hostapd, edit /etc/hostapd/hostapd.conf like so:

interface=wlan0
#driver=nl80211
ssid=Dietrich
hw_mode=g
channel=6
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=ITSASECRET
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

and in /etc/default/hostapd, set DAEMON_CONF="/etc/hostapd/hostapd.conf".

Depending on your wifi card, you may or may not need the driver= line in hostapd.conf; google can help you if it doesn’t work out of the box.

dnsmasq

The last step in getting our access point running is handling DHCP requests from connecting machines. For this I chose dnsmasq over udhcpd. It is lightweight, and handles both dhcp and dns, with really simple configuration. (Aside: I wanted to handle DNS because I have a few servers running on pi that are also available on the public net, but local machines can’t route to my public IP from the LAN [this is a failing of my cheap dlink router] – so mailserver.bradmont.net resolves to the pi’s LAN address while at home, and to my public IP anywhere else.)

Here’s my /etc/dnsmasq.conf, again, with comments removed:

server=8.8.8.8
server=8.8.4.4
no-dhcp-interface=eth0
addn-hosts=/etc/hosts.dns
dhcp-range=192.168.1.50,192.168.1.75,12h

This is fairly straightforward. I’m using Google’s public DNS servers since my ISP’s DNs wouldn’t be available through the VPN. I disable DHCP on the wired network – we only want to serve the wifi from here. the addn-hosts line is where I do the DNS juju to access my mailserver.

iptables NAT masquerading

To enable NAT masquearding over tun0 we do the same thing we did on the remote server, but over tun0 rather than venet0:

root@pi# echo 1 > /proc/sys/net/ipv4/ip_forward
root@pi# iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE

Of course, we want to do this on every boot, so make an upstart script out of it, /etc/init.d/ip_forward

#!/bin/bash
### BEGIN INIT INFO
# Provides:          ip_forward
# Required-Start:    $remote_fs $syslog $network $openvpn
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start daemon at boot time
# Description:       Enable service provided by daemon.
### END INIT INFO
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE

and enable it with

update-rc.d ip_forward enable

If the VPN router is the only thing you’re using your pi for, you’re done.

At this point, if you have route-nopull commented out of your openvpn client.conf, you should be able to connect to the wifi network and have your traffic routed over the VPN. This was the easy part.

routing tables

This was the hard part. It took me a while, and some help from /r/linuxadmin to figure it out. If you’re interested, you can read the thread where I asked about it, and this page that explains the basics of routing through multiple uplinks.

Here’s the network diagram again, with the relevant IP addresses, to help understand what all the routing directives are doing. Network diagram with IP addresses

We need to set up two routing tables that work in parallel, one for the local network and one for the natted vpn/wifi network. We create the routing tables by editing /etc/iproute2/rt_tables. This file already contains several default tables, do not touch them. Instead, just add two more, like so:

123 ether
124 vpn_hotspot

Of course, you can name them whatever you like. I tried to use meaningful names. The numbers are pretty arbitrary, just pick unused ones.

Next we set up all of the routes. These four blocks of code make up /etc/openvpn/routing.sh, which is mentioned above in /etc/openvpn/client.conf.

First we set up the main routing table, so the host knows where all of these networks are. It just routes each network through the interface attached to it. The one thing that is not obvious is traffic out via 10.8.0.21, which is neither the IP of the VPN host, nor of pi’s VPN interface. Instead, it is the point-to-point (P-t-P) address of tun0 on pi, as given by ifconfig. To be honest, I’m not totally sure where it comes from, but it acts as the gateway address to the VPN.

#!/bin/sh
# main routing table
ip route add 192.168.0.0/24 dev eth0 src 192.168.0.3 
ip route add 10.8.0.0/16 dev tun0 src 10.8.0.22
ip route add 10.8.0.1 via 10.8.0.21
ip route add default via 192.168.0.1

Next we do the routing table for the local (wired) network: anything from 192.168.0.3 (eth0) goes to 192.168.0.0/24 network, and this routing table (ether) has 192.168.0.1 as its default gateway.

# routes on local routing table 
ip route add 192.168.0.0/24 dev eth0 src 192.168.0.3 table ether
ip route add default via 192.168.0.1 table ether

Then the table for the VPN and hotspot. Traffic from the tun0 goes to the VPN, from wlan0 to the wifi network, and tun0’s P-t-P address is the default gateway.

# routes on vpn/hotspot routing table 
ip route add 10.8.0.0/16 dev tun0 src 10.8.0.22 table vpn_hotspot
ip route add 192.168.1.0/24 dev wlan0 src 192.168.1.1 table vpn_hotspot
ip route add default via 10.8.0.21 table vpn_hotspot

Finally, we tell the machine to which table it should send traffic coming from each network.

# routing rules
ip rule add from 192.168.0.0/24 table ether
ip rule add from 10.8.0.0/16 table vpn_hotspot
ip rule add from 192.168.1.0/24 table vpn_hotspot

Stick all of these in /etc/openvpn/routing.sh, and then uncomment up /etc/openvpn/routing.sh from /etc/openvpn/client.conf, make it executable, restart openvpn, and you should be golden! Now any devices connected to the dietrich hotspot will be routed through the VPN. Great for security or getting around geo-locks.

Easier Switching

After using this setup for a while, I got a little frustrated with having to switch the Chromecast’s wifi network all the time. I set up a simpler way, here’s how.

Please note, I wrote this up about a month and a half after putting it all together, so it is possible that I’ve missed a step. If something is missing or doesn’t make sense, please leave a comment.

comments powered by Disqus