This tutorial will look at how network namespaces can be defined in systemd service unit definitions. This example will at running the Nginx service inside a separate network namespace that has its own physical interface. Like most of my tutorials, this will be done on a Fedora system.
What are Network Namespaces?
Network namespaces are an important component of containerization in Linux. A network namespace (netns) allows a running process to see different network interfaces, routes, and firewall rules from the rest of the system. There are a number of use cases for network namespaces, such as running multiple servers, testing/staging environments and providing isolation of services.
Creating a Network Namespace
We begin by creating a generic systemd service for creating a named network namespace. I add the following to
/usr/lib/systemd/system/netns@.service. In systemd, the
@ mean the service takes a parameter which is passed to the unit via
%i. E.g, we can run
sudo systemctl start firstname.lastname@example.org.
Unit to set up the interface
Next, we create a definition in
/usr/lib/systemd/system/attach-enp3s1f0@.service. This service associates the
enp3s1f0 with a specified network namespace. It also sets up addresses within the network namespace. In iproute2, the command to run a process within a specified network namespace is
ip netns exec $namespace $command.
Running a process inside a network namespace
As a simple test, I define
/usr/lib/systemd/system/webserver.service which runs a simple TCP server over netcat inside the netns. Notice the
JoinsNamespaceOfemail@example.com option, stating that the service join the network namespace of an already running service. I added some extra privilege constraint such as
CapabilityBoundingSet= which are unrelated to network namespaces - these are for another post :)
Trying it out
After setting the above files, I run the following commands
enp3s1f0 interface has disappeared from the system, as it's no longer in the default namespace
Device "enp3s1f0" does not exist.
Let's see if its reachable
When a service is running in an alternate network namespace, it is possible to use the service's port on the host system, over all interfaces.
$ curl http://0.0.0.0:8080
$ curl http://192.168.0.80:8080
Services running in an alternate network namespace are unaffected by local firewall rules on the host system.
$ curl --max-time=3 http://0.0.0.0:8080
curl: (28) Connection timed out after 3001 milliseconds
$ curl http://192.168.0.80:8080
Associating the nginx service with the network namespace
PrivateNetworking=true to the
[Service] section and the following lines to the
[Unit] section of
Requires=webserver.service After=webserver.service JoinsNamespaceOffirstname.lastname@example.org
I then run
sudo systemctl daemon-reload; sudo systemctl start nginx.service.
Let's test it out
A couple of final things to note. Network namespaces are a form of specific isolation - they only concern networking, not filesystems, user rights, etc. These partial isolation systems can be joined to form general isolation, this is how Docker and containerd operate. Systemd and the related technologies on a modern Linux system are extremely powerful and there's a lot of isolation that can be done within systemd service definitions, such as privilege and capability dropping, which will be covered in more detail in a later post.
Standalone use of network namespaces can be useful for situations when the only form of isolation required is on the networking side. For example, running a service (such as the transmission-daemon bittorrent) client over a VPN, without requiring a VPN on the rest of the system.
I hope you found this post informative