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.
- Each ip must contain the subnet using the CIDR notation.
- You can assign multiple ips at the same time by separating them with comma. All ips can be IPv4 or IPv6.
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.