Skip to content

Instantly share code, notes, and snippets.

@thediveo
Created January 9, 2020 20:05
Show Gist options
  • Save thediveo/2c2785e552d6ba606ba3fe84cd7c8f6f to your computer and use it in GitHub Desktop.
Save thediveo/2c2785e552d6ba606ba3fe84cd7c8f6f to your computer and use it in GitHub Desktop.
Demonstrates keeping namespaces alive by keeping an open fd for them, as well as how the discovery powers of "lsns" and "lsuserns" differ.
# nsfdref.py
#
# Creates two network namespaces, referencing the first one by fd, and
# showing the output of "lsns" versus "lsuserns". "lsuserns" is able
# to discover the first network namespace from the fd which keeps the
# namespace alive.
#
# Requires these Python3 packages:
# - pip3 install unshare
# - pip3 install python-prctl
# - pip3 install linuxns-rel
# - pip3 install sty
# Gives access to the unshare(2) system call, which allows us to create
# new namespaces.
import unshare
# Provides access to effective, permitted, and inheritable capabilities
# sets. However, the python-prctl pacakge doesn't give us access to
# ambient capabilities.
import prctl
# Allows using the prctl(2) system call for changing the ambient
# capabilities of a process. And also gives us access to the setns(2)
# system call for switching namespaces.
import ctypes
import os
import subprocess
import sty # color me ... bad?
# We need setns() and prctl()...
libc = ctypes.CDLL('libc.so.6')
# prctl(2) related constants for changing ambient capabilities.
PR_CAP_AMBIENT = 47
PR_CAP_AMBIENT_RAISE = 2
PR_CAP_AMBIENT_LOWER = 3
# Sets the ambient capabilities to the inheritable capabilities.
def sync_ambient_caps():
for capidx, capname in enumerate(prctl.ALL_CAP_NAMES):
res = libc.prctl(
PR_CAP_AMBIENT,
PR_CAP_AMBIENT_RAISE if getattr(prctl.cap_inheritable, capname) else PR_CAP_AMBIENT_LOWER,
prctl.ALL_CAPS[capidx],
0, 0)
if res:
raise OSError(-res, os.strerror(-res))
# Runs "lsns" and "lsuserns" to show discovered network namespaces.
# Since we're running sandboxed in our own user namespace, this gives
# us access only to the namespaces within our user namespace sandbox,
# but not without.
def shownetns():
# lsuserns need CAP_SYS_PTRACE superpowers in order to be able to
# access the file descriptors of this process: otherwise, it cannot
# discover the still-alive fd reference to the first network
# namespace.
if not prctl.cap_inheritable.sys_ptrace:
prctl.cap_inheritable.sys_ptrace = True
sync_ambient_caps()
print(sty.ef.b + sty.fg.da_red + sty.bg.da_white + '☞☞☞☞ lsns:' + sty.bg.rs + sty.fg.rs + sty.rs.bold_dim)
subprocess.run(['lsns', '-t', 'net'])
print(sty.ef.b + sty.fg.da_green + sty.bg.da_white + '☞☞☞☞ lsuserns:' + sty.bg.rs + sty.fg.rs + sty.rs.bold_dim)
subprocess.run(['lsuserns', '-cd'])
def step(s):
print(sty.bg.da_blue + sty.fg.white + s + sty.fg.rs + sty.bg.rs)
# Show the network namespace we were started with...
step('❶ started')
print('#0 net:[%d]' % os.stat('/proc/self/ns/net').st_ino)
# Now create a new user namespace, as we are allowed to do so even when
# unprivileged ... and this then gives us all capabilities within this
# new user namespace (but within it and its children, but now in any
# parent user namespace or sibling user namespace. Then create a first
# network namespace and automatically switch into it...
print()
step('❷ creating and switching to netns #1, then open fd referencing netns #1...')
unshare.unshare(unshare.CLONE_NEWUSER + unshare.CLONE_NEWNET)
netns1fd = os.open('/proc/self/ns/net', os.O_RDONLY)
print('#1 net:[%d]' % os.stat(netns1fd).st_ino)
# Create a second network namespace, now switching from the first one
# into this new second one. Then show the output of the "lsns" versus
# "lsuserns": notice how "lsns" isn't able to discover the still
# existing first network namespace, as it does not check process file
# descriptors.
print()
step('❸ creating and switching to netns #2...')
unshare.unshare(unshare.CLONE_NEWNET)
print('#2 net:[%d]' % os.stat('/proc/self/ns/net').st_ino)
shownetns()
# And finally: switch back to the first network namespace we created
# earlier and that we kept alive using our fd, then print again the
# discovery results of "lsns" versus "lsuserns".
print()
step('❹ switching back to fd-referenced netns #1...')
libc.setns(netns1fd, unshare.CLONE_NEWNET)
print('#1 net:[%d]' % os.stat('/proc/self/ns/net').st_ino)
shownetns()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment