This page looks best with JavaScript enabled

Make Your Own Travel Router With A NanoPi And Armbian

 ·  ☕ 11 min read

When you travel, you may sometimes want to be able to access some resources that are present on your flat’s network, make your traffic go through a VPN or what not because you don’t trust the network you are in (wether it is a hotel/coffee shop or what not). This blogpost is going to explain how you can build a nice travel router for when you travel around, so no one steals your nudes or snoops on the sketchy p0rn you watch.

What we are going to achieve

I’ll show you how you can build a small armbian-based router that enables you you:

  • Connect to some WAN
  • Have a local LAN with a custom DHCP range
  • Have a second LAN (potentially accessible over WiFi) that routes all your traffic through a wireguard VPN tunnel
  • Have a local DNS over TLS resolver so you avoid leaking DNS queries

Bill of materials

For this project, I purchased a £60 Nano Pi R1 off Amazon. You can find a cheaper one (10 quids less) if you are ready to drop from 1G to 512M of RAM, and even cheaper if you drop the metal casing. But I took the beefier version and the metal casing because having a nice packaging is always nice.

I then used an extra WiFi dongle I used on my old Raspberry Pies, which is probably worth 10ish quids.

A nano pi r1

Slap an OS on it !

Initially I wanted to setup the router using OpenWRT, but as usual I didn’t check the hardware’s compatibility before buying it and OpenWRT is not supported yet, sucks to be me but oh well, I’ll use an other operating system there, Armbian. This is basically a Debian fork targetted at embedded systems, that embeds a recent kernel and a bunch of cool things such as built in Wireguard support.

Get the OS there (you can either use Debian Buster or Ubuntu Bionic at the time of writing, I went with Buster, but Bionic should be pretty similar), burn it on an SD card and insert it on the device.

Log into the box

Just SSH on the box using ssh root@<ip> using the password 1234. You will be prompted to setup a new password, then do whatever you want to feel comfortable on the box, like installing tmux, vim, zsh if you are into that.

Initial settings

Predictible interface names

I’m not sure about you, but I really hate systemd style interface names and I want my eth* and wlan* names: To do that add the following line to the /boot/armbianEnv.txt file:

extraargs=net.ifnames=0

Disable IPv6

I have no need for IPv6, I’ll just disable it, add to /etc/sysctl.conf:

net.ipv6.conf.all.disable_ipv6=1
net.ipv6.conf.default.disable_ipv6=1

Install a few packets

1
2
3
4
$ apt-get install -y \
    bridge-utils \
    wireguard-tools \
    tcpdump

Reboot

1
$ reboot

Setting up the network interfaces

So if you look at what you have right now, you should see the following

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
root swagmachine ~ # ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether f6:5e:56:31:05:2b brd ff:ff:ff:ff:ff:ff
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 12:81:4e:91:08:48 brd ff:ff:ff:ff:ff:ff
4: eth1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast master lan0 state DOWN mode DEFAULT group default qlen 1000
    link/ether 8a:d7:e4:18:a5:bd brd ff:ff:ff:ff:ff:ff
5: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master vpn0 state UP mode DEFAULT group default qlen 1000
    link/ether 00:0f:55:a8:2d:52 brd ff:ff:ff:ff:ff:ff
6: wlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master lan0 state UP mode DORMANT group default qlen 1000
    link/ether a2:a9:5e:07:b5:5b brd ff:ff:ff:ff:ff:ff

Remember the following:

  • eth0 is the WAN interface, the one you connect to the upstream network
  • eth1 is the LAN interface, the one you can connect to with your laptop for instance
  • wlan0 is the internal wifi interface, we will use it as a VPN only access point
  • wlan1 is the USB wifi dongle, we will use it to create a local network without VPN

Setting up the wireguard tunnel

I won’t expand on how to setup a wireguard tunnel here, I refet you to this excellent article by brouberol

At the end of it you should end up with a wg0 interface. You can create as many as you want, if you want to add a wg1 interface that would allow you to connect to your home network for instance.

Here for reference is my own /etc/wireguard/wg0.conf

[Interface]
PrivateKey = [REDACTED]
ListenPort = 5555
SaveConfig = false
Address = 192.168.11.10/32
Table = 42

[Peer]
PublicKey = [REDACTED]
AllowedIPs = 0.0.0.0/0
Endpoint = [REDACTED]:5555

Note that the Table = 42 parameter is super important since otherwise Wireguard is going to interfere with your other routing tables and rules, which you do not want. The 42 number is arbitrary you can pick whatever you want as long as it matches the routing table number you assign to the vpn routing table that you will create in the next section.

Start the interface now

1
$ systemctl start wg-quick@wg0

Create a custom routing table

We need a custom routing table for the VPN only network, this will allow us to make sure that the traffic on this network is routed accordingly to this table, to avoid it going out unencrypted through eth0 and leaking to potential curious eyes. Alter the /etc/iproute2/rt_tables file as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ cat /etc/iproute2/rt_tables
#
# reserved values
#
255     local
254     main
253     default
42      vpn
0       unspec
#
# local
#
#1      inr.ruhep

The 42 vpn means that we create a routing table with id 42 and assign it the name vpn. The ID can be anything between 1 and 252, and the name can be anything.

Setting up the bridges

We are going to create the following bridges:

  • vpn0, which we will use to route the VPN traffic
  • lan0, which we will use to route the local LAN traffic
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ cat /etc/network/interfaces.d/lan0
auto lan0
iface lan0 inet static
        address 10.69.0.1
        netmask 255.255.255.0
        bridge_ports eth1
        # Optionally you can force the lan0 trafic to be routed through the main routing table
        # this can be useful if you have a setup with several routing tables depending on the
        # input interface. In this setup it is optional but I'll leave it for reference

        #post-up /sbin/ip rule add iif lan0 lookup main priority 999
        #down /sbin/ip rule del iif lan0 lookup main priority 999 || true

Here we setup lan0 as the master for eth1, and assign the range 10.69.0.1/24. Next up the vpn0 interface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ cat /etc/network/interfaces.d/vpn0
auto vpn0
iface vpn0 inet static
        address 10.69.1.1
        netmask 255.255.255.0
        bridge_ports none
        pre-up ip link add $IFACE type bridge
        post-up /sbin/ip rule add iif vpn0 lookup vpn priority 1000
        post-up /sbin/ip rule add iif vpn0 blackhole priority 1001
        down /sbin/ip route del default dev wg0 table vpn || true
        down /sbin/ip rule del iif vpn0 lookup vpn priority 1000 || true
        down /sbin/ip rule del iif vpn0 blackhole priority 1001 || true

There is a bunch of stuff to unpack here. We create the vpn0 interface as a bridge (with the pre-up command). Then we assign the 10.69.1.1/24 range to the interface.

Then we have a bunch of post-up and down commands, which are invoked respectively when the interface is brought up and down.

  • pre-up ip link add $IFACE type bridge creates the interface
  • post-up /sbin/ip route add default dev wg0 table vpn sets the wg0 interface as the default interface for the vpn routing table we created above
  • post-up /sbin/ip rule add iif vpn0 lookup vpn priority 1000 inserts the routing table in the routing evaluation list with a high priority (higher than the standard one) for all the packets that come from the vpn0 interface.
  • post-up /sbin/ip rule add iif vpn0 blackhole priority 1001 same as above, except we nullroute the packets. This makes sure that if the packet do not match any routes from the routing table it gets dropped instead of leaked.

You may now bring the interfaces up

1
2
# ifup vpn0
# ifup lan0

Setup the wifi accesspoints

Install hostapd if it is not already present

1
apt-get install hostapd

And now create the two networks, the LAN:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ cat /etc/hostapd-lan.conf
interface=wlan1
hw_mode=g
ieee80211n=1
ieee80211d=1
channel=10
bridge=lan0
country_code=GB
wmm_enabled=1
ssid=travel wireless
wpa=2
wpa_passphrase=somepassword
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
auth_algs=1

And the VPN

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cat /etc/hostapd-vpn.conf 
interface=wlan0
hw_mode=g
channel=5
bridge=vpn0
driver=nl80211
ieee80211n=1
ht_capab=[DSSS_CK-40][HT20+]
country_code=GB
ieee80211d=1
ctrl_interface=/var/run/hostapd
ctrl_interface_group=0
logger_syslog=0
logger_syslog_level=0
wmm_enabled=1
ssid=vpn network
wpa=2
wpa_passphrase=someotherpassword
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
auth_algs=1

Note the bridge= configuration directives that instruct hostapd to setup the interfaces with vpn0 and lan0 as masters.

Then edit the default hostapd arguments to make sure these 2 config files are loaded:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# cat /etc/default/hostapd
# Defaults for hostapd initscript
#
# See /usr/share/doc/hostapd/README.Debian for information about alternative
# methods of managing hostapd.
#
# Uncomment and set DAEMON_CONF to the absolute path of a hostapd configuration
# file and hostapd will be started during system boot. An example configuration
# file can be found at /usr/share/doc/hostapd/examples/hostapd.conf.gz
#
DAEMON_CONF="/etc/hostapd-lan.conf /etc/hostapd-vpn.conf"

Then restart the service

1
# systemctl restart hostapd

You should now see your 2 SSID announced.

Setup DHCP

We use dnsmasq for DHCP, it should be installed by default. Setup the following configuration file and restart the service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
cat /etc/dnsmasq.conf
interface=vpn0
listen-address=10.69.0.1,10.69.1.1
server=6,10.69.0.1
domain-needed
bogus-priv
dhcp-range=interface:vpn0,10.69.1.50,10.69.1.150,12h
dhcp-range=interface:lan0,10.69.0.50,10.69.0.150,12h
dhcp-option=interface:vpn0,3,10.69.1.1
dhcp-option=interface:lan0,3,10.69.0.1
dhcp-option=interface:vpn0,6,10.69.1.1
dhcp-option=interface:lan0,6,10.69.0.1

This sets up the IP ranges for the two interfaces and forces them to use the router as the resolver.

Setup DNS

Setup unbound

We will now setup unbound as a DNS resolver, update the config and restart the service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
include: "/etc/unbound/unbound.conf.d/*.conf"

server:
  verbosity: 1
  interface: 10.69.1.1
  interface: 10.69.0.1
  interface: 127.0.0.1
  access-control: 0.0.0.0/0 allow
  do-ip4: yes
  do-ip6: no
  do-tcp: yes
  logfile: /var/log/unbound/unbound.log
  hide-identity: yes
  hide-version: yes
  harden-glue: yes
  use-caps-for-id: yes
  do-not-query-localhost: no

forward-zone:
  name: "."
  forward-addr: 1.1.1.1@853
  forward-addr: 9.9.9.9@853
  forward-ssl-upstream: yes

This sets up unbound to forward the DNS queries to cloudflare and quad9 DNS servers, using TLS so you would not leak the queries.

Nuke systemd-resolved

It has the annoying habbit to change /etc/resolve.conf, just edit the file to make it look like

1
nameserver 127.0.0.1

Then

1
# chattr +i /etc/resolv.conf

This will ensure that no one can modify the file.

Setup iptables

You now need to setup the iptables rules to allow everything to work, edit the /etc/iptables.ipv4.nat file (this will only work on armbian):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
*filter

-A INPUT -i lo -j ACCEPT -m comment --comment "Allow local traffic"

-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Allow established connexions"

-A INPUT -p icmp -j ACCEPT -m comment --comment "Allow pings"

-A INPUT -p tcp --dport 22          -j ACCEPT -m comment --comment "Allow SSH"

-A INPUT -p tcp --dport 53 -j ACCEPT -m comment --comment "Allow DNS"
-A INPUT -p udp --dport 68 -j ACCEPT -m comment --comment "Allow DHCP"
-A INPUT -p udp --dport 67 -j ACCEPT -m comment --comment "Allow DHCP"

# Forward to eth0
-A FORWARD -i lan0 -o eth0 -j ACCEPT
-A FORWARD -o lan0 -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# Forward to wg0
-A FORWARD -i vpn0 -o wg0 -j ACCEPT
-A FORWARD -o vpn0 -i wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT

-A INPUT -i lan0 -j ACCEPT
-A INPUT -i vpn0 -j ACCEPT
COMMIT

# NAT rules
*nat
-A POSTROUTING -s 10.69.0.0/24 -o eth0 -j MASQUERADE -m comment --comment "LAN masquerade"
-A POSTROUTING -s 10.69.1.0/24 -o wg0 -j MASQUERADE -m comment --comment "VPN masquerade"
COMMIT

*raw
:PREROUTING ACCEPT
:OUTPUT ACCEPT
COMMIT

*nat
:PREROUTING ACCEPT
:POSTROUTING ACCEPT
:OUTPUT ACCEPT
COMMIT

*mangle
:PREROUTING ACCEPT
:INPUT ACCEPT
:FORWARD ACCEPT
:OUTPUT ACCEPT
:POSTROUTING ACCEPT
COMMIT

*filter
:INPUT ACCEPT
:FORWARD ACCEPT
:OUTPUT ACCEPT
COMMIT

# Default policies
*filter
-A INPUT -j DROP
-A FORWARD -j DROP
COMMIT

Finish it !

Now reboot, and you should have a functional travel router that allows you to secure your communication with a VPN wireless network !


Thomas
WRITTEN BY
Thomas
I am a Site Reliability Engineer, currently working from London. I hate that I like computers. I try to post potentially useful stuff from time to time.