问题描述
业务程序内部出现了某种异常,每分钟会创建删除一次特定名称的网络接口,运行一段时间后 /run 空间占满,观测到 /run/systemd/netif/links 目录下有巨量文件产生,且每一个都占用内存。
环境信息
内核版本:5.10
systemd 版本:219
问题触发过程示例
-
执行 ip 命令创建 tap 口然后删除
ip tuntap add dev taptest mode tap ip tuntap del dev taptest mode tap
-
查看 /run/systemd/netif/links 目录下的内容
ls -lh /run/systemd/netif/links
当网络接口大量创建时 /run/systemd/netif/links 中的文件内容就会持续增加。
初步分析
一个网络设备接口会分配一个 index,同时会在 /run/systemd/netif/links 下创建一个文件,接口 id 可以使用 ip link show 命令查看。
ip link show 命令执行示例:
[root@localhost] # ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether 6a:1c:60:57:1b:9e brd ff:ff:ff:ff:ff:ff
link/ether 98:30:00:32:18:7b brd ff:ff:ff:ff:ff:ff
5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 02:42:70:b8:d6:e2 brd ff:ff:ff:ff:ff:ff
7: vethfa4d604@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether 2e:f2:5c:87:73:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
9: veth550eac7@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether 66:6c:c4:ea:fc:d2 brd ff:ff:ff:ff:ff:ff link-netnsid 1
第一个字段为网络接口的索引号。它是一个唯一标识符,用于在系统中标识不同的网络接口。
内核中网络接口索引的分配方式
测试环境观测到的情况:
按照大小从 1 开始分配,每次加一,释放后不会回收
chatgpt 中搜素到的内容:
在内核中,分配网络接口索引的行为是由内核网络子系统负责的。当内核启动时,网络子系统会扫描系统中的网络接口,并为每个接口分配一个唯一的索引号。
网络接口索引号是一个非负整数,通常从1开始递增。索引号的分配是基于接口的注册顺序和接口类型等因素进行的。例如,以太网接口(如 eth0
)可能会先于无线接口(如 wlan0
)获得较小的索引号。
内核使用这些索引号来标识和管理系统中的网络接口。它们在内核的网络子系统中被用作唯一标识符,用于识别和操作特定的网络接口。应用程序和工具可以使用这些索引号来引用和配置网络接口。
需要注意的是,网络接口索引号在系统重启时可能会发生变化。因此,在编写依赖于索引号的应用程序或脚本时,应该小心处理索引号的变化情况,以避免出现错误。
问题分析
-
是否有配置让内核在接口释放后立刻回收索引?
经搜索,并不支持此种配置。
-
/run/systemd/netif/links 目录的功能
/run/systemd/netif/links
目录是 Systemd 管理网络接口的运行时状态的目录之一。在 Systemd 中,网络接口被表示为链接(links),这些链接文件以接口名称的形式存在于该目录中。每个接口的链接文件包含有关该接口的信息,例如接口的索引号、MAC 地址、设备类型等。这些信息可以通过读取链接文件来获取。
此目录的功能包括:
- 提供接口的运行时状态信息:通过查看接口的链接文件,可以获取接口的索引号、MAC 地址、设备类型等信息。这些信息对于网络配置和故障排除非常有用。
- 动态更新接口状态:当系统中的网络接口发生变化(如添加、删除、重命名接口)时,Systemd 会相应地更新
/run/systemd/netif/links
目录中的链接文件。这样,其他程序可以监视该目录,以获取接口状态的实时更新。 - 与其他 Systemd 单元的集成:Systemd 可以根据接口的状态变化触发其他 Systemd 单元的启动、停止或重启。例如,可以配置一个 Systemd 服务单元,在某个接口启动或停止时自动执行一些操作。
需要注意的是,
/run/systemd/netif/links
目录中的链接文件是运行时状态的快照,只反映当前系统中的接口状态。它们在系统重新启动时会被清除,并根据实际情况重新生成。 -
是否有配置控制在接口被移除时 /run/systemd/netif/links 下的对应文件被移除?
在 Systemd 中,没有直接的配置选项可以控制
/run/systemd/netif/links
目录中的链接在接口被移除时进行释放。这个目录中的链接文件是由 Systemd 动态管理的,并且会在接口状态发生变化时进行更新。当一个网络接口被移除时,Systemd 会相应地更新
/run/systemd/netif/links
目录中的链接文件,将对应的链接文件删除。这样可以确保该接口的状态信息不再存在于目录中。然而,需要注意的是,删除链接文件并不会立即释放接口的索引号。接口的索引号通常由内核管理,并且会在系统重启或其他接口需要分配新的索引号时才会重新使用。
如果您需要在接口被移除时立即释放接口的索引号,一种可行的方法是通过重新启动网络服务或重启系统来实现。这样可以确保删除的接口的索引号被立即释放并可以重新分配。
对比测试
ubuntu 环境
内核版本:5.15
systemd 版本:systemd245
测试情况:
- 接口索引的使用也是一样的行为,删除接口不会立刻释放接口索引,会持续累增
- /run/systemd/netif/links 目录下并不会创建任何文件,全局搜索也搜索不到
内核代码分析
register_netdevice 的时候,内核调用 dev_new_index 分配标识,其代码如下:
static int dev_new_index(struct net *net)
{
int ifindex = net->ifindex;
for (;;) {
if (++ifindex <= 0)
ifindex = 1;
if (!__dev_get_by_index(net, ifindex))
return net->ifindex = ifindex;
}
}
上述代码在当前 namespace 的 net 结构中获取 ifindex 的现值,然后对其进行递增操作,当溢出时,从 1 开始分配,分配完成一个后再检索有没有接口占用此 index,有人占用则继续循环,无人占用则修改 net 结构中的 ifindex 并返回 ifindex。此返回值会被赋值到 netdevice 结构中的 ifindex 字段上,下一次再分配就从此 ifindex 开始递增。
unregister_netdevice 的时候,netdevice 会从内核的各种表项中移除,不涉及 ifindex 相关操作。
真正的问题与解决方案
systemd-networkd 进程未在网络接口被删除的时候移除 /run/systemd/netif/links
目录中的对应文件触发了此问题。
官方已经修复了此问题,相关 patch 如下:
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index cc9dc39..b3e2101 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -315,6 +315,8 @@ static void link_free(Link *link) {
if (link->manager)
hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex));
+ (void) unlink(link->state_file);
+
free(link->ifname);
free(link->state_file);
相关地址如下:
https://github.com/coreos/systemd/pull/30/commits/641481fbad7e66ac8d9bda8d3454e837f452495a
https://github.com/coreos/bugs/issues/1081
可以通过更新 systemd 版本到 228 之上、合入此 patch 重新发布 systemd 组件来解决此问题。
其它解决方案
命令行方式规避方法:
rm -r /run/systemd/netif/links && systemctl restart systemd-networkd