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.
PyPi: https://pypi.org/project/linuxns-rel/ … install with
pip3 install linuxns-rel
GitHub project: https://github.com/TheDiveO/linuxns_rel
ioctl-ns(2): http://man7.org/linux/man-pages/man2/ioctl_ns.2.html
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
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, namelyget_userns()
andget_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()
andget_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'