2015-03-04 3 views
5

Sto eseguendo alcune applicazioni in più spazio dei nomi di rete. E ho bisogno di creare una connessione socket per l'indirizzo di loopback + una porta specifica in ciascuno dello spazio dei nomi. Si noti che la "porta specifica" è la stessa in tutti gli spazi dei nomi di rete. C'è un modo per creare una connessione socket come questa in Python?Posso aprire socket in più domini di rete dal mio codice Python?

Apprezzare qualsiasi puntatore!

+0

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

+0

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

risposta

8

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.

+0

Grazie mille per questo! Ha funzionato per me! C'è un modo per eseguire solo le funzioni setns() o simili come root mentre tutto il resto è ancora come utente? – jackiesyu