systemd-networkd 是当前大量 Linux 发行版默认使用的网络管理组件,配置简单、依赖少、和 systemd 的生态结合紧密,但它对 DHCP Option 81(Client FQDN)的处理,长期处于一种“功能存在、文档没说清”的状态。这个问题在日常拿 IP 时基本看不出来,一旦你需要 DHCP 服务器把主机名自动注册进 DNS,就会变成非常难定位的障碍。很多人检查了防火墙、域名后缀、Windows DHCP 作用域选项,最后才发现问题出在客户端发出的 Option 81 标志位上。
Option 81 是 DHCP 协议里用来传递客户端 FQDN 的选项,DNS 服务器在收到 DHCP 服务器的更新请求时,会结合这个选项里的标志位和域名内容来决定如何注册 A/AAAA 记录和 PTR 记录。systemd-networkd 会发送这个选项,但发送的触发条件、标志位的取值逻辑、FQDN 怎么拼装,在 systemd.network(5) 手册里一直没有完整展开,和传统 dhclient 的行为也不完全一致,在线讨论往往散落在各个 issue 和论坛帖子里。所以这篇文章把协议背景、实际行为、抓包验证、配置思路和排查清单整理到一起,方便你按图索骥。
文章会先讲清楚 Option 81 在 RFC 4702 里的报文结构和标志位含义,再分析 systemd-networkd 实际是怎么处理这个选项的,然后给出一套可以在隔离网络里完整复现的抓包流程,最后是常见问题和配置建议。适合对 systemd-networkd 有基本认识、遇到 DHCP 自动注册 DNS 不生效,或者正在考虑从 dhclient 迁移到 systemd-networkd 的读者。
1. systemd-networkd 核心能力速览
这里先从整体看一下 systemd-networkd 在 DHCP 场景里的定位,以及它对 Option 81 的支持程度。很多人误以为“支持 DHCP”就等于“支持 DNS 动态更新”,这是本篇文章最需要纠正的判断。
| 能力项 | 说明 |
|---|---|
| 项目类型 | Linux 网络管理服务组件,属于 systemd 项目 |
| 主要功能 | 管理网络设备、DHCP 客户端、DHCP 服务端、VLAN、网桥、隧道、路由策略、LLDP 等 |
| DHCP 客户端 | 内置 DHCPv4 / DHCPv6 客户端,通过 .network 文件配置 |
| Option 81 支持 | 支持发送 Client FQDN,但触发条件和标志位取值在历史版本中说明不完整 |
| 配置位置 | /etc/systemd/network/.network、.netdev、*.link |
| 启动方式 | systemctl enable --now systemd-networkd |
| 日志查看 | journalctl -u systemd-networkd |
| 常用查询命令 | networkctl status、networkctl renew |
| 适用场景 | 服务器、容器、嵌入式设备、不需要桌面网络管理工具的 Linux 主机 |
| 与 DNS 动态更新关系 | 是否真正生效取决于 DHCP 服务器对 Option 81 标志位的处理策略,与 networkd 本身关系不大 |
从表格能看出,systemd-networkd 本身是一个功能很宽的网络管理组件。对于本文讨论的主题,核心结论是:它会作为 DHCP 客户端发出 Option 81 报文,但并不会像某些老牌 DHCP 客户端那样,主动去完成 DNS 注册动作。DNS 更新是否发生,取决于 DHCP 服务器以及 DNS 服务器的配置。这就引出了“未文档化行为”的关键问题:客户端发送 Option 81 时声明的标志位,通常决定了服务器愿不愿意接管更新。
2. DHCP Option 81 协议背景:Client FQDN 选项到底做了什么
DHCP 选项的编号体系在 RFC 2132 中定义,Option 81 是后来由 RFC 4702 补充的 Client FQDN 选项。它的作用比 Option 12(Host Name)更复杂。Option 12 只传一个短主机名字符串,比如web01;Option 81 则携带完整的 FQDN、以及一组控制 DNS 更新行为的标志位。
Option 81 的报文结构如下:
| 字段 | 长度 | 说明 |
|---|---|---|
| Code | 1 字节 | 固定为 81 |
| Length | 1 字节 | 后续数据总长度 |
| Flags | 1 字节 | 控制 DNS 更新的标志位 |
| RCODE1 | 1 字节 | 正向更新响应码,客户端请求中为 0 |
| RCODE2 | 1 字节 | 反向更新响应码,客户端请求中为 0 |
| Domain Name | 变长 | FQDN 的 DNS wire format,比如test.example.com编码为04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 |
其中 Flags 字段是最关键的部分。RFC 4702 对标志位的定义如下:
| Bit | 名称 | 含义(RFC 4702 简述) |
|---|---|---|
| bit 0 | S | 设为 1 时,服务器应该执行正向更新(A/AAAA 记录) |
| bit 1 | O | 设为 1 时,服务器应该执行反向更新(PTR 记录) |
| bit 2 | E | 设为 1 时,FQDN 字段使用截断编码,与 DNS wire format 兼容 |
| bit 3 | N | 设为 1 时,客户端自己执行 DNS 更新,且不希望服务器执行更新 |
| bit 4 | A | 设为 1 时,服务器应该忽略客户端提供的部分名字,改用服务器配置 |
| bit 5-7 | 保留 | 通常为 0 |
组合起来常见的情况有几种:如果 flags 为 0,表示客户端没有明确要求服务器做正向或反向更新,也没有声明自己会做更新,这个状态最容易造成两边都不管;如果 flags=1,表示客户端希望服务器做正向更新;如果 flags=3,表示客户端希望服务器同时做正向和反向更新;如果 flags 的 bit 3(N=1)被置位,则明确告知服务器“我自己会处理 DNS 更新”,服务器一般不会再接管。
在实际生产环境里,Windows DHCP 客户端和 Linux 下的 dhclient、systemd-networkd 对标志位的选择并不一样,这直接导致了同一个 DHCP 服务器在不同客户端上的 DNS 自动注册表现不同。理解这一点是排查 DNS 注册问题的前提。还有一点要注意:DHCP 服务器并不是一定要按选项去执行,很多服务器实现会忽略标志位或采用自己的策略,所以文档标准与实际表现会有差异,这也再次说明抓包确认的重要性。
3. systemd-networkd 对 Option 81 的实际处理逻辑
从 systemd 的源码结构和社区反馈来看,systemd-networkd 对 Option 81 的发送是存在的,但行为集中在 DHCP 客户端模块里。在实际使用中,是否发送 Option 81,与主机名是否能被获取以及[DHCPv4] 段的 SendHostname 配置有直接关系。如果主机名可以拿到,且 SendHostname 没有被关闭,networkd 会在 DHCP Discover 和 DHCP Request 报文中一并携带 Option 12 和 Option 81;如果主机名为空,或者关闭了 SendHostname,则 Option 81 大概率不会出现。
这中间有一个容易踩到的坑:systemd.network(5) 手册里对SendHostname=和Hostname=的说明,只是从“是否发送主机名”的角度描述,并没有明确告诉用户,这个开关还会联动触发 Option 81 的携带。对很多从 dhclient 迁移过来的管理员来说,这就是典型的未文档化行为:你在 .network 文件里写着SendHostname=true,结果发现 DHCP 报文里多了一个 Option 81,而 DHCP 服务器和 DNS 服务器又因为这个选项的存在,改变了原本的更新策略。
再看标志位。从当前可见的 systemd 客户端实现来看,networkd 在构造 Option 81 时,默认 flags 接近于 0,也就是客户端没有明确要求服务器执行正向或反向更新。这样一来,如果 DHCP 服务器是一个对标志位敏感的实现,它就认为客户端可能要自己做更新,于是不会主动写 DNS 记录。而 systemd-networkd 本身并不具备 DDNS 更新能力,最终结果就是主机拿到了 DHCP 地址,但 DNS 里始终没有记录。
另外还有 FQDN 拼装细节。系统 hostname 如果只填了短主机名,比如web01,networkd 在发送 Option 81 时是否会自动附加 DNS 后缀,在不同版本和不同配置下并不一致。这会导致一个问题:DNS 服务器收到 Option 81 后,注册的名字可能是web01而不是web01.example.com,造成主机名和预期 FQDN 对不上。这个行为同样缺乏完整文档,只有通过抓包才能确认当前版本到底发出去了什么。
4. 环境准备与验证工具
要验证 systemd-networkd 里的 Option 81 行为,推荐准备一个隔离的测试网络,避免在生产环境里制造多余的 DNS 记录。如果只有一台 Linux 机器,用虚拟网桥加网络命名空间也能模拟,但最简单的方案是准备一台测试机、一个小型交换机或直连网线,以及一台可以跑 dnsmasq 的机器作为 DHCP/DNS 服务器。
客户端需要安装抓包工具。Debian/Ubuntu 下可以执行:
apt update apt install -y tcpdump dhcpdumpRHEL/Fedora/CentOS 下执行:
dnf install -y tcpdump dhcpdump如果发行版软件源里没有 dhcpdump,也可以只用 tcpdump,它已经能够解码 Option 81。另外建议安装 tshark,用于对抓包文件做字段级过滤,分析 Option 81 的 flags 更方便:
dnf install -y wireshark-cli测试环境里建议用 dnsmasq 起一个最小的 DHCP/DNS 服务,这样服务端行为完全可控。下面是一个最小 dnsmasq 配置示例:
# /etc/dnsmasq.d/test-dhcp.conf interface=enp0s8 bind-interfaces dhcp-range=192.168.56.100,192.168.56.200,12h dhcp-option=option:router,192.168.56.1 port=53 no-resolv log-dhcp log-queries启动服务:
systemctl enable --now dnsmasq客户端机器需要确认 systemd-networkd 已启用,并确认当前网卡由 networkd 接管:
systemctl status systemd-networkd networkctl status如果网卡由 NetworkManager 等其他组件管理,需要先停用相应服务,或者在一个没有图形界面的测试机上做验证,避免两套网络管理组件互相干扰。
5. 抓包复现 Option 81 的未文档化行为
完成环境准备后,在客户端机器上写一个最小 .network 配置,显式指定要发送的 hostname:
# /etc/systemd/network/20-dhcp.network [Match] Name=enp0s3 [Network] DHCP=ipv4 [DHCPv4] UseDNS=true UseNTP=false SendHostname=true Hostname=web01配置写好后,重启网络服务让配置生效:
systemctl restart systemd-networkd随后用 tcpdump 在客户端网卡上抓取 DHCP 报文。DHCPv4 使用 UDP 67/68 端口,抓包命令如下:
tcpdump -i enp0s3 -n -vv -s 0 -w option81.pcap 'port 67 or port 68'开启抓包后,让客户端重新走一次 DHCP 流程。重启 networkd 会触发重新获取地址,也可以用 networkctl 主动续租:
networkctl renew enp0s3等抓包结束后,用 tcpdump 直接检查 pcap 文件里的 Option 81 字段。不同版本的 tcpdump 输出格式有差异,通常能搜到 fqdn 或 client-fqdn 关键字:
tcpdump -r option81.pcap -nn -vv 'port 67 or port 68'如果安装了 tshark,可以更精确地过滤字段:
tshark -r option81.pcap -Y "dhcp.option.fqdn" -V在这个输出里,重点看两个内容:第一,DHCP Discover 和 DHCP Request 报文中是否出现了 Option 81;第二,Flag 字段的值是多少。如果Hostname=web01配置生效,报文里应该同时包含 Option 12(Host Name)和 Option 81(Client FQDN)。Flags 字段如果在当前版本中表现为 0x00,就说明客户端没有要求服务器执行正向或反向更新,这很可能就是 DNS 记录不生成的直接原因。
需要再强调一次:由于 systemd 版本之间存在差异,实际观察结果可能不同。如果当前版本已经调整了 flags 的取值逻辑,你看到的可能是 0x01 或其他值。这正好说明,脱离版本讨论“systemd-networkd 的 Option 81 行为”是不严谨的,一定要以实际抓包为准。
6. 配置与调优:如何控制 Option 81 的发送
如果经过抓包,确认 systemd-networkd 发送的 Option 81 不是你想要的行为,可以按下面几个方向调整。
6.1 完全不发送 Option 81
如果网络环境不需要 DNS 动态更新,或者你希望把主机名注册策略完全交给 DHCP 服务器,最简单的办法是关闭 SendHostname。在 [DHCPv4] 段里设置:
[DHCPv4] SendHostname=false这样 systemd-networkd 在构造 DHCP 报文时不会携带主机名相关选项,Option 81 通常也不会出现。这个配置对排查问题很有用,可以先关闭它,对比一下 DHCP 服务器在收到和收不到 Option 81 两种情况下的 DNS 更新行为。
6.2 修改发送的 FQDN
如果你确实需要在报文中携带 Option 81,但希望使用不同的主机名,可以通过 Hostname 指定:
[DHCPv4] SendHostname=true Hostname=web01.lab.example.com注意,主机名中是否包含点号会影响 FQDN 的拼装方式。如果只写短主机名,systemd-networkd 在部分版本里不会自动带上 DNS 后缀,DNS 服务器最终注册的名字可能不是你预期的完整域名。因此,建议在测试阶段把完整的 FQDN 写进去,再通过抓包确认实际发送的 Domain Name 字段。
6.3 在 DHCP 服务器端调整更新策略
客户端发送的 Option 81 只是一个请求,最终是否做 DNS 更新由服务器端决定。如果服务器端对标志位比较敏感,可以考虑在服务端静态绑定主机名,或者强制开启 DDNS。以 dnsmasq 为例,可以通过 DHCP host 文件固定主机名和 IP 的对应关系,再让 dnsmasq 自动写 DNS 记录:
# /etc/dnsmasq.d/test-dhcp.conf 追加 dhcp-host=52:54:00:12:34:56,web01,192.168.56.101 dhcp-fqdndnsmasq 会优先参考 Option 81 中的标志位来决定是否注册,所以在条件允许时,把测试用的 DHCP 环境尽量贴近生产环境,才能得出可靠的结论。如果是 Windows DHCP 服务器,需要在 IPv4 作用域的“DNS”选项卡里勾选“根据此连接的 DNS 后缀在 DNS 中注册此连接的地址”,并且确认“动态更新 DNS A 和 PTR 记录”没有被禁用。不同服务器实现差异较大,最终都要用抓包和 DNS 查询来验证。
6.4 如果必须由客户端自己更新 DNS
systemd-networkd 本身不会在执行完 DHCP 流程后调用 nsupdate 去更新 DNS。如果网络策略要求客户端主动注册 DNS,比如Windows 域环境里的某些特殊情况,就需要额外写一个 systemd 服务,在拿到 IP 后调用 nsupdate 更新记录。这个方案比依赖 DHCP 服务器做 DDNS 更复杂,但胜在不受 Option 81 标志位影响。可以简单跑一个脚本,监视 .network 文件启动后的地址变化,然后调用 nsupdate:
#!/bin/bash # /usr/local/bin/ddns-update.sh HOST=web01 DOMAIN=lab.example.com IP=$1 nsupdate <<EOF server 192.168.56.10 update delete $HOST.$DOMAIN. A update add $HOST.$DOMAIN. 300 A $IP send EOF这种方式要求 DNS 服务器允许来自客户端的 nsupdate 请求,生产环境需要单独认证配置,不建议在不了解安全边界的情况下直接启用。
7. 从 DHCP 服务器侧验证 DNS 动态更新是否生效
只抓客户端的包还不够,要在服务端确认 DHCP 服务器到底有没有根据 Option 81 执行 DNS 更新。这里以 dnsmasq 为例,启动时打开日志:
systemctl stop dnsmasq dnsmasq --conf-file=/etc/dnsmasq.conf --log-dhcp --log-queries当客户端完成 DHCP 流程后,dnsmasq 日志会显示租约分配信息。如果 DNS 更新成功,日志里会有类似“DHCPACK(eth0) 192.168.56.101 52:54:00:12:34:56 web01”或写 A/PTR 记录的提示;如果 Option 81 标志位让 dnsmasq 认为客户端自己会更新,则可能看不到任何 DNS 写入动作。
在客户端机器上用 dig 验证 DNS 记录:
dig @192.168.56.10 web01.lab.example.com dig @192.168.56.10 -x 192.168.56.101如果 A 记录和 PTR 记录都没有返回 NXDOMAIN,说明 DNS 注册成功。如果抓包时看到客户端已经发了 Option 81,但 dnsmasq 不写入 DNS,主要怀疑点就是 flags 值不符合服务器预期。
另外,Windows DHCP 服务器的排查路径略有不同。可以在 DHCP 管理控制台里看租约记录的“DNS 更新”列,也可以启动 DHCP 服务器上的审核日志,跟踪是否有动态注册请求被拒绝。由于 Windows DHCP 服务器对 Option 81 的处理相当严格,很多时候会直接把注册行为交给客户端,如果你的 Linux 主机用的是 systemd-networkd,就需要特别关注这一点。
8. 常见问题与排查方法
这里整理一份直接可用的排查清单,按“客户端、服务端、DNS”三个层面排查。下面表格里的问题在 systemd-networkd 部署中比较有代表性。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| DHCP 能获取 IP,但 DNS 查不到主机名 | 客户端发送的 Option 81 标志位让服务器不做更新,或服务器端 DDNS 未启用 | 抓包确认 Option 81 的 Flags,检查 DHCP 服务器日志 | 在服务端强制更新,或用其他 DHCP 客户端对比 |
| .network 里设置了 Hostname,但报文里没有 Option 81 |