Sometimes, you have multiple Internet connections, whether physical or virtual, and you want a few programs to access the Internet through one connection without making it the default gateway. For example, if you want a program to connect to the Internet through a VPN, but without forcing the entire system’s traffic through the VPN as well.

The traditional way to do this is with packet marking with iptables and an ip rule to force marked packets through a different routing table to send the traffic to the correct destination. However, as the source IP was selected before routing, an SNAT rule in iptables is required to change the source IP. This is ugly and clearly a hack.

However, since around 2013, Linux has introduced networking namespaces, which can be managed via ip netns as part of the iproute2 package. We can easily exploit this feature to achieve the desired goal with minimal fuss.

Assumptions

In this tutorial, we will assume that you have two network interfaces, eth0 and tap0, with gateways 10.0.0.1 and 192.168.0.1. The system’s default route is 10.0.0.1 on eth0, but you want to run curl and have the traffic go through tap0. This should be easily adaptable to your situation.

Furthermore, we assume that our IP on tap0 is 192.168.0.2/24, and that all the commands listed here are run as root.

We will only cover IPv4 in this guide, but the same concept works with IPv6. Only different IP addresses are necessary.

Steps

The first step is to create a new network namespace, which we will call test. To do this, run ip netns add test. To run programs inside this namespace, we can use ip netns exec test <command> [arguments...].

We then need to create a pair of virtual network interfaces to enable communication between the original namespace and the test namespace. To do this, we run ip link add veth_test type veth peer name vpeer_test. This creates a pair of interfaces veth_test and vpeer_test. The former will used in the original namespace, while the latter will be used in the test namespace. We can move vpeer_test to the test namespace by running ip link set vpeer_test netns test.

To make this interface work, we need to create a new network bridge, which we will call br0 in this example. This is so that tap0 and veth_test will be connected at the link level, allowing vpeer_test to be connected to the same network as tap0.

This can easily be done with brctl:

brctl addbr br0
brctl addif br0 tap0 veth_test
ip link set br0 up

If the host is to retain access to tap0, all configuration on tap0 will have to be replicated onto br0. You might be able to do this with:

ip addr flush dev tap0
ip addr add 192.168.0.2/24 dev br0

We need to assign an IP for the vpeer_test interface in the test namespace. In this example, we will use 192.168.0.3.

To configure the interface vpeer_test (add IP, default route, and bring it up), we can run:

ip netns exec test ip addr add 192.168.0.3/24 dev vpeer_test
ip netns exec test ip link set vpeer_test up
ip netns exec test ip route add default dev vpeer_test via 192.168.0.1

At this point, ip netns exec test curl will now access the Internet through tap0. For example, running ip netns exec test curl ifconfig.co will return tap0’s public IP address.

/etc/network/interfaces

The bridge configuration can be simplified by using /etc/network/interfaces, if you are using Debian or Ubuntu:

iface br0 inet static
    bridge_ports br0 veth_test
    address 192.168.0.2
    netmask 255.255.255.0

Then, the bridge can easily be brought up by

ip addr flush dev tap0
ifup br0

Summary

The following script should perform the setup steps automatically:

#!/bin/sh
ip netns add test
ip link add veth_test type veth peer name vpeer_test
ip link set vpeer_test netns test
ip addr flush dev tap0
brctl addbr br0
brctl addif br0 tap0 veth_test
ip link set br0 up
ip addr add 192.168.0.2/24 dev br0
ip netns exec test ip addr add 192.168.0.3/24 dev vpeer_test
ip netns exec test ip link set vpeer_test up
ip netns exec test ip route add default dev vpeer_test via 192.168.0.1

Then, prefix commands with ip netns exec test to run them in this namespace.

To undo the setup steps, simply run:

#!/bin/sh
ip link set br0 down
brctl delbr br0
ip link del veth_test
ip netns del test