Network namespaces with nsenter
Sun, Jul 9, 2017Network namespacing in Linux allows for multiple isolated network stacks on a single host. It is one of many such isolation techniques used by common container technologies, but we have some tools like nsenter
available that allow us to manually “break into” these namespaces.
Network Namespaces as Used by Docker
One of the key components of containerization in Linux is the concept of namespacing. Processes are executed in their own PID namespace (so that, for example, the init process in the container can always get PID 1). Users are created in their own namespace so that UIDs don’t collide with those of the host. Filesystems are mounted in their own namespace, etc. Namespaces isolate the container’s resources from those of the host and other containers.
With docker, unless you run a container with --net=host
, it will run in its own network namespace. This means the network is completely isolated from the host’s network, except for the docker0 bridge (default) or additional connecting pieces you explicitly set up. Per namespaces(7)
:
Network namespaces (CLONE_NEWNET)
Network namespaces provide isolation of the system resources asso‐
ciated with networking: network devices, IPv4 and IPv6 protocol
stacks, IP routing tables, firewalls, the /proc/net directory, the
/sys/class/net directory, port numbers (sockets), and so on.
This is a fundamental component of modern Linux containers.
Isolation in Practice
To see what this isolation means, run a netcat server inside a container:
$ sudo docker run --rm alpine nc -l 127.0.0.1 -p 44444
On the host, we can’t reach the server, since it’s operating in its own network namespace. In other words, the 127.0.0.1 loopback address seen inside the container is isolated away from the 127.0.0.1 loopback address on the host:
# on the host
$ echo "hello" | nc -v 127.0.0.1 44444
nc: connect to 127.0.0.1 port 44444 (tcp) failed: Connection refused
Entering a Namespace
Good news: we can run processes on the host in the container’s net namespace! This is really helpful for, say, inspecting some process the container runs that binds to loopback (as in our example).
First, we need to find the identifier of the net namespace. Typically, if you create the namespace yourself using iproute2 tooling, you might use:
ip netns exec $namespace_id <command>
to run some arbitrary command inside a given namespace.
However, ip netns
only knows about namespaces listed in /var/run/netns/
, and docker doesn’t symlink its namespaces there. Thankfully, nsenter(1)
allows us to run a command and provide the PID of a process whose namespace(s) we want to execute within. For example:
Find PID for docker container:
$ sudo docker inspect -f '{% raw %}{{.State.Pid}}{% endraw %}' c6fca78
16929
Execute a shell inside the PID’s network namespace, from the host. We can specify the -n
flag to indicate we want to enter the respective network namespace.
$ sudo nsenter -t 16929 -n /bin/sh
Successfully connect to the netcat server running in the container:
# from within the shell we just opened
$ echo "hello" | nc -v 127.0.0.1 44444
Connection to 127.0.0.1 44444 port [tcp/*] succeeded!
It’s helpful to note that nsenter
discovers the network namespace at /proc/$PID/ns/net
, where the namespace is accessible (by syscalls like setns(2)
) via file descriptor.