Questo è stato un problema divertente.
Aggiornamento: Mi è piaciuto così tanto che ho impacchettato la soluzione come un modulo Python installabile, disponibile da https://github.com/larsks/python-netns.
È possibile accedere a un altro spazio dei nomi di rete tramite la chiamata di sistema setns()
. Questa chiamata non è esposta in modo nativo da Python, quindi per poterlo utilizzare dovresti (a) trovare un modulo di terze parti che lo avvolga, o (b) usare qualcosa come il modulo ctypes
per renderlo disponibile nel tuo Codice Python
Utilizzando la seconda opzione (ctypes
), sono arrivato fino a questo codice:
#!/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 restore
# 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()
Prima di eseguire il codice, ho creato due spazi dei nomi di rete:
# ip netns add red
# ip netns add blue
ho aggiunto un'interfaccia interna di ogni spazio dei nomi, in modo che la configurazione finale assomigliava a questa:
# ip netns exec red ip a
1: lo: <LOOPBACK> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <LOOPBACK> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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
.515.053.691,36321 milioni
L'esecuzione del codice (come root
, perché è necessario essere root per uso fanno della chiamata setns
), posso collegare a uno 192.168.115.2:7777
(la red
namespace) o 192.168.113.2:7777
(la blue
namespace) e le cose funzionano come previsto.
Puoi chiarire la tua domanda? Sembra che tu stia chiedendo "come posso aprire un socket tcp con Python?", Per il quale ci sono già molti esempi e documentazione. Finché stai eseguendo il tuo codice Python all'interno dello spazio dei nomi della rete di destinazione non c'è nulla di speciale nel tuo codice; funzionerà esattamente come il codice in esecuzione nel namespace della rete globale. – larsks
Grazie per la domanda. La mia domanda è più di connettersi a più spazio dei nomi di rete all'interno di un singolo programma/processo. Capisco che potrei avere ogni socket tcp aperto in ciascuno dei suoi processi avviando il processo in un particolare spazio dei nomi, ma in tal caso, avrò un numero di processi pari al # del namespace ... cercando di evitare questo. – jackiesyu