NetworkManager管理DNS配置
遇到一个场景,装完操作系统通过 GUI 进行网络配置、安装业务平台并直接修改 /etc/resolv.conf 文件,改变了DNS地址配置。过了几天后发现配置被修改,影响到了业务平台,但排查以后确认无人操作过 /etc/resolv.conf 文件。最后追查到 NetworkManager 被重启过,每次 NetworkManager 重启都会出现DNS恢复到系统安装时初始网络配置写的 DNS。NetworkManager 的处理流程是什么,跟着日志和源代码一起看看吧☺️
NetworkManager日志
这里复现问题的环境是 Rocky 9.6,网络的初始配置如下
1 | [root@localhost misaka]# nmcli device show ens33 |
RHEL发行版的日志位置通常是 /var/log/message,起初是想通过现有日志看看是否能找到有用信息。这里打开两个窗口,分别执行
1 | tail -f /var/log/messages | grep NetworkManager |
可以看到有如下输出:
1 | Jul 12 23:33:45 localhost systemd[1]: NetworkManager-wait-online.service: Deactivated successfully. |
将此段日志,让豆包分析,豆包反馈日志记录了 NetworkManager 从”旧进程关闭”到”新进程启动并完成初始化””的过程,关键节点包括:
- 旧进程正常关闭:收到 SIGTERM 信号后,旧 NetworkManager 进程(PID 171123)正常退出,状态为 success。
- 新进程启动:新进程(PID 178235)以版本 1.52.0-4.el9_6 启动,读取配置文件并初始化核心模块。
- 设备识别与激活:成功识别回环设备(lo)和以太网设备(ens33),并完成激活,最终网络状态达到 CONNECTED_GLOBAL(全局连接)。
- 服务初始化完成:启动过程无错误,所有核心功能(DNS 管理、DHCP 客户端、设备插件等)正常加载。
其中能说明有DNS相关的操作如下:
1 | Jul 12 23:33:45 localhost NetworkManager[178235]: <info> [1752334425.1556] dns-mgr: init: dns=default,systemd-resolved rc-manager=symlink (auto) |
当 systemd-resolved 服务不存在时,dns=default 会使 NetworkManager 直接管理 DNS,生成 /etc/resolv.conf,并通过 rc-manager=symlink 创建符号链接。
NetworkManager源码
到这里其实只能看出 NetworkManager 确实操作了/etc/resolv.conf的变更,但具体如何执行还是无法清晰获取,接下来转换思路。
由 NetworkManager 管理的 DNS 配置会在 /etc/resolv.conf 中包含以下注释:
1 | Generated by NetworkManager |
直接使用 Generated by NetworkManager 字符串检索代码,作为入口,来看下代码中的处理流程。
在 NetworkManager 源代码 src/core/dns/nm-dns-manager.c 中存在函数 create_resolv_conf,其中包含了相应的字符串,且生成 nameserver 配置。
1 | static char * |
在IDE中继续搜索该函数存在多次调用,分别为
1 | char * |
因为存在多个函数调用,还是无法确定具体调用流程。但在上述源码中可以看到包含 _LOGT 的日志函数调用,是否可以通过日志配置来观测 NetworkManager 的详细行为呢?
跟随IDE可以在 nm-logging-fwd.h 中可以看到一些关于日志级别的定义:
1 | static inline int |
可以看到最详细的日志是 TRACE 级别。那么要观测 NetworkManager 详细输出,就可以在 /etc/NetworkManager/conf.d/99-logging.conf 中添加如下内容,该文件默认不存在直接创建即可,重启 NetworkManager 时会被加载。
1 | [logging] |
打开两个shell会话,并分别执行以下命令,就可以通过 journalctl 看到详细的日志输出,将该日志截取出来,分析执行步骤即可:
1 | journalctl -fu NetworkManager |
因为 TRACE 日志量非常大,这里就不全放出了。通过检索/etc/resolv.conf关键字,在 TRACE 日志中找到了如下内容:
1 | Jul 13 01:11:01 localhost.localdomain NetworkManager[275984]: <debug> [1752340261.9116] dns-mgr: (device_l3cd_changed): queueing DNS updates (1) |
可以看出,正是由 update_resolv_conf 函数产生的写操作。那么通过日志,并结合源代码,利用IDE可以追溯出具体的函数调用链为:
nm_policy_class_init() -> constructed() -> device_added() -> devices_list_register() -> device_l3cd_changed() -> nm_dns_manager_set_ip_config() -> update_dns() -> update_resolv_conf_no_stub()、update_resolv_conf() -> create_resolv_conf() -> write_resolv_conf_contents()
其中 nm_policy_class_init() 是 GObject 框架下的类初始化函数,其调用机制遵循 GObject 的类注册流程。具体来说,这个函数会在 NMPolicy 类型注册时被自动调用,而非通过显式函数调用。跟随IDE可以发现在nm-policy.c文件中存在如下一行
1 | G_DEFINE_TYPE(NMPolicy, nm_policy, G_TYPE_OBJECT) |
这个宏会展开为类型注册代码,最终触发 nm_policy_class_init() 的调用。
NetworkManager 由 meson 进行构建,在 meason.build 中包含了 subdir(‘src’),而 src/meson.build 中又包含了 subdir(‘core’),在执行 meson build 后在 build 目录中生成的 build.ninja 文件中包含了构建过程。
1 | build src/core/libNetworkManager.a.p/nm-policy.c.o: c_COMPILER ../src/core/nm-policy.c || src/libnm-core-public/nm-core-enum-types.h |
NetworkManager 管理 DNS 配置和 NetworkManager 的配置有关。有default、systemd-resolved、dnsmasq 三种方式。我个人观察到在 Centos7、Rocky 9 上通常是采用 default 方式进行,而 Ubuntu 20.04 中采用的是 systemd-resolvd。
1 | Ubuntu 20.04的日志输出 |
systemd-resolved 是 systemd 套件的一部分,用于处理 DNS 解析和其他网络名称解析任务。它提供了 DNS 缓存、多重 DNS 解析、DNS-over-TLS 等功能。在使用systemd-resolved的 Ubuntu 系统中,/etc/resolv.conf通常是指向/run/systemd/resolve/stub-resolv.conf的符号链接,所有 DNS 查询会被转发到systemd-resolved的本地代理(127.0.0.53),然后由systemd-resolved根据/run/systemd/resolve/resolv.conf的配置进行实际的 DNS 解析。
到这里就基础梳理出每次 NetworkManager 重启时,对 DNS 配置进行操作的处理流程。
那么解决文章开头 NetworkManager 每次重启都还原成初始网络配置中的 DNS 问题的答案也非常简单:
在/etc/NetworkManager/system-connections/ens33.nmconnection中注释dns配置,然后在/etc/resolv.conf中添加目标nameserver
在/etc/NetworkManager/system-connections/ens33.nmconnection中修改成目标dns配置,如果有多个,要注意写分号呦(例如: dns=8.8.8.8;114.114.114.114;)
NetworkManager编译
在排查问题前有想过可能需要 debug 或者自行加一些日志,但实际项目 TRACE日志记录的非常详细,这一点是值得去学习的。同时为了IDE读取比较顺利,这里就学习了下 NetworkManager 的编译方法。我尝试了 Rocky 9 和 Ubuntu 20.04 上的编译,进行下记录。
Meson构建
NetworkManager 使用 Meson 构建,Meson 是一个开源构建系统,旨在实现极快的速度,更重要的是,尽可能地方便用户使用。目前有很多项目在使用,比如 GNOME、KDE 等。
Meson 官网: https://mesonbuild.com/index.html
Github 地址: https://github.com/mesonbuild/meson
ninja编译
Meson 和 Ninja 通常会配合使用,Meson 负责构建项目依赖关系,Ninja 负责编译代码。Ninja 是一个轻量的构建系统,主要关注构建的速度。Ninja 使用 .ninja 文件定义构建规则,语法简洁,通常由 Meson 等工具自动生成。
Ninja 官网: https://ninja-build.org/
Github 地址:https://github.com/ninja-build/ninja
Rocky 9.6
1、克隆项目
1 | yum install vim tree curl wget tree |
2、打开rocky devel repo,将 enable 改为1,要不有些 devel 包找不到
1 | vim /etc/yum.repos.d/rocky-devel.repo |
3、安装 Meson 和 Ninja
1 | yum install python3-pip |
4、安装 NetworkManager 所需要的依赖。怎么确定这些依赖呢?项目依赖可以在 meson.build 文件中搜索dependency配置项。
1 | yum install -y cmake uuid libuuid-devel libudev-devel dbus-devel glib2-devel libndp-devel gobject-introspection-devel libaudit-devel audit-libs-devel polkit-devel gnutls-devel nss-devel nspr-devel ppp-devel ModemManager-glib-devel mobile-broadband-provider-info-devel jansson-devel libpsl-devel libcurl-devel readline-devel libedit-devel newt-devel |
5、执行 Meson 构建和 Ninja 编译
1 | cd NetworkManager && meson build |
ninja 会显示编译进度,执行的很快:
1 | [649/988] Compiling C object src/core/libNetworkManager.a.p/devices_nm-device-ip-tunnel.c.o |
Ubuntu 20.04
1、克隆项目
1 | apt install vim tree curl wget tree git |
2、安装 Meson 和 Ninja
1 | python3 -m pip install --upgrade pip |
3、安装 NetworkManager 所需要的依赖,因为 20.04 默认的 Cmake 版本对于 Meson 最新版本来说低一些,所以需要先处理下 CMake PPA 源。
1 | wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null |
4、执行 Meson 构建和 Ninja 编译
1 | cd NetworkManager && meson build |
构建、编译完成
1、在上面执行 meson build 通过后会生成如下内容,证明可以进入到 ninja 阶段。如果有报错提示,就按信息解决即可。
1 | Message: |
2、ninja 执行完进度条会消失掉,直接进去看文件即可。编译 NetworkManager 完成后的目录及大小
1 | du -sh ./* | sort -h -r |
3、NetworkManager 编译后的产物会分散在 src 下的各个子目录中,如果有安装需要,可以执行 ninja install 命令。
到这里学习就结束啦☺️
参考链接
1、https://apt.kitware.com/
2、https://people.freedesktop.org/~lkundrak/nm-docs/NetworkManager.conf.html
3、https://www.alibabacloud.com/help/zh/alinux/user-guide/networkmanager-configuration-files-and-common-configurations