Start multiprocessing.process in namespace

2.3k Views Asked by At

I'm trying to start a new process from within an already created namespace (named 'test').

I've looked into a few methods including nsenter:

import subprocess
from nsenter import Namespace

with Namespace(mypid, 'net'):
    # output network interfaces as seen from within the mypid's net NS:
    subprocess.check_output(['ip', 'a'])

But I cant seem to find a reference of where to find the var, mypid...!

Ideally I'd like to keep dependancies like nsenter to a minimum (for portability) so i'd probably like to go down the ctypes route, something like (although there is no syscall for netns...):

nsname = 'test'
netnspath = '%s%s' % ('/run/netns/', nsname)
netnspath = netnspath.encode('ascii')

libc = ctypes.CDLL('libc.so.6')

printdir(libc)

fd = open(netnspath)
print libc.syscall(???, fd.fileno())

OR (taken from http://tech.zalando.com/posts/entering-kernel-namespaces-with-python.html)

import ctypes
libc = ctypes.CDLL('libc.so.6')
# replace MYPID with the container's PID
fd = open('/proc/<MYPID>/ns/net')
libc.setns(fd.fileno(), 0)
# we are now inside MYPID's network namespace

However, I still have to know the PID, plus my libc does not have setns!

Any thoughts on how I could obtain the PID would be great!

TIA!

3

There are 3 best solutions below

4
On

The problem with the nsenter module is that you need to provide it with the PID of a process that is already running inside your target namespace. This means that you can't actually use this module to make use of a network namespace that you have created using something like ip netns add.

The kernel's setns() system call takes a file descriptor rather than a PID. If you're willing to solve it with ctypes, you can do something like this:

from ctypes import cdll
libc = cdll.LoadLibrary('libc.so.6')
_setns = libc.setns

CLONE_NEWIPC = 0x08000000
CLONE_NEWNET = 0x40000000
CLONE_NEWUTS = 0x04000000

def setns(fd, nstype):
    if hasattr(fd, 'fileno'):
        fd = fd.fileno()

    _setns(fd, nstype)

def get_netns_path(nspath=None, nsname=None, nspid=None):
    '''Generate a filesystem path from a namespace name or pid,
    and return a filesystem path to the appropriate file.  Returns
    the nspath argument if both nsname and nspid are None.'''

    if nsname:
        nspath = '/var/run/netns/%s' % nsname
    elif nspid:
        nspath = '/proc/%d/ns/net' % nspid

    return nspath

If your libc doesn't have the setns() call, you may be out of luck (although where are you running that you have a kernel recent enough to support network namespaces but a libc that doesn't?).

Assuming you have a namespace named "blue" available (ip netns add blue) you can run:

with open(get_netns_path(nsname="blue")) as fd:
    setns(fd, CLONE_NEWNET)
    subprocess.check_call(['ip', 'a'])

Note that you must run this code as root.

1
On

This works, however I'm unsure at what the 0 does as part of the syscall. So if someone could enlighten me that would be great!

import ctypes

nsname = 'test'
netnspath = '%s%s' % ('/run/netns/', nsname)
netnspath = netnspath.encode('ascii')

libc = ctypes.CDLL('libc.so.6')

fd = open(netnspath)
print libc.syscall(308, fd.fileno(), 0)
2
On

After finding this question we've updated python-nsenter so it is now able to enter namespaces via an arbitrary path in addition to providing the pid.

For example if you wanted to enter a namespace created by ip netns add you can now do something like:

with Namespace('/var/run/netns/foo', 'net'):
    # do something in the namespace
    pass

Version 0.2 is now available via PyPi with this update.