任务
我需要明确地、没有“整体”猜测地找到同行另一个网络命名空间中 veth 端的网络接口。
理论 。/。现实
尽管这里有很多文档和答案,但假设网络接口的 ifindex 索引在网络命名空间中每个主机都是全局唯一的,这在很多情况下并不成立:ifindex/iflink
含糊不清。甚至环回也已经显示出相反的情况,在任何网络命名空间中 ifindex 均为 1。另外,根据容器环境,ifindex
数字在不同的命名空间中被重用。这使得追踪 veth 连接成为一场噩梦,特别是有大量容器和主机桥,veth 对等点都以 @if3 左右结尾......
示例:link-netnsid
是0
启动一个 Docker 容器实例,只是为了获得veth
从主机网络命名空间连接到新容器网络命名空间的新对......
$ sudo docker run -it debian /bin/bash
现在,在主机网络命名空间中列出网络接口(我省略了与此问题无关的那些接口):
$ ip 链接显示 1: lo: mtu 65536 qdisc noqueue 状态 UNKNOWN 模式 DEFAULT 组默认 qlen 1000 链接/环回 00:00:00:00:00:00 brd 00:00:00:00:00:00 ... 4: docker0: mtu 1500 qdisc noqueue 状态 UP 模式 DEFAULT 组默认 链接/以太 02:42:34:23:81:f0 brd ff:ff:ff:ff:ff:ff ... 16:vethfc8d91e@if15:mtu 1500 qdisc noqueue master docker0状态UP模式DEFAULT组默认 链接/以太 da:4c:f7:50:09:e2 brd ff:ff:ff:ff:ff:ff link-netnsid 0
正如您所看到的,虽然iflink
是明确的,但link-netnsid
是 0,尽管对等端位于不同的网络命名空间中。
作为参考,请检查容器的未命名网络命名空间中的 netnsid:
$ sudo lsns -t 网络 NS 类型 NPROCS PID 用户命令 ... ... 4026532469 网络 1 29616 根 /bin/bash $ sudo nsenter -t 29616 -n ip 链接显示 1: lo: mtu 65536 qdisc noqueue 状态 UNKNOWN 模式 DEFAULT 组默认 qlen 1000 链接/环回 00:00:00:00:00:00 brd 00:00:00:00:00:00 15: eth0@if16: mtu 1500 qdisc noqueue 状态 UP 模式 DEFAULT 组默认 链接/以太 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
因此,对于两个 veth 端ip link show
(和 RTNETLINK fwif),它们告诉我们它们与 netnsid 0 位于同一网络命名空间中。在假设 link-netnsid 是本地而不是全局的情况下,这要么是错误的,要么是正确的。我找不到任何文档可以明确说明 link-netnsids 的范围。
/sys/class/net/...
不去救援?
我研究过 /sys/class/net/如果/...但只能找到ifindex和iflink元素;这些都有详细记录。 “ip link show”似乎也只以著名的“@if#”符号的形式显示对等 ifindex。或者我错过了一些额外的网络命名空间元素?
底线/问题
是否有任何系统调用允许检索 veth 对对等端丢失的网络命名空间信息?
答案1
这是我用来寻找如何理解这个问题的方法。可用的工具似乎可用于命名空间部分(带有一些卷积),并且(已更新)使用 /sys/ 可以轻松获取对等方的索引。所以它很长,请耐心等待。它分为两部分(不按逻辑顺序,但名称空间首先有助于解释索引命名),使用通用工具,而不是任何自定义程序:
- 网络命名空间
- 接口索引
网络命名空间
link-netnsid
此信息可通过输出中的属性获得ip link
,并且可以与 输出中的 id 相匹配ip netns
。可以将容器的网络命名空间与 关联起来ip netns
,从而将其用作ip netns
专用工具。当然,为此做一个特定的程序会更好(每个部分末尾有一些有关系统调用的信息)。
关于 nsid 的描述,以下是man ip netns
说明的内容(强调我的):
ip netns set NAME NETNSID - 为对等网络命名空间分配一个 id
此命令为对等网络命名空间分配一个 id。该id仅在当前网络命名空间中有效。这个id将被内核在一些netlink消息中使用。如果内核需要时没有分配id,则由内核自动分配。一旦分配,就无法更改。
虽然创建命名空间ip netns
不会立即创建 netnsid,但只要将 veth half 设置为其他命名空间,就会创建它(在当前命名空间上,可能是“主机”)。所以它总是针对典型的容器进行设置。
以下是使用 LXC 容器的示例:
# lxc-start -n stretch-amd64
出现了一个新的 veth 链接veth9RPX4M
(可以使用 进行跟踪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
这个链接有属性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
更新3:我不明白找回全局名称是一个问题。这里是:
# 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
现在可以通过以下方式检索信息:
# ip netns | grep stretch-amd64
stretch-amd64 (id: 4)
它确认 veth 的对等点位于具有相同 nsid = 4 = link-netnsid 的网络命名空间中。
容器/ ip netns
“关联”可以被删除(只要容器正在运行,就无需删除命名空间):
# ip netns del stretch-amd64
注意:nsid 命名是针对每个网络命名空间的,通常第一个容器以 0 开头,可用的最低值将在新的命名空间中回收。
关于使用系统调用,以下是从 strace 猜测的信息:
对于链接部分:它需要一个
AF_NETLINK
套接字(用socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)
)打开,询问(sendmsg()
) 带有消息类型的链接信息RTM_GETLINK
并检索(recvmsg()
) 回复消息类型RTM_NEWLINK
。对于netns nsid部分:同样的方法,查询消息是type
RTM_GETNSID
与回复类型RTM_NEWNSID
。
我认为处理这个问题的更高级别的库存在:库。无论如何,这是一个话题所以。
接口索引
现在可以更容易地理解为什么该指数似乎具有随机行为。我们来做一个实验:
首先输入一个新的网络命名空间以获得一个干净的(索引)石板:
# 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
正如 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
当它为每个节点显示 @if2 时,很明显,对等方的名称空间接口索引和索引不是全局的,而是每个名称空间的。当它显示实际的接口名称时,它是与同一名称空间中的接口的关系(无论是 veth 的对等点、桥接器、债券……)。那么为什么 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
更新:再次阅读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
在这个命名空间(即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
但请记住,每个0:
都有一个单独的 0,它恰好映射到相同的对等命名空间(即:命名空间test
,甚至不是主机)。它们无法直接比较,因为它们与名称空间相关联。因此,整个可比较且独特的信息应该是:
test0:0:2
test1:0:3
test2:0:4
test3:0:5
test4:0:6
一旦确认“test0:0”==“test1:0”等(在本例中为 true,所有映射到test
由调用的网络命名空间ip netns
),那么它们就可以真正进行比较。
关于系统调用,仍然查看 strace 结果,信息是从上面检索的RTM_GETLINK
。现在应该有所有可用信息:
local:接口索引SIOCGIFINDEX
/if_nametoindex
对等体:nsid 和接口索引RTM_GETLINK
。
所有这些可能应该与库。
答案2
2023 年更新:西门子现已发布边缘鲨作为 OSS,它提供了一个漂亮的图形 Web UI,呈现容器、主机等中网络接口的关系。它使用本答案中概述的方法的基于 Go 的实现,并具有更多功能。
非常感谢@AB,他为我填补了一些缺失的部分,特别是关于 s 的语义netnsid
。他的 PoC 非常有启发性。然而,他的 PoC 中关键的缺失部分是如何将本地名称空间netnsid
与其全局唯一的网络命名空间 inode 编号相关联,因为只有这样我们才能明确连接正确的对应对veth
。
总结并给出一个小的 Python 示例,如何以编程方式收集信息,而无需依赖ip netns
及其需要挂载的东西:RTNETLINK 在查询网络接口时实际上返回 netnsid。它是IFLA_LINK_NETNSID
属性,仅在需要时出现在链接的信息中。如果它不存在,那么就不需要它——并且我们必须假设对等索引引用命名空间本地网络接口。
要记住的重要一课netnsid
是IFLA_LINK_NETSID
本地在向 RTNETLINK 请求链接信息时获得它的网络命名空间中定义。在不同的网络命名空间中获取的相同值netnsid
可能会标识不同的对等命名空间,因此请注意不要使用netnsid
其命名空间之外的名称空间。但是哪个唯一可识别的网络命名空间(inode
编号)映射到哪个netnsid
?
事实证明,最新版本的lsns
截至 2018 年 3 月,能够netnsid
在其网络命名空间 inode 编号旁边显示正确的信息!所以有是一种将 local 映射到命名空间 inode 的方法netnsid
,但实际上是倒退的!它更像是一个预言(带有小写的 ell)而不是查找:RTM_GETNSID 需要一个网络名称空间标识符作为 PID 或 FD(到网络名称空间),然后返回netnsid
.看https://stackoverflow.com/questions/50196902/retriving-the-netnsid-of-a-network-namespace-in-python有关如何询问 Linux 网络命名空间 oracle 的示例。
因此,您需要枚举可用的网络命名空间(通过/proc
和/或/var/run/netns
),然后对于给定的veth
网络接口附加到您找到它的网络命名空间,询问您netnsid
在开始时枚举的所有网络命名空间的 s (因为你事先永远不知道哪个是哪个),最后在附加到 的命名空间后,根据您在步骤 3 中创建的本地映射将对等点netnsid
的映射到命名空间索引节点号。veth
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))
答案3
我创建了一个简单的脚本,列出了具有关联 veth 接口的所有容器:https://github.com/samos123/docker-veth/blob/master/docker-veth.sh
让我解释一下它是如何工作的:
- 查找容器的PID
pid=$(docker inspect --format '{{.State.Pid}}' $containerID)
- 使用输入网络命名空间
nsenter
nsenter -t $pid -n ip a
eth0@ifX
您会注意到容器网络命名空间内有一个接口。 X 告诉您主机网络上的接口索引。然后可以使用该索引来确定哪个 veth 属于该容器。
运行以下命令查找 veth 接口:
ifindex=$(nsenter -t $pid -n ip link | sed -n -e 's/.*eth0@if\([0-9]*\):.*/\1/p')
veth=$(ip -o link | grep ^$ifindex | sed -n -e 's/.*\(veth[[:alnum:]]*@if[[:digit:]]*\).*/\1/p')
echo $veth
包含更多详细信息的博客文章:http://samos-it.com/posts/enter-namespace-of-other-containers-from-a-pod.html