How to Run a Program with Specific Network Configuration Nov 9th 2021 Words: 718

Problem

I have a second gateway in my network, and I want a specific program to use that gateway.

Solution

This can be achieved by using linux kernel feature: network namespace.

A network namespace is logically another copy of the network stack, with its own routes, firewall rules, and network devices.

My goal is to create a network namespace that uses the second gateway, and run my program within it.

Procedure

Add a network namespace named proxy:

1
sudo ip netns add proxy

With the namespace created, the following syntax can be used to execute a command inside a network namespace:

1
sudo ip netns exec <namespace> <command> [args]

However, the namespace does not have Internet connection yet, thus a virtual network interface (read more) must be created. Many tutorials I read descibe the method utilizing veth to create a NATed network, but the method would not work in my case, since the default gateway in a routing table must be direct connected. The namespace must be connected to the LAN appearing as a direct connected device. Macvlan bridge is the virtual interface type I prefered for this task. Note in macvlan bridge mode, host to client communication may not work.

1
2
3
4
# get interface name (eth0, eno0, enp34s0, ...)
iname=$(ip -o link show | sed -rn '/^[0-9]+: en/{s/.: ([^:]*):.*/\1/p}')
# add the macvlan virtual interface
sudo ip link add macvlan-proxy link $iname type macvlan mode bridge

Verify the interface with ip a

Now assign the created macvlan interface to the namespace:

1
sudo ip link set macvlan-proxy netns proxy

The interface is now moved to the proxy and become invisible in the default namespace.

Before an IP address can be assigned to the interface, it must be brought up:

1
sudo ip netns exec proxy ip link set lo up

Note that by default, the lo interface is not up, means that you cannot use 127.0.0.1 or localhost in the namespace by default. This can easily be fixed:

1
sudo ip netns exec proxy ip link set macvlan-proxy up

Now the IP address can be assigned to the macvlan interface:

1
sudo ip netns exec proxy ip addr add 192.168.1.254/24 dev macvlan-proxy

Set the routing table and the namespace is ready to use:

1
sudo ip netns exec proxy ip route add default via 192.168.1.2

Test:

1
sudo ip netns exec proxy curl http://google.com

The network namespace does not persist after boot, but to remove it manually:

1
sudo ip netns del proxy

Script

I worte a script for easy creation. Usage: netns.sh <on|off>

netns.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/usr/bin/env sh

NAMESPACE=proxy
GATEWAY=172.16.1.15
STATIC_IP=172.16.1.97/24
DNS_SERVER=172.16.1.1

# check root permission
if [ "$(id -u)" != "0" ]; then
echo "Please run as root"
exit 1
fi

# parse current network config
iname=$(ip -o link show | sed -rn '/^[0-9]+: en/{s/.: ([^:]*):.*/\1/p}') # enp34s0

on() {
# create namespace
ip netns add $NAMESPACE
# create macvlan
ip link add macvlan-$NAMESPACE link $iname type macvlan mode bridge
# assign macvlan to the namespace
ip link set macvlan-$NAMESPACE netns $NAMESPACE
# make resolve.conf
mkdir -p /etc/netns/$NAMESPACE
touch /etc/netns/$NAMESPACE/resolv.conf
# bring up the interfaces
ip netns exec $NAMESPACE ip link set lo up
ip netns exec $NAMESPACE ip link set macvlan-$NAMESPACE up
# set IP address of the macvlan interface
[ -z "$STATIC_IP" ] && ip netns exec $NAMESPACE dhclient macvlan-$NAMESPACE || ip netns exec $NAMESPACE ip addr add $STATIC_IP dev macvlan-$NAMESPACE
sleep 1
# add default route in the namespace
[ -z "$STATIC_IP" ] && ip netns exec $NAMESPACE ip route del default
ip netns exec $NAMESPACE ip route add default via $GATEWAY
# custom dns
[ -z "$DNS_SERVER" ] || echo "nameserver $DNS_SERVER" > /etc/netns/$NAMESPACE/resolv.conf

echo "Namespace '$NAMESPACE' created with gateway '$GATEWAY'"
}

off() {
ip netns del $NAMESPACE
rm -rf /etc/netns/$NAMESPACE
}


if [ "$1" = "on" ]; then
on
elif [ "$1" = "off" ]; then
off
else
echo -e "Invalid args! \nUsage: netns.sh [on|off]"
exit 1
fi

Reference