blob: 3f3131813c99f7585aa3a11ef4b69cf709d802fe [file] [log] [blame]
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Support for Linux namespaces"""
from __future__ import print_function
import ctypes
import ctypes.util
import errno
import os
# Note: We avoid cros_build_lib here as that's a "large" module and we want
# to keep this "light" and standalone. The subprocess usage in here is also
# simple by design -- if it gets more complicated, we should look at using
# the RunCommand helper.
import subprocess
import sys
from chromite.lib import osutils
from chromite.lib import process_util
CLONE_FS = 0x00000200
CLONE_FILES = 0x00000400
CLONE_NEWNS = 0x00020000
CLONE_NEWUTS = 0x04000000
CLONE_NEWIPC = 0x08000000
CLONE_NEWUSER = 0x10000000
CLONE_NEWPID = 0x20000000
CLONE_NEWNET = 0x40000000
def SetNS(fd, nstype):
"""Binding to the Linux setns system call. See setns(2) for details.
Args:
fd: An open file descriptor or path to one.
nstype: Namespace to enter; one of CLONE_*.
Raises:
OSError: if setns failed.
"""
try:
fp = None
if isinstance(fd, basestring):
fp = open(fd)
fd = fp.fileno()
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
if libc.setns(ctypes.c_int(fd), ctypes.c_int(nstype)) != 0:
e = ctypes.get_errno()
raise OSError(e, os.strerror(e))
finally:
if fp is not None:
fp.close()
def Unshare(flags):
"""Binding to the Linux unshare system call. See unshare(2) for details.
Args:
flags: Namespaces to unshare; bitwise OR of CLONE_* flags.
Raises:
OSError: if unshare failed.
"""
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
if libc.unshare(ctypes.c_int(flags)) != 0:
e = ctypes.get_errno()
raise OSError(e, os.strerror(e))
def _ReapChildren(pid):
"""Reap all children that get reparented to us until we see |pid| exit.
Args:
pid: The main child to watch for.
Returns:
The wait status of the |pid| child.
"""
pid_status = 0
while True:
try:
(wpid, status) = os.wait()
if pid == wpid:
# Save the status of our main child so we can exit with it below.
pid_status = status
except OSError as e:
if e.errno == errno.ECHILD:
break
else:
raise
return pid_status
def CreatePidNs():
"""Start a new pid namespace
This will launch all the right manager processes. The child that returns
will be isolated in a new pid namespace.
If functionality is not available, then it will return w/out doing anything.
Returns:
The last pid outside of the namespace.
"""
first_pid = os.getpid()
try:
# First create the namespace.
Unshare(CLONE_NEWPID)
except OSError as e:
if e.errno == errno.EINVAL:
# For older kernels, or the functionality is disabled in the config,
# return silently. We don't want to hard require this stuff.
return first_pid
else:
# For all other errors, abort. They shouldn't happen.
raise
# Now that we're in the new pid namespace, fork. The parent is the master
# of it in the original namespace, so it only monitors the child inside it.
# It is only allowed to fork once too.
pid = os.fork()
if pid:
# Reap the children as the parent of the new namespace.
process_util.ExitAsStatus(_ReapChildren(pid))
else:
# Make sure to unshare the existing mount point if needed. Some distros
# create shared mount points everywhere by default.
try:
osutils.Mount('none', '/proc', 0, osutils.MS_PRIVATE | osutils.MS_REC)
except OSError as e:
if e.errno != errno.EINVAL:
raise
# The child needs its own proc mount as it'll be different.
osutils.Mount('proc', '/proc', 'proc',
osutils.MS_NOSUID | osutils.MS_NODEV | osutils.MS_NOEXEC |
osutils.MS_RELATIME)
pid = os.fork()
if pid:
# Watch all of the children. We need to act as the master inside the
# namespace and reap old processes.
process_util.ExitAsStatus(_ReapChildren(pid))
# The grandchild will return and take over the rest of the sdk steps.
return first_pid
def CreateNetNs():
"""Start a new net namespace
We will bring up the loopback interface, but that is all.
If functionality is not available, then it will return w/out doing anything.
"""
# The net namespace was added in 2.6.24 and may be disabled in the kernel.
try:
Unshare(CLONE_NEWNET)
except OSError as e:
if e.errno == errno.EINVAL:
return
else:
# For all other errors, abort. They shouldn't happen.
raise
# Since we've unshared the net namespace, we need to bring up loopback.
# The kernel automatically adds the various ip addresses, so skip that.
try:
subprocess.call(['ip', 'link', 'set', 'up', 'lo'])
except OSError as e:
if e.errno == errno.ENOENT:
print('warning: could not bring up loopback for network; '
'install the iproute2 package', file=sys.stderr)
else:
raise
def SimpleUnshare(mount=True, uts=True, ipc=True, net=False, pid=False):
"""Simpler helper for setting up namespaces quickly.
If support for any namespace type is not available, we'll silently skip it.
Args:
mount: Create a mount namespace.
uts: Create a UTS namespace.
ipc: Create an IPC namespace.
net: Create a net namespace.
pid: Create a pid namespace.
"""
# The mount namespace is the only one really guaranteed to exist --
# it's been supported forever and it cannot be turned off.
if mount:
Unshare(CLONE_NEWNS)
# The UTS namespace was added 2.6.19 and may be disabled in the kernel.
if uts:
try:
Unshare(CLONE_NEWUTS)
except OSError as e:
if e.errno != errno.EINVAL:
pass
# The IPC namespace was added 2.6.19 and may be disabled in the kernel.
if ipc:
try:
Unshare(CLONE_NEWIPC)
except OSError as e:
if e.errno != errno.EINVAL:
pass
if net:
CreateNetNs()
if pid:
CreatePidNs()