Expose network namespace created by Docker
Disclaimer: this is probably *not* the best way for doing this, but it's pretty good for educational purposes.
During a debug session I wanted to connect an application to a service tha ran in a docker container. This was for test-purposes only, so hackish and fast are the keywords.
First of all, I'm not a Docker expert, but I've a pretty good understanding on Linux internals, namespaces and how things works on a Linux system. So I started to use the tools I had.
Create a container to work with
This is not the container I wanted to debug, but to have something to demonstrate the concept:
FROM ubuntu MAINTAINER Marcus Folkesson <marcus.folkesson@gmail.com> RUN apt-get update RUN apt-get update RUN apt-get install -y socat RUN ["/bin/sh"]
Create and start the container
1docker build -t netns .
2docker run -ti netns /bin/bash
Inside the container, create a TCP-server with socat:
1root@c8a2438ad58e:/# socat - TCP-L:1234
Lets play around
I used to list network namespaces with ip netns list, but the command gave me no outputs even while the docker container was running.
That was unexpected. I wonder where ip actually look for namespaces. To find out I used strace and looked for the openat system call:
1$ strace -e openat ip netns list
2...
3openat(AT_FDCWD, "/var/run/netns", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 5
4...
Ok, ip netns list does probably look for files representing network namespaces in /var/run/netns.
Lets try to create an entry (dockerns) on that location:
1$ sudo touch /var/run/netns/dockerns
2$ ip netns list
3Error: Peer netns reference is invalid.
4Error: Peer netns reference is invalid.
5dockerns
Good. It tries to dereference the namespace!
All namespaces for a certain PID is exposed in procfs. For example, here are the namespaces that my bash session belongs to:
1$ ls -al /proc/self/ns/
2total 0
3dr-x--x--x 2 marcus marcus 0 15 dec 12.35 .
4dr-xr-xr-x 9 marcus marcus 0 15 dec 12.35 ..
5lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 cgroup -> 'cgroup:[4026531835]'
6lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 ipc -> 'ipc:[4026531839]'
7lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 mnt -> 'mnt:[4026531841]'
8lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 net -> 'net:[4026531840]'
9lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 pid -> 'pid:[4026531836]'
10lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 pid_for_children -> 'pid:[4026531836]'
11lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 time -> 'time:[4026531834]'
12lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 time_for_children -> 'time:[4026531834]'
13lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 user -> 'user:[4026531837]'
14lrwxrwxrwx 1 marcus marcus 0 15 dec 12.35 uts -> 'uts:[4026531838]'
Now we only should do the same for the container.
First, find the ID of the running container with docker ps:
1$ docker ps
2CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c8a2438ad58e netns "/bin/bash" About a minute ago Up 59 seconds dazzling_lewi
Inspect the running container to determine the PID:
1$ docker inspect -f '{{.State.Pid}}' c8a2438ad58e
236897
36897, there we have it.
Lets see which namespaces it has:
1$ sudo ls -al /proc/36897/ns
2total 0
3dr-x--x--x 2 root root 0 15 dec 09.59 .
4dr-xr-xr-x 9 root root 0 15 dec 09.59 ..
5lrwxrwxrwx 1 root root 0 15 dec 10.01 cgroup -> 'cgroup:[4026533596]'
6lrwxrwxrwx 1 root root 0 15 dec 10.01 ipc -> 'ipc:[4026533536]'
7lrwxrwxrwx 1 root root 0 15 dec 10.01 mnt -> 'mnt:[4026533533]'
8lrwxrwxrwx 1 root root 0 15 dec 09.59 net -> 'net:[4026533538]'
9lrwxrwxrwx 1 root root 0 15 dec 10.01 pid -> 'pid:[4026533537]'
10lrwxrwxrwx 1 root root 0 15 dec 10.01 pid_for_children -> 'pid:[4026533537]'
11lrwxrwxrwx 1 root root 0 15 dec 10.01 time -> 'time:[4026531834]'
12lrwxrwxrwx 1 root root 0 15 dec 10.01 time_for_children -> 'time:[4026531834]'
13lrwxrwxrwx 1 root root 0 15 dec 10.01 user -> 'user:[4026531837]'
14lrwxrwxrwx 1 root root 0 15 dec 10.01 uts -> 'uts:[4026533534]'
As we can see, the container have different IDs for the most (user and time is shared) namespaces.
Now we have the network namespace for the container, lets bind mount it to var/run/netns/dockerns:
1$ sudo mount -o bind /proc/36897/ns/net /var/run/netns/dockerns
And run ip netns list again:
1$ ip netns list
2dockerns (id: 0)
Nice.
Start socat in the dockerns network namespace and connect to localhost:1234:
1$ sudo ip netns exec dockerns socat - TCP:localhost:1234
2hello
3Hello from the outside world
4Hello from inside docker
It works! We are now connected to the service running in the container!
Conclusion
It's fun to play around, but there are room for improvements.
For example, a better way to list namespaces is to use lsns as this tool looks after namespaces in more paths including /run/docker/netns/.
Also, a more "correct" way is probably to create a virtual ethernet device and attach it to the same namespace.
veth example
To do that, we first need to determine the PID for the container:
1$ lsns -t net
2 NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
3 ...
4 4026533538 net 3 36897 root 0 /run/docker/netns/06a083424158 /bin/bash
Create the public dockerns namespace (create /var/run/netns/dockerns as we did earlier but with ip netns attach):
1$ sudo ip netns attach dockerns 36897
Create virtual interfaces, assign network namespace and create routes:
1$ sudo ip link add veth0 type veth peer name veth1
2$ sudo ip link set veth1 netns dockerns
3$ sudo ip address add 192.168.3.1/24 dev veth0
4$ sudo ip netns exec dockerns ip a add 192.168.3.2/24 dev veth1
5$ sudo ip link set up veth0
6$ sudo ip netns exec dockerns ip l set up veth1
7$ sudo ip route add 10.0.42.0/24 via 192.168.3.2
That is pretty much it.