Raspberry Pi Router on a Stick

I recently setup a new SSID to enable quick and easy access to a VPN protected network when I needed it, also making this easily accessible for family members who are not computer-savvy.

I used a Raspberry Pi 3 for the task, as a router on a stick. This guide shares the configuration/ commands used to set this up so that:

  • All traffic sent to the Raspberry Pi (from devices using it as their default gateway) will be routed via the VPN
  • DNS requests sent to the Raspberry Pi (again, where clients are set to use it as DNS server) will be routed via the VPN
  • When the VPN disconnects all traffic, including DNS is dropped until such time as the VPN reconnects

Network Topology

The network topology is fairly simple:

VLAN NameSubnetEdge RouterX IPPi IP
Trusted 192.168.1.0/24 192.168.1.254 N/A
VPN 172.16.1.0/24 172.16.1.254 172.16.1.1

Configuration

Assuming you have the latest Raspbian Lite image running and updated via apt-get update / apt-get dist-upgrade you’re good to go.Flash Raspbian Lite latest onto an SDCard

Install Log2RAM

If you’re using an SDCard it might not be a bad idea to use Log2Ram to protect it – simply put this reserves ~40MB of RAM and logs directly to this, rather than the write-sensitive SDCard. Every hour logs are rotated/ zipped and dropped back onto the SDCard. The only downside is the potential to lose the last hour of logs on a hard reset/ power off.

curl -Lo log2ram.tar.gz https://github.com/azlux/log2ram/archive/master.tar.gz
tar xf log2ram.tar.gz
cd log2ram-master
chmod +x install.sh && sudo ./install.sh
cd ..
rm -r log2ram-master

Disable IPv6

Most VPN service providers don’t offer a robust IPv6 service thus this represents a potential leak/ weakness in your setup.

echo "# Disable ipv6" | sudo tee -a /etc/sysctl.conf
echo "net.ipv6.conf.all.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf
echo "net.ipv6.conf.default.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf
echo "net.ipv6.conf.lo.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Enable IPv4 Forwarding

echo -e '\n#Enable IP Routing\nnet.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Set Static IP

Ensure you update the code below with the relevant IP address/ subnet mask, gateway and DNS servers for your network/ VPN provider:

# Replace x.x.x.x and y.y.y.y with your VPN providers DNS server addresses
echo "interface eth0" | sudo tee -a /etc/dhcpcd.conf
echo "static ip_address=172.16.1.1/24" | sudo tee -a /etc/dhcpcd.conf
echo "static routers=172.16.1.254" | sudo tee -a /etc/dhcpcd.conf
echo "static domain_name_servers=x.x.x.x y.y.y.y" | sudo tee -a /etc/dhcpcd.conf

Define Static Routes

Based on the example topology I want to be able to manage this device from the “trusted” network, I therefore need to create a static route back to this network, via the Edge Router X. Without this, when OpenVPN connects it will block connectivity to this network.

echo "ip route add 192.168.1.0/24 via 172.16.1.254 dev eth0" | sudo tee /lib/dhcpcd/dhcpcd-hooks/40-routes

Install OpenVPN

sudo apt-get install -y openvpn

Configure OpenVPN

This configuration will work with NordVPN, you may need to modify the script to accommodate a different VPN provider:

############ Create .secrets file
echo <username> | sudo tee -a /etc/openvpn/.secrets
echo <password> | sudo tee -a /etc/openvpn/.secrets
sudo chmod 600 /etc/openvpn/.secrets

############ Download Config File
sudo wget -O /etc/openvpn/openvpn.conf https://<path to opvenvpn config file>

############ Configure VPN Client
sudo sed -i "s|auth-user-pass|auth-user-pass .secrets|g" /etc/openvpn/openvpn.conf 

sudo sed -i '/auth-user-pass .secrets/a \
script-security 2 \
up /etc/openvpn/update-resolv-conf \
down /etc/openvpn/update-resolv-conf' /etc/openvpn/openvpn.conf 

sudo sed -i '/client/a \
redirect-gateway' /etc/openvpn/openvpn.conf 

sudo sed -i 's/#AUTOSTART="all"/ AUTOSTART="all"/g' /etc/default/openvpn

############ Enable Service and Connect to VPN
sudo systemctl daemon-reload
sudo systemctl restart openvpn
sudo systemctl enable openvpn

Install DNS Masq

##### Install/ Configure DNS Masq to prevent DNS Leaks
# Replace x.x.x.x and y.y.y.y with your VPN providers DNS server addresses
sudo apt-get install dnsmasq
sudo sed -i "s|#interface=|interface=eth0|g" /etc/dnsmasq.conf
echo "server=x.x.x.x" | sudo tee -a /etc/dnsmasq.conf
echo "server=y.y.y.y" | sudo tee -a /etc/dnsmasq.conf
sudo systemctl daemon-reload
sudo systemctl enable dnsmasq
sudo systemctl restart dnsmasq

Firewall Configuration

Replace x.x.x.x with the IP address of the OpenVPN server, stored in the config file and ensure you review the “trusted” network IP address range / delete the rule if this is not required.

sudo sysctl -p
sudo apt-get install iptables-persistent
sudo systemctl enable netfilter-persistent
########### Flush
sudo systemctl stop openvpn
sudo iptables -P INPUT ACCEPT
sudo iptables -F
sudo iptables -t nat -F
########### NAT
sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
########### INPUT
sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT
sudo iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 22 -j ACCEPT
sudo iptables -P INPUT DROP
########### FORWARD
sudo iptables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT
sudo iptables -P FORWARD DROP
########### OUTPUT
# Replace x.x.x.x with the IP address of the OpenVPN server, stored in the config file
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -d x.x.x.x/32 -p udp -m udp --dport 1194 -m comment --comment "vpn-server" -j ACCEPT
sudo iptables -A OUTPUT -o tun0 -m comment --comment "vpn" -j ACCEPT
sudo iptables -A OUTPUT -o eth0 -j DROP
########### Save
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp -m tcp --dport 53 -j DNAT --to-destination 127.0.0.1
sudo netfilter-persistent save

Testing

You can test the configuration on the VPN server itself using the command below, this should return the IP address of your VPN server:

curl ipecho.net/plain; echo

You can also speed test this configuration using speedtest-cli:

sudo apt-get install -y speedtest-cli
speedtest-cli

Now, either point your clients DNS and default gateway at the Raspberry Pi, or setup a new DHCP Scope to automatically do this.

Why Raspbian?

I tested this configuration with Alpine Linux (v3.9.2 arm7 – I couldn’t get armhf to boot and arm64 crashed/ generated kernel panic on add rules to iptables!) but got stuck in a requirements loop where:

  • All traffic is routed across VPN interface, when VPN is down no traffic can route
  • OpenVPN would fail to connect due to clock skew/ certificate verification failing based on future validity
  • Chrony requires DNS (despite using IP addresses in the configuration) and thus fails to sync time, so system clock skew is huge on startup – this fails as OpenVPN connection is down

If I enabled DNS traffic to traverse eth0 (i.e. not over the VPN) Chrony would work, but this left the setup open to DNS leaks. I also tried an iptables rule to accept all traffic from the chrony uid, this also failed to resolve the issue – I could still see a series of uid 0 (root) owned DNS requests that were dropped.

Raspbian Lite installs locally (no overlay filesystem, so no clock issues) and is fully supported by Raspberry Pi foundation (so gets regular updates etc). Given the limited potential benefits/ gains for changing this to a.n.other operating system I decided that this will do/ is “good enough!”