我可以通过我的Python代码在多个网络名称空间中打开套接字吗? [英] Can I open sockets in multiple network namespaces from my Python code?

查看:64
本文介绍了我可以通过我的Python代码在多个网络名称空间中打开套接字吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在多个网络名称空间中运行某些应用程序.我需要在每个名称空间中创建到回送地址+一个特定端口的套接字连接.请注意,所有网络名称空间中的特定端口"都是相同的.有什么办法可以在python中创建这样的套接字连接吗?

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 that the "specific port" is the same across all network namespaces. Is there a way I can create a socket connection like this in python?

欣赏任何指针!

推荐答案

这是一个有趣的问题.

更新:我非常喜欢它,以至于我将解决方案打包为可安装的Python模块,可从 https://github.com/larsks/python-netns .

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.

您可以通过使用setns()系统调用来访问另一个网络名称空间.此调用不是由Python本身公开的,因此,要使用它,您要么(a)需要找到包装它的第三方模块,要么(b)使用ctypes模块之类的东西来使它可用在您的Python代码中.

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.

使用第二个选项(ctypes),我想到了以下代码:

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: <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

运行代码(与root一样,因为您需要是root用户才能执行 利用setns调用),我可以连接到任一 192.168.115.2:7777(red名称空间)或192.168.113.2:7777 (blue名称空间),一切按预期进行.

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.

这篇关于我可以通过我的Python代码在多个网络名称空间中打开套接字吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆