Linux Namespace Relations in Python

Introspection of Linux kernel namespace relationships, such as owning user namespace, parent namespace of a PID or user namespace, the owner’s user ID of a namespace, and some more.

CLI

This library comes with three simple CLI tools: lsuserns, lspidns, and graphns. The first two tools simply pretty-print the tree of Linux user (or PID) namespaces as can be discovered from the visible running processes. And the third? We’ll see…

To add some spice to the output, first open some user namespaces in a separate terminal session (fails? see how to enable user_namespaces in the kernel? for unprivileged “unshare”):

$ unshare -Ur unshare -Ur unshare -Ur unshare -Ur
$ lsuserns
user:[4026531837] process "init" owner root (0)
├── user:[4026532465] process "firefox" owner foobar (1000)
├── user:[4026532523] process owner foobar (1000)
│   └── user:[4026532524] process owner foobar (1000)
│       └── user:[4026532525] process owner foobar (1000)
│           └── user:[4026532526] process "bash" owner foobar (1000)
├── user:[4026532699] process "firefox" owner foobar (1000)
├── user:[4026532868] process "firefox" owner foobar (1000)
└── user:[4026532467] process owner foobar (1000)

Note

You may want to use sudo lsuserns instead to really see all available user namespaces.

Bored of ASCII? Let’s see a real graph (make sure that you’ve installed graphviz on your system, such as apt-get install graphviz); this example was taken using a freshly started chromium, the above user namespace command, and finally also a firefox open.

$ sudo -E graphns
_images/hns-graph.svg

Note

sudo -E ensures that you see all available user and PID namespaces, and also ensures that the graph viewer window correctly uses your desktop environment theme.

Note

All CLI tools are implemented in the same module linuxns_rel.tools.lshierns.

Examples

Without much ado, let’s try some examples. Simply import the linuxns_rel package to discover Linux kernel namespace relationships in Python.

>>> import linuxns_rel

Let’s get the owner user namespace of your Python script, and then print the user name of this owner.

>>> from pwd import getpwuid
>>> with linuxns_rel.get_userns('/proc/self/ns/net') as owner_ns:
...     owner_uid = linuxns_rel.get_owner_uid(owner_ns)
...     print('owning user:', getpwuid(owner_uid).pw_name)
owning user: root

Let’s get the parent user namespace of the user namespace our Python interpreter runs in.

>>> with linuxns_rel.get_parentns('/proc/self/ns/user') as parent_userns:
...     pass
Traceback (most recent call last):
  ...
PermissionError: [Errno 1] Operation not permitted

Now, here’s the caveat: when we run this inside either the so-called “root” user namespace so that there’s no parent, or we don’t have the privileges to learn the parent, then Linux will in both cases return an error, wrapped in a Python PermissionError.

All functions expecting a namespace reference, either accept:

  • a string that represents a filesystem path, such as ‘/proc/self/ns/user’.

  • a TextIO object, as returned by open() or some of the namespace relation functions, namely get_userns() and get_parentns().

  • a file descriptor or file number, such as returned by fileno().

Please note that there is no way to get a filesystem path name returned by get_userns() and get_parentns(): as Linux kernel namespaces might even not be referenced in the filesystem and due to the way unix-like filesystems work in general, there is no way to get back a filesystem path name from an open file.

API

linuxns_rel.CLONE_NEWCGROUP = 33554432

cgroup namespace type constant.

linuxns_rel.CLONE_NEWIPC = 134217728

inter-process communication namespace type constant.

linuxns_rel.CLONE_NEWNET = 1073741824

network namespace type constant.

linuxns_rel.CLONE_NEWNS = 131072

mount namespace type constant.

linuxns_rel.CLONE_NEWPID = 536870912

PID namespace type constant.

linuxns_rel.CLONE_NEWUSER = 268435456

user namespace type constant.

linuxns_rel.CLONE_NEWUTS = 67108864

uts (that is, *nix timesharing system) namespace type constant.

linuxns_rel.get_nstype(nsref: Union[str, IO, int]) → int[source]

Returns the type of namespace. The namespace can be referenced either via an open file, file descriptor, or path string.

The type returned is one of: CLONE_NEWNS, CLONE_NEWCGROUP, CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWUSER, CLONE_NEWPID, CLONE_NEWNET.

>>> import linuxns_rel
>>> linuxns_rel.get_nstype('/proc/self/ns/net') == linuxns_rel.CLONE_NEWNET
True

If you already have an open file referencing a Linux namespace, then you might use that directly:

>>> with open('/proc/self/ns/net') as netns_f:
...     linuxns_rel.get_nstype(netns_f) == linuxns_rel.CLONE_NEWNET
True
linuxns_rel.get_owner_uid(usernsref: Union[str, IO, int]) → int[source]

Returns the user ID of the owner of a user namespace, that is, the user ID of the process that created the user namespace. The user namespace parameter can be either an open file, file descriptor, or path string.

>>> import linuxns_rel
>>> linuxns_rel.get_owner_uid('/proc/self/ns/user')
0
linuxns_rel.get_parentns(nsref: Union[str, IO, int]) → IO[source]

Returns the parent namespace of a namespace, in form of a file object. The namespace parameter can be either an open file, file descriptor, or path string.

You can then use the returned file object for further namespace operations, such as iterative calls to get_parentns(), specifying each time the file object returned by the previous call.

However, it is impossible to retrieve a filesystem path, because Linux kernel namespaces might even not appear in the filesystem at all (such as “hidden” user namespaces without processes).

At this time, only two out of the implemented Linux kernel namespaces are hierarchical: the PID and user namespaces. All other namespaces are flat, despite what you first might have expected. In the case of user namespaces, the parent of a user namespace is the same as the owning user namespace of a user namespace.

In case you’ve reached the top of the hierarchical namespaces (at least from your point of view), then you’ll get a PermissionError. This also happens when you have insufficient rights to further go up a namespace hierarchy.

>>> import linuxns_rel
>>> with linuxns_rel.get_parentns('/proc/self/ns/user') as parent_owner_f:
...     pass
Traceback (most recent call last):
  ...
PermissionError: [Errno 1] Operation not permitted

When you ask for the parent namespace of a _flat_ namespace, you’ll get an OSError instead:

>>> with linuxns_rel.get_parentns('/proc/self/ns/uts') as parent_owner_f:
...     pass
Traceback (most recent call last):
  ...
OSError: [Errno 22] Invalid argument
linuxns_rel.get_userns(nsref: Union[str, IO, int]) → IO[source]

Returns the user namespace owning a namespace, in form of a file object referencing the owning user namespace. The owned namespace parameter can be either an open file, file descriptor, or path string.

So let’s get the owning user namespace of the PID namespace that our Python interpreter is using, and print it’s user ID. It will be root (unless you are running this inside a sandbox which uses a different owning user namespace).

>>> import linuxns_rel
>>> with linuxns_rel.get_userns('/proc/self/ns/pid') as owning_userns_f:
...     linuxns_rel.get_owner_uid(owning_userns_f)
0

Please note that the owning user namespace of a user namespace is its parent user namespace, so get_userns() and get_parentns() are synonymous in this case.

linuxns_rel.nstype_str(nstype: int) → str[source]

Returns the type name for a certain namespace type. Typically, this function is used in the context of get_nstype(), where all you got is a file, but not a path to (incorrectly) guess the type name of namespace from.

>>> import linuxns_rel
>>> linuxns_rel.nstype_str(
...     linuxns_rel.get_nstype('/proc/self/ns/net'))
'net'

So, what type of namespace does get_userns() return? As you might already guess: a user namespace. But let’s check that:

>>> linuxns_rel.nstype_str(
...     linuxns_rel.get_nstype(
...         linuxns_rel.get_userns('/proc/self/ns/net')))
'user'

Indices and tables