Network namespaces with nsenter

Network 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 -p 44444

On the host, we can’t reach the server, since it’s operating in its own network namespace. In other words, the loopback address seen inside the container is isolated away from the loopback address on the host:

# on the host
$ echo "hello" | nc -v 44444
nc: connect to 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

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 44444
Connection to 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.