💾 Archived View for perso.pw › blog › articles › linux-vpn-netns.gmi captured on 2024-07-09 at 01:02:26. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
NIL# 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
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.
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:
#!/bin/sh # your VPN configuration file CONFIG=/etc/wireguard/my-vpn.conf # 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.
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.
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.
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.