| #!/usr/bin/python |
| # |
| # Copyright 2020 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Namespace related support code.""" |
| |
| import ctypes |
| import ctypes.util |
| import errno |
| import os |
| import socket |
| import sys |
| |
| import net_test |
| import sock_diag |
| import tcp_test |
| |
| # //include/linux/fs.h |
| MNT_FORCE = 1 # Attempt to forcibily umount |
| MNT_DETACH = 2 # Just detach from the tree |
| MNT_EXPIRE = 4 # Mark for expiry |
| UMOUNT_NOFOLLOW = 8 # Don't follow symlink on umount |
| |
| # //include/uapi/linux/fs.h |
| MS_RDONLY = 1 # Mount read-only |
| MS_NOSUID = 2 # Ignore suid and sgid bits |
| MS_NODEV = 4 # Disallow access to device special files |
| MS_NOEXEC = 8 # Disallow program execution |
| MS_SYNCHRONOUS = 16 # Writes are synced at once |
| MS_REMOUNT = 32 # Alter flags of a mounted FS |
| MS_MANDLOCK = 64 # Allow mandatory locks on an FS |
| MS_DIRSYNC = 128 # Directory modifications are synchronous |
| MS_NOATIME = 1024 # Do not update access times. |
| MS_NODIRATIME = 2048 # Do not update directory access times |
| MS_BIND = 4096 # |
| MS_MOVE = 8192 # |
| MS_REC = 16384 # |
| MS_SILENT = 32768 # |
| MS_POSIXACL = (1<<16) # VFS does not apply the umask |
| MS_UNBINDABLE = (1<<17) # change to unbindable |
| MS_PRIVATE = (1<<18) # change to private |
| MS_SLAVE = (1<<19) # change to slave |
| MS_SHARED = (1<<20) # change to shared |
| MS_RELATIME = (1<<21) # Update atime relative to mtime/ctime. |
| MS_STRICTATIME = (1<<24) # Always perform atime updates |
| MS_LAZYTIME = (1<<25) # Update the on-disk [acm]times lazily |
| |
| # //include/uapi/linux/sched.h |
| CLONE_NEWNS = 0x00020000 # New mount namespace group |
| CLONE_NEWCGROUP = 0x02000000 # New cgroup namespace |
| CLONE_NEWUTS = 0x04000000 # New utsname namespace |
| CLONE_NEWIPC = 0x08000000 # New ipc namespace |
| CLONE_NEWUSER = 0x10000000 # New user namespace |
| CLONE_NEWPID = 0x20000000 # New pid namespace |
| CLONE_NEWNET = 0x40000000 # New network namespace |
| |
| libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) |
| |
| # See the relevant system call's man pages and: |
| # https://docs.python.org/3/library/ctypes.html#fundamental-data-types |
| libc.mount.argtypes = (ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, |
| ctypes.c_ulong, ctypes.c_void_p) |
| libc.sethostname.argtype = (ctypes.c_char_p, ctypes.c_size_t) |
| libc.umount2.argtypes = (ctypes.c_char_p, ctypes.c_int) |
| libc.unshare.argtypes = (ctypes.c_int,) |
| |
| |
| def Mount(src, tgt, fs, flags=MS_NODEV|MS_NOEXEC|MS_NOSUID|MS_RELATIME): |
| ret = libc.mount(src, tgt, fs, flags, None) |
| if ret < 0: |
| errno = ctypes.get_errno() |
| raise OSError(errno, '%s mounting %s on %s (fs=%s flags=0x%x)' |
| % (os.strerror(errno), src, tgt, fs, flags)) |
| |
| |
| def ReMountProc(): |
| libc.umount2('/proc', MNT_DETACH) # Ignore failure: might not be mounted |
| Mount('proc', '/proc', 'proc') |
| |
| |
| def ReMountSys(): |
| libc.umount2('/sys', MNT_DETACH) # Ignore failure: might not be mounted |
| Mount('sysfs', '/sys', 'sysfs') |
| |
| |
| def SetFileContents(f, s): |
| open(f, 'w').write(s) |
| |
| |
| def SetHostname(s): |
| ret = libc.sethostname(s, len(s)) |
| if ret < 0: |
| errno = ctypes.get_errno() |
| raise OSError(errno, '%s while sethostname(%s)' % (os.strerror(errno), s)) |
| |
| |
| def UnShare(flags): |
| ret = libc.unshare(flags) |
| if ret < 0: |
| errno = ctypes.get_errno() |
| raise OSError(errno, '%s while unshare(0x%x)' % (os.strerror(errno), flags)) |
| |
| |
| def DumpMounts(hdr): |
| print('') |
| print(hdr) |
| sys.stdout.write(open('/proc/mounts', 'r').read()) |
| print('---') |
| |
| |
| # Requires at least kernel configuration options: |
| # CONFIG_NAMESPACES=y |
| # CONFIG_NET_NS=y |
| # CONFIG_UTS_NS=y |
| def IfPossibleEnterNewNetworkNamespace(): |
| """Instantiate and transition into a fresh new network namespace if possible.""" |
| |
| sys.stdout.write('Creating clean namespace... ') |
| |
| # sysctl only present on 4.14 and earlier Android kernels |
| if net_test.LINUX_VERSION < (4, 15, 0): |
| TCP_DEFAULT_INIT_RWND = "/proc/sys/net/ipv4/tcp_default_init_rwnd" |
| # In root netns this will succeed |
| init_rwnd_sysctl = open(TCP_DEFAULT_INIT_RWND, "w") |
| |
| try: |
| UnShare(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWNET) |
| except OSError as err: |
| print('failed: %s (likely: no privs or lack of kernel support).' % err) |
| return False |
| |
| try: |
| # DumpMounts('Before:') |
| Mount('none', '/', None, MS_REC|MS_PRIVATE) |
| ReMountProc() |
| ReMountSys() |
| # DumpMounts('After:') |
| SetHostname('netns') |
| SetFileContents('/proc/sys/net/ipv4/ping_group_range', '0 2147483647') |
| net_test.SetInterfaceUp('lo') |
| except: |
| print('failed.') |
| # We've already transitioned into the new netns -- it's too late to recover. |
| raise |
| |
| if net_test.LINUX_VERSION < (4, 15, 0): |
| # In non-root netns this open might fail due to non-namespace-ified sysctl |
| # ie. lack of kernel commit: |
| # https://android-review.googlesource.com/c/kernel/common/+/1312623 |
| # ANDROID: namespace'ify tcp_default_init_rwnd implementation |
| try: |
| init_rwnd_sysctl = open(TCP_DEFAULT_INIT_RWND, "w") |
| except IOError as e: |
| if e.errno != errno.ENOENT: |
| raise |
| # Note! if the netns open above succeeded (and thus we don't reach here) |
| # then we don't need to actually update the sysctl, since we'll be able to do |
| # that in the sock_diag_test.py TcpRcvWindowTest test case setUp() call instead. |
| # |
| # As such this write here is *still* to the root netns sysctl |
| # (because we obtained a file descriptor *prior* to unshare/etc...) |
| # and handles the case where the sysctl is not namespace aware and thus |
| # affects the entire system. |
| init_rwnd_sysctl.write("60"); |
| |
| print('succeeded.') |
| return True |
| |
| |
| def HasEstablishedTcpSessionOnPort(port): |
| sd = sock_diag.SockDiag() |
| |
| sock_id = sd._EmptyInetDiagSockId() |
| sock_id.sport = port |
| |
| states = 1 << tcp_test.TCP_ESTABLISHED |
| |
| matches = sd.DumpAllInetSockets(socket.IPPROTO_TCP, "", |
| sock_id=sock_id, states=states) |
| |
| return len(matches) > 0 |