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 os 22import socket 23import sys 24 25import net_test 26import sock_diag 27import tcp_test 28 29# pylint: disable=bad-whitespace 30 31# //include/linux/fs.h 32MNT_FORCE = 1 # Attempt to forcibily umount 33MNT_DETACH = 2 # Just detach from the tree 34MNT_EXPIRE = 4 # Mark for expiry 35UMOUNT_NOFOLLOW = 8 # Don't follow symlink on umount 36 37# //include/uapi/linux/fs.h 38MS_RDONLY = 1 # Mount read-only 39MS_NOSUID = 2 # Ignore suid and sgid bits 40MS_NODEV = 4 # Disallow access to device special files 41MS_NOEXEC = 8 # Disallow program execution 42MS_SYNCHRONOUS = 16 # Writes are synced at once 43MS_REMOUNT = 32 # Alter flags of a mounted FS 44MS_MANDLOCK = 64 # Allow mandatory locks on an FS 45MS_DIRSYNC = 128 # Directory modifications are synchronous 46MS_NOATIME = 1024 # Do not update access times. 47MS_NODIRATIME = 2048 # Do not update directory access times 48MS_BIND = 4096 # 49MS_MOVE = 8192 # 50MS_REC = 16384 # 51MS_SILENT = 32768 # 52MS_POSIXACL = (1<<16) # VFS does not apply the umask 53MS_UNBINDABLE = (1<<17) # change to unbindable 54MS_PRIVATE = (1<<18) # change to private 55MS_SLAVE = (1<<19) # change to slave 56MS_SHARED = (1<<20) # change to shared 57MS_RELATIME = (1<<21) # Update atime relative to mtime/ctime. 58MS_STRICTATIME = (1<<24) # Always perform atime updates 59MS_LAZYTIME = (1<<25) # Update the on-disk [acm]times lazily 60 61# //include/uapi/linux/sched.h 62CLONE_NEWNS = 0x00020000 # New mount namespace group 63CLONE_NEWCGROUP = 0x02000000 # New cgroup namespace 64CLONE_NEWUTS = 0x04000000 # New utsname namespace 65CLONE_NEWIPC = 0x08000000 # New ipc namespace 66CLONE_NEWUSER = 0x10000000 # New user namespace 67CLONE_NEWPID = 0x20000000 # New pid namespace 68CLONE_NEWNET = 0x40000000 # New network namespace 69 70# pylint: enable=bad-whitespace 71 72libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) 73 74# See the relevant system call's man pages and: 75# https://docs.python.org/3/library/ctypes.html#fundamental-data-types 76libc.mount.argtypes = (ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, 77 ctypes.c_ulong, ctypes.c_void_p) 78libc.sethostname.argtypes = (ctypes.c_char_p, ctypes.c_size_t) 79libc.umount2.argtypes = (ctypes.c_char_p, ctypes.c_int) 80libc.unshare.argtypes = (ctypes.c_int,) 81 82 83def Mount(src, tgt, fs, flags=MS_NODEV|MS_NOEXEC|MS_NOSUID|MS_RELATIME): 84 ret = libc.mount(src.encode(), tgt.encode(), fs.encode() if fs else None, 85 flags, None) 86 if ret < 0: 87 err = ctypes.get_errno() 88 raise OSError(err, '%s mounting %s on %s (fs=%s flags=0x%x)' 89 % (os.strerror(err), src, tgt, fs, flags)) 90 91 92def ReMountProc(): 93 libc.umount2(b'/proc', MNT_DETACH) # Ignore failure: might not be mounted 94 Mount('proc', '/proc', 'proc') 95 96 97def ReMountSys(): 98 libc.umount2(b'/sys/fs/cgroup', MNT_DETACH) # Ign. fail: might not be mounted 99 libc.umount2(b'/sys/fs/bpf', MNT_DETACH) # Ignore fail: might not be mounted 100 libc.umount2(b'/sys', MNT_DETACH) # Ignore fail: might not be mounted 101 Mount('sysfs', '/sys', 'sysfs') 102 Mount('bpf', '/sys/fs/bpf', 'bpf') 103 Mount('cgroup2', '/sys/fs/cgroup', 'cgroup2') 104 105 106def SetFileContents(f, s): 107 with open(f, 'w') as set_file: 108 set_file.write(s) 109 110 111def SetHostname(s): 112 hostname = s.encode() 113 ret = libc.sethostname(hostname, len(hostname)) 114 if ret < 0: 115 err = ctypes.get_errno() 116 raise OSError(err, '%s while sethostname(%s)' % (os.strerror(err), s)) 117 118 119def UnShare(flags): 120 ret = libc.unshare(flags) 121 if ret < 0: 122 err = ctypes.get_errno() 123 raise OSError(err, '%s while unshare(0x%x)' % (os.strerror(err), flags)) 124 125 126def DumpMounts(hdr): 127 print('') 128 print(hdr) 129 with open('/proc/mounts', 'r') as mounts: 130 sys.stdout.write(mounts.read()) 131 print('---') 132 133 134# Requires at least kernel configuration options: 135# CONFIG_NAMESPACES=y 136# CONFIG_NET_NS=y 137# CONFIG_UTS_NS=y 138def EnterNewNetworkNamespace(): 139 """Instantiate and transition into a fresh new network namespace.""" 140 141 try: 142 UnShare(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWNET) 143 except OSError as err: 144 print('failed: %s (likely: no privs or lack of kernel support).' % err) 145 raise 146 147 try: 148 # DumpMounts('Before:') 149 Mount('none', '/', None, MS_REC|MS_PRIVATE) 150 ReMountProc() 151 ReMountSys() 152 # DumpMounts('After:') 153 SetHostname('netns') 154 SetFileContents('/proc/sys/net/ipv4/ping_group_range', '0 2147483647') 155 net_test.SetInterfaceUp('lo') 156 except: 157 print('failed.') 158 # We've already transitioned into the new netns -- it's too late to recover. 159 raise 160 161 print('succeeded.') 162 163 164def HasEstablishedTcpSessionOnPort(port): 165 sd = sock_diag.SockDiag() 166 167 sock_id = sd._EmptyInetDiagSockId() # pylint: disable=protected-access 168 sock_id.sport = port 169 170 states = 1 << tcp_test.TCP_ESTABLISHED 171 172 matches = sd.DumpAllInetSockets(socket.IPPROTO_TCP, b'', 173 sock_id=sock_id, states=states) 174 175 return True if matches else False 176