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.