请教一个Linux TCPIP协议栈关于ARP的问题

问题描述: 在配置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_NOARPip 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() 片段,判断顺序是:

  1. RTN_MULTICASTarp_mc_map()(多播地址可按算法映射到以太网多播 MAC)
  2. IFF_NOARPIFF_LOOPBACKneigh->ha = dev->dev_addr(不做任何分辨/派生)
  3. RTN_BROADCAST 或 IFF_POINTOPOINTneigh->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) :cross_mark: 不符合 对于以太网接口,理论上应使用广播 MAC 封装广播 IP。
协议互操作与实现容忍度(实际 RFC 行为) :white_check_mark: 合理 RFC 未规定“ARP 关闭时必须仍发送广播帧”,Linux 的行为可视为实现选择,不违反协议必备要求。
工程意义 :white_check_mark: 正确 避免了非以太网 NOARP 接口误发广播帧,保持内核一致性。


简要结论:FreeBSD 的实现与 Linux 不同。在 FreeBSD 里,即使接口被设为 NOARPifconfig <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,从而“吃掉”了广播语义。