| # SPDX-License-Identifier: GPL-2.0 |
| |
| import ctypes |
| import os |
| import random |
| import string |
| import subprocess |
| import time |
| from pathlib import Path |
| |
| from .utils import ip |
| |
| libc = ctypes.cdll.LoadLibrary('libc.so.6') |
| |
| |
| class NetNS: |
| def __init__(self, name=None): |
| if name: |
| self.name = name |
| else: |
| self.name = ''.join(random.choice(string.ascii_lowercase) for _ in range(8)) |
| ip('netns add ' + self.name) |
| |
| def __del__(self): |
| if self.name: |
| ip('netns del ' + self.name) |
| self.name = None |
| |
| def __enter__(self): |
| return self |
| |
| def __exit__(self, ex_type, ex_value, ex_tb): |
| self.__del__() |
| |
| def __str__(self): |
| return self.name |
| |
| def __repr__(self): |
| return f"NetNS({self.name})" |
| |
| |
| class UserNetNS: |
| """Network namespace owned by a non-init user namespace.""" |
| |
| def __init__(self): |
| self.name = ''.join( |
| random.choice(string.ascii_lowercase) for _ in range(8)) |
| self.user_ns_path = f"/run/userns/{self.name}" |
| self.net_ns_path = f"/run/netns/{self.name}" |
| self._user_mounted = False |
| self._net_mounted = False |
| |
| os.makedirs("/run/userns", exist_ok=True) |
| os.makedirs("/run/netns", exist_ok=True) |
| |
| Path(self.user_ns_path).touch() |
| Path(self.net_ns_path).touch() |
| |
| with subprocess.Popen( |
| ["unshare", "--user", "--net", "--map-root-user", |
| "sleep", "infinity"]) as proc: |
| try: |
| pid = proc.pid |
| init_user = os.readlink("/proc/self/ns/user") |
| for _ in range(200): |
| try: |
| if os.readlink(f"/proc/{pid}/ns/user") != init_user: |
| break |
| except OSError: |
| pass |
| time.sleep(0.01) |
| else: |
| raise RuntimeError("unshare child did not create userns") |
| |
| subprocess.run(["mount", "--bind", f"/proc/{pid}/ns/user", |
| self.user_ns_path], check=True) |
| self._user_mounted = True |
| subprocess.run(["mount", "--bind", f"/proc/{pid}/ns/net", |
| self.net_ns_path], check=True) |
| self._net_mounted = True |
| finally: |
| proc.kill() |
| |
| def __del__(self): |
| if self._net_mounted: |
| subprocess.run(["umount", self.net_ns_path], check=False) |
| self._net_mounted = False |
| if self._user_mounted: |
| subprocess.run(["umount", self.user_ns_path], check=False) |
| self._user_mounted = False |
| for path in (self.net_ns_path, self.user_ns_path): |
| try: |
| os.unlink(path) |
| except OSError: |
| pass |
| |
| def __enter__(self): |
| return self |
| |
| def __exit__(self, ex_type, ex_value, ex_tb): |
| self.__del__() |
| |
| def __str__(self): |
| return self.name |
| |
| def __repr__(self): |
| return f"UserNetNS({self.name})" |
| |
| |
| class NetNSEnter: |
| def __init__(self, ns_name): |
| self.ns_path = f"/run/netns/{ns_name}" |
| |
| def __enter__(self): |
| self.saved = open("/proc/thread-self/ns/net") |
| with open(self.ns_path) as ns_file: |
| libc.setns(ns_file.fileno(), 0) |
| return self |
| |
| def __exit__(self, exc_type, exc_value, traceback): |
| libc.setns(self.saved.fileno(), 0) |
| self.saved.close() |