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