Route all transmission-daemon traffic over OpenVPN by using network namespaces

So I have:

  • Debian 11 server with transmission-daemon installed,
  • an OpenVPN client config file.

I need to connect to OpenVPN but not to route anything except Transmission through the tunnel. Let's do this!

Starting the OpenVPN client service

Place your .ovpn config to the /etc/openvpn/client directory and change its extension to .conf.

If server pushes defalut route, add following line to the config to ignore it:

pull-filter ignore redirect-gateway

Enable and start OpenVPN:

systemctl enable openvpn-client@your-config-name
systemctl start openvpn-client@your-config-name

You can check if it's working by running for example systemctl status openvpn-client@your-config-name.

Networking configuration

Create network namespace called vpn and a pair of veth devices.

VETH_ADDR=10.200.1.1
VPEER_ADDR=10.200.1.2

OUT_IFACE=tun0 # this is the OpenVPN tunnel interface
VETH_IFACE=vethns1
VPEER_IFACE=vpeerns1
VPN_NS=vpn

ip netns add $VPN_NS
ip link add $VETH_IFACE type veth peer name $VPEER_IFACE
ip link set $VPEER_IFACE netns $VPN_NS
ip addr add $VETH_ADDR/24 dev $VETH_IFACE
ip link set $VETH_IFACE up

ip netns exec $VPN_NS ip addr add $VPEER_ADDR/24 dev $VPEER_IFACE
ip netns exec $VPN_NS ip link set $VPEER_IFACE up
ip netns exec $VPN_NS ip link set lo up
ip netns exec $VPN_NS ip route add default via $VETH_ADDR

Routing

Define new routing table, call it vpn:

echo "100 vpn" >> /etc/iproute2/rt_tables

Add fwmark matching rule for this table (I use value of 100 here just for convenience, it can be anything, but must match the value in iptables rule):

ip rule add fwmark 100 table vpn

Add tun0 as the default route of the table vpn (10.8.0.8 is ipv4 address assigned to my tun0 device):

ip r add default via 10.8.0.8 dev tun0 table vpn

Okay, routing and netns stuff is done, time for some iptables.

Firewall configuration

Let's say 192.168.1.2 is the ipv4 address of our linux box. 9091 is the transmission's web interface port and we want it to be accessible:

iptables -t nat -A PREROUTING -d 192.168.1.2 -p tcp -m tcp --dport 9091 -j DNAT --to-destination 10.200.1.2:9091
iptables -t nat -A POSTROUTING -s $VPEER_ADDR/24 -o $OUT_IFACE -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 10.200.1.0/24 -j MASQUERADE

iptables -t mangle -A PREROUTING -s 10.200.1.0/24 ! -d 10.200.1.1 -j MARK --set-xmark 0x64/0xffffffff

At this point, internet should be accessible from within the vpn netns and all traffic coming from apps launched inside it must flow through the tun0 device.

Does it work? Check it before continuing

Let's run some tests, first, see if you can at least ping google:

ip netns exec vpn ping 8.8.8.8

If it works, try DNS resolving next. Sometimes it may be a problem, depending on your system DNS resolver:

ip netns exec vpn host google.com

If it fails to resolve, it's most probably because you have some local resolver server put to your /etc/resolv.conf (pushed by your router, for example), and it's, obviously, not accessible from within the vpn namespace because all traffic goes through tun0.

There are several ways of solving the issue.

  1. Add some rules to the vpn routing table for your LAN. I don't like it because it kinda defeats the purpose: we wanted to route ALL traffic, coming from that netns, through the VPN.
  2. (Not really a solution.) ip-netns util supports overriding resolv.conf for programs launched by ip netns exec. Just create a file named /etc/netns/vpn/resolv.conf and put some external resolver there, and it will be used instead of /etc/resolv.conf. It's ok for ip netns exec, but we're not going to use it for starting transmission-daemon.
  3. Just change the system resolver (in /etc/resolv.conf). For instance, set it to some public DNS service and mark the file immutable with chattr +i to prevent it from being modified by DHCP clients, network managers and other smart stuff. Or, if you feel that you can't stand the lack of privacy when using public DNS services, you can create some more complicated setup with local dnsmasq resolver listening at localhost:53 and dnscrypt-proxy as its backend... well it's up for you. Just make it work.
  4. I could make another trick and also add a mount namespace with substituted /etc/resolv.conf, but I didn't bother. I just use the 3rd option.

Launching Transmission

Our preparations are finally done and we're ready to launch transmission-daemon, but we need to launch it inside our new vpn network namespace. Fortunately, since version 242 systemd supports NetworkNamespacePath option which is going to be very handy for us.

Copy the unit file, we're going to edit it:

cp /lib/systemd/system/transmission-daemon.service /etc/systemd/system/

Under the [Service] section add the following line: NetworkNamespacePath=/run/netns/vpn

Probably reload systemd configuration and enable service if needed:

systemctl daemon-reload
systemctl enable transmission-daemon

Start it!

systemctl start transmission-daemon

Try connecting to 192.168.1.2:9091 and happy torrenting!

If you have any comments, contact me by email.
powered by OpenBSD
© ch1p 2021