How to traverse network namespaces
I’m working on a special routing daemon which peeks into network namespaces a lot. I use pyroute2
library to manage routes, and my initial intention was to use NetNS
facility in the pyroute2. Their approach to namespaces is to use a wrapper/helper, which spans a process inside a namespace and communicates back to the main process.
It’s very inefficient for my case, because I need to scan many namespaces on each run of a control loop, with expected run time for few hundreds of them in less then a second. I’ve needed a better version.
After some thinking and dubious hallucinations from ChatGPT I was able to write my code to do so. It’s a rather involved, but it really solve my problem of ‘fast namespace traversal’:
- It goes directly from one namespace into another, e.g. do not waste time on returning back to root namespace.
- It do not span processes, therefore run fast (at Python scale of fast, not the Rust one, sorry). Nevertheless, even ‘python fast’ is much faster than spanning 300+ processes every few seconds.
In this example I use netifaces
library to prove that I’ve switched to namespace. Its use is not mandatory.
import os
import ctypes
import netifaces
CLONE_NEWNET = 0x40000000
libc = ctypes.CDLL("libc.so.6", use_errno=True)
def ifaces():
for iface in netifaces.interfaces():
print(netifaces.ifaddresses(iface))
def ns(fd):
libc.setns(fd, CLONE_NEWNET)
root_fd = os.open('/proc/self/ns/net', os.O_RDONLY)
fd1 = os.open('/run/netns/qrouter-2d719c54-55f7-4ed7-98cd-3d08637dbb76', os.O_RDONLY)
fd2 = os.open('/run/netns/qrouter-1d7b1125-9954-4782-bf3d-70ef4ebf3277', os.O_RDONLY)
ifaces()
print("\n\n\nIn 1")
ns(fd1)
ifaces()
print("\n\n\nIn 2")
ns(fd2)
ifaces()
print("\n\nDone")
ns(root_fd)
ifaces()
The hallucination I’m talking about is use of /proc/self/ns/net
to obtain fd to original namespace. ChatGPT proposed to use open(‘/’)
, which is absurd, but it sparked my imagination to find a suitable source for FD, and ‘/proc/self’ is really good source.
The technique here is to call ‘setns’ function from libc directly. We need a constant for that (man 2 setns
), and a usual ctypes cruft.