deep
2025 年10 月 29 日 11:02
1
问题描述:
在配置Linux系统网络时,通过ip link set dev eth0 arp off的方式,关闭了eth0网卡的arp功能,此时通过ip link可以看到eth0网卡是NOARP的状态。
编写一个程序,通过UDP发送目的IP地址为255.255.255.255的有限广播包,此时通过tcpdump抓包可以看到,数据包的src mac和dst mac都是发送源设备的mac地址,按照我的理解这应该是不正确的,理论上这种广播包的dst mac应该是ff:ff:ff:ff:ff:ff才对。
分析内核协议栈源码,发现在arp.c中有这么一段:
static int arp_constructor(struct neighbour *neigh)
{
// ...
if (neigh->type == RTN_MULTICAST) {
neigh->nud_state = NUD_NOARP;
arp_mc_map(addr, neigh->ha, dev, 1);
} else if (dev->flags & (IFF_NOARP | IFF_LOOPBACK)) {
neigh->nud_state = NUD_NOARP;
memcpy(neigh->ha, dev->dev_addr, dev->addr_len);
} else if (neigh->type == RTN_BROADCAST ||
(dev->flags & IFF_POINTOPOINT)) {
neigh->nud_state = NUD_NOARP;
memcpy(neigh->ha, dev->broadcast, dev->addr_len);
}
// ...
}
想确认这的判断条件里面,出于什么原因考虑导致跑断NOARP的优先级高于BROADCAST的优先级?
您好, 人肉 chatgpt proxy 为您服务
结论先给:这是内核刻意的行为,不是 bug。
在 IFF_NOARP(ip link set dev eth0 arp off)条件下,邻居子系统不再从 L3 目的地址派生任何 L2 目的地址 ——除了“可算法映射”的多播之外——因此即便 L3 是 255.255.255.255(广播),也不会 去使用设备的广播 MAC,而是把邻居项的硬件地址 neigh->ha 直接设成本机 MAC(dev->dev_addr) 。这正是你在 tcpdump 里看到“dst MAC = src MAC = 本机 MAC”的原因。
为什么 NOARP 的优先级高于 BROADCAST?
看你贴的 arp_constructor() 片段,判断顺序是:
RTN_MULTICAST → arp_mc_map()(多播地址可按算法映射到以太网多播 MAC)
IFF_NOARP 或 IFF_LOOPBACK → neigh->ha = dev->dev_addr(不做任何分辨/派生)
RTN_BROADCAST 或 IFF_POINTOPOINT → neigh->ha = dev->broadcast(用链路层广播地址)
这个顺序体现了内核的设计约束:
只要设备处于 NOARP,就绝不基于 L3 语义去推导 L2 目的地址 (即使 L3 是 broadcast),因为 NOARP 最初就是为不支持 ARP/无广播语义的介质 (PPP、某些隧道、虚拟/专用链路等)准备的。这类介质上使用 dev->broadcast 要么没有意义,要么可能是垃圾值/误配置,反而会造成错误的泛洪。
因此在 NOARP 下,唯一允许的“自动派生”只有多播(有明确的算法映射),其他一律走“本机 MAC”这一条安全兜底路径;而 BROADCAST 分支只有在设备并非 NOARP 时才会生效。这个含义也能从 ip-link 手册与邻居子系统文档侧面印证:NOARP 是“关闭 ARP/邻居解析”的设备级标志,影响邻居表填充与输出路径的选择(而不是仅仅“不收不发 ARP”那么简单)。(man7.org )
换句话说:NOARP 表示“不要做任何基于 L3 的 L2 目的推导” ;
多播是特例(可算法映射);
广播只在设备允许 ARP/邻居解析时使用。
历史上,很多驱动/链路(例如教学用的 snull、一些隧道类 netdev)都会设置 IFF_NOARP,因为链路对端并不存在传统意义上的“邻居”可被 ARP 解析,驱动/内核也就不会去构造广播 MAC。(LWN.net )
这就是为什么你在以太网把 ARP 关掉会“看起来不对”
在以太网 上你确实“客观存在广播 MAC = ff:ff:ff:ff:ff:ff”,但当你手动把设备打上 NOARP 标志时,你等于告诉内核 :“这个链路没有 可用的 ARP/广播语义,不要做任何派生”。内核就按上述优先级在邻居项里把 ha 设成 dev->dev_addr,最终导致以太网帧的 dst MAC = 本机 MAC 。这一点从邻居子系统的构造流程(arp_constructor/neigh_create 填充 neigh->ha)也能对应上。(Linux Foundation Wiki )
想要在“ARP 关闭”的前提下依然发广播帧,有哪些做法?
不要用 arp off 。保持 arp on,如果只是想禁用 ARP 协议本身的交互,可用 ebtables/TC 过滤 ARP 帧,或用 sysctl 调整 ARP 回应策略(arp_ignore/arp_announce 等),而不是把设备置为 NOARP。这样 L3 广播依旧会走 L2 广播。(Linux IP )
原始套接字自行构帧 :用 PF_PACKET/AF_PACKET 手动把以太网目的地址设为 ff:ff:ff:ff:ff:ff,绕过邻居解析与 neigh->ha。
临时开 ARP 发完再关 :ip link set dev eth0 arp on → 发送需要的广播 → arp off(不推荐长期折腾,易踩坑)。
静态邻居表 适用于单播,不适用于广播:ip neigh add … lladdr … 只能解决“已知对端 MAC 的单播”,对广播无效。
一句话总结
NOARP 的语义是“完全不做 L2 目的派生(除可算法映射的多播)”,因此它必须优先于 BROADCAST 分支 ;
在以太网上手动 arp off 会让内核故意不使用 广播 MAC,这正是你观察到的现象;
需要广播行为就别把设备置为 NOARP,或改用原始套接字自行构帧。
奇怪的冷知识又增加了
补充:
三、结论:是否符合 RFC?
角度
是否符合
说明
以太网标准严格语义(RFC 894)
不符合
对于以太网接口,理论上应使用广播 MAC 封装广播 IP。
协议互操作与实现容忍度(实际 RFC 行为)
合理
RFC 未规定“ARP 关闭时必须仍发送广播帧”,Linux 的行为可视为实现选择,不违反协议必备要求。
工程意义
正确
避免了非以太网 NOARP 接口误发广播帧,保持内核一致性。
简要结论:FreeBSD 的实现与 Linux 不同 。在 FreeBSD 里,即使接口被设为 NOARP(ifconfig <if> -arp),对 IPv4 广播报文(如 255.255.255.255 / 子网定向广播)仍会在 L2 使用以太网广播 MAC ff:ff:ff:ff:ff:ff 发送 。这是因为 FreeBSD 在 arpresolve() 中先 处理 M_BCAST/M_MCAST,完成 L2 目的地址填充后直接返回 ;对 IFF_NOARP 的检查发生在其后 ,因此不会覆盖广播/多播的映射逻辑。相对地,你前面看到的 Linux 代码是在 NOARP 分支里优先把 neigh->ha 设为本机 MAC,从而“吃掉”了广播语义。