我在多个网络命名空间中运行一些应用程序。我需要创建到环回地址的套接字连接+每个名称空间中的特定端口。请注意,“特定端口”在所有网络命名空间中都是相同的。有没有办法在python中创建这样的socket连接?
欣赏任何指针!
答案 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:7777
(red
命名空间)或192.168.113.2:7777
(blue
命名空间)并且事情按预期工作。
答案 1 :(得分:0)
我在研究网络命名空间并使用 python 与它们交互时偶然发现了这篇文章。关于您关于运行 setns() 或类似功能的非 root 用户的问题,我相信这是可以实现的。在创建本文中提到的 red
和 blue
命名空间的小脚本中,您还可以在新命名空间内设置 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 用户身份运行代码,我建议您查看提供的功能和所有选项。