The*_*veO 11 networking network-namespaces veth
我需要明确地并且没有“整体”猜测地在另一个网络命名空间中找到veth 端的对等网络接口。
尽管有很多文档并且在 SO 上的回答也假设网络接口的 ifindex 索引在每个主机的网络命名空间中是全局唯一的,但这在许多情况下并不成立:ifindex/iflink 是模棱两可的。甚至环回已经显示出相反的情况,在任何网络命名空间中都有一个 ifindex 为 1。此外,根据容器环境,ifindex数字会在不同的命名空间中重用。这使得跟踪 veth 布线成为一场噩梦,特别是有很多容器和一个带有 veth peers 的主机桥都以@if3 左右结尾......
link-netnsid是0启动一个 Docker 容器实例,只是为了获得一个新的 veth从主机网络命名空间连接到新容器网络命名空间对......
$ sudo docker run -it debian /bin/bash
现在,在主机网络命名空间中列出网络接口(我已经省略了对这个问题不感兴趣的那些接口):
$ ip链接显示
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
链接/环回 00:00:00:00:00:00 brd 00:00:00:00:00:00
...
4: docker0: mtu 1500 qdisc noqueue state UP mode DEFAULT group default
链接/以太 02:42:34:23:81:f0 brd ff:ff:ff:ff:ff:ff
...
16: vethfc8d91e@if15: mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
链接/以太 da:4c:f7:50:09:e2 brd ff:ff:ff:ff:ff:ff 链接-netnsid 0
如您所见,虽然iflink是明确的,但link-netnsid是 0,尽管对端位于不同的网络命名空间中。
作为参考,检查容器未命名网络命名空间中的netnsid:
$ sudo lsns -t net
NS 类型 NPROCS PID 用户命令
...
...
4026532469 净 1 29616 根 /bin/bash
$ sudo nsenter -t 29616 -n ip 链接显示
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
链接/环回 00:00:00:00:00:00 brd 00:00:00:00:00:00
15: eth0@if16: mtu 1500 qdisc noqueue state UP mode DEFAULT group default
链接/以太 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff 链接-netnsid 0
因此,对于两个 veth 端ip link show(和 RTNETLINK fwif),它们告诉我们它们与 netnsid 0 位于同一网络命名空间中。假设 link-netnsids 是本地而不是全局,这要么是错误的,要么是正确的。我找不到任何明确说明 link-netnsids 应该具有的范围的文档。
/sys/class/net/... 不去救援?我查看了 /sys/class/net/ if /... 但只能找到 ifindex 和 iflink 元素;这些都是有据可查的。“ip link show”似乎也仅以(著名的)“@if#”符号的形式显示对等 ifindex。还是我错过了一些额外的网络命名空间元素?
是否有任何系统调用允许为 veth 对的对等端检索丢失的网络命名空间信息?
这是我用来寻找如何理解这个问题的方法。可用工具对于命名空间部分似乎可用(有一些卷积),并且(更新)使用 /sys/ 可以轻松获取对等方的索引。所以它很长,请耐心等待。它分为两部分(不按逻辑顺序,但命名空间首先有助于解释索引命名),使用通用工具,而不是任何自定义程序:
此信息可与link-netnsid输出中的属性一起使用,并可与输出中ip link的 id 匹配ip netns。可以将容器的网络命名空间与“关联” ip netns,从而ip netns用作专用工具。当然为此做一个特定的程序会更好(每部分末尾有关系统调用的一些信息)。
关于 nsid 的描述,以下是man ip netns说明(强调我的):
ip netns set NAME NETNSID - 为对等网络命名空间分配一个 id
此命令为对等网络命名空间分配一个 id。此 id 仅在当前网络命名空间中有效。内核将在某些 netlink 消息中使用此 ID。如果内核需要的时候没有分配id,内核会自动分配。一旦分配,就无法更改。
虽然创建命名空间ip netns不会立即创建 netnsid,但只要将 veth half 设置为其他命名空间,就会创建它(在当前命名空间上,可能是“主机”)。所以它总是为一个典型的容器设置。
这是一个使用 LXC 容器的示例:
# lxc-start -n stretch-amd64
Run Code Online (Sandbox Code Playgroud)
veth9RPX4M出现了一个新的 veth 链接(可以使用 进行跟踪ip monitor link)。以下是详细信息:
# ip -o link show veth9RPX4M
44: veth9RPX4M@if43: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue master lxcbr0 state LOWERLAYERDOWN mode DEFAULT group default qlen 1000
link/ether fe:25:13:8a:00:f8 brd ff:ff:ff:ff:ff:ff link-netnsid 4
Run Code Online (Sandbox Code Playgroud)
这个链接有属性link-netnsid 4,告诉对方在网络命名空间中,nsid 4.如何验证它是LXC容器?获取此信息的最简单方法是ip netns通过执行联机帮助页中提示的操作,使其相信它创建了容器的网络命名空间。
# mkdir -p /var/run/netns
# touch /var/run/netns/stretch-amd64
# mount -o bind /proc/$(lxc-info -H -p -n stretch-amd64)/ns/net /var/run/netns/stretch-amd64
Run Code Online (Sandbox Code Playgroud)
UPDATE3:我不明白找回全局名称是一个问题。这里是:
# ls -l /proc/$(lxc-info -H -p -n stretch-amd64)/ns/net
lrwxrwxrwx. 1 root root 0 mai 5 20:40 /proc/17855/ns/net -> net:[4026532831]
# stat -c %i /var/run/netns/stretch-amd64
4026532831
Run Code Online (Sandbox Code Playgroud)
现在通过以下方式检索信息:
# ip netns | grep stretch-amd64
stretch-amd64 (id: 4)
Run Code Online (Sandbox Code Playgroud)
它确认 veth 的对等方在具有相同 nsid = 4 = link-netnsid 的网络命名空间中。
ip netns可以删除容器/ “关联”(只要容器正在运行,无需删除命名空间):
# ip netns del stretch-amd64
Run Code Online (Sandbox Code Playgroud)
注意:nsid 命名是针对每个网络命名空间的,通常第一个容器从 0 开始,可用的最低值与新命名空间一起回收。
关于使用系统调用,这里是从strace猜测的信息:
对于链接部分:它需要一个AF_NETLINK套接字(以socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)开头sendmsg()),使用消息类型询问()链接的信息,并以消息类型RTM_GETLINK检索(recvmsg())回复RTM_NEWLINK。
对于 netns nsid 部分:相同的方法,查询消息类型RTM_GETNSID为回复类型RTM_NEWNSID。
我认为处理这个问题的级别稍高的库在那里:libnl。无论如何,这是SO的主题。
现在更容易理解为什么索引似乎具有随机行为。我们来做一个实验:
首先输入一个新的网络命名空间以获得干净的(索引)板:
# ip netns add test
# ip netns exec test bash
# ip netns id
test
# ip -o link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
Run Code Online (Sandbox Code Playgroud)
正如 OP 所指出的, lo 从索引 1 开始。
让我们添加 5 个网络命名空间,创建 veth 对,然后在它们上面放置一个 veth 端:
# for i in {0..4}; do ip netns add test$i; ip link add type veth peer netns test$i ; done
# ip -o link|sed 's/^/ /'
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether e2:83:4f:60:5a:30 brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: veth1@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether 22:a7:75:8e:3c:95 brd ff:ff:ff:ff:ff:ff link-netnsid 1
4: veth2@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether 72:94:6e:e4:2c:fc brd ff:ff:ff:ff:ff:ff link-netnsid 2
5: veth3@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether ee:b5:96:63:62:de brd ff:ff:ff:ff:ff:ff link-netnsid 3
6: veth4@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether e2:7d:e2:9a:3f:6d brd ff:ff:ff:ff:ff:ff link-netnsid 4
Run Code Online (Sandbox Code Playgroud)
当它为每个人显示 @if2 时,很明显它是对等的命名空间接口索引和索引不是全局的,而是每个命名空间的。当它显示一个实际的接口名称时,它是与同一名称空间中的接口的关系(无论是 veth 的 peer、bridge、bond...)。那么为什么 veth0 没有显示对等点呢?ip link当索引与自身相同时,我相信这是一个错误。只需移动两次对等链接就可以“解决”它,因为它强制更改了索引。我也确定有时会ip link做其他混淆,而不是显示 @ifXX,而是在当前命名空间中显示具有相同索引的界面。
# ip -n test0 link set veth0 name veth0b netns test
# ip link set veth0b netns test0
# ip -o link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth0@if7: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether e2:83:4f:60:5a:30 brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: veth1@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether 22:a7:75:8e:3c:95 brd ff:ff:ff:ff:ff:ff link-netnsid 1
4: veth2@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether 72:94:6e:e4:2c:fc brd ff:ff:ff:ff:ff:ff link-netnsid 2
5: veth3@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether ee:b5:96:63:62:de brd ff:ff:ff:ff:ff:ff link-netnsid 3
6: veth4@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000\ link/ether e2:7d:e2:9a:3f:6d brd ff:ff:ff:ff:ff:ff link-netnsid 4
Run Code Online (Sandbox Code Playgroud)
更新:再次阅读 OP 问题中的信息,对等的索引(但不是 nsid)可以通过.cat /sys/class/net/ interface /iflink
更新2:
所有这些 iflink 2 可能看起来模棱两可,但独特的是 nsid 和 iflink 的组合,而不是单独的 iflink。对于上面的例子是:
interface nsid:iflink
veth0 0:7
veth1 1:2
veth2 2:2
veth3 3:2
veth4 4:2
Run Code Online (Sandbox Code Playgroud)
在这个命名空间(即 namespace test)中,永远不会有两个相同的 nsid:pair 。
如果从每个对等网络中查看相反的信息:
namespace interface nsid:iflink
test0 veth0 0:2
test1 veth0 0:3
test2 veth0 0:4
test3 veth0 0:5
test4 veth0 0:6
Run Code Online (Sandbox Code Playgroud)
但请记住,0:每个人都有一个单独的 0,它恰好映射到同一个对等命名空间(即: namespace test,甚至不是主机)。不能直接比较它们,因为它们绑定到它们的命名空间。所以整个可比较和独特的信息应该是:
test0:0:2
test1:0:3
test2:0:4
test3:0:5
test4:0:6
Run Code Online (Sandbox Code Playgroud)
一旦它的证实,“TEST0:0” ==“测试1:0”等。(在这个例子中真,都映射到被叫网命名空间test的ip netns),那么他们可以真正比较。
关于系统调用,还在看strace结果,信息如上从RTM_GETLINK. 现在应该有所有可用的信息:
本地:带有SIOCGIFINDEX/
peer 的接口索引:nsid 和带有.if_nametoindexRTM_GETLINK
所有这些都应该与libnl一起使用。
非常感谢@AB 为我填补了一些缺失的部分,特别是关于netnsids的语义。他的 PoC 很有指导意义。然而,他的 PoC 中关键的缺失部分是如何将本地netnsid与其全局唯一的网络命名空间 inode 编号相关联,因为只有这样我们才能明确地连接正确的veth对应对。
总结并给出一个小的 Python 示例,如何以编程方式收集信息而不必依赖ip netns及其需要挂载的东西:RTNETLINK 在查询网络接口时实际上返回 netnsid。它是IFLA_LINK_NETNSID属性,仅在需要时出现在链接的信息中。如果它不存在,那么就不需要它——我们必须假设对等索引指的是一个命名空间本地网络接口。
要带回家的重要教训是netnsid/IFLA_LINK_NETSID仅在您向 RTNETLINK 请求链接信息时获得它的网络命名空间中本地定义。netnsid在不同的网络命名空间中获得的具有相同值的A可能会标识不同的对等命名空间,因此请注意不要使用netnsid其命名空间之外的名称。但是哪个唯一可识别的网络命名空间(inode编号)映射到哪个netnsid?
事实证明,lsns截至 2018 年 3 月的最新版本能够netnsid在其网络命名空间 inode 编号旁边显示正确的内容!因此,有是映射本地的方式netnsids到命名空间的inode,但它实际上是倒退!它更像是一个预言机(带有小写的 ell)而不是查找:RTM_GETNSID 需要一个网络命名空间标识符作为 PID 或 FD(到网络命名空间),然后返回netnsid. 有关如何询问 Linux 网络命名空间 oracle 的示例,请参阅/sf/ask/3513783171/。
因此,您需要枚举可用的网络名称空间(通过/proc和/或/var/run/netns),然后对于给定的veth网络接口附加到您找到它的网络名称空间,要求netnsid您在开头枚举的所有网络名称空间的s(因为你永远不知道在此之前哪个是哪个),最后映射netnsid了的veth对每个您在步骤3中创建连接到后的局部地图的命名空间inode编号veth的命名空间。
import psutil
import os
import pyroute2
from pyroute2.netlink import rtnl, NLM_F_REQUEST
from pyroute2.netlink.rtnl import nsidmsg
from nsenter import Namespace
# phase I: gather network namespaces from /proc/[0-9]*/ns/net
netns = dict()
for proc in psutil.process_iter():
netnsref= '/proc/{}/ns/net'.format(proc.pid)
netnsid = os.stat(netnsref).st_ino
if netnsid not in netns:
netns[netnsid] = netnsref
# phase II: ask kernel "oracle" about the local IDs for the
# network namespaces we've discovered in phase I, doing this
# from all discovered network namespaces
for id, ref in netns.items():
with Namespace(ref, 'net'):
print('inside net:[{}]...'.format(id))
ipr = pyroute2.IPRoute()
for netnsid, netnsref in netns.items():
with open(netnsref, 'r') as netnsf:
req = nsidmsg.nsidmsg()
req['attrs'] = [('NETNSA_FD', netnsf.fileno())]
resp = ipr.nlm_request(req, rtnl.RTM_GETNSID, NLM_F_REQUEST)
local_nsid = dict(resp[0]['attrs'])['NETNSA_NSID']
if local_nsid != 2**32-1:
print(' net:[{}] <--> nsid {}'.format(netnsid, local_nsid))
Run Code Online (Sandbox Code Playgroud)