Run OpenVPN in a Linux network namespace

2022-02-04

Users may wish to conceal their home IP for a variety of reasons. This is often done with a proxy such as Tor. Redirecting all traffic through a proxy is slow however and may not be necessary for all applications. Making only specific applications use a proxy can be done using Linux network namespaces.

This guide assumes you’re using OpenVPN, though any type of VPN should work.

Creating a new network namespace

A new network namespace can be created with the ip utility:

$ ip netns add mynetns

To execute a program inside this namespace, use ip netns exec

$ ip netns exec mynetns ip l
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

For ip, a shorter syntax is:

$ ip -n mynetns l

To allow connections to the outside world a veth device must be added.

$ ip l add mynetns_a type veth peer name mynetns_b
$ ip l
...
6: mynetns_b@mynetns_a: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 0a:3d:7c:b3:f9:a4 brd ff:ff:ff:ff:ff:ff
7: mynetns_a@mynetns_b: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 0e:94:83:65:95:e8 brd ff:ff:ff:ff:ff:ff

We now need to move one of the interfaces into the proper namespace:

$ ip l set mynetns_a netns mynetns
$ ip -n mynetns l
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
7: mynetns_a@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 0e:94:83:65:95:e8 brd ff:ff:ff:ff:ff:ff link-netnsid 0

Give the interfaces an IP:

$ ip -n mynetns a add 192.168.0.2/24 dev mynetns_a
$ ip a add 192.168.0.1/24 dev mynetns_b

Bring the interfaces up:

$ ip -n mynetns l set up dev mynetns_a
$ ip l set up dev mynetns_b

You should now be able to send packets through the interface:

$ ping 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.040 ms

Forwarding packets

We now need to forward packets to the interface. Since we’re using IPv4 we’ll use NAT:

$ echo 1 > /proc/sys/net/ipv4/ip_foward
$ iptables -t filter -A FORWARD -i mynetns_b -j ACCEPT
$ iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

Configure the default route:

$ ip -n mynetns r add default via 192.168.0.1

To test if it works, try pinging a public IP:

$ ip netns exec ping demindiro.com
PING demindiro.com (104.244.79.104) 56(84) bytes of data.
64 bytes from mail.salt-inc.org (104.244.79.104): icmp_seq=1 ttl=62 time=0.165 ms

Running OpenVPN inside the namespace

Running any service inside the namespace is trivial:

$ cd /etc/openvpn
$ ip netns exec openvpn myvpn.conf

Service script

This run script can be used with runit:

#!/bin/sh

ip netns add mynetns
ip l add mynetns_a type veth peer name mynetns_b
ip l set mynetns_a netns mynetns
ip a add 192.168.0.1/24 dev mynetns_b
ip -n mynetns a add 192.168.0.2/24 dev mynetns_a
ip l set up mynetns_b
ip -n mynetns l set up mynetns_a
ip -n mynetns r add default via 192.168.0.1

echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t filter -A FORWARD -i mynetns_b -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

cd /etc/openvpn
exec ip netns exec mynetns openvpn myvpn.conf

For cleanup, use this finish script:

#!/bin/sh

ip netns del mynetns

echo 0 > /proc/sys/net/ipv4/ip_forward
iptables -t filter -D FORWARD -i mynetns_b -j ACCEPT
iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

In any service that should use the VPN, add this:

sv start myvpn || exit

# ...

exec ip netns exec mynetns myservice ...