-
-
Notifications
You must be signed in to change notification settings - Fork 244
/
__init__.py
365 lines (304 loc) · 9.83 KB
/
__init__.py
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
'''
Netns management overview
=========================
Pyroute2 provides basic namespaces management support.
Here's a quick overview of typical netns tasks and
related pyroute2 tools.
Move an interface to a namespace
--------------------------------
Though this task is managed not via `netns` module, it
should be mentioned here as well. To move an interface
to a netns, one should provide IFLA_NET_NS_FD nla in
a set link RTNL request. The nla is an open FD number,
that refers to already created netns. The pyroute2
library provides also a possibility to specify not a
FD number, but a netns name as a string. In that case
the library will try to lookup the corresponding netns
in the standard location.
Create veth and move the peer to a netns with IPRoute::
from pyroute2 import IPRoute
ipr = IPRoute()
ipr.link('add', ifname='v0p0', kind='veth', peer='v0p1')
idx = ipr.link_lookup(ifname='v0p1')[0]
ipr.link('set', index=idx, net_ns_fd='netns_name')
Create veth and move the peer to a netns with IPDB::
from pyroute2 import IPDB
ipdb = IPDB()
ipdb.create(ifname='v0p0', kind='veth', peer='v0p1').commit()
with ipdb.interfaces.v0p1 as i:
i.net_ns_fd = 'netns_name'
Manage interfaces within a netns
--------------------------------
This task can be done with `NetNS` objects. A `NetNS` object
spawns a child and runs it within a netns, providing the same
API as `IPRoute` does::
from pyroute2 import NetNS
ns = NetNS('netns_name')
# do some stuff within the netns
ns.close()
One can even start `IPDB` on the top of `NetNS`::
from pyroute2 import NetNS
from pyroute2 import IPDB
ns = NetNS('netns_name')
ipdb = IPDB(nl=ns)
# do some stuff within the netns
ipdb.release()
ns.close()
Spawn a process within a netns
------------------------------
For that purpose one can use `NSPopen` API. It works just
as normal `Popen`, but starts a process within a netns.
List, set, create and remove netns
----------------------------------
These functions are described below. To use them, import
`netns` module::
from pyroute2 import netns
netns.listnetns()
Please be aware, that in order to run system calls the
library uses `ctypes` module. It can fail on platforms
where SELinux is enforced. If the Python interpreter,
loading this module, dumps the core, one can check the
SELinux state with `getenforce` command.
'''
import io
import os
import os.path
import errno
import ctypes
import ctypes.util
import pickle
import struct
import traceback
from pyroute2 import config
from pyroute2.common import basestring
try:
file = file
except NameError:
file = io.IOBase
# FIXME: arch reference
__NR = {'x86_': {'64bit': 308},
'i386': {'32bit': 346},
'i686': {'32bit': 346},
'mips': {'32bit': 4344,
'64bit': 5303}, # FIXME: NABI32?
'armv': {'32bit': 375},
'aarc': {'32bit': 375,
'64bit': 268}, # FIXME: EABI vs. OABI?
'ppc6': {'64bit': 350},
's390': {'64bit': 339}}
__NR_setns = __NR.get(config.machine[:4], {}).get(config.arch, 308)
CLONE_NEWNET = 0x40000000
MNT_DETACH = 0x00000002
MS_BIND = 4096
MS_REC = 16384
MS_SHARED = 1 << 20
NETNS_RUN_DIR = '/var/run/netns'
__saved_ns = []
def _get_netnspath(name):
netnspath = name
dirname = os.path.dirname(name)
if not dirname:
netnspath = '%s/%s' % (NETNS_RUN_DIR, name)
netnspath = netnspath.encode('ascii')
return netnspath
def listnetns(nspath=None):
'''
List available network namespaces.
'''
if nspath:
nsdir = nspath
else:
nsdir = NETNS_RUN_DIR
try:
return os.listdir(nsdir)
except OSError as e:
if e.errno == errno.ENOENT:
return []
else:
raise
def _get_ns_by_inode(nspath=NETNS_RUN_DIR):
'''
Return a dict with inode as key and
namespace name as value
'''
ns_by_dev_inode = {}
for ns_name in listnetns(nspath=nspath):
ns_path = os.path.join(nspath, ns_name)
st = os.stat(ns_path)
if st.st_dev not in ns_by_dev_inode:
ns_by_dev_inode[st.st_dev] = {}
ns_by_dev_inode[st.st_dev][st.st_ino] = ns_name
return ns_by_dev_inode
def ns_pids(nspath=NETNS_RUN_DIR):
'''
List pids in all netns
If a pid is in a unknown netns do not return it
'''
result = {}
ns_by_dev_inode = _get_ns_by_inode(nspath)
for pid in os.listdir('/proc'):
if not pid.isdigit():
continue
try:
st = os.stat(os.path.join('/proc', pid, 'ns', 'net'))
except OSError as e:
if e.errno in (errno.EACCES, errno.ENOENT):
continue
raise
try:
ns_name = ns_by_dev_inode[st.st_dev][st.st_ino]
except KeyError:
continue
if ns_name not in result:
result[ns_name] = []
result[ns_name].append(int(pid))
return result
def pid_to_ns(pid=1, nspath=NETNS_RUN_DIR):
'''
Return netns name which matches the given pid,
None otherwise
'''
try:
st = os.stat(os.path.join('/proc', str(pid), 'ns', 'net'))
ns_by_dev_inode = _get_ns_by_inode(nspath)
return ns_by_dev_inode[st.st_dev][st.st_ino]
except OSError as e:
if e.errno in (errno.EACCES, errno.ENOENT):
return None
raise
except KeyError:
return None
def _create(netns, libc=None):
libc = libc or ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
netnspath = _get_netnspath(netns)
netnsdir = os.path.dirname(netnspath)
# init netnsdir
try:
os.mkdir(netnsdir)
except OSError as e:
if e.errno != errno.EEXIST:
raise
# this code is ported from iproute2
done = False
while libc.mount(b'', netnsdir, b'none', MS_SHARED | MS_REC, None) != 0:
if done:
raise OSError(ctypes.get_errno(), 'share rundir failed', netns)
if libc.mount(netnsdir, netnsdir, b'none', MS_BIND, None) != 0:
raise OSError(ctypes.get_errno(), 'mount rundir failed', netns)
done = True
# create mountpoint
os.close(os.open(netnspath, os.O_RDONLY | os.O_CREAT | os.O_EXCL, 0))
# unshare
if libc.unshare(CLONE_NEWNET) < 0:
raise OSError(ctypes.get_errno(), 'unshare failed', netns)
# bind the namespace
if libc.mount(b'/proc/self/ns/net', netnspath, b'none', MS_BIND, None) < 0:
raise OSError(ctypes.get_errno(), 'mount failed', netns)
def create(netns, libc=None):
'''
Create a network namespace.
'''
rctl, wctl = os.pipe()
pid = os.fork()
if pid == 0:
# child
error = None
try:
_create(netns, libc)
except Exception as e:
error = e
error.tb = traceback.format_exc()
msg = pickle.dumps(error)
os.write(wctl, struct.pack('I', len(msg)))
os.write(wctl, msg)
os._exit(0)
else:
# parent
msglen = struct.unpack('I', os.read(rctl, 4))[0]
error = pickle.loads(os.read(rctl, msglen))
os.close(rctl)
os.close(wctl)
os.waitpid(pid, 0)
if error is not None:
raise error
def remove(netns, libc=None):
'''
Remove a network namespace.
'''
libc = libc or ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
netnspath = _get_netnspath(netns)
libc.umount2(netnspath, MNT_DETACH)
os.unlink(netnspath)
def setns(netns, flags=os.O_CREAT, libc=None):
'''
Set netns for the current process.
The flags semantics is the same as for the `open(2)`
call:
- O_CREAT -- create netns, if doesn't exist
- O_CREAT | O_EXCL -- create only if doesn't exist
Note that "main" netns has no name. But you can access it with::
setns('foo') # move to netns foo
setns('/proc/1/ns/net') # go back to default netns
See also `pushns()`/`popns()`/`dropns()`
Changed in 0.5.1: the routine closes the ns fd if it's
not provided via arguments.
'''
newfd = False
libc = libc or ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
if isinstance(netns, basestring):
netnspath = _get_netnspath(netns)
if os.path.basename(netns) in listnetns(os.path.dirname(netns)):
if flags & (os.O_CREAT | os.O_EXCL) == (os.O_CREAT | os.O_EXCL):
raise OSError(errno.EEXIST, 'netns exists', netns)
else:
if flags & os.O_CREAT:
create(netns, libc=libc)
nsfd = os.open(netnspath, os.O_RDONLY)
newfd = True
elif isinstance(netns, file):
nsfd = netns.fileno()
elif isinstance(netns, int):
nsfd = netns
else:
raise RuntimeError('netns should be a string or an open fd')
error = libc.syscall(__NR_setns, nsfd, CLONE_NEWNET)
if newfd:
os.close(nsfd)
if error != 0:
raise OSError(ctypes.get_errno(), 'failed to open netns', netns)
def pushns(newns=None, libc=None):
'''
Save the current netns in order to return to it later. If newns is
specified, change to it::
# --> the script in the "main" netns
netns.pushns("test")
# --> changed to "test", the "main" is saved
netns.popns()
# --> "test" is dropped, back to the "main"
'''
global __saved_ns
__saved_ns.append(os.open('/proc/self/ns/net', os.O_RDONLY))
if newns is not None:
setns(newns, libc=libc)
def popns(libc=None):
'''
Restore the previously saved netns.
'''
global __saved_ns
fd = __saved_ns.pop()
try:
setns(fd, libc=libc)
except Exception:
__saved_ns.append(fd)
raise
os.close(fd)
def dropns(libc=None):
'''
Discard the last saved with `pushns()` namespace
'''
global __saved_ns
fd = __saved_ns.pop()
try:
os.close(fd)
except Exception:
pass