| # 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() |