summaryrefslogtreecommitdiff
path: root/net/test/namespace.py
blob: c8f8f46cb9003988004d9afe335f14b355015f19 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/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 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... ')

  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

  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