¿Puedo abrir sockets en múltiples espacios de nombres de red desde mi código Python?

Estoy ejecutando alguna aplicación en varios espacios de nombres de red. Y necesito crear una conexión de socket a la dirección de loopback + un puerto específico en cada uno de los espacios de nombres. Tenga en cuenta que el “puerto específico” es el mismo en todos los espacios de nombres de red. ¿Hay alguna manera de crear una conexión de socket como esta en Python?

Aprecia cualquier puntero!

Este fue un problema divertido.

Actualización : Me gustó tanto que empaqueté la solución como un módulo de Python instalable, disponible en https://github.com/larsks/python-netns .

Puede acceder a otro espacio de nombres de red mediante el uso de la llamada al sistema setns() . Esta llamada no está expuesta de forma nativa por Python, por lo que para poder usarla (a) necesitaría encontrar un módulo de terceros que la envuelva, o (b) usar algo como el módulo ctypes para que esté disponible en su Código Python.

Usando la segunda opción ( ctypes ), se me ocurrió este código:

 #!/usr/bin/python import argparse import os import select import socket import subprocess # Python doesn't expose the `setns()` function manually, so # we'll use the `ctypes` module to make it available. from ctypes import cdll libc = cdll.LoadLibrary('libc.so.6') setns = libc.setns # This is just a convenience function that will return the path # to an appropriate namespace descriptor, give either a path, # a network namespace name, or a pid. def get_ns_path(nspath=None, nsname=None, nspid=None): if nsname: nspath = '/var/run/netns/%s' % nsname elif nspid: nspath = '/proc/%d/ns/net' % nspid return nspath # This is a context manager that on enter assigns the process to an # alternate network namespace (specified by name, filesystem path, or pid) # and then re-assigns the process to its original network namespace on # exit. class Namespace (object): def __init__(self, nsname=None, nspath=None, nspid=None): self.mypath = get_ns_path(nspid=os.getpid()) self.targetpath = get_ns_path(nspath, nsname=nsname, nspid=nspid) if not self.targetpath: raise ValueError('invalid namespace') def __enter__(self): # before entering a new namespace, we open a file descriptor # in the current namespace that we will use to restre # our namespace on exit. self.myns = open(self.mypath) with open(self.targetpath) as fd: setns(fd.fileno(), 0) def __exit__(self, *args): setns(self.myns.fileno(), 0) self.myns.close() # This is a wrapper for socket.socket() that creates the socket inside the # specified network namespace. def nssocket(ns, *args): with Namespace(nsname=ns): s = socket.socket(*args) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) return s def main(): # Create a socket inside the 'red' namespace red = nssocket('red') red.bind(('0.0.0.0', 7777)) red.listen(10) # Create a socket inside the 'blue' namespace blue = nssocket('blue') blue.bind(('0.0.0.0', 7777)) blue.listen(10) poll = select.poll() poll.register(red, select.POLLIN) poll.register(blue, select.POLLIN) sockets = { red.fileno(): { 'socket': red, 'label': 'red', }, blue.fileno(): { 'socket': blue, 'label': 'blue', } } while True: events = poll.poll() for fd, event in events: sock = sockets[fd]['socket'] label = sockets[fd]['label'] if sock in [red, blue]: newsock, client = sock.accept() sockets[newsock.fileno()] = { 'socket': newsock, 'label': label, 'client': client, } poll.register(newsock, select.POLLIN) elif event & select.POLLIN: data = sock.recv(1024) if not data: print 'closing fd %d (%s)' % (fd, label) poll.unregister(sock) sock.close() continue print 'DATA %s [%d]: %s' % (label, fd, data) if __name__ == '__main__': main() 

Antes de ejecutar este código, creé dos espacios de nombres de red:

 # ip netns add red # ip netns add blue 

Agregué una interfaz dentro de cada espacio de nombres, para que la configuración final se pareciera a esto:

 # ip netns exec red ip a 1: lo:  mtu 65536 qdisc noop state DOWN group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 816: virt-0-0:  mtu 1500 qdisc noqueue state UNKNOWN group default link/ether f2:9b:6a:fd:87:77 brd ff:ff:ff:ff:ff:ff inet 192.168.115.2/24 scope global virt-0-0 valid_lft forever preferred_lft forever inet6 fe80::f09b:6aff:fefd:8777/64 scope link valid_lft forever preferred_lft forever # ip netns exec blue ip a 1: lo:  mtu 65536 qdisc noop state DOWN group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 817: virt-1-0:  mtu 1500 qdisc noqueue state UNKNOWN group default link/ether 82:94:6a:1b:13:16 brd ff:ff:ff:ff:ff:ff inet 192.168.113.2/24 scope global virt-1-0 valid_lft forever preferred_lft forever inet6 fe80::8094:6aff:fe1b:1316/64 scope link valid_lft forever preferred_lft forever 

Ejecutando el código (como root , porque necesitas ser root para hacer uso de la llamada setns ), puedo conectarme a 192.168.115.2:7777 (el espacio de nombres red ) o 192.168.113.2:7777 (el espacio de nombres blue ) y las cosas funcionan como se espera.