当前的 Debian bootpc和 bootpd(来自bootp)似乎无法在当今的 Linux 内核环境中真正协同工作。似乎存在一个 🐔/🥚 问题;bootpd
将回复作为单播 UDP 数据包发送到尚未配置的 IP 地址。然后,客户端的内核会丢弃它们,而不会将其传送到bootpc
客户端的套接字,因为该 IP 地址(尚未)是主机上的有效本地地址。
这是怎么实现的?
- 是否有一个内核参数或者其他修改可以让内核将这些数据包发送给进程
bootpc
? - 是否存在一种配置
bootpd
可以让它使用全 1 或全 0 的目标 IP 地址,而不是尚未配置的客户端 IP 单播地址?
我们有一位潜在客户,他拥有大型 bootp 非 DHCP 基础设施。老式 bootp 支持是他们的要求之一。
问题详细信息
bootpd
将其答复数据包作为单播数据包发送到 bootpc 客户端的 MAC 地址,使用有效载荷中的地址的目标 IP 地址值,告诉客户端用来配置自身的地址。- 然后内核丢弃这些数据包,而不是将它们传递给
bootpc
请求它们的进程。 bootpc
已正确打开监听套接字0.0.0.0:68 0.0.0.0:*
(请参阅下面的 netstat 输出)- 我从几个方面验证了这个分析:
- 我已经运行了
tcpdump
,可以看到回复到达了 NIC - 我运行
dropwatch
后发现数据包被丢弃,原因是IPINADDRERROR
,这基本上意味着“无效的 IP 地址” - 我可以
bootpd
通过省略实际 IP 分配来欺骗使用 0.0.0.0 作为目标 IP 地址;当我这样做时,bootpc
会获取响应并处理它们。然而这没有帮助,因为客户端没有获得 IP 地址 - 我尝试在发出请求时将 IP 地址添加到接口
bootpc
。添加后,下一个回复将进入该过程。
- 我已经运行了
- 我也尝试过这个
bootpc --serverbcast
选项。由于类似的原因,它失败了:bootpd
将回复发送到子网广播地址(例如 10.0.43.255)- 由于接口上尚未配置 IP 地址和子网掩码,因此内核没有理由将其视为自身的有效地址。
- 这是我们当前的
bootptab
配置:
.vs-default:\
:sm=255.255.254.0:\
:gw=10.0.42.1:\
:ds=10.0.42.1:\
:hn:
client-ad02-vs:\
ht=1:\
ha=0xea4a1fad0002:\
ip=10.0.42.31:\
tc=.vs-default:
Netstat 输出显示bootpc's listening socket
:
$ sudo netstat -unlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
udp 0 0 0.0.0.0:68 0.0.0.0:* 2295/bootpc
黑客解决方法
我想到了一个解决方法,感觉有点儿不太好用。特别是,我认为它利用了一种意想不到的行为,也许我们不能指望它在将来发挥作用:
- iptables conntrack 模块有一个
ctstate
值为 的匹配DNAT
。 - 这可用于接受具有无法识别的目标 IP 地址的数据包
- 我不认为它旨在匹配任意的 DST IP:
- 它只能用于由同一主机的内核进行 NAT 的数据包
- 我认为它的目的是匹配地址已被重写的数据包(我们没有这样做),但它是一种“廉价”的实现,它忽略 NAT 映射表,而不是验证是否存在与该特定数据包匹配的条目。
- 我担心这篇文章将来会被删除,或者被重写,以减少混乱
- 为了使其工作,我必须将这些数据包端口映射到它们现有的 DST 端口,只是为了将 NAT 连接状态数据放到数据包上,否则
--ctstate DNAT
不适用于该数据包。
背景/尽职调查
- 我们的测试平台使用的是 Linux 6.1.0 上的 Debian 12。
- 我们的测试平台主机来自 UTM Debian 12现成的 VM 映像
bootpd
命令行选项很少,没有一个涉及回复、端口或地址。手册页没有提到“广播”或“单播”,对“地址”或“目标”的引用也很少且不相关。- 我们的实际产品是定制 Linux 5.19.9 内核上的 Debian 12(行为方式相同)
- 测试平台客户端和服务器之间的 IP 连接完全正常
bootpc
拒绝发送请求,除非以下情况属实:- 接口上没有可路由的 IP 地址(允许使用 169.254/16,尽管我也尝试过不使用)
- 有一个 0/0 默认路由指向预期的 bootp 服务器的接口
- 否则它说
network unreachable
- 注意:
--dev <iface>
在bootpc
命令行上指定对此没有帮助
- 否则它说
- 我仍在评估
bootptab
配置选项,看看是否有任何选项会影响这一点。还没有找到关于它们的良好参考,man 手册bootptab
页对它们的介绍非常简洁。
替代解决方法
我已经能够使用一个不太复杂的iptables
解决方案来实现这一点:我添加了一个与 UDP 端口 68(bootpc)匹配的 NAT 规则,并将目标 IP 地址映射到 255.255.255.255(并保留相同的 UDP 端口)。
这可行,但我认为这是黑客行为。显然这不是协议的本意,所以如果可能的话,我更喜欢“真正的”解决方案。
DHCP 的行为方式相同;大多数 DHCP 客户端(我预计 BOOTP 客户端也一样)使用原始套接字在通常的内核处理之前接收整个 IP 数据包(以及在尚未有任何 IP 地址发送 IP 数据包时发送 IP 数据包)。客户端软件本身在发送发现或请求时构建完整的 IP 和 UDP 标头,然后使用 BPF¹ 过滤入站数据包,而不是通常的 bind()-to-local-port。
(除此之外,这还可以绕过 iptables 入站过滤和 UDP 校验和修复。)
应重写客户端以使用原始套接字(
AF_PACKET
、PF_LINK
或类似的)。例如,systemd-networkd 使用此源
socket(AF_PACKET, SOCK_DGRAM, ...)
中的类似功能。dhcpcd 客户端在某些系统上使用,在其他系统上使用,同样使用 BPF¹过滤数据包,而不是让内核执行此操作。AF_LINK
AF_PACKET
为了使事情简单化,一些部分可以通过 libpcap 来处理。
¹(这是“经典”的 BPF – 伯克利数据包过滤器 – 而不是现代 eBPF,因此它不是 Linux 独有的,也不需要任何不寻常的功能。)
服务器应该遵守 BOOTP 或 DHCP 请求中的“希望广播”标志。Debian 的
bootpc
客户端会在您传递时设置它--serverbcast
(因为它显然不使用原始套接字):但是,服务器仅在使用Debian 修补的选项
bootp
进行编译时才会遵守此标志:DHCP
如果您尝试将新的 Bootp服务器集成到网络中现有的 Bootp 客户端,那么 ISC
dhcpd
可能更适合您,因为它似乎具有 Bootp 兼容模式。(是的,它现在已经 EOL,但至少它比 Debian 的服务器 EOL时间短bootp
——后者似乎最后一次使用是在 1995 年)。同样,如果您尝试让客户端系统与现有的 Bootp 基础设施协同工作,请考虑是否可以在 Bootp 模式下使用某些 DHCP 客户端。