Feed all traffic through OpenVPN for a specific network namespace only

You can start the OpenVPN link inside a namespace and then run every command you want to use that OpenVPN link inside the namespace. Details on how to do it are presented in Running an OpenVPN tunnel inside a network namespace, by Sebastian Thorarensen.

I tried it and it does work.  The idea is to provide a custom script to carry out the up and route-up phases of the OpenVPN connection inside a specific namespace instead of the global one.  Here is an answer based on the above source, but modified to add Google DNS to resolv.conf.

First create an --up script for OpenVPN.  This script will create the VPN tunnel interface inside a network namespace called vpn, instead of the default namespace.

$ cat > netns-up << 'EOF'
#!/bin/sh
case $script_type in
        up)
                ip netns add vpn
                ip netns exec vpn ip link set dev lo up
                mkdir -p /etc/netns/vpn
                echo "nameserver 8.8.8.8" > /etc/netns/vpn/resolv.conf
                ip link set dev "$1" up netns vpn mtu "$2"
                ip netns exec vpn ip addr add dev "$1" \
                        "$4/${ifconfig_netmask:-30}" \
                        ${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"}
                test -n "$ifconfig_ipv6_local" && \
                        ip netns exec vpn ip addr add dev "$1" \
                                "$ifconfig_ipv6_local"/112
                ;;
        route-up)
                ip netns exec vpn ip route add default via "$route_vpn_gateway"
                test -n "$ifconfig_ipv6_remote" && \
                        ip netns exec vpn ip route add default via \
                                "$ifconfig_ipv6_remote"
                ;;
        down)
                ip netns delete vpn
                ;;
esac
EOF

Then start OpenVPN and tell it to use our --up script instead of executing ifconfig and route.

openvpn --ifconfig-noexec --route-noexec --up netns-up --route-up netns-up --down netns-up

Now you can start programs to be tunneled like this:

ip netns exec vpn command

The only catch is that you need to be root to invoke ip netns exec ... and maybe you do not want your application to run as root.  The solution is simple:

sudo ip netns exec vpn sudo -u $(whoami) command

It turns out that you can put a tunnel interface into a network namespace. My entire problem was down to a mistake in bringing up the interface:

ip addr add dev $tun_tundv \
    local $ifconfig_local/$ifconfig_cidr \
    broadcast $ifconfig_broadcast \
    scope link

The problem is "scope link", which I misunderstood as only affecting routing. It causes the kernel to set the source address of all packets sent into the tunnel to 0.0.0.0; presumably the OpenVPN server would then discard them as invalid per RFC1122; even if it didn't, the destination would obviously be unable to reply.

Everything worked correctly in the absence of network namespaces because openvpn's built-in network configuration script did not make this mistake. And without "scope link", my original script works as well.

(How did I discover this, you ask? By running strace on the openvpn process, set to hexdump everything it read from the tunnel descriptor, and then manually decoding the packet headers.)


The error on attempting to create the veth devices is caused by a change of how ip interprets the command line arguments.

The correct invocation of ip to create a pair of veth devices is

ip link add name veth0 type veth peer name veth1

(name instad of dev)

Now, how to get traffic out from the namespace to the VPN tunnel? Since you have only tun devices at your disposal, the "host" must route. I.e. create the veth pair and put one into the namespace. Connect the other via routing to the tunnel. Thus, enable forwarding, and then add the necessary routes.

For the sake of example suppose that eth0 is your main interface, tun0 is your VPN tunnel interface, and veth0/veth1 the pair of interfaces of which veth1 is in the namespace. Within the namespace you add just a default route for veth1.

On the host you need to employ policy routing, see here for instance. What you need to do:

Add/append an entry like

1   vpn

to /etc/iproute2/rt_tables. By this you can call the (yet to be created) table by name.

Then use the following statements:

ip rule add iif veth0 priority 1000 table vpn
ip rule add iif tun0 priority 1001 table vpn
ip route add default via <ip-addr-of-tun0> table vpn
ip route add <ns-network> via <ip-addr-of-veth0> table vpn

I cannot try that out here with a setup like yours, but this should do exactly what you want. You may augment that by packet filter rules such that neither the vpn nor the "guest" net are disturbed.

N.B. Moving tun0 into the namespace in the first place looks like the right thing to do. But like you I didn't get that to work. Policy routing looks like the next right thing to do. Mahendra's solution is applicable if you know the networks behind the VPN and all other applications will never access those networks. But your initial condition ("all of the traffic, and only the traffic, to/from specific processes goes through the VPN") sounds as if the latter cannot be guaranteed.