NSWG Part 1: Network namespaces

Namespaces allow to partition sets of resources in the Linux kernel. Things that happen in one namespace are independent of things that happen in other namespaces.

A network namespace has, among other things, it's own set of network interfaces, firewall rules and routing table.

The wireguard vpn integrates very nicely with network namespaces (you can see more details at https://www.wireguard.com/netns/), that allows network isolation with very little use of firewall rules.

I started to work on a tool called nswg to automate common tasks pertaining network namespaces and wireguard. The name comes from ns=namespace and wg=wireguard.

nswg is a single bash script that is publicly available on codeberg. To use it you just need to download the file called nswg and put it somewhere in your path.

Note that you need to be the root user or have sudo access in order to use nswg.

In this post I give a very brief introduction to network namespaces and show how to use nswg to interact with them. Given that this post is already long I will talk about the integration with wireguard in another post.

Along this post code blocks that start with # require root privileges.

Network namespaces

In a typical GNU/Linux installation when the OS starts, there is an initial network namespace containing all the network interfaces. You can create new ones with the ip command from iproute2.

ip netns add myns

You need to be root or use sudo for the command above to work. This creates a named network namespace called myns.

To see the list of interfaces and their addresses in myns you can run

ip -n myns addr
Which should give an output similar to
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00  

myns only contains the loopback interface. In general to run ip on a specific namespace you use the option -n followed by the name of the namespace.

Running programs in different network namespaces

Suppose you want to run a program in myns. For instance suppose you want to run iptables -L to see the its firewall rules in the filter table. For that you do

ip netns exec myns iptables -L

The method used above works well when you want to run something as the super user. Running something as a normal user on a different network namespace is a little trickier. For this reason nswg has the command run.

To use it you specify the name of the network namespace with the option -n, then run and the rest is the command that is executed as the same user invoking nswg.

For example if you want to run curl in the new namespace you can do

nswg -n myns run curl https://www.wikipedia.org

which should fail since only the loopback device exists in the namespace.

Things are a little more complicated with browsers. If you are already running a browser in the default namespace and you open a new one it will simply create a window on the running browser and it will continue using the default namespace.

With firefox, if you want to run one on the default namespace and another one in a different namespace you can do it by using a different profile

To run firefox in myns and have a menu to create a new profile for the given namespace you can run the following

nswg -n myns run  firefox --no-remote -P &

The --no-remote part is for firefox to start a new instance instead of opening a window in an already existing firefox that was not started in the network namespace.

Connecting to a different network namespaces

I know what you are thinking "Booooriiiing..., Why would I care about firewall rules when only the loopback device is available? I cannot connect to anything with this!"

Alright! alright!. Let us go a little further, lets connect this new boring namespace to our current namespace.

One way to connect two namespaces is to use a veth pair. These are devices that can communicate with each other while being in different namespaces, as long as you assign them ip's in the same network subnet.

I won't go through the details of how to create and configure the veth pair. If you would like to see how to do this, there is a step by step guide in this blog post.

The connect command of nswg automates the task. You only need to provide the target namespace (option -n), the ip address of the veth device in the current namespace (option -ipa) and the ip address for the veth in the target namespace (option -ipb).

Two points about how ip's need to be provided.

nswg -n myns \
       -ipa '10.0.1.1/24,fc00::1/112' \
       -ipb '10.0.1.2/24,fc00::2/112' \
       connect
We can use ping to check the connection but lets do something different. The following block of code creates a temporary folder, then it creates in there the file index.html with html code that displays NSWG TEST. Then python is used to start a server listening on port 8001 that shows the file we added to that folder. After you close the python server (with Control+C for example), the temporary folder is erased.
example_folder="$(mktemp -d)"

echo '<!DOCTYPE html><html><body><h1> NSWG TEST </h1></body></html>' > \
    "${example_folder}/index.html"
    
nswg -n myns run \
    python3 -m http.server \
    --directory "$example_folder" \
    --bind :: \
    8001

rm -r "$example_folder"

NOTE: If you have a python version below 3.8 IPv6 is not supported in the http.server module and you need to get rid of the line with --bind :: and you'll only be able to connect using IPv4.

At this point with your browser you should be able to see the site we created by visiting http://10.0.1.2:8001 and http://[fc00::2]:8001.

Note that the python process that serves the website is occupying the port 8001 on myns and not in your default namespace. This means that if you run a browser in the default network namespace, visiting http://localhost:8001 should give you a connection problem message (assuming you are not running something else on that port); but if you open a browser in myns, visiting http://localhost:8001 should show you the website.

With the current setup, programs that run in myns can connect to the veth pair, which you can verify with

nswg -n myns run ping 10.0.1.1

But it cannot connect to anything else.

nswg -n myns run ping wikipedia.org

Let us undo all the modifications we have done to myns before passing to the next section. For that we can do

nswg -n myns cleanup

Connecting a namespace to the internet

To allow the new namespace it is necessary to tell nswg how to tunnel the connection. In other words, you need to tell it what the gateway is. You can use the option -gw for this. As with all ip's provided to nswg, the gateway must contain the subnet using the CIDR notation.

Most routers only support IPv4 so we do it here for IPv4 only. The gateway you need to pass is the one of your current namespace, which you can find by running ip route and it should be the ip that appears on the line that starts with default. Then you need to add the subnet. For this you can check the ip and subnet of your device connected to the internet.

To make this a little easier, nswg has the command guess-gw that guesses your IPv4 gateway with subnet and prints it on the screen. It should work in most cases.

The following block gets the gateway using guess-gw and then passes it to nswg.

gateway=$(nswg guess-gw)
nswg -n myns \
  -ipa '10.0.1.1/24' \
  -ipb '10.0.1.2/24' \
  -gw $gateway \
  connect
Now you should be able to connect to the internet from myns. Pinging wikipedia should work now:
nswg -n myns run ping wikipedia.org
If you want you can also select a dns to be used by programs running inside the created namespace. For that you need to pass the -dns option. To do that let us first cleanup again with nswg -n myns cleanup and assume you want to set the dns to 9.9.9.9.
gateway=$(nswg guess-gw)
  nswg -n myns \
    -ipa '10.0.1.1/24' \
    -ipb '10.0.1.2/24' \
    -gw $gateway \
    -dns 9.9.9.9 \
    connect
If you use -dns and you are running privacy related applications (like a vpn) you need to take some further measures to make sure there are no DNS leaks. I will talk about this in a different post.