1#!/usr/bin/python3 2# 3# Copyright 2020 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Namespace related support code.""" 18 19import ctypes 20import ctypes.util 21import errno 22import os 23import socket 24import sys 25 26import net_test 27import sock_diag 28import tcp_test 29 30# //include/linux/fs.h 31MNT_FORCE = 1 # Attempt to forcibily umount 32MNT_DETACH = 2 # Just detach from the tree 33MNT_EXPIRE = 4 # Mark for expiry 34UMOUNT_NOFOLLOW = 8 # Don't follow symlink on umount 35 36# //include/uapi/linux/fs.h 37MS_RDONLY = 1 # Mount read-only 38MS_NOSUID = 2 # Ignore suid and sgid bits 39MS_NODEV = 4 # Disallow access to device special files 40MS_NOEXEC = 8 # Disallow program execution 41MS_SYNCHRONOUS = 16 # Writes are synced at once 42MS_REMOUNT = 32 # Alter flags of a mounted FS 43MS_MANDLOCK = 64 # Allow mandatory locks on an FS 44MS_DIRSYNC = 128 # Directory modifications are synchronous 45MS_NOATIME = 1024 # Do not update access times. 46MS_NODIRATIME = 2048 # Do not update directory access times 47MS_BIND = 4096 # 48MS_MOVE = 8192 # 49MS_REC = 16384 # 50MS_SILENT = 32768 # 51MS_POSIXACL = (1<<16) # VFS does not apply the umask 52MS_UNBINDABLE = (1<<17) # change to unbindable 53MS_PRIVATE = (1<<18) # change to private 54MS_SLAVE = (1<<19) # change to slave 55MS_SHARED = (1<<20) # change to shared 56MS_RELATIME = (1<<21) # Update atime relative to mtime/ctime. 57MS_STRICTATIME = (1<<24) # Always perform atime updates 58MS_LAZYTIME = (1<<25) # Update the on-disk [acm]times lazily 59 60# //include/uapi/linux/sched.h 61CLONE_NEWNS = 0x00020000 # New mount namespace group 62CLONE_NEWCGROUP = 0x02000000 # New cgroup namespace 63CLONE_NEWUTS = 0x04000000 # New utsname namespace 64CLONE_NEWIPC = 0x08000000 # New ipc namespace 65CLONE_NEWUSER = 0x10000000 # New user namespace 66CLONE_NEWPID = 0x20000000 # New pid namespace 67CLONE_NEWNET = 0x40000000 # New network namespace 68 69libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) 70 71# See the relevant system call's man pages and: 72# https://docs.python.org/3/library/ctypes.html#fundamental-data-types 73libc.mount.argtypes = (ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, 74 ctypes.c_ulong, ctypes.c_void_p) 75libc.sethostname.argtypes = (ctypes.c_char_p, ctypes.c_size_t) 76libc.umount2.argtypes = (ctypes.c_char_p, ctypes.c_int) 77libc.unshare.argtypes = (ctypes.c_int,) 78 79 80def Mount(src, tgt, fs, flags=MS_NODEV|MS_NOEXEC|MS_NOSUID|MS_RELATIME): 81 ret = libc.mount(src.encode(), tgt.encode(), fs.encode() if fs else None, 82 flags, None) 83 if ret < 0: 84 errno = ctypes.get_errno() 85 raise OSError(errno, '%s mounting %s on %s (fs=%s flags=0x%x)' 86 % (os.strerror(errno), src, tgt, fs, flags)) 87 88 89def ReMountProc(): 90 libc.umount2(b'/proc', MNT_DETACH) # Ignore failure: might not be mounted 91 Mount('proc', '/proc', 'proc') 92 93 94def ReMountSys(): 95 libc.umount2(b'/sys/fs/cgroup', MNT_DETACH) # Ignore failure: might not be mounted 96 libc.umount2(b'/sys/fs/bpf', MNT_DETACH) # Ignore failure: might not be mounted 97 libc.umount2(b'/sys', MNT_DETACH) # Ignore failure: might not be mounted 98 Mount('sysfs', '/sys', 'sysfs') 99 Mount('bpf', '/sys/fs/bpf', 'bpf') 100 Mount('cgroup2', '/sys/fs/cgroup', 'cgroup2') 101 102 103def SetFileContents(f, s): 104 with open(f, 'w') as set_file: 105 set_file.write(s) 106 107 108def SetHostname(s): 109 hostname = s.encode() 110 ret = libc.sethostname(hostname, len(hostname)) 111 if ret < 0: 112 errno = ctypes.get_errno() 113 raise OSError(errno, '%s while sethostname(%s)' % (os.strerror(errno), s)) 114 115 116def UnShare(flags): 117 ret = libc.unshare(flags) 118 if ret < 0: 119 errno = ctypes.get_errno() 120 raise OSError(errno, '%s while unshare(0x%x)' % (os.strerror(errno), flags)) 121 122 123def DumpMounts(hdr): 124 print('') 125 print(hdr) 126 with open('/proc/mounts', 'r') as mounts: 127 sys.stdout.write(mounts.read()) 128 print('---') 129 130 131# Requires at least kernel configuration options: 132# CONFIG_NAMESPACES=y 133# CONFIG_NET_NS=y 134# CONFIG_UTS_NS=y 135def EnterNewNetworkNamespace(): 136 """Instantiate and transition into a fresh new network namespace.""" 137 138 sys.stdout.write('Creating clean namespace... ') 139 140 # sysctl only present on 4.14 and earlier Android kernels 141 if net_test.LINUX_VERSION < (4, 15, 0): 142 TCP_DEFAULT_INIT_RWND = "/proc/sys/net/ipv4/tcp_default_init_rwnd" 143 # In root netns this will succeed 144 init_rwnd_sysctl = open(TCP_DEFAULT_INIT_RWND, "w") 145 146 try: 147 UnShare(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWNET) 148 except OSError as err: 149 print('failed: %s (likely: no privs or lack of kernel support).' % err) 150 raise 151 152 try: 153 # DumpMounts('Before:') 154 Mount('none', '/', None, MS_REC|MS_PRIVATE) 155 ReMountProc() 156 ReMountSys() 157 # DumpMounts('After:') 158 SetHostname('netns') 159 SetFileContents('/proc/sys/net/ipv4/ping_group_range', '0 2147483647') 160 net_test.SetInterfaceUp('lo') 161 except: 162 print('failed.') 163 # We've already transitioned into the new netns -- it's too late to recover. 164 raise 165 166 if net_test.LINUX_VERSION < (4, 15, 0): 167 # In non-root netns this open might fail due to non-namespace-ified sysctl 168 # ie. lack of kernel commit: 169 # https://android-review.googlesource.com/c/kernel/common/+/1312623 170 # ANDROID: namespace'ify tcp_default_init_rwnd implementation 171 try: 172 init_rwnd_sysctl = open(TCP_DEFAULT_INIT_RWND, "w") 173 except IOError as e: 174 if e.errno != errno.ENOENT: 175 raise 176 # Note! if the netns open above succeeded (and thus we don't reach here) 177 # then we don't need to actually update the sysctl, since we'll be able to do 178 # that in the sock_diag_test.py TcpRcvWindowTest test case setUp() call instead. 179 # 180 # As such this write here is *still* to the root netns sysctl 181 # (because we obtained a file descriptor *prior* to unshare/etc...) 182 # and handles the case where the sysctl is not namespace aware and thus 183 # affects the entire system. 184 init_rwnd_sysctl.write("60"); 185 186 print('succeeded.') 187 188 189def HasEstablishedTcpSessionOnPort(port): 190 sd = sock_diag.SockDiag() 191 192 sock_id = sd._EmptyInetDiagSockId() 193 sock_id.sport = port 194 195 states = 1 << tcp_test.TCP_ESTABLISHED 196 197 matches = sd.DumpAllInetSockets(socket.IPPROTO_TCP, b"", 198 sock_id=sock_id, states=states) 199 200 return len(matches) > 0 201