Always on Vpn and Captive Portals
I was attending a meetup that was taking place in a bar in Utrecht. The first thing you want to do is to make a connection to the internet and get started. The location used a captive portal, however. You know: you have the name of the wireless network (SSID) and the password, but when you try to open any web page, you are directed towards a login page where you have to accept the terms and conditions of whoever is operating the network.
But what if you use an always-on VPN? You cannot connect to the network, because your MAC and IP address are not whitelisted yet by the operator. And you cannot get to the login page, because you do not allow any traffic outside your VPN.
ufw (uncomplicated firewall) as my firewall of choice, mainly
because the alternative,
iptables, always looked too complicated and
ufw served its purpose. The rules I have for
ufw are currently:
# ufw status verbose Status: active Logging: on (low) Default: deny (incoming), deny (outgoing), disabled (routed) New profiles: skip To Action From -- ------ ---- Anywhere on wlp1s0 ALLOW IN 192.168.178.0/24 Anywhere ALLOW OUT Anywhere on tun0-unrooted 192.168.178.0/24 ALLOW OUT Anywhere on wlp1s0 1194 ALLOW OUT Anywhere on wlp1s0 Anywhere (v6) ALLOW OUT Anywhere (v6) on tun0-unrooted 1194 (v6) ALLOW OUT Anywhere (v6) on wlp1s0
By default, I deny all incoming and outgoing connections. I only allow
incoming connections from hosts on the same network. As for outgoing
connections, I only allow them to other hosts over the LAN (to any
port), and everywhere else only over port 1194 (the OpenVPN port).
wlp1s0 is the name of my wireless interface,
tun0-unrooted of the
VPN tunnel. These rules were inspired by this Arch Linux
So we need to allow some traffic outside of the VPN tunnel to accept the terms and conditions and to register our machine at the captive portal. The best thing to do would be to allow a single, trusted application to access this portal, one that would be used exclusively for this task. If you would allow you regular browser to bypass the VPN, it would send all kind of traffic over the untrusted network for the rest of the world to freely sniff around in (think add-ons, other browser tabs, automatic updates). So we would need a dedicated web browser for this task. I’m on Linux using Firefox as my default browser, so GNOME Web would be a good choice for this purpose. (Gnome Web was previously known as Epiphany, and is still available under that name on a lot of distributions) .
First, we need to determine what kind of traffic we want to allow. The application will need to have outbound access to ports 80 (HTTP) and 443 (HTTPS) for web traffic, and it will also need to be able to resolve domain names using DNS, so port 53 should also be opened.
However, it’s not that easy to allow one particular application access
to the internet if you use UFW. When you look at the man page for UFW,
you see you can specify “apps”. Apps (or application profiles) are
basically just text files in INI-format that live in the
To list all (predefined) profiles:
# ufw app list
To create a profile for our purposes, we put the following in a file
[Epiphany] title=Epiphany description=Epiphany web browser ports=80/tcp|443/tcp|53
The “ports” field is clarified in the man page:
The ‘ports’ field may specify a ‘|’-separated list of ports/protocols where the protocol is optional. A comma-separated list or a range (specified with ‘start:end’) may also be used to specify multiple ports, in which case the protocol is required.
In our case we allow TCP traffic over ports 80 (HTTP) and 443 (HTTPS) and both UDP and TCP traffic over 53 (DNS). We can now use this profile:
# ufw insert 1 allow out to any app Epiphany Rule added Rule added (v6) # ufw status verbose [...snip...] To Action From -- ------ ---- Anywhere on wlp1s0 ALLOW IN 192.168.178.0/24 80/tcp (Epiphany) ALLOW OUT Anywhere 443/tcp (Epiphany) ALLOW OUT Anywhere 53 (Epiphany) ALLOW OUT Anywhere Anywhere ALLOW OUT Anywhere on tun0-unrooted 192.168.178.0/24 ALLOW OUT Anywhere on wlp1s0 1194 ALLOW OUT Anywhere on wlp1s0 80/tcp (Epiphany (v6)) ALLOW OUT Anywhere (v6) 443/tcp (Epiphany (v6)) ALLOW OUT Anywhere (v6) 53 (Epiphany (v6)) ALLOW OUT Anywhere (v6) Anywhere (v6) ALLOW OUT Anywhere (v6) on tun0-unrooted 1194 (v6) ALLOW OUT Anywhere (v6) on wlp1s0 # ufw reload # don't forget to reload firewall after making changes! Firewall reloaded
(Note that we use
insert 1 to make sure the rule is placed in the
first position. With UFW, the first rule matched wins.)
Now we can use our dedicated browser to go to the captive portal page and accept the terms and conditions.
Checking traffic with
You need to be careful, however, not to use any other applications during this time. If you launch Firefox, for example, it can also use the opened ports to communicate with the outside world. I’d like to use Wireshark to see what communications are taking place during this time.
When you are registered with the WiFi provider and are done with the
captive portal, you should first disable the profile again with UFW. We
can do this by specifying the rule we added earlier, but prepending
# ufw delete allow out to any app Epiphany
Another way to delete rules from UFW is by first doing
ufw status numbered and then
ufw delete <number>. However, since we have added 6
rules, this may take a while. Also, if you can’t remember the exact rule
that was used, you can use
ufw show added to show all added rules and
Better solutions: beyond UFW
Now, we see that using UFW isn’t exactly ideal to deal with always-on VPN and captive portals. What if you have an email application (or something else) running in the background when you have allowed all those ports to bypass the VPN tunnel? And also, you have to enable and disable the application profile every time you encounter a captive portal. It would be better if we could allow only a single, named, demarcated application to bypass the VPN.
One solution I’ve read about makes use of what I like to call “the
Android way”: every installed application is a user with its own home
directory. This means that applications don’t have access to each other
files, but more importantly, this gives the opportunity to allow only a
specific user to access the internet outside of the VPN. This way, we
could create a user
epiphany that runs Gnome Web to access the captive
AFWall+, an open-source Android
application, uses this method to implement a pretty effective firewall.
It also uses
iptables as a back-end. I might have to finally bite the
bullet and learn
iptables after all…