Looping AIs (Siri, Alexa, Google Home)

There are plenty of videos showing how to trick Amazon Echo (Alexa) and Google Home into an infinite loop. Some folks used calendar entries, others rely on the good old Simon says trick. Since we share our realm with at least four AIs (that we know of), my wife and I decided to level up in the looping game.

It turns out, there are quite a few challenges to overcome when you want to integrate Siri or Cortana into the loop. Let’s start with a summary video. Skip to the end to see the outtakes 😅

Basic Idea

The basic idea is to make one AI say something that triggers the second AI to say something that triggers the next AI and so on… We used calendar events and notes. Mostly because you can easily manipulate them while the experiment is running. This also makes for a great game: Manipulate the AIs’ conversation without any human speaking. Just cloud data manipulation. It’s harder than one may think!

Training Siri

Siri has a cool feature: She doesn’t listen to everyone. To use Siri, one has to make her accustomed with ones own voice. This is done by speaking pre-defined phrases like “Hey Siri, how is the weather today?” Luckily, those phrases are not randomized but stay the same for every training session. I see an attack vector there. 😬

To make Google Home say the golden phrases we created an calendar event with the phrases as title. Unfortunately, Google Home speaks to fast (or computer-ish?) for Siri to catch up. We figured out that adding dots or commas slowed Google Home down a bit. At least enough for Siri to catch up.

Another caveat we found is, that Google Home loves to truncate long calendar event titles. We had to change the event title for every step of the training process. That was tedious and we tried several times until Siri was trained well. One time, we accidentally trained her to the phrase “HeySiri HeySiri”. 😂

This is how the calendar entry looked like:

Listening into the past

We discovered that Siri likes to listen into the past. When we made Google Home say something like “You have one appointment and the title is: Hey Siri…”, Siri would not start listen at the phrase “Hey Siri” or after the phrase “Hey Siri” but also grab a couple of phonemes from earlier to the activation phrase. Sounds scary, but what do we expect from an always-listening AI, right?

Trivia: Look at what time the screenshot was taken. Coincidence, I promise!

Training Cortana

We were not able to train Cortana, she would not listen to an artificial voice. It may be that the microphone wasn’t perfect on the laptop we used. Maybe Microsoft did very well on Cortana’s recognition algorithms and/or artificial neural networks. Since we were in a hurry, we threw Cortana out of the race. We leave this fruit hanging for someone else to grab it.

Note Yep, I am calling these things AI all the time. That is for convenience, I do know quite a bit about AI, natural language processing, machine learning and artificial neural networks. And I do know these gadgets are merely ANI (Artificial Narrow Intelligence) and far from AGI (Artificial General Intelligence) or what some may call strong AI. Still, I like to anthropomorphize and call them my AIs.

How to configure a WireGuard tunnel on OpenWrt using LuCi

A couple of months ago I worked on a concept for a sophisticated, IPv6-only overlay network spanning multiple sites and various devices. It is part of a a long-term project, which means assessing not only current, but also future protocols was suitable. The WireGuard cryptokey routing protocol was one of the candidates. The more I work with this still experimental protocol, the more I am convinced that this will become one of the major VPN protocols. It is lean and clean, easy to configure and exceptionally reliable. Furthermore, it seems to be very secure. But as a word of warning, I am less of a cryptography auditor and more of a programmer and network engineer.

I do believe in WireGuard and had the luck to participate in the project by contributing documentation and regularly testing the snapshots. It is a small, agile (BS Bingo!) and responsive group. The development speed is amazing and the head developer probably never sleeps 😮

Today I’d like to show you how to configure a WireGuard tunnel using OpenWrt/LEDE and luci-proto-wireguard. I developed luci-proto-wireguard during the past weeks as a side project. With the help from beta testers and experienced OpenWrt folks, the code matured and now awaits merging into the official repositories.

For this howto I assume you run the latest snapshot of, let’s say OpenWrt. I will also assume that you have a basic understanding of WireGuard.

First step is to create the WireGuard interface. Go to the Interfaces page and create a new interface. Select WireGuard VPN in the dropdown menu. If this option does not show up, then you are missing luci-proto-wireguard 💩. Head over to Software and install it.

Think of good name for the interface, in this article we will proceed using foo 😬 Next thing you will see is the interface configuration page. I tried to make it as self-explanatory as possible by including helpful hints below the options. Most important configuration data are the Private Key of the interface and the Public Key of at least one peer. Also, don’t forget to add the network or address of the other end of the tunnel to Allowed IPs. Otherwise the tunnel won’t work as expected.

If you like to add some post-quantum resistance, you can do so in the advanced tab.

In the firewall tab, you can create a new zone or assign the interface to an existing zone. I recommend doing this after the device is set up and working.

Click Save and Apply once you are satisfied.

Now you should have a WireGuard tunnel interface, but it has not been assigned an IP address yet. I wanted to allow a wide range of setups and enable everyone to do even the weirdest things with their routers. So I removed the direct addressing feature that I was implemented in an earlier version. Luckily, you can create a static configuration on top of foo by creating a new device and selecting Static address as protocol.

It is important to select foo as the underlying interface, either by finding it in the interface list, or, if it does not (yet) show up there, by typing @foo into the custom interface field.

Voilà! We now have the standard static addressing page. Configure according to your VPN concept and hit Save and Apply to proceed.

You should now see both interfaces in your interface list. I recommend putting them into the same firewall zone for easier administration. You can tell that I moved them into the same zone from the color of the interfaces. Interfaces foo and bar share the same firewall zone color.

I’d like to add some monitoring, but that isn’t ready yet. In the meantime, you can check on your WireGuard interface(s) using wg on the command line.

If you find any bugs, please report them. Thanks for reading and happy cryptokey routing everyone!

Hint On some devices it may be necessary to restart the device after after installing luci-proto-wireguard, so that the netifd daemon correctly loads the helper script that comes with wireguard-tools. Thanks Stefan for pointing this out!

Additional antennas for Turris Omnia

In an earlier post I mentioned the rather disappointing wifi signal strength of my Turris Omnia. Other users reported similar issues, so it looks like I am not the only one suffering. To put things into perspective: My router’s signal strength is about 75% compared to the previous COTS device (100%). Clearly, we are talking about #FirstWorldProblems here. 😢

It is still unclear why some devices remain under expectations.

  • Is it a hardware issue? Or software?
  • Is it the driver blob?
  • Is there a problem with the connectors?
  • Are the antennas broken?
  • Could it be an issue with the two pre-antenna filters?
  • Why are both bands affected?

I wanted to rule out antenna and connector issues to help the community find the problem. The idea is to replace the filters, stock antennas and stock HF cables. So I went on a shopping spree and bought five dual-band antennas with 5dBi gain and corresponding HF pigtails.

Two filters are connected upstream of the antennas on the left and right (red circles). The third antenna in the middle is directly connected to the 2.4GHz wifi card.

I replaced the filters and the gray stock HF cables. Each antenna is now directly connected to one of the wifi cards via pigtail. I also replaced the stock antennas, to refute the broken antennas theory.

No special tools were needed, there were already five SMA holes in the case.

And now the most important piece of information: Did it work? Unfortunately, no, I have not seen a significant improvement of signal strength, regardless which band I looked at.

At least we now know that a hardware issue including the antennas or filters is less likely. The quest continues… 📶❓

Turris Omnia: My first impression

A long time ago I backed the CZ.NIC Turris Omnia open source router on Indigogo. Like all good campaigns, it was delayed and overfunded (by 800%). A couple of weeks ago I received my perk for supporting the campaign: The Turris Omnia.

And what a great piece of router hardware that is! I love it! I almost regret that I went for the cheaper option with 1GB RAM instead of 2GB. However, I have not hit the RAM limit yet. But you never know, right? 🙃

The Omnia comes in a clean package and with a plug of your choice and three dual-band antennas. I chose Type A, but of course the device is also avalaible with EU plug if needed.

The front is equiped with programmable, dimmable RGB LEDs, including two extra LEDs that can be used for almost anything.

On the back we find five gigabit Ethernet ports for the LAN side. Another gigabig Ethernet port is available for the WAN side. One can also plug in an SFP and go fiber. However, only one of the WAN ports may be used at a time.

Let’s peek inside the Turris Omnia: Uncluttered and with room for expansion. Awesome!

The filters that merge the signals from the two wifi cards were a bit loose. That was easy to fix, here is a video that shows how:

Furthermore, I think that the wifi signal strength is a bit disappointing, it is beyond my expectations. Nevertheless, this is a great piece of hardware worth every dollar.

Building an encrypted travel wifi router

This article is about building a secure travel wifi router using a RaspberryPi and the Wireguard VPN protocol. It is a long and technical article describes how I stopped worrying about untrusted and insecure wifis in hotel rooms and conference venues.

The Problem

I travel a lot and therefore often rely on wifi provided in aircrafts, hotels or conference venues. Unfortunately, the state of security of those uplinks is worrying, connections are often buggy and rarely encrypted. A WPA2-protected wifi with pre-shared key (PSK) does not provide individual security. Everyone knowing the password can easily eavesdrop on all the traffic, not just their own. Only few sites offer more secure wifi, e.g. facilitating WPA enterprise and individual accounts.

Why don’t I just use a VPN on my devices then? Well, first I carry quite a few devices, and not all of them are capable of running a modern VPN. Secondly, some of them can not handle IPv6-only VPN connections. That’s a show stopper for me. Furthermore, many hotspots are protected by a captive portal that requires me to login to the portal on every device before I can establish a VPN tunnel. Given that I am allowed to connect more than one device at all. Even worse, some captive portals require re-authentication every 12 or 24 hours or whenever a devices re-enters the area of wifi coverage. The most important reason why I avoid using on-device VPN termination whenever possible is that devices can easily be tricked to circumvent the VPN connection for some traffic. The most harmless threat being DNS leakage, but more sophisticated attacks include fake proxy configuration, rogue routers and all sorts of MITM attacks on HTTPS and other protocols.

The Solution

I tried many different approaches to face the problem over the last couple of years.

Here are my findings on what a sufficient solution should be capable of:

  • Provide a private, secure wifi for my devices.
  • Private data passing the untrusted wifi must be encrypted.
  • Do not leak any data from inside the secure wifi.
  • Mitigate most common attacks by not trusting the untrusted wifi’s link layer at all.
  • Provide a way to quickly (re-)authenticate on captive portals.
  • On the untrusted wifi, act like any other of-the-shelf device. Appear to be normal :)

May I proudly present the current iteration of my solution:

Encrypted Travel Wifi Setup Diagram

Let’s go through the above network diagram from the bottom up. At first, we have the devices we want to securely connect to the Internet. That is, for example, a notebook, a phone and another random gadget. They join the private wifi provided by private router. The private router encrypts all traffic that is headed towards the Internet using a VPN. The encrypted traffic is then routed through the untrusted wifi (e.g. an open hotel wifi) via the access device. This can be a cheap smartphone or a pocket router. I strongly suggest using something with a screen and a browser, because the access device not only has to provide an attack-free link to the private router, but also needs to authenticate to all kinds of weird captive portals. Android 6.0 with automatic security patches is a good idea and has successfully been tested with this setup. For providing the uplink for the private router I recommend USB tethering. Not only does the USB cable charge the access device, it also provides enough freedom to place it somewhere where the untrusted wifi signal is strong.

We gain a security benefit from using a dedicated access device for shielding the untrusted wifi’s link layer from the private router. Sadly, many untrusted wifis are legacy-IP only, in such environments we pay for the benefit with an additional layer of NAT. However, more firewalls are better they said, right?

Many Firewalls

Back to topic: Once the encrypted traffic worked its way from the private router via the access device, through the untrusted wifi it finally reaches the Internet. Which, of course, we don’t trust either, although most of our packets from the private wifi will end up there eventually. Encrypted traffic finally hits the VPN server where it will be decrypted and routed properly (read: released into the wild, wild Internet).

Too abstract? Here are two possible setups for clarification.

Encrypted Travel Wifi Setup Mobile Phone

The photo above shows a mobile phones being used as the access device for the private router.

Encrypted Travel Wifi Setup Wired WAN

Here I used a small OpenWRT router as access device for a wired, but untrusted network. I could have connected the private router directly to the wired network if it was a bit more trustworthy.

Let’s start tinkering! The remainder of this article describes a setup that

  • protects agains eavesdropping on the untrusted wifi,
  • circumvents device limits in the untrusted wifi,
  • shields your devices from typical attacks against VPNs on the link-layer of the the untrusted wifi,
  • and gives you access to the whole Internet in locations where they only have legacy IP and/or censorship.


  • A small linux-capable computer with integrated or attached wifi hardware, preferably a RaspberryPi 3 or C.H.I.P. This will become the private router.
  • A server, preferably a dual-stacked virtual instance running Debian Linux Jessie. This will become the VPN server.
  • A spare Global Unicast /64 that is routed to the VPN server. We will use this prefix on the private wifi.
  • A smartphone, preferably running a recent version of hardened Android. Beware of super-cheap devices, some of them perform terribly when running in tethering mode. You have been warned!
  • Basic understanding of IP routing, policy routing, packet filtering and Linux CLI
  • No fear to compile a Linux kernel module. Scared? Don’t be, it’s not that hard, really!
  • Endurance, as this is not a 10 minute project, but it’s worth it!


Brief overview of what we are going to do:

  • Addressing
  • VPN server
    • Wireguard
    • VPN
    • Routing
    • Recursive, validating DNS
    • Filtering
  • Private router
    • Wireguard
    • VPN
    • Private wifi
    • Caching DNS Forwarder
    • Routing
    • Filtering
  • Legacy IP (optional)
  • Connect access device
  • Celebrate!


We want our addressing to be close to the one shown in the following graphic, just with different numbers of course:

Interfaces, Addresses and Prefixes

For the in-tunnel addressing, basically a point-to-point connection, we use Unique Local Addresses (ULA). I strongly suggest generating an individual pseudo-random RFC4193 prefix out of fc00::/7. Use this fancy online tool from our friends at SixXS to generate yourself your very own prefix! I’ll be using fd12:3456:7890::/48 for the remainder of this article. Please replace those addresses accordingly.

The private wifi uses a slice of your Global Unicast prefix, whatever this may be. I happen to have a /48 prefix, but heard from others that they got even bigger chunks from their registry. No worries, a single, routable /64 is sufficient!

VPN Server

The VPN server

  • takes care of routing the private wifi prefix through the tunnel to the private router,
  • encrypts all packets entering the tunnel (from the Internet to the private router),
  • decrypts all packets leaving the tunnel (from the private router to the Internet), and
  • acts as a first line of defense for unwanted packets from the Internet.

We start with a fresh install of Debian Linux Jessie, for example on a small VM in a datacenter. Then we configure network connectivity, backup service and basic filter ruleset to our personal preferences. You probably have your own deployment and configuration method and tools, so I refrain from bugging you with basic system administrator tasks and just trust your workflow. At this point you should have the machine ready to be accessed via SSH and know how to gain superuser privileges.


Wireguard is a new, promising VPN protocol. After many years of working with OpenVPN, L2TP, IPsec and even SSH as VPN, working with Wireguard feels just awesome. It is simple, extremely reliable and it just works.

Here’s how the creators define their protocol:

WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPSec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN.

As Wireguard is an in-kernel VPN implementation, it is either already part of your favorite distribution or you have to build it from source. We are using Debian Jessie on the VPN server, which means we have to install a backported kernel and build the Wireguard kernel module and userspace tools.

Add this line to /etc/apt/sources.list:

deb http://ftp.de.debian.org/debian/ jessie-backports main

Then update the package list, install the latest kernel and reboot:

# apt-get update && apt-get dist-upgrade
# apt-get install -t jessie-backports linux-image-amd64
# reboot

Now we need some headers and tools:

# apt-get install libmnl-dev linux-headers-amd64 build-essential git

Now it’s time to grab a copy of the source and compile it.

$ git clone https://git.zx2c4.com/WireGuard
$ cd WireGuard/src
$ make

If everything went well, we can install the module and tools:

# make install

With modprobe wireguard we load the module into the running kernel. By adding a line reading wireguard to /etc/modules the system does this automatically after the next reboot.


Head over to the Wireguard website and browse through the documentation to make yourself comfortable with the concept. Wireguard is a crypto-routing, in-kernel, device-based VPN technology. If you have a hard time understanding what this means, consider giving the documentation another shot. It took me a while to grasp how nice and fancy the approach is compared to other VPN technologies. Especially, if one plans to establish mostly static routes, like we will in the upcoming sections.

I assume we are all set and ready for our first Wireguard tunnel? Let’s do it!

First step in asymmetric cryptography is always to generate a key pair. We do this by creating a private key and then deriving a public key from it. Wireguard’s own userspace tool wg takes care of this:

$ wg genkey > server_private.key
$ wg pubkey > server_public.key < server_private.key

The first key pair is meant to be used for the server. To proceed with this article, we need the router’s public key, so we will generate the key pair right now:

$ wg genkey > router_private.key
$ wg pubkey > router_public.key < router_private.key

I usually suggest storing the key pair only on the same system on which it is used. Please transfer the private key securely to the private router later and remove it from the VPN server (e.g. use shred).

This should leave us with two files for the server and another two files for the private router. They contain the VPN server’s private and public key. Make sure you don’t confuse these two! The tunnel won’t work if the private and public keys of the endpoints are not correctly distributed!

The tunnel endpoint will be the wg0 interface, which we need to configure. I prefer using the config files instead of the very long CLI commands of wg. So, here is the first part of the /etc/wg0.conf file:

ListenPort = 500

We usually don’t run services on privileged ports unless necessary, and yet here I am using port 500. Why is that? Well, my argument goes like this:

  • This is an in-kernel VPN protocol, so opening port 500 does not require any additional capabilities to the ones the kernel already has. Which is ALL THE CAPABILITIES :)
  • UDP port 500 is commonly used for IPSec, which increases the chances that this port is not blocked in an maybe restricted wifi.


The second part of the /etc/wg0.conf file looks like this:

AllowedIPs = fd12:3456:7890::2/128, 2001:db8:aaa:bbbb::/64

What may be confusing when done the first time is the AllowedIPs directive. Let me go into detail here, as it is essential for secure crypto-routing that we filter for source addresses. When a packet enters the tunnel, it gets encrypted and becomes the payload of a Wireguard packet, which itself is the payload of a UDP datagram which in turn is the payload of an IP packet. For the sake of simplicity, let’s ignore the UDP header for a moment, as it does not add any value to the discussion.

VPN Packet

So, we have our Wireguard packet coming in from another endpoint, and the payload is the original IP packet. Without AllowedIPs, we would decrypt the payload, thus get the original packet, and route it according to our routing table. How could we know the source address of the original packet wasn’t spoofed? Do we trust our endpoint that much? Probably not! This is why we put some restrictions on the original packet’s source address using AllowedIPs. Even if the encrypted packet authenticates and decrypts properly, we would not route it unless its payload (read: the original packet) came from within an allowed prefix.

Now it’s time to tell the system to bring up the wg0 interface on boot. A quite convenient way is adding a corresponding section to /etc/network/interfaces:

auto wg0
iface wg0 inet6 manual
  pre-up ip link add dev wg0 type wireguard

This creates the interface using ip (a userspace tool for the kernel’s RTNETLINK API). The interface, however, will still lack some essential information, e.g. IP address and Wireguard-specific configuration data. The IP address can be set using ip even before the interface comes up:

  pre-up ip address add fd12:3456:7890::1 peer fd12:3456:7890::2 dev wg0

And we can also apply the /etc/wg0.conf configuration file while the interface is still down:

  pre-up wg setconf wg0 /etc/wg0.conf

The next directive actually brings up the interface we just configured:

  up ip link set up dev wg0

We can also explicitly allow IP forwarding on the new interface. This step may or may not be required, depending on your sysctl.conf settings.

  post-up echo 1 > /proc/sys/net/ipv6/conf/wg0/forwarding

It is good style to remove the wg0 interface on shutdown. That may also prevent hard to debug errors in some cases.

  down ip link del dev wg0

The complete section looks like this:

auto wg0
iface wg0 inet6 manual
  pre-up ip link add dev wg0 type wireguard
  pre-up ip address add fd12:3456:7890::1 peer fd12:3456:7890::2 dev wg0
  pre-up wg setconf wg0 /etc/wg0.conf
  up ip link set up dev wg0
  post-up echo 1 > /proc/sys/net/ipv6/conf/wg0/forwarding
  down ip link del dev wg0

Remember to set appropriate file permission for all files containing private key data! From now on, the wg0 interface should come up right after the system boot. Why don’t you try it out now?


At first, we have to globally enable routing by setting the corresponding variable in /etc/sysctl.conf:


After that we apply the change:

# sysctl -p

The next step is to add routes for the private wifi, because devices in that network will want to receive packets via the VPN tunnel. There are plenty of places where routes may be set up. On Debian-based distributions I prefer to add the routes when the related interface comes up. That’s best done using the post-up directive in /etc/network/interfaces:

auto wg0
iface wg0 inet6 manual
  post-up ip route add 2001:db8:aaaa:bbbb::/64 via fd12:3456:7890::2 dev wg0

That’s it for routing on the VPN server. The rest will be taken care of by the default and interface routes. Easy, wasn’t it?

Recursive, validating DNS

The Domain Name System (DNS) is an essential part of connectivity. Inside the private wifi we want a DNS server that

  • responds fast,
  • validates resource records,
  • does not make excessive use of the maybe limited hotel wifi bandwidth, and
  • prevents DNS leaks to protect our privacy.

To have a fast response time, the DNS server should cache results from previous queries, serving them directly from the private router to the connected clients. The DNS server on the private router must not resolve recursively, as it can produce a lot of back and forth traffic. Bandwidth and latency may be suboptimal in the typical hotel wifi situation. Validating resource records can be done by using DNSSEC, but adds some extra data that needs to be fetched.

I came up with this diagram to solve the problem:

Encrypted Travel Wifi DNS Diagram

This DNS setup uses two DNS servers, one on the private router and another one on the VPN server. It has the nice advantage that we can run the bandwidth-heavy, latency-critical and computing operations on the server, which is expected to be better connected and also more powerful than the private router. The on-server DNS instance takes care of resolving recursively, validating and some caching, the local DNS server on the private router just forwards queries and caches the responses. Since the connecting between these two DNS servers happens to be inside the tunnel, we consider the responses from the recursive server trusted (as in: not modified during transit, no need to run DNSSEC again). The tunnel also prevents DNS leakage.

To install the DNS daemon on the VPN server just run:

# apt-get install unbound

The configuration file /etc/unbound/unbound.conf I used looks like this:

  num-threads: 4

  interface: ::0
  interface-automatic: no

  max-udp-size: 3072

  access-control: ::0/0                 refuse
  access-control: ::1                   allow
  access-control: fd12:3456:7890::2/128 allow

  harden-glue: yes
  harden-dnssec-stripped: yes
  harden-referral-path: yes
  unwanted-reply-threshold: 10000000

  val-clean-additional: yes
  val-permissive-mode: yes
  val-log-level: 1

  cache-min-ttl: 1800
  cache-max-ttl: 14400
  prefetch: yes
  prefetch-key: yes

This configuration tells unbound to listen on any interface, but to only allow queries from localhost (::1) and the private router via VPN (fd12:3456:7890::2/128). Modify the file according to your addressing scheme. Then start the daemon by running:

# systemctl start unbound

To test the setup just run a query against the server:

$ dig www.danrl.com. SOA +dnssec @::1

The answer should contain ad flags, look for something like this:

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57665
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 4, ADDITIONAL: 1

If everything is fine, we enable the DNS daemon permanently:

# systemctl enable unbound


Let me say a few words regarding filtering first: Filter rules are constantly evolving as new attacks and threats appear or protocols develop. Proper filter rule management is therefore a must-have for all systems we are responsible for. Furthermore, filter rules are not pure science but are also highly influenced by what one considers best practice. I have seen many well-thought-through filter rule sets, but I rarely see two that are the same. This leads me to the conclusion that filtering is sometimes more art than science and everyone has personal preferences on how rules should be ordered or look like. I will assume that you set up your own basic filtering right after you installed the operating system and that you know best how you want to manage your rules. That said, we will only discuss rules here that are specific for the problem we are solving. You are expected to add the discussed rules to you existing rule set where you think they are placed best.

If you already have connection tracking in place, please skip the next rule. Otherwise just add the following rules to the INPUT and FORWARD chains at a very early stage.

-A INPUT   -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

We have to allow incoming Wireguard packets, remember port 500?

-A INPUT -p udp -m udp --dport 500 -m conntrack --ctstate NEW -j ACCEPT

I suggest putting this rule in the legacy IP filter as well. It will allow the tunnel to operate on legacy IP, which is often the only protocol that is available in some places. As of 2016, the market penetration of state-of-the-art IP in hotel and venue wifis is still shamefully low. We have to do better, folks! But that’s another (long) story…

We run a recursive DNS service on the VPN server to provide validated resource records (RR) for the private router. We should allow the private router to talk to the DNS service to make them work:

-A INPUT -s fd12:3456:7890::2/128 -p tcp -m tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -s fd12:3456:7890::2/128 -p udp -m udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT

On a side note, if TCP port 53 sounds odd to you, it is well inside the bounds of specification. It wasn’t used widely during the legacy IP era and before DNSSEC became (somewhat) popular.

To make life a bit easier, especially when debugging, we allow forwarding of packets that stay in the tunnel (if they hit the VPN server at all).

-A FORWARD -i wg0 -o wg0 -m conntrack --ctstate NEW -j ACCEPT

Finally we want to allow forwarding of packets from the private wifi to the Internet.

-A FORWARD -s 2001:db8:aaa:bbbb::/64 -i wg0 -o eth0 -m conntrack --ctstate NEW -j ACCEPT

That’s it from filtering for now.

Private Router

The private router provides the private wifi and acts as the client side of the tunnel.

I chose a RaspberryPi 3 as hardware platform for the private router, because it has a built-in wifi chip. Other platforms work well, too. I had this setup working on much smaller devices, too, e.g. an OpenWRT-capable router of the size of an USB thumb drive.

For the operating system I used Raspbian Lite as provided by the RaspberryPi Foundation. To operate, the private router needs an uplink, which can be provided either via Ethernet or USB tethering. Therefore we configure the corresponding interfaces to automatically gain connectivity. We add the following lines to /etc/network/interfaces:

allow-hotplug eth0
iface eth0 inet dhcp

allow-hotplug usb0
iface usb0 inet dhcp


The Wireguard installation is quite similar to the one we performed on the VPN server:

# apt-get install libmnl-dev linux-headers-rpi build-essential git
$ git clone https://git.zx2c4.com/WireGuard
$ cd WireGuard/src
$ make
# make install
# modprobe wireguard

Again, consider adding a line reading wireguard to /etc/modules.


We will configure the private router’s end of the tunnel, the wg0 interface, using a configuration file (/etc/wg0.conf):

ListenPort = 4000

Endpoint = vpn.example.com:500
AllowedIPs = ::/0
PersistentKeepalive = 21

The ListenPort directive has a sometimes misleading name. Wireguard allows configurations that mock the more common client server model. In that case, ListenPort on the client becomes the source port of outgoing packets. Technically, the Wireguard module is also listening on this port, but let’s ignore this fact for the moment. In our case ListenPort will become the outgoing port and our filter rules will prevent any incoming packets that are not covered by connection tracking. The directive Endpoint expects the hostname of the VPN server followed by a colon and the port number. Since we want to receive packets from the Internet through the tunnel, we set AllowedIPs to ::/0.

The wg0 interface on the private router is similar to the one on the VPN server, just with opposite adressing. We add the interface configuration to /etc/network/interfaces:

auto wg0
iface wg0 inet6 manual
  pre-up ip link add dev wg0 type wireguard
  pre-up ip address add fd12:3456:7890::2 peer fd12:3456:7890::1 dev wg0
  pre-up wg setconf wg0 /etc/wg0.conf
  up ip link set up dev wg0
  post-up echo 1 > /proc/sys/net/ipv6/conf/wg0/forwarding
  down ip link del dev wg0

After that we can fire up the interface using Debian’s ifup scripts:

# ifup wg0

Private Wifi

The RapsberryPi has built-in wifi that is compatible with hostapd, we can run a software access point on it. Hooray!

Here is how my /etc/hostapd/hostapd.conf looks like (except wpa_passphrase of course):



There is no need to start hostapd on system boot. The ifup scripts can take care of that when the wlan0 interface comes up. In my experience, the daemon comes up more smoothly this way. Now is also a good time to configure addressing on wlan0 in /etc/network/interfaces:

auto wlan0
iface wlan0 inet6 static
  hostapd /etc/hostapd/hostapd.conf
  address 2001:db8:aaaa:bbbb::1
  netmask 64

Let’s fire up the interface and test our configuration:

# ifup wlan0

You should now be able to see and join the SSID privatewifi with your favorite device. However, joining may fail due to a lack of addressing. We need to distribute router advertisements to give joining devices a chance to learn about the on-link prefix. I may be biased towards the awesome ratools regarding this task 😉. However, as of August 2016, ratools is not available in Debian’s repositories and would require installation from source. To not make things more complicated as they already are, let’s stick with radvd which is old but mature:

  # apt-get install radvd

Configuration for radvd takes place in /etc/radvd.conf:

interface wlan0 {
  IgnoreIfMissing on;
  AdvSendAdvert on;
  MaxRtrAdvInterval 300;
  AdvLinkMTU 1423;
  prefix 2001:db8:aaaa:bbbb::/64 {
    AdvOnLink on;
    AdvAutonomous on;
    AdvValidLifetime 3600;
    AdvPreferredLifetime 1800;
  RDNSS 2001:db8:aaaa:bbbb::1 {
    AdvRDNSSLifetime 1800;

This configuration just works and advertises reasonable values, although there is some room for improvements. You can play around with MaxRtrAdvInterval to directly save airtime or AdvPreferredLifetime and AdvRDNSSLifetime to indirectly save airtime by influencing client behavior.

Our Wireguard tunnel has a MTU of 1423 octets, and since we are going to push almost everything from the private wifi through the tunnel, we should advertise this limitation. This is why I put in the AdvLinkMTU option.

Please note that we already advertise the resolving DNS server here, which we will install and configure in the next step.

Caching DNS Forwarder

Let’s set up the forwarding and caching DNS server we just talked about. Again, unbound is our friend:

# apt-get install unbound

The /etc/unbound/unbound.conf configuration file looks a bit different this time:

  num-threads: 4

  interface: 2001:db8:aaaa:bbbb::1
  interface-automatic: no

  access-control: ::0/0                   refuse
  access-control: 2001:db8:aaaa:bbbb::/64 allow

  cache-min-ttl: 1800
  cache-max-ttl: 14400
  prefetch: yes

      name: "."
      forward-addr: fd12:3456:7890::1

The most important part is everything below forward-zone. The dot means all zones and forward-addr is the upstream DNS server to which we forward requests to. Make sure it is one of the listening addresses from the VPN server’s unbound.conf file.

Time to start the daemon:

# systemctl start unbound

And now, testing! Resolving a domain using our new DNS server should look something like this:

$ host www.danrl.com 2001:db8:aaaa:bbbb::1
Address: 2001:db8:aaaa:bbbb::1#53
www.danrl.com has IPv6 address 2400:cb00:2048:1::681c:25

If everything works fine, enable the daemon:

# systemctl enable unbound


Routing on the private router is slightly more complicated than on the VPN server. We have to use policy routing to make sure a packet never leaves our trusted networks, which are the tunnel and the private wifi. Even if a better route exists, the kernel must not forward any packet from a trusted network to an untrusted one.

First we have to enable forwarding via /etc/sysctl.conf:


And apply the change:

# sysctl -p

We will be using a custom routing table for the private wifi, as we don’t want packets from there to use the system’s main routing table. This allows us to route everything coming from the private wifi through the VPN tunnel, even though the system uses other routes for its own packets. Create a custom routing table by adding the line 200 privatewifi to /etc/iproute2/rt_tables. The file should look something like this afterwards:

# reserved values
255 local
254 main
253 default
0   unspec
# custom
200 privatewifi

This ensures the custom routing table will be created after the next reboot.

Now we have a custom route table, but it lacks content. It is just empty:

# ip route show table privatewifi
[nothing to see here]

Here are the requirements for our custom routing:

  • We want the custom table to be flushed before the VPN tunnel comes up, so that we start with a clean table every time the VPN flaps (if it flaps at all).
  • We want the default route to point to the VPN server’s wg0 interface. This is the server’s in-tunnel address if you will.
  • We want the interface route (on-link prefix) of interface wlan0 to be present in the table, so that local packets do not get routed away. Yes, sounds strange, but that does happen if the interface route is missing. Policy routing and custom routing tables are tricky sometimes.
  • We want to force every packet that wants to leave the private wifi to use our custom routing table. Here is where policy routing jumps in.

Phew! If that’s a bit too much to comprehend, just go through the bullet points one more time and draw the situation with pen and paper. It’s OK to get confused when dealing with policy routing and multiple routing tables. Even the experts make terrible mistakes applying this magic sometimes 🤕

Here is how I implemented policy routing on the private router. I like to keep the rules separated by interface, and I also like to place them in /etc/network/interfaces:

iface wg0 inet6 manual
  post-up ip route flush table privatewifi
  post-up ip route add default via fd12:3456:7890::1 dev wg0 table privatewifi

iface wlan0 inet6 static
  post-up ip -6 route add 2001:db8:aaaa:bbbb::/64 dev wlan0 table privatewifi
  post-up ip -6 rule add from 2001:db8:aaaa:bbbb::/64 lookup privatewifi


Again, I assume we have a decent basic filtering set up. The following rules allow clients on the wifi to access the caching DNS forwarder. Since DNS queries and responses can be quite large these days, we also have to consider that some clients may ask using TCP.

-A INPUT -s 2001:db8:aaa:bbbb::/64 -p udp -m udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -s 2001:db8:aaa:bbbb::/64 -p tcp -m tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT

Finally, clients may want to access the Internet. We therefore allow packets coming from inside the private wifi to be forwarded to the VPN server using the Wireguard interface wg0. It is very important that we do not allow any other outgoing interface! Having a strict forwarding rule prevents leaks caused by wrong routing. Wrong routing can happen if we made a mistake at the policy routing stage or if someone successfully injects wrong routes, e.g. via a compromised access device. Also, if the private router used without the additional protection of an access device, route injection becomes a more likely attack. So, here is the corresponding rule:

-A FORWARD -i wlan0 -o wg0 -s 2001:db8:aaa:bbbb::/64 -m conntrack --ctstate NEW -j ACCEPT

Connect Access Device

It’s simple, just connect your access device, e.g. the suggested Android smartphone, to the private router and start enjoying your encrypted wifi with secured Internet access.

A longer version is available, too.

Legacy IP (optional)

To access the legacy Internet one could set up NAT64 (preferred) or run the whole setup dual-stacked. If you like to run a dual stack network, you just have to repeat the above steps involving IP addresses using legacy IP addresses and legacy networks instead. It is pretty straightforward, except one caveat: ICMPv4 path MTU discovery in this VPN setup is not working well with some legacy-only servers and websites (they still exist!). Some packets may be dropped just because of their size, with no way for a device connected to the private wifi to determine the right packet size. A quick and dirty fix is to mangle legacy TCP connections and force a lower maximum segment size (MSS) on them.

On the VPN server add this line to your filter rules:

-t mangle -A FORWARD -o wg0 -p tcp -m tcp --tcp-flags SYN,RST SYN -s -j TCPMSS --set-mss 1360


Here is my private router in action at the Detroit Metropolitan Airport, providing secure wifi for my gadgets while waiting for my flight to DEFCON.

The private router in action!