summaryrefslogtreecommitdiffstats
path: root/node-admin/scripts/pyroute2/netns
diff options
context:
space:
mode:
Diffstat (limited to 'node-admin/scripts/pyroute2/netns')
-rw-r--r--node-admin/scripts/pyroute2/netns/__init__.py123
-rw-r--r--node-admin/scripts/pyroute2/netns/nslink.py310
-rw-r--r--node-admin/scripts/pyroute2/netns/process/__init__.py39
-rw-r--r--node-admin/scripts/pyroute2/netns/process/base_p2.py7
-rw-r--r--node-admin/scripts/pyroute2/netns/process/base_p3.py7
-rw-r--r--node-admin/scripts/pyroute2/netns/process/proxy.py163
6 files changed, 649 insertions, 0 deletions
diff --git a/node-admin/scripts/pyroute2/netns/__init__.py b/node-admin/scripts/pyroute2/netns/__init__.py
new file mode 100644
index 00000000000..696ff3a14a6
--- /dev/null
+++ b/node-admin/scripts/pyroute2/netns/__init__.py
@@ -0,0 +1,123 @@
+# By Peter V. Saveliev https://pypi.python.org/pypi/pyroute2. Dual licensed under the Apache 2 and GPLv2+ see https://github.com/svinota/pyroute2 for License details.
+'''
+Network namespaces management
+=============================
+
+Pyroute2 provides basic namespaces management support. The
+`netns` module contains several tools for that.
+
+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 os
+import errno
+import ctypes
+import sys
+
+if sys.maxsize > 2**32:
+ __NR_setns = 308
+else:
+ __NR_setns = 346
+
+CLONE_NEWNET = 0x40000000
+MNT_DETACH = 0x00000002
+MS_BIND = 4096
+MS_REC = 16384
+MS_SHARED = 1 << 20
+NETNS_RUN_DIR = '/var/run/netns'
+
+
+def listnetns():
+ '''
+ List available network namespaces.
+ '''
+ try:
+ return os.listdir(NETNS_RUN_DIR)
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ return []
+ else:
+ raise
+
+
+def create(netns, libc=None):
+ '''
+ Create a network namespace.
+ '''
+ libc = libc or ctypes.CDLL('libc.so.6', use_errno=True)
+ # FIXME validate and prepare NETNS_RUN_DIR
+
+ netnspath = '%s/%s' % (NETNS_RUN_DIR, netns)
+ netnspath = netnspath.encode('ascii')
+ netnsdir = NETNS_RUN_DIR.encode('ascii')
+
+ # 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 remove(netns, libc=None):
+ '''
+ Remove a network namespace.
+ '''
+ libc = libc or ctypes.CDLL('libc.so.6', use_errno=True)
+ netnspath = '%s/%s' % (NETNS_RUN_DIR, netns)
+ netnspath = netnspath.encode('ascii')
+ 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
+ '''
+ libc = libc or ctypes.CDLL('libc.so.6', use_errno=True)
+ netnspath = '%s/%s' % (NETNS_RUN_DIR, netns)
+ netnspath = netnspath.encode('ascii')
+
+ if netns in listnetns():
+ 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)
+ ret = libc.syscall(__NR_setns, nsfd, CLONE_NEWNET)
+ if ret != 0:
+ raise OSError(ctypes.get_errno(), 'failed to open netns', netns)
+ return nsfd
diff --git a/node-admin/scripts/pyroute2/netns/nslink.py b/node-admin/scripts/pyroute2/netns/nslink.py
new file mode 100644
index 00000000000..67d5fff0921
--- /dev/null
+++ b/node-admin/scripts/pyroute2/netns/nslink.py
@@ -0,0 +1,310 @@
+# By Peter V. Saveliev https://pypi.python.org/pypi/pyroute2. Dual licensed under the Apache 2 and GPLv2+ see https://github.com/svinota/pyroute2 for License details.
+'''
+NetNS
+=====
+
+A NetNS object is IPRoute-like. It runs in the main network
+namespace, but also creates a proxy process running in
+the required netns. All the netlink requests are done via
+that proxy process.
+
+NetNS supports standard IPRoute API, so can be used instead
+of IPRoute, e.g., in IPDB::
+
+ # start the main network settings database:
+ ipdb_main = IPDB()
+ # start the same for a netns:
+ ipdb_test = IPDB(nl=NetNS('test'))
+
+ # create VETH
+ ipdb_main.create(ifname='v0p0', kind='veth', peer='v0p1').commit()
+
+ # move peer VETH into the netns
+ with ipdb_main.interfaces.v0p1 as veth:
+ veth.net_ns_fd = 'test'
+
+ # please keep in mind, that netns move clears all the settings
+ # on a VETH interface pair, so one should run netns assignment
+ # as a separate operation only
+
+ # assign addresses
+ # please notice, that `v0p1` is already in the `test` netns,
+ # so should be accessed via `ipdb_test`
+ with ipdb_main.interfaces.v0p0 as veth:
+ veth.add_ip('172.16.200.1/24')
+ veth.up()
+ with ipdb_test.interfaces.v0p1 as veth:
+ veth.add_ip('172.16.200.2/24')
+ veth.up()
+
+Please review also the test code, under `tests/test_netns.py` for
+more examples.
+
+By default, NetNS creates requested netns, if it doesn't exist,
+or uses existing one. To control this behaviour, one can use flags
+as for `open(2)` system call::
+
+ # create a new netns or fail, if it already exists
+ netns = NetNS('test', flags=os.O_CREAT | os.O_EXIST)
+
+ # create a new netns or use existing one
+ netns = NetNS('test', flags=os.O_CREAT)
+
+ # the same as above, the default behaviour
+ netns = NetNS('test')
+
+To remove a network namespace::
+
+ from pyroute2 import NetNS
+ netns = NetNS('test')
+ netns.close()
+ netns.remove()
+
+One should stop it first with `close()`, and only after that
+run `remove()`.
+
+'''
+
+import os
+import errno
+import atexit
+import select
+import struct
+import threading
+import traceback
+from socket import SOL_SOCKET
+from socket import SO_RCVBUF
+from pyroute2.config import MpPipe
+from pyroute2.config import MpProcess
+from pyroute2.iproute import IPRoute
+from pyroute2.netlink.nlsocket import NetlinkMixin
+from pyroute2.netlink.rtnl.iprsocket import MarshalRtnl
+from pyroute2.iproute import IPRouteMixin
+from pyroute2.netns import setns
+from pyroute2.netns import remove
+
+
+def NetNServer(netns, rcvch, cmdch, flags=os.O_CREAT):
+ '''
+ The netns server supposed to be started automatically by NetNS.
+ It has two communication channels: one simplex to forward incoming
+ netlink packets, `rcvch`, and other synchronous duplex to get
+ commands and send back responses, `cmdch`.
+
+ Channels should support standard socket API, should be compatible
+ with poll/select and should be able to transparently pickle objects.
+ NetNS uses `multiprocessing.Pipe` for this purpose, but it can be
+ any other implementation with compatible API.
+
+ The first parameter, `netns`, is a netns name. Depending on the
+ `flags`, the netns can be created automatically. The `flags` semantics
+ is exactly the same as for `open(2)` system call.
+
+ ...
+
+ The server workflow is simple. The startup sequence::
+
+ 1. Create or open a netns.
+
+ 2. Start `IPRoute` instance. It will be used only on the low level,
+ the `IPRoute` will not parse any packet.
+
+ 3. Start poll/select loop on `cmdch` and `IPRoute`.
+
+ On the startup, the server sends via `cmdch` the status packet. It can be
+ `None` if all is OK, or some exception.
+
+ Further data handling, depending on the channel, server side::
+
+ 1. `IPRoute`: read an incoming netlink packet and send it unmodified
+ to the peer via `rcvch`. The peer, polling `rcvch`, can handle
+ the packet on its side.
+
+ 2. `cmdch`: read tuple (cmd, argv, kwarg). If the `cmd` starts with
+ "send", then take `argv[0]` as a packet buffer, treat it as one
+ netlink packet and substitute PID field (offset 12, uint32) with
+ its own. Strictly speaking, it is not mandatory for modern netlink
+ implementations, but it is required by the protocol standard.
+
+ '''
+ try:
+ nsfd = setns(netns, flags)
+ except OSError as e:
+ cmdch.send(e)
+ return e.errno
+ except Exception as e:
+ cmdch.send(OSError(errno.ECOMM, str(e), netns))
+ return 255
+
+ #
+ try:
+ ipr = IPRoute()
+ rcvch_lock = ipr._sproxy.lock
+ ipr._s_channel = rcvch
+ poll = select.poll()
+ poll.register(ipr, select.POLLIN | select.POLLPRI)
+ poll.register(cmdch, select.POLLIN | select.POLLPRI)
+ except Exception as e:
+ cmdch.send(e)
+ return 255
+
+ # all is OK so far
+ cmdch.send(None)
+ # 8<-------------------------------------------------------------
+ while True:
+ events = poll.poll()
+ for (fd, event) in events:
+ if fd == ipr.fileno():
+ bufsize = ipr.getsockopt(SOL_SOCKET, SO_RCVBUF) // 2
+ with rcvch_lock:
+ rcvch.send(ipr.recv(bufsize))
+ elif fd == cmdch.fileno():
+ try:
+ cmdline = cmdch.recv()
+ if cmdline is None:
+ poll.unregister(ipr)
+ poll.unregister(cmdch)
+ ipr.close()
+ os.close(nsfd)
+ return
+ (cmd, argv, kwarg) = cmdline
+ if cmd[:4] == 'send':
+ # Achtung
+ #
+ # It's a hack, but we just have to do it: one
+ # must use actual pid in netlink messages
+ #
+ # FIXME: there can be several messages in one
+ # call buffer; but right now we can ignore it
+ msg = argv[0][:12]
+ msg += struct.pack("I", os.getpid())
+ msg += argv[0][16:]
+ argv = list(argv)
+ argv[0] = msg
+ cmdch.send(getattr(ipr, cmd)(*argv, **kwarg))
+ except Exception as e:
+ e.tb = traceback.format_exc()
+ cmdch.send(e)
+
+
+class NetNSProxy(object):
+
+ netns = 'default'
+ flags = os.O_CREAT
+
+ def __init__(self, *argv, **kwarg):
+ self.cmdlock = threading.Lock()
+ self.rcvch, rcvch = MpPipe()
+ self.cmdch, cmdch = MpPipe()
+ self.server = MpProcess(target=NetNServer,
+ args=(self.netns, rcvch, cmdch, self.flags))
+ self.server.start()
+ error = self.cmdch.recv()
+ if error is not None:
+ self.server.join()
+ raise error
+ else:
+ atexit.register(self.close)
+
+ def recv(self, bufsize, flags=0):
+ return self.rcvch.recv()
+
+ def close(self):
+ self.cmdch.send(None)
+ self.server.join()
+
+ def proxy(self, cmd, *argv, **kwarg):
+ with self.cmdlock:
+ self.cmdch.send((cmd, argv, kwarg))
+ response = self.cmdch.recv()
+ if isinstance(response, Exception):
+ raise response
+ return response
+
+ def fileno(self):
+ return self.rcvch.fileno()
+
+ def bind(self, *argv, **kwarg):
+ if 'async' in kwarg:
+ kwarg['async'] = False
+ return self.proxy('bind', *argv, **kwarg)
+
+ def send(self, *argv, **kwarg):
+ return self.proxy('send', *argv, **kwarg)
+
+ def sendto(self, *argv, **kwarg):
+ return self.proxy('sendto', *argv, **kwarg)
+
+ def getsockopt(self, *argv, **kwarg):
+ return self.proxy('getsockopt', *argv, **kwarg)
+
+ def setsockopt(self, *argv, **kwarg):
+ return self.proxy('setsockopt', *argv, **kwarg)
+
+
+class NetNSocket(NetlinkMixin, NetNSProxy):
+
+ def bind(self, *argv, **kwarg):
+ return NetNSProxy.bind(self, *argv, **kwarg)
+
+ def close(self):
+ NetNSProxy.close(self)
+
+ def _sendto(self, *argv, **kwarg):
+ return NetNSProxy.sendto(self, *argv, **kwarg)
+
+ def _recv(self, *argv, **kwarg):
+ return NetNSProxy.recv(self, *argv, **kwarg)
+
+
+class NetNS(IPRouteMixin, NetNSocket):
+ '''
+ NetNS is the IPRoute API with network namespace support.
+
+ **Why not IPRoute?**
+
+ The task to run netlink commands in some network namespace, being in
+ another network namespace, requires the architecture, that differs
+ too much from a simple Netlink socket.
+
+ NetNS starts a proxy process in a network namespace and uses
+ `multiprocessing` communication channels between the main and the proxy
+ processes to route all `recv()` and `sendto()` requests/responses.
+
+ **Any specific API calls?**
+
+ Nope. `NetNS` supports all the same, that `IPRoute` does, in the same
+ way. It provides full `socket`-compatible API and can be used in
+ poll/select as well.
+
+ The only difference is the `close()` call. In the case of `NetNS` it
+ is **mandatory** to close the socket before exit.
+
+ **NetNS and IPDB**
+
+ It is possible to run IPDB with NetNS::
+
+ from pyroute2 import NetNS
+ from pyroute2 import IPDB
+
+ ip = IPDB(nl=NetNS('somenetns'))
+ ...
+ ip.release()
+
+ Do not forget to call `release()` when the work is done. It will shut
+ down `NetNS` instance as well.
+ '''
+ def __init__(self, netns, flags=os.O_CREAT):
+ self.netns = netns
+ self.flags = flags
+ super(NetNS, self).__init__()
+ self.marshal = MarshalRtnl()
+
+ def post_init(self):
+ pass
+
+ def remove(self):
+ '''
+ Try to remove this network namespace from the system.
+ '''
+ remove(self.netns)
diff --git a/node-admin/scripts/pyroute2/netns/process/__init__.py b/node-admin/scripts/pyroute2/netns/process/__init__.py
new file mode 100644
index 00000000000..a211a3993f6
--- /dev/null
+++ b/node-admin/scripts/pyroute2/netns/process/__init__.py
@@ -0,0 +1,39 @@
+# By Peter V. Saveliev https://pypi.python.org/pypi/pyroute2. Dual licensed under the Apache 2 and GPLv2+ see https://github.com/svinota/pyroute2 for License details.
+import types
+import subprocess
+
+
+class MetaPopen(type):
+ '''
+ API definition for NSPopen.
+
+ All this stuff is required to make `help()` function happy.
+ '''
+ def __init__(cls, *argv, **kwarg):
+ super(MetaPopen, cls).__init__(*argv, **kwarg)
+ # copy docstrings and create proxy slots
+ cls.api = {}
+ for attr_name in dir(subprocess.Popen):
+ attr = getattr(subprocess.Popen, attr_name)
+ cls.api[attr_name] = {}
+ cls.api[attr_name]['callable'] = \
+ isinstance(attr, (types.MethodType, types.FunctionType))
+ cls.api[attr_name]['doc'] = attr.__doc__ \
+ if hasattr(attr, '__doc__') else None
+
+ def __dir__(cls):
+ return list(cls.api.keys()) + ['release']
+
+ def __getattribute__(cls, key):
+ try:
+ return type.__getattribute__(cls, key)
+ except AttributeError:
+ attr = getattr(subprocess.Popen, key)
+ if isinstance(attr, (types.MethodType, types.FunctionType)):
+ def proxy(*argv, **kwarg):
+ return attr(*argv, **kwarg)
+ proxy.__doc__ = attr.__doc__
+ proxy.__objclass__ = cls
+ return proxy
+ else:
+ return attr
diff --git a/node-admin/scripts/pyroute2/netns/process/base_p2.py b/node-admin/scripts/pyroute2/netns/process/base_p2.py
new file mode 100644
index 00000000000..402329846c0
--- /dev/null
+++ b/node-admin/scripts/pyroute2/netns/process/base_p2.py
@@ -0,0 +1,7 @@
+# By Peter V. Saveliev https://pypi.python.org/pypi/pyroute2. Dual licensed under the Apache 2 and GPLv2+ see https://github.com/svinota/pyroute2 for License details.
+from pyroute2.netns.process import MetaPopen
+
+
+class NSPopenBase(object):
+
+ __metaclass__ = MetaPopen
diff --git a/node-admin/scripts/pyroute2/netns/process/base_p3.py b/node-admin/scripts/pyroute2/netns/process/base_p3.py
new file mode 100644
index 00000000000..1356958e24a
--- /dev/null
+++ b/node-admin/scripts/pyroute2/netns/process/base_p3.py
@@ -0,0 +1,7 @@
+# By Peter V. Saveliev https://pypi.python.org/pypi/pyroute2. Dual licensed under the Apache 2 and GPLv2+ see https://github.com/svinota/pyroute2 for License details.
+from pyroute2.netns.process import MetaPopen
+
+
+class NSPopenBase(object, metaclass=MetaPopen):
+
+ pass
diff --git a/node-admin/scripts/pyroute2/netns/process/proxy.py b/node-admin/scripts/pyroute2/netns/process/proxy.py
new file mode 100644
index 00000000000..11695ac3d67
--- /dev/null
+++ b/node-admin/scripts/pyroute2/netns/process/proxy.py
@@ -0,0 +1,163 @@
+# By Peter V. Saveliev https://pypi.python.org/pypi/pyroute2. Dual licensed under the Apache 2 and GPLv2+ see https://github.com/svinota/pyroute2 for License details.
+'''
+NSPopen
+=======
+
+The `NSPopen` class has nothing to do with netlink at
+all, but it is required to have a reasonable network
+namespace support.
+
+'''
+
+
+import types
+import atexit
+import threading
+import subprocess
+from pyroute2.netns import setns
+from pyroute2.config import MpQueue
+from pyroute2.config import MpProcess
+try:
+ from pyroute2.netns.process.base_p3 import NSPopenBase
+except Exception:
+ from pyroute2.netns.process.base_p2 import NSPopenBase
+
+
+def _handle(result):
+ if result['code'] == 500:
+ raise result['data']
+ elif result['code'] == 200:
+ return result['data']
+ else:
+ raise TypeError('unsupported return code')
+
+
+def NSPopenServer(nsname, flags, channel_in, channel_out, argv, kwarg):
+ # set netns
+ try:
+ setns(nsname, flags=flags)
+ except Exception as e:
+ channel_out.put(e)
+ return
+ # create the Popen object
+ child = subprocess.Popen(*argv, **kwarg)
+ # send the API map
+ channel_out.put(None)
+
+ while True:
+ # synchronous mode
+ # 1. get the command from the API
+ call = channel_in.get()
+ # 2. stop?
+ if call['name'] == 'release':
+ break
+ # 3. run the call
+ try:
+ attr = getattr(child, call['name'])
+ if isinstance(attr, types.MethodType):
+ result = attr(*call['argv'], **call['kwarg'])
+ else:
+ result = attr
+ channel_out.put({'code': 200, 'data': result})
+ except Exception as e:
+ channel_out.put({'code': 500, 'data': e})
+ child.wait()
+
+
+class NSPopen(NSPopenBase):
+ '''
+ A proxy class to run `Popen()` object in some network namespace.
+
+ Sample to run `ip ad` command in `nsname` network namespace::
+
+ nsp = NSPopen('nsname', ['ip', 'ad'], stdout=subprocess.PIPE)
+ print(nsp.communicate())
+ nsp.wait()
+ nsp.release()
+
+ The only difference in the `release()` call. It explicitly ends
+ the proxy process and releases all the resources.
+ '''
+
+ def __init__(self, nsname, *argv, **kwarg):
+ '''
+ The only differences from the `subprocess.Popen` init are:
+ * `nsname` -- network namespace name
+ * `flags` keyword argument
+
+ All other arguments are passed directly to `subprocess.Popen`.
+
+ Flags usage samples. Create a network namespace, if it doesn't
+ exist yet::
+
+ import os
+ nsp = NSPopen('nsname', ['command'], flags=os.O_CREAT)
+
+ Create a network namespace only if it doesn't exist, otherwise
+ fail and raise an exception::
+
+ import os
+ nsp = NSPopen('nsname', ['command'], flags=os.O_CREAT | os.O_EXCL)
+ '''
+ # create a child
+ self.nsname = nsname
+ if 'flags' in kwarg:
+ self.flags = kwarg.pop('flags')
+ else:
+ self.flags = 0
+ self.channel_out = MpQueue()
+ self.channel_in = MpQueue()
+ self.lock = threading.Lock()
+ self.released = False
+ self.server = MpProcess(target=NSPopenServer,
+ args=(self.nsname,
+ self.flags,
+ self.channel_out,
+ self.channel_in,
+ argv, kwarg))
+ # start the child and check the status
+ self.server.start()
+ response = self.channel_in.get()
+ if isinstance(response, Exception):
+ self.server.join()
+ raise response
+ else:
+ atexit.register(self.release)
+
+ def release(self):
+ '''
+ Explicitly stop the proxy process and release all the
+ resources. The `NSPopen` object can not be used after
+ the `release()` call.
+ '''
+ with self.lock:
+ if self.released:
+ return
+ self.released = True
+ self.channel_out.put({'name': 'release'})
+ self.channel_out.close()
+ self.channel_in.close()
+ self.server.join()
+
+ def __dir__(self):
+ return list(self.api.keys()) + ['release']
+
+ def __getattribute__(self, key):
+ try:
+ return object.__getattribute__(self, key)
+ except AttributeError:
+ with self.lock:
+ if self.released:
+ raise RuntimeError('the object is released')
+
+ if self.api.get(key) and self.api[key]['callable']:
+ def proxy(*argv, **kwarg):
+ self.channel_out.put({'name': key,
+ 'argv': argv,
+ 'kwarg': kwarg})
+ return _handle(self.channel_in.get())
+ proxy.__doc__ = self.api[key]['doc']
+ return proxy
+ else:
+ self.channel_out.put({'name': key})
+ return _handle(self.channel_in.get())