Networking

如何找到 veth 對等體 ifindex 的網路命名空間?

  • December 30, 2019

任務

我需要明確且沒有“整體”猜測的情況下在另一個網路命名空間中找到veth 端的對等網路介面。

理論 。/。現實

儘管有很多文件和關於 SO 的答案都假設網路介面的 ifindex 索引在網路名稱空間中每個主機都是全域唯一的,但這在許多情況下並不成立ifindex/iflink 是模棱兩可的。甚至環回也已經表明相反的情況,在任何網路命名空間中的 ifindex 都是 1。此外,根據容器環境,ifindex數字會在不同的命名空間中重用。這使得跟踪 veth 佈線成為一場噩夢,尤其是有很多容器和一個帶有 veth 對等點的主機橋都以 @if3 左右結尾……

範例:link-netnsid0

啟動一個 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 網路
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”似乎也只以(in)著名的“@if#”符號的形式顯示對等 ifindex。還是我錯過了一些額外的網路命名空間元素?

底線/問題

是否有任何系統呼叫允許檢索 veth 對的對等端失去的網路命名空間資訊?

非常感謝@AB,他為我填補了一些缺失的部分,特別是關於netnsids 的語義。他的 PoC 很有啟發性。然而,他的 PoC 中關鍵缺失的部分是如何將本地netnsid與其全域唯一的網路命名空間 inode 編號相關聯,因為只有這樣我們才能明確地連接正確的veth對應對。

總結並給出一個小的 Python 範例,如何以程式方式收集資訊而無需依賴ip netns及其需要掛載的東西:RTNETLINK 在查詢網路介面時實際上返回 netnsid。它是IFLA_LINK_NETNSID屬性,僅在需要時出現在連結的資訊中。如果它不存在,那麼就不需要它——我們必須假設對等索引指的是命名空間本地網路介面。

要帶回家的重要教訓是,netnsid/IFLA_LINK_NETSID僅在您向 RTNETLINK 詢問連結資訊時獲得的網路命名空間內**本地定義。**在不同的網路命名空間中獲得相同值的 Anetnsid可能會標識不同的對等命名空間,因此請注意不要使用netnsid其命名空間之外的名稱。但是哪個唯一可辨識的網路命名空間(inode編號)映射到哪個netnsid

事實證明,lsns截至 2018 年 3 月的最新版本能夠很好地netnsid在其網路命名空間 inode 編號旁邊顯示正確!所以有一種方法可以將 local 映射netnsid到命名空間 inode,但實際上是倒退的!它更像是一個 oracle(帶有小寫 ell)而不是查找:RTM_GETNSID 需要一個網路名稱空間標識符作為 PID 或 FD(到網路名稱空間),然後返回netnsid. 有關如何詢問 Linux 網路命名空間 oracle 的範例,請參閱https://stackoverflow.com/questions/50196902/retrieving-the-netnsid-of-a-network-namespace-in-python 。

因此,您需要列舉可用的網路命名空間(通過/proc和/或/var/run/netns),然後對於給定的veth網路介面附加到您找到它的網路命名空間,詢問netnsid您在開始時列舉的所有網路命名空間的 s(因為您永遠不會事先知道哪個是哪個),最後根據您在第 3 步中創建的本地映射,在附加到 的命名空間後netnsid,將對等節點映射到命名空間 inode 編號。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))

這是我遵循的方法來了解如何理解這個問題。可用的工具似乎可以用於命名空間部分(帶有一些卷積),並且(已更新)使用 /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 容器?獲取此資訊的最簡單方法是通過執行 manpage 中提示的操作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

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

現在通過以下方式檢索資訊:

# 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_GETNSIDwith reply type 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

正如 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

在這個命名空間(即命名空間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” 等(在這個例子中是真的,都映射到由 呼叫的網路命名空間test),ip netns那麼它們就可以真正進行比較。

關於系統呼叫,仍然查看 strace 結果,資訊如上從RTM_GETLINK. 現在應該有所有可用的資訊:

本地:帶有SIOCGIFINDEX/ 對等的介面索引:nsid 和帶有 . 的介面索引。if_nametoindex
RTM_GETLINK

所有這些都應該與libnl一起使用。

引用自:https://unix.stackexchange.com/questions/441876