]> git.ipfire.org Git - thirdparty/lldpd.git/blame - tests/integration/fixtures/namespaces.py
priv: disable LLDP in firmware for Intel X7xx cards on FreeBSD
[thirdparty/lldpd.git] / tests / integration / fixtures / namespaces.py
CommitLineData
e0a84778
VB
1import contextlib
2import ctypes
3import errno
4import os
5import pyroute2
6import pytest
7import signal
98745499 8import multiprocessing
e0a84778
VB
9
10# All allowed namespace types
8b549648
VB
11NAMESPACE_FLAGS = dict(
12 mnt=0x00020000,
13 uts=0x04000000,
14 ipc=0x08000000,
15 user=0x10000000,
16 pid=0x20000000,
17 net=0x40000000,
18)
19STACKSIZE = 1024 * 1024
e0a84778 20
8b549648 21libc = ctypes.CDLL("libc.so.6", use_errno=True)
e0a84778
VB
22
23
24@contextlib.contextmanager
25def keep_directory():
26 """Restore the current directory on exit."""
27 pwd = os.getcwd()
28 try:
29 yield
30 finally:
31 os.chdir(pwd)
32
33
08e05799 34def mount_sys(target="/sys"):
8b549648
VB
35 flags = [2 | 4 | 8] # MS_NOSUID | MS_NODEV | MS_NOEXEC
36 flags.append(1 << 18) # MS_PRIVATE
37 flags.append(1 << 19) # MS_SLAVE
08e05799 38 for fl in flags:
8b549648 39 ret = libc.mount(b"none", target.encode("ascii"), b"sysfs", fl, None)
08e05799
VB
40 if ret == -1:
41 e = ctypes.get_errno()
42 raise OSError(e, os.strerror(e))
43
44
98745499
VB
45def mount_tmpfs(target, private=False):
46 flags = [0]
47 if private:
8b549648
VB
48 flags.append(1 << 18) # MS_PRIVATE
49 flags.append(1 << 19) # MS_SLAVE
98745499 50 for fl in flags:
8b549648 51 ret = libc.mount(b"none", target.encode("ascii"), b"tmpfs", fl, None)
98745499
VB
52 if ret == -1:
53 e = ctypes.get_errno()
54 raise OSError(e, os.strerror(e))
55
56
57def _mount_proc(target):
8b549648
VB
58 flags = [2 | 4 | 8] # MS_NOSUID | MS_NODEV | MS_NOEXEC
59 flags.append(1 << 18) # MS_PRIVATE
60 flags.append(1 << 19) # MS_SLAVE
98745499 61 for fl in flags:
8b549648 62 ret = libc.mount(b"proc", target.encode("ascii"), b"proc", fl, None)
98745499
VB
63 if ret == -1:
64 e = ctypes.get_errno()
65 raise OSError(e, os.strerror(e))
66
67
68def mount_proc(target="/proc"):
69 # We need to be sure /proc is correct. We do that in another
70 # process as this doesn't play well with setns().
71 if not os.path.isdir(target):
72 os.mkdir(target)
73 p = multiprocessing.Process(target=_mount_proc, args=(target,))
74 p.start()
75 p.join()
76
77
e0a84778
VB
78class Namespace(object):
79 """Combine several namespaces into one.
80
81 This gets a list of namespace types to create and combine into one. The
82 combined namespace can be used as a context manager to enter all the
83 created namespaces and exit them at the end.
84 """
85
86 def __init__(self, *namespaces):
0ca939b0 87 self.next = []
e0a84778
VB
88 self.namespaces = namespaces
89 for ns in namespaces:
90 assert ns in NAMESPACE_FLAGS
91
92 # Get a pipe to signal the future child to exit
93 self.pipe = os.pipe()
94
95 # First, create a child in the given namespaces
96 child = ctypes.CFUNCTYPE(ctypes.c_int)(self.child)
97 child_stack = ctypes.create_string_buffer(STACKSIZE)
98 child_stack_pointer = ctypes.c_void_p(
8b549648
VB
99 ctypes.cast(child_stack, ctypes.c_void_p).value + STACKSIZE
100 )
e0a84778
VB
101 flags = signal.SIGCHLD
102 for ns in namespaces:
103 flags |= NAMESPACE_FLAGS[ns]
104 pid = libc.clone(child, child_stack_pointer, flags)
105 if pid == -1:
106 e = ctypes.get_errno()
107 raise OSError(e, os.strerror(e))
108
109 # If a user namespace, map UID 0 to the current one
8b549648
VB
110 if "user" in namespaces:
111 uid_map = "0 {} 1".format(os.getuid())
112 gid_map = "0 {} 1".format(os.getgid())
113 with open("/proc/{}/uid_map".format(pid), "w") as f:
e0a84778 114 f.write(uid_map)
8b549648
VB
115 with open("/proc/{}/setgroups".format(pid), "w") as f:
116 f.write("deny")
117 with open("/proc/{}/gid_map".format(pid), "w") as f:
e0a84778
VB
118 f.write(gid_map)
119
120 # Retrieve a file descriptor to this new namespace
8b549648
VB
121 self.next = [
122 os.open("/proc/{}/ns/{}".format(pid, x), os.O_RDONLY) for x in namespaces
123 ]
e0a84778
VB
124
125 # Keep a file descriptor to our old namespaces
8b549648
VB
126 self.previous = [
127 os.open("/proc/self/ns/{}".format(x), os.O_RDONLY) for x in namespaces
128 ]
e0a84778
VB
129
130 # Tell the child all is done and let it die
131 os.close(self.pipe[0])
8b549648 132 if "pid" not in namespaces:
e0a84778 133 os.close(self.pipe[1])
ad8971ec 134 self.pipe = None
e0a84778
VB
135 os.waitpid(pid, 0)
136
ad8971ec
VB
137 def __del__(self):
138 for fd in self.next:
139 os.close(fd)
140 for fd in self.previous:
141 os.close(fd)
142 if self.pipe is not None:
143 os.close(self.pipe[1])
144
e0a84778
VB
145 def child(self):
146 """Cloned child.
147
148 Just be here until our parent extract the file descriptor from
149 us.
150
151 """
152 os.close(self.pipe[1])
153
154 # For a network namespace, enable lo
8b549648 155 if "net" in self.namespaces:
12e81bd1 156 with pyroute2.IPRoute() as ipr:
8b549648
VB
157 lo = ipr.link_lookup(ifname="lo")[0]
158 ipr.link("set", index=lo, state="up")
e0a84778 159 # For a mount namespace, make it private
8b549648
VB
160 if "mnt" in self.namespaces:
161 libc.mount(
162 b"none",
163 b"/",
164 None,
165 # MS_REC | MS_PRIVATE
166 16384 | (1 << 18),
167 None,
168 )
e0a84778
VB
169
170 while True:
171 try:
172 os.read(self.pipe[0], 1)
173 except OSError as e:
174 if e.errno in [errno.EAGAIN, errno.EINTR]:
175 continue
176 break
177
178 os._exit(0)
179
180 def fd(self, namespace):
181 """Return the file descriptor associated to a namespace"""
182 assert namespace in self.namespaces
183 return self.next[self.namespaces.index(namespace)]
184
185 def __enter__(self):
186 with keep_directory():
187 for n in self.next:
188 if libc.setns(n, 0) == -1:
189 ns = self.namespaces[self.next.index(n)] # NOQA
190 e = ctypes.get_errno()
191 raise OSError(e, os.strerror(e))
192
193 def __exit__(self, *exc):
194 with keep_directory():
195 err = None
196 for p in reversed(self.previous):
197 if libc.setns(p, 0) == -1 and err is None:
198 ns = self.namespaces[self.previous.index(p)] # NOQA
199 e = ctypes.get_errno()
200 err = OSError(e, os.strerror(e))
201 if err:
202 raise err
203
204 def __repr__(self):
8b549648 205 return "Namespace({})".format(", ".join(self.namespaces))
e0a84778
VB
206
207
208class NamespaceFactory(object):
209 """Dynamically create namespaces as they are created.
210
211 Those namespaces are namespaces for IPC, net, mount and UTS. PID
212 is a bit special as we have to keep a process for that. We don't
213 do that to ensure that everything is cleaned
214 automatically. Therefore, the child process is killed as soon as
215 we got a file descriptor to the namespace. We don't use a user
216 namespace either because we are unlikely to be able to exit it.
217
218 """
219
98745499 220 def __init__(self, tmpdir):
e0a84778 221 self.namespaces = {}
98745499 222 self.tmpdir = tmpdir
e0a84778
VB
223
224 def __call__(self, ns):
225 """Return a namespace. Create it if it doesn't exist."""
226 if ns in self.namespaces:
227 return self.namespaces[ns]
98745499 228
8b549648 229 self.namespaces[ns] = Namespace("ipc", "net", "mnt", "uts")
98745499
VB
230 with self.namespaces[ns]:
231 mount_proc()
232 mount_sys()
233 # Also setup the "namespace-dependant" directory
234 self.tmpdir.join("ns").ensure(dir=True)
235 mount_tmpfs(str(self.tmpdir.join("ns")), private=True)
236
e0a84778
VB
237 return self.namespaces[ns]
238
239
240@pytest.fixture
98745499
VB
241def namespaces(tmpdir):
242 return NamespaceFactory(tmpdir)