User Tools

Site Tools


vpn-rpi4

ExpressVPN Access Point using a Raspberry Pi 4

  • Hardware: Raspberry Pi 4, model B
  • OS: Raspbian GNU/Linux 10 (buster)
  • Network:
    • eth0 (wired) connected to home network:
      • CIDR: 192.168.167.0/24
      • DHCP range: 192.168.167.100 to 192.168.167.250
      • Gateway / router: 192.168.167.1

Desired outcome

  • Raspberry Pi 4 acting as WiFi access point
  • Provides own DHCP service, so it can nominate …
  • … itself as gateway
  • … itself as DNS server
  • Provides internet access to WiFi client stations …
  • … via ExpressVPN when that's connected
  • … via home network when it is now

Strategy

Create a new WiFi network (teleport) which is bridged with the wired network, so traffic can flow between them. For this to work, the DHCP ranges of the two networks should be in the _same subnet_ (192.168.167.0/24), but not overlap. Wired uses '.100' to '.250', so the new WiFi will use '.40' to '.50'

The Raspberry Pi itself gets its internet connection via the wired network - i.e. it uses the same router as other wired hosts (192.168.167.1).

DHCP requests from the WiFi network will be answered by the Raspberry Pi (using dnsmasq), and _not_ answered by my wired network. Since the networks are bridged my home network's router will attempt to answer DHCP requests - so we'll need to:

  1. Prevent DHCP requests from WiFi stations being answered by home network
  2. Prevent DHCP requests from wired network being answered by Raspberry Pi

The Raspberry Pi itself will not ask for IP addresses from the wired network. This keeps things simple, as the only IP on the Raspberry Pi will be on the bridge interface and it will be entirely predictable so we can embed it into the DHCP offers we give out (i.e. write it into /etc/dnsmasq.conf)

The Raspberry Pi's DHCP offers will nominate itself as the gateway (i.e. default route) and DNS server. It will have to perform Network Address Translation (“MASQUERADE”) for all traffic leaving my house (e.g. via home network or VPN).

ExpressVPN also tunnels DNS traffic, and *blocks* attempts to use DNS other than its own. This is a good thing, but I cannot get ExpressVPN and openresolv to play nicely: updates to /etc/resolv.conf either don't happen, or don't take affect. So I'll simply uninstall openresolv and/or disable systemd-resolved.

Setup

Create the following in /etc/network/interfaces.d/ as root:

eth0:

auto eth0
iface eth0 inet manual

wlan0:

auto wlan0
iface wlan0 inet manual
  nohook wpa_supplicant	# Conflicts with running in Access Point mode

br0:

auto br0
iface br0 inet static
  address 192.168.167.48
  gateway 192.168.167.1
  bridge_ports eth0 wlan0

  # Ethernet Bridging: Block DHCP from our WiFi stations going out over our
  # wired connection (we want to be the ones to answer DHCP requests, not our
  # ISP)
  # UDP port 67: BOOTP server
  # UDP port 68: BOOTP client
  up   ebtables -t filter -A FORWARD --protocol IPv4 --ip-protocol UDP --ip-destination-port 67:68 -j DROP
  down ebtables -t filter -D FORWARD --protocol IPv4 --ip-protocol UDP --ip-destination-port 67:68 -j DROP

  # Ethernet Bridging: Be deaf to DHCP requests originating on the wired
  # connection (home network), we are not their DHCP server. (.. and dnsmasq
  # cannot distinguish the source, as it all appears to be coming from br0)
  # UDP port 67: BOOTP server
  # UDP port 68: BOOTP client
  up   ebtables -t filter -A INPUT --protocol IPv4 --ip-protocol UDP --ip-destination-port 67:68 -i eth0 -j DROP
  down ebtables -t filter -D INPUT --protocol IPv4 --ip-protocol UDP --ip-destination-port 67:68 -i eth0 -j DROP

  # Internet Protocol Network Address Translation when using this bridge, and
  # any ExpressVPN tun0 which come and go with VPN connections
  up   iptables -t nat -A POSTROUTING -o br0 -j MASQUERADE
  up   iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
  down iptables -t nat -D POSTROUTING -o br0 -j MASQUERADE
  down iptables -t nat -D POSTROUTING -o tun0 -j MASQUERADE

Then run:

sudo apt remove openresolv
sudo apt install dnsmasq hostapd

Remove /etc/resolv.conf if it is a symbolic link, and recreate as a regular file:

nameserver 1.0.0.1
nameserver 8.8.4.4
nameserver 1.1.1.1
nameserver 8.8.8.8

Modify /etc/dnsmasq.conf:

dhcp-range=192.168.167.40,192.168.167.47,1h
dhcp-authoritative
clear-on-reload
bridge-interface=br0,wlan0

Restart dnsmasq service to pick up changes:

sudo systemctl restart dnsmasq

Modify /etc/default/hostapd and change value of DAEMON_CONF:

DAEMON_CONF="/etc/hostapd/hostapd.conf"

Create /etc/hostapd/hostapd.conf:

ctrl_interface=/var/run/hostapd
ctrl_interface_group=0
auth_algs=1
beacon_int=100

ssid=teleport
wpa_passphrase=CHANGEME

country_code=US

interface=wlan0
bridge=br0
driver=nl80211

wpa=2
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

macaddr_acl=0

logger_syslog=0
logger_syslog_level=4
logger_stdout=-1
logger_stdout_level=0

hw_mode=a
wmm_enabled=1

# N
ieee80211n=1
require_ht=1
ht_capab=[MAX-AMSDU-3839][HT40+][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]

# AC
ieee80211ac=1
require_vht=1
ieee80211d=0
ieee80211h=0
vht_capab=[MAX-AMSDU-3839][SHORT-GI-80]
vht_oper_chwidth=1
channel=36
vht_oper_centr_freq_seg0_idx=42

ignore_broadcast_ssid=0

Restart service hostapd to pick up changes:

sudo systemctl restart hostapd

Now apply network changes, and disable DHCP client daemon since we don't want our bridge's slave interfaces getting ideas above their station.

systemctl disable --now dhcpcd systemd-resolved
ifdown --verbose --all
ifup   --verbose --all

Debugging

pi@raspberrypi4:~ $ ip -br link
lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
eth0             UP             dc:a6:32:20:f7:7d <BROADCAST,MULTICAST,UP,LOWER_UP>
wlan0            UP             dc:a6:32:20:f7:7e <BROADCAST,MULTICAST,UP,LOWER_UP>
br0              UP             dc:a6:32:20:f7:7d <BROADCAST,MULTICAST,UP,LOWER_UP>


pi@raspberrypi4:~ $ ip -br addr
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP
wlan0            UP
br0              UP             192.168.167.48/24 fe80::dea6:32ff:fe20:f77d/64


pi@raspberrypi4:~ $ sudo iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
  386 25981 MASQUERADE  all  --  *      br0     0.0.0.0/0            0.0.0.0/0
    0     0 MASQUERADE  all  --  *      tun0    0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination


pi@raspberrypi4:~ $ sudo ebtables -L
Bridge table: filter

Bridge chain: INPUT, entries: 0, policy: ACCEPT

Bridge chain: FORWARD, entries: 1, policy: ACCEPT
-p 0x800 --ip-proto udp --ip-dport 67 -j DROP

Bridge chain: OUTPUT, entries: 0, policy: ACCEPT


pi@raspberrypi4:~ $ ip route
default via 192.168.167.1 dev br0 onlink
192.168.167.0/24 dev br0 proto kernel scope link src 192.168.167.48


pi@raspberrypi4:~ $ journalctl -u hostapd
-- Logs begin at Wed 2020-04-29 22:45:20 BST, end at Wed 2020-04-29 22:47:25 BST. --
Apr 29 22:45:56 raspberrypi4 systemd[1]: Starting Advanced IEEE 802.11 AP and IEEE 802.1X/WPA/WPA2/EAP Authenticator...
Apr 29 22:45:56 raspberrypi4 hostapd[710]: Configuration file: /etc/hostapd/hostapd.conf
Apr 29 22:45:56 raspberrypi4 hostapd[710]: wlan0: interface state UNINITIALIZED->COUNTRY_UPDATE
Apr 29 22:45:56 raspberrypi4 systemd[1]: Started Advanced IEEE 802.11 AP and IEEE 802.1X/WPA/WPA2/EAP Authenticator.


pi@raspberrypi4:~ $ journalctl -u dnsmasq
-- Logs begin at Wed 2020-04-29 22:45:20 BST, end at Wed 2020-04-29 22:47:25 BST. --
Apr 29 22:45:56 raspberrypi4 systemd[1]: Starting dnsmasq - A lightweight DHCP and caching DNS server...
Apr 29 22:45:56 raspberrypi4 dnsmasq[711]: dnsmasq: syntax check OK.
Apr 29 22:45:56 raspberrypi4 dnsmasq[751]: started, version 2.80 cachesize 150
Apr 29 22:45:56 raspberrypi4 dnsmasq[751]: DNS service limited to local subnets
Apr 29 22:45:56 raspberrypi4 dnsmasq[751]: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify dumpfile
Apr 29 22:45:56 raspberrypi4 dnsmasq-dhcp[751]: DHCP, IP range 192.168.167.40 -- 192.168.167.47, lease time 1h
Apr 29 22:45:56 raspberrypi4 dnsmasq[751]: reading /etc/resolv.conf
Apr 29 22:45:56 raspberrypi4 dnsmasq[751]: using nameserver 10.89.0.1#53
Apr 29 22:45:56 raspberrypi4 dnsmasq[751]: read /etc/hosts - 5 addresses
Apr 29 22:45:56 raspberrypi4 systemd[1]: Started dnsmasq - A lightweight DHCP and caching DNS server.
Apr 29 22:46:16 raspberrypi4 dnsmasq-dhcp[751]: DHCPREQUEST(br0) 192.168.167.44 8c:85:90:53:bd:55
Apr 29 22:46:16 raspberrypi4 dnsmasq-dhcp[751]: DHCPACK(br0) 192.168.167.44 8c:85:90:53:bd:55 Roberts-MBP
vpn-rpi4.txt · Last modified: 2021/04/25 20:13 by robm