me Murukesh Mohanan

Naming a netns with systemd Private Mounts

  1. Technology
  2. Linux

In my previous update, I used a symbolic link to set up the name for new namespace, like so:

ln -sf /proc/self/ns/net /var/run/netns/vpn

Months later, this resulted in a face-meet-palm moment as I realized that since /proc/self changes for every process, this link was meaningless. It didn’t prevent the rest of the setup from working fine, of course, as none of that uses the vpn name to refer to the netns. However, if I wanted to run a command inside, then I had a problem. I embarked upon yet another journey to see how I could name if /proc/self wasn’t option. A couple of ways came to mind:

/bin/sh -c 'ln -sf /proc/$$/ns/net /var/run/netns/vpn'
/bin/sh -c 'ip netns attach vpn $$'

The first option didn’t work. Since the process in question died immediately, the link would become invalid. The second option, in which ip netns attach does mount shenanigans, seemed like it should work. Apparently, it remounts /var/run/netns as a bind-mount to itself, making it shared in the process, so that mounts in it are propagated to child namespaces. Then it mounts the netns in a subdirectory there, so it can be accessed independently of any process. However, once systemd starts our services in private namespaces, it is too late - even using the + prefix to elevate our commands beyond these namespaces doesn’t seem to work, and the mounts aren’t propagated correctly.

So this should be something that’s done before our services start. I first tried using a separate netns-default service just for naming the original netns. Once the initial setup was done, one would think that ip netns attach should then start working even in restricted services if run with elevated privileges. Quelle surprise, ip tries to do the mount shenanigans all over again and fails:

ip[471]: mount --make-shared /var/run/netns failed: Operation not permitted

Then I fell back to using this in the VPN service override:

ExecStartPost=/usr/bin/ln -sf /proc/${MAINPID}/ns/net /var/run/netns/vpn

This way, the netns will be accessible at least as long as the VPN process stays alive. After thinking a bit more, I decided to go for a third service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# /etc/systemd/system/netns-vpn-post.service
[Unit]
Description=VPN network namespace (post)
ConditionPathExists=!/var/run/netns/vpn
After=<vpn>.service

[Install]
WantedBy=<vpn>.service

[Service]
Type=oneshot
RemainAfterExit=yes
# Hat-tip to A.B. here: https://serverfault.com/a/1097323/229499
ExecStartPre=:/bin/bash -c 'declare $(systemctl show --property MainPID <vpn>.service); ip netns attach vpn $MainPID'

This service doesn’t have to deal with private namespaces, and just sets up the name using ip netns attach. Note the : at the start of the command so that systemd leaves $ alone. Now everything looks nice:

% ip netns list
default
vpn (id: 0)

In the end, though, I went with just disabling PrivateMounts for the netns setup service, for which it doesn’t really matter, and running the ip netns attach commands there. Having PrivateMounts for the services run in that service might be fine, but for this one, it really was more trouble than it was worth.