0

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!

geekscrap
  • 965
  • 2
  • 12
  • 26

3 Answers3

2

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.

larsks
  • 277,717
  • 41
  • 399
  • 399
  • That looks like a solution to me, I'll test it... FYI I'm actually testing on Kali which apparently has netns support but no setns()...! Do you know how to test to see if the kernel has been built with setns? Might try spin up a VM.... – geekscrap May 08 '15 at 16:39
  • hmmm Debian Wheezy doesn't have setns either... What you running on?! – geekscrap May 08 '15 at 16:44
  • I've tested this on both Fedora 21 and Ubuntu Trusty (14.04). – larsks May 08 '15 at 16:57
  • You need a kernel >= 3.0 and glibc >= 2.14 to use setns(). You can check your glibc version by executing your libc.so.6. – cnelson Jan 08 '16 at 15:13
0

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)
geekscrap
  • 965
  • 2
  • 12
  • 26
0

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.

cnelson
  • 1,355
  • 11
  • 14
  • Thanks @cnelson! I haven't looked at this project for a while as this was a major stumbling block (I started using the ctypes method, but it gave me weird results) I'll have a go and feedback! Many thanks – geekscrap Jan 09 '16 at 15:56
  • Just as a side question, how would I go about setting the entire process into the namespace indefinitely? Do we always have to use 'with'? – geekscrap Jan 09 '16 at 16:06