Raspberry Pi server and VPN-routing wifi hotspotTue, May 19, 2015
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
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:
- OpenVPN Server, with NAT/Masq
- OpenVPN Client
- WiFi access point
- hostapd to handle the wifi connection security
- dnsmasq for DHCP and DNS
- iptables NAT Masquerading
- The routing tables
We’ll go through them one at a time.
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
# 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 18.104.22.168" push "dhcp-option DNS 22.214.171.124" comp-lzo
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
pi. the other
push directives tell the client what DNS server to use; these two are Google’s publicly available servers.
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.
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
/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 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
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
pi’s wireless. If you have
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
dnsmasq. We also need iptables:
apt-get install hostapd dnsmasq iptables
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
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.
The last step in getting our access point running is handling DHCP requests from connecting machines. For this I chose
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.)
/etc/dnsmasq.conf, again, with comments removed:
server=126.96.36.199 server=188.8.131.52 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
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,
#!/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.
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.
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
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
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.
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