Can I open sockets in multiple network namespaces from my Python code?

前端 未结 1 1996
时光取名叫无心
时光取名叫无心 2021-02-06 07:33

I am running some application in multiple network namespace. And I need to create socket connection to the loopback address + a specific port in each of the name space. Note tha

1条回答
  •  醉话见心
    2021-02-06 08:18

    This was a fun problem.

    Update: I liked it so much that I packed up the solution as an installable Python module, available from https://github.com/larsks/python-netns.

    You can access another network namespace through the use of the setns() system call. This call isn't exposed natively by Python, so in order to use it you would either (a) need to find a third-party module that wraps it, or (b) use something like the ctypes module to make it available in your Python code.

    Using the second option (ctypes), I came up with this code:

    #!/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()
    

    Prior to running this code, I created two network namespaces:

    # ip netns add red
    # ip netns add blue
    

    I added an interface inside of each namespace, so that the final configuration looked like this:

    # 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
    

    Running the code (as root, because you need to be root in order to make use of the setns call), I can connect to either 192.168.115.2:7777 (the red namespace) or 192.168.113.2:7777 (the blue namespace) and things work as expected.

    0 讨论(0)
提交回复
热议问题