我可以从Python代码中打开多个网络命名空间中的套接字吗?

时间:2015-03-04 03:12:34

标签: python sockets namespaces

我在多个网络命名空间中运行一些应用程序。我需要创建到环回地址的套接字连接+每个名称空间中的特定端口。请注意,“特定端口”在所有网络命名空间中都是相同的。有没有办法在python中创建这样的socket连接?

欣赏任何指针!

2 个答案:

答案 0 :(得分:10)

这是一个有趣的问题。

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

您可以通过setns()系统调用访问另一个网络命名空间。这个调用不是由Python本身公开的,所以为了使用它,你要么(a)需要找到包装它的第三方模块,或者(b)使用类似ctypes模块的东西来制作它它可以在你的Python代码中找到。

使用第二个选项(ctypes),我想出了这个代码:

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

在运行此代码之前,我创建了两个网络命名空间:

# ip netns add red
# ip netns add blue

我在每个命名空间内添加了一个接口,以便最后 配置看起来像这样:

# 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:7777red命名空间)或192.168.113.2:7777blue命名空间)并且事情按预期工作。

答案 1 :(得分:0)

我在研究网络命名空间并使用 python 与它们交互时偶然发现了这篇文章。关于您关于运行 setns() 或类似功能的非 root 用户的问题,我相信这是可以实现的。在创建本文中提到的 redblue 命名空间的小脚本中,您还可以在新命名空间内设置 linux 功能,允许非 root 用户附加和绑定。直接从手册页,我们看到了这样的描述:call setns(2) (requires CAP_SYS_ADMIN in the target namespace);

功能可以添加到二进制文件中,例如 python2.7,也可以添加到 systemd 进程中。例如,如果您查看 centos 7 或 RHEL 7 上的默认 openvpn-server 服务文件,您可以看到添加的功能,以便它可以在没有 root 权限的情况下运行:CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN.....

我知道这不是原始问题的答案,但我目前没有足够的声誉来回复评论。如果您有安全意识并希望以非 root 用户身份运行代码,我建议您查看提供的功能和所有选项。