About me: My name is Solène Rapenne, pronouns she/her. I like learning and sharing knowledge. Hobbies: '(BSD OpenBSD Qubes OS Lisp cmdline gaming security QubesOS internet-stuff). I love percent and lambda characters. OpenBSD developer solene@. No AI is involved in this blog.

Contact me: solene at dataswamp dot org or @solene@bsd.network (mastodon).

I'm a freelance OpenBSD, FreeBSD, Linux and Qubes OS consultant, this includes DevOps, DevSecOps, technical writing or documentation work.

If you enjoy this blog, you can sponsor my open source work financially so I can write this blog and contribute to Free Software as my daily job.

WireGuard and Linux network namespaces

Written by Solène, on 02 July 2024.
Tags: #security #vpn #network #linux

Comments on Fediverse/Mastodon

1. Introduction §

This guide explains how to setup a WireGuard tunnel on Linux using a dedicated network namespace so you can choose to run a program on the VPN or over clearnet.

I have been able to figure the setup thanks to the following blog post, I enhanced it a bit using scripts and sudo rules.

Mo Ismailzai's blog: Creating WireGuard jails with Linux network namespaces

2. Explanations §

By default, if you connect WireGuard tunnel, its "allowedIps" field will be used as a route with a higher priority than your current default route. It is not always ideal to have everything routed through a VPN, so you will create a dedicated network namespace that uses the VPN as a default route, without affecting all other software.

Unfortunately, compared to OpenBSD rdomain (which provide the same features in this situation), network namespaces are much more complicated to deal with and requires root to run a program under a namespace.

You will create a SAFE sudo rule to allow your user to run commands under the new namespace, making it more practical for daily use.

3. Setup §

3.1. VPN tunnel and namespace §

You need a wg-quick compatible WireGuard configuration file, but do not make it automatically used at boot.

Create a script (for root use only) with the following content, then make it executable:


# your VPN configuration file

# this directory is used to have a per netns resolver file
mkdir -p /etc/netns/vpn/

# cleanup any previous VPN in case you want to restart it
ip netns exec vpn ip l del tun0
ip netns del vpn

# information to reuse later
DNS=$(awk '/^DNS/ { print $3 }' $CONFIG)
IP=$(awk '/^Address/ { print $3 }' $CONFIG)

# the namespace will use the DNS defined in the VPN configuration file
echo "nameserver $DNS" > /etc/netns/vpn/resolv.conf

# now, it creates the namespace and configure it
ip netns add vpn
ip -n vpn link set lo up
ip link add tun0 type wireguard
ip link set tun0 netns vpn
ip netns exec vpn wg setconf tun0 <(wg-quick strip "$CONFIG")
ip -n vpn a add "$IP" dev tun0
ip -n vpn link set tun0 up
ip -n vpn route add default dev tun0
ip -n vpn add

# extra check if you want to verify the DNS used and the public IP assigned
#ip netns exec vpn dig ifconfig.me
#ip netns exec vpn curl https://ifconfig.me

This script autoconfigure the network namespace and the VPN interface + the DNS server to use. There are extra checks at the end of the script that you can uncomment if you want to take a look at the public IP and DNS resolver used just after connection.

Running this script will make the netns "vpn" available for use.

The command to run a program under the namespace is ip netns exec vpn your command, it can only be run as root.

3.2. Sudo rule §

Now you need a specific rule so you can use sudo to run a command in vpn netns as your own user without having to log in as root.

Add this to your sudo configuration file, in my example I allow the user solene to run commands as solene for the netns vpn:

solene ALL=(root) NOPASSWD: /usr/sbin/ip netns exec vpn /usr/bin/sudo -u solene -- *

When using this command line, you MUST use full paths exactly as in the sudo configuration file, this is important otherwise it would allow you to create a script called ip with whatever commands and run it as root, while /usr/sbin/ip can not be spoofed by a local script in $PATH.

If I want a shell session with the VPN, I can run the following command:

sudo /usr/sbin/ip netns exec vpn /usr/bin/sudo -u solene -- bash

This runs bash under the netns vpn, so any command I'm running from it will be using the VPN.

4. Limitations §

It is not a real limitation, but you may be caught by it, if you make a program listening on localhost in the netns vpn, you can only connect to it from another program in the same namespace. There are methods to connect two namespaces, but I do not plan to cover it, if you need to search about this setup, it can be done using socat (this is explained in the blog post linked earlier) or a local bridge interface.

5. Conclusion §

Network namespaces are a cool feature on Linux, but it is overly complicated in my opinion, unfortunately I have to deal with it, but at least it is working fine in practice.