Skip to main content
  1. Posts/

Enhancing Privacy in 2020

·12 mins

This post focuses on enhancing user privacy on the network level via identity based routing and taking back control from the Internet of Targets. If you find this post interesting and useful, consider showing it through PayPal. Even small amounts will help fund this website and all the research I put into different topics. Feedback and suggestions are welcome as well. You will find an email address to contact me in the pane on the left.

Problem Statement #

Over the years, companies and corporations have become ever more hungry for everything related to their users’ geolocation, telemetry, demography, relationship with one another, interests, convictions, social preferences - you name it. At the same time, users wanting to consume digital services meet a lot of ridiculous restrictions depending on where they live and how they access the Internet. Ecojails, in one form or another are created by multi-national corporations in order to capitalize everything about their users’ behavior. In 2020, this has all been exacerbated by everyone suddenly working from home if possible.

Motivation #

This is why I wanted to research how identity-based routing could enhance users’ privacy in a totally transparent way. I’ve never been a big fan of VPNs as a security solution, but have come to realize that they have a role to play in privacy. Since soon everything needs to be online to function from a vacuum cleaner to dish washer to toaster, it is increasingly difficult to keep the Internet of Targets at bay. Moreover, our personal telemetry devices feed out a constant stream of information to the ecojail masters, be they Apple, Google, Microsoft, Amazon, Alibaba or Netflix. Taking back control will not be easy and one will evidently need to compromise along the way, but realization is the first step to recovery.

Identity-Based Routing? #

Identity-based routing is a concept, where the connected devices’ identity defines how it connects out to the Internet and what types of services it has available. Usually, it is touted as a vehicle for corporate control, but I wanted to turn this concept upside down and empower users to select how their different devices get connected.

Requirements #

Sketching out the requirements for the routing service, I iteratively discovered the following constraints:

  • I needed a commercial and fairly reputable VPN provider with enough presence in different geolocations to serve the exit points for the service.
  • I needed a VPN solution, which offers flexibility and OpenVPN fit the bill, since it was the only solution that easily interoperates with commercial VPN services.
  • The gateway needed to be fairly secure, so naturally I chose OpenBSD to run the VPN clients on.
  • Egress route needed to be easily adjustable, for which I ended up using routing domains provided by OpenBSD.
  • To manage the identities, I needed to document all L2 identities and serve the L3 addresses based on the routing need. Instead of segmenting devices I wanted to group them together and offer a flexible way to move them from one tunnel to another.
  • I needed to ditch IPv6, since it leaks information through the tunnels if not managed carefully (further research topic)
  • To segregate the Internet of Targets, I needed a local network segment, which is not routed to the Internet at all, or triaged into a tunnel with much stricter egress controls.

Master Plan #

As stated above, having a subscription to a commercial VPN provider which offers easy access to their egress points with enough capacity and geolocations is key. I’m not going to promote the one I chose, but there are many out there. Do your research and trial them out. UDP-based tunnels will most likely work better in a consumer grade connectivity situation. For maximum throughput and stability, configure your CPE device as a bridge. It is great if the VPN provider offers egress points for different needs: different geolocations for accessing content more locally, double VPNs for better anonymity or even egress points, which hook up with TOR in a controlled manner.

OpenVPN with Routing Domains #

One of the most difficult parts of this project was to make OpenVPN play nice with OpenBSD. As OpenVPN is a port/package, it needs quite a lot of retro-fitting especially if you want to use it with privsep and in my case run multiple client instances side-by-side.

The best solution I found, was not to enable the daemon as an rcctl service, rather than configure the tunnel interfaces and OpenVPN client instances via /etc/hostname.if entries, one for each tunnel. In addition, I needed to use and external shell script to retro-fit OpenVPN into a given routing domain, as the concept is something one cannot handle with it natively. Also, OpenVPN logging and documentation clearly shows that it is a kitchen sink and the best help with the options to trial and err with I found through the OpenBSD package manual page and the odd write-up on the subject.

Routing Domain? #

Routing domain is a concept on OpenBSD, which makes it possible to segregate applications and their routes from one another into separate kernel routing tables. This feature became a crucial component for my project, since VPN providers often use the same address space in their configurations. Without routing domains, the overlap would make it impossible to run multiple clients side-by-side. Routing tables are numbered lookup tables for egress routing domains. By default, OpenBSD has four tun interfaces numbered tun0 to tun3. I used rdomains 10 to 13 to make the numbering symmetric, since rdomains start with 1 and run up to 255.

Here’s an example configuration for tunnel0, which resides in rdomain 10.

rdomain 10
!/usr/local/sbin/openvpn --dev tun0 --writepid /var/run/tun0 --daemon \
        --config /etc/openvpn/tun0.ovpn --script-security 2 \
        --route-noexec --route-up /etc/openvpn/scripts/tun0/ \
        --ifconfig-noexec --up /etc/openvpn/scripts/tun0/

The configuration brings up the interface and defines the routing domain in the kernel, whereafter it sets up an OpenVPN connection into that routing domain. As stated above, OpenVPN is not “rdomain aware”, which is why a shell script is needed to do three things:

  • Set up the interface with ifconfig through the openvpn –up parameter.
  • Set up the tunnel interface routing through the –route-up parameter.
  • Set up the route-to firewall rule based on the source identity.

At present, I am not entirely sure if I actually need to do the inverse via the –down-pre and –down options. Moreover, I am doing some of the configuration also in the actual vpn config, so I will probably need to move those options away from the config and keep the provider defaults as is in the config.

user _openvpn
group _openvpn
#chroot /var
auth-user-pass /etc/openvpn/auth.txt

The additional parameters I found out through trial and error and there is one that still I cannot use, namely the chroot. The reason for this is that openvpn has to restart at times for various reasons and it seems that in privsep mode it is then unable to use the /dev/tun socket if it has been chrooted under /var for example.

Moreover, the openvpn chroot did not work through /var/empty, which is something that I’ll need to figure out in the future as well. The main thing for me of course is that I do not need to run openvpn as root, so I accept this glitch for now. Since in this setup I am only using one VPN provider, I can/must use the same creds for all the tunnels.

N.B. that for the privsep to work, the /dev/tunX sockets must be owned by the privsep user. Otherwise, the openvpn process will die, whenever the server issues a reconfig to its clients for one reason or the other.

PF for Routing, Control and Visibility #

One of the interesting concepts about routing domains is that the rtable entries must exist in the kernel before they can be referenced in the PF rules. In practice, this means that the given tun interface must be defined and an rdomain mapping for it has to exist.

For this example setup below, I have defined four /27 blocks to exemplify identity based routing based on the source address via pf. If the source address matches a given subnet, all the packets are NAT’d to a specific tunnel (tun0 - tun3).

match out on tun0 inet from to any rtable 10 nat-to (tun0) round-robin
match out on tun1 inet from to any rtable 11 nat-to (tun1) round-robin
match out on tun2 inet from to any rtable 12 nat-to (tun2) round-robin
match out on tun3 inet from to any rtable 13 nat-to (tun3) round-robin

Since we cannot know which IP address to route the packets to before the openvpn tunnel and route are up, I define anchors into the PF configuration below to make it easy for an external script to inject relevant route-to rules into it.

anchor "tunnel0" all
anchor "tunnel1" all
anchor "tunnel2" all
anchor "tunnel3" all

The “up” Script #

As exemplified above, the referenced above in the interface config is responsible for interacting with openvpn and the kernel in order to configure the interface with the correct client side (left) L3 identity. This is handled by the case “up” and ifconfig. The “route-up” call adds a relevent routing entry into the kernel so that the packets passed into the tunnel interface get routed to the OpenVPN server.


case "${script_type}" in
                /sbin/ifconfig "${dev}" "${ifconfig_local}" \
                        netmask "${ifconfig_netmask}" mtu "${tun_mtu}" rdomain 10
                route -T10 -qn add -net 127 -reject
                route -T10 -n add -inet ${ifconfig_local} 
                rule="pass in quick on em1 from to any route-to (tun0 ${ifconfig_local})"
                echo $rule | pfctl -a tunnel0 -f -

                kill `cat /var/run/tun0`
                echo "Unknown script type ${script_type}" | logger -t up

exit 0

For some reason the only way I could make this work with openvpn was to use a “synthetic” default route / pointed at the left side unicast address, instead of using the right-side default gateway detailed in some of the write-ups. The problem I have is that when I tried pointing the default route to the right side of the tunnel, I got a mysterious “network unreachable” from the kernel.

The last part of the route-up adds the route-to rule via pfctl into PF. The association between the tunnel number and the rule is achieved by the anchor rule detailed above. E.g. in this case all the packets from a specific /27 are routed to tun0 through the tunnel0 anchor. The route points at the local unicast address, since none of the openvpn variables referenced in the various examples actually worked in practice.

The “down” verb in the config kills the openvpn process and it is there more for house-keeping purposes.

DHCP for Identity Management #

In order to make it easy for the users to have their devices’ traffic be automatically routed to a given tunnel, I used L2 based mapping of the device identities in the dhcpd config. This way, when a user signals that they want to use a device for a specific purpose, all I need to do is make sure that the L3 identity handed out by dhcpd is in the right subnet.

For example if a user signals that their use case matches the service description of tunnel0 defined above, I use a host entry such as this one to place the device in that subnet (

host userdev1 { hardware ethernet xx:xx:xx:xx:xx:xx; fixed-address; }

Dealing with the Internet of Targets #

For the Internet of Targets, I use a subnet which is not routed to any tunnel, effectively putting these devices in an unroutable part of the network. Having a PF rule in place to log the outbound connections from these devices has taught me many a lesson about their less documented life on the Internet.

So far, I’ve used pf, ntpd and unbound to satisfy the needs of these lesser evils and if you own UNIFI or Apple wireless AP products, putting these entries in your pf and unbound rules will keep them mostly happy about their “children’s Internet”.

local-data: " IN A"
local-data: " IN A"
local-data: " IN A"
local-data: " IN A"
local-data: " IN A"
local-data: " IN A"

Of course it is a good idea to have ntpd listening on the interface an NTP hungry device wants to connect to.

listen on

The PF will then simply allow the redirected “miscreants” to satisfy their needs with the appropriate protocol.

pass in quick proto udp from <subnets> to <gw_addrs> port = 53
pass in quick proto udp from <subnets> to <gw_addrs> port = 123

Conclusions and Lessons Learned #

Working out the kinks of this new gateway service concept has taught me many new things about privacy and how to help yourself and your close ones to make better decisions on how to access the Internet. The prototype I’ve built is far from perfect, but it is stable from the users’ perspective and has already resulted in many interesting user experiences which make their life fuller and less preyed upon. I fully realize that the network level things are only the foundation for user privacy and later on I plan to document my experiences in helping people do better with their opsec. Using tunnels, I can better respect the privacy of service users and can focus on protecting the infrastructure instead. Never has tcpdumping been easier on the WAN interface to exclude user traffic and monitor what’s going on the Internet instead. Simply excluding port 1194 makes it easy to monitor for leaks or even look at the inbound scanner traffic from botnets with love.

Where to from here? #

Working out the minor glitches from the setup will probably result in a new post some time in the short to medium term. Making unbound rtable aware is an interesting idea as well. Ifstated and carp will most likely get my attention if I want to productize the setup in a way that I set it up for someone else. Building a web UI for user device registration would be a cool addition. Using aggr(4) or trunk(4) to create a unified subnet for all the user traffic might be a called for as well. Many ideas abound, but the only limits are available time and a sufficient supply of beer.