rp_filter 的一个例子

我们都知道 Linux 反向路由查询,它的原理很简单,检查流入本机的 IP 地址是否合法,是否可能路由进来,是否是最佳路由。但是像多数网络问题,理论很简单,代码你看了也能懂,可实际情况往往比较复杂。之前一直没有碰到过实际中的例子,最近总算碰到一个。

情况是这样的,我有两个 vlan 设备,eth0.7 和 eth0.9,都是经过 vconfig 创建的虚拟网卡,eth0 硬件本身不能处理 vlan tag。现在的问题是,我给这两个网卡配置了同一个 IP 地址,192.168.122.74。你也许会感觉奇怪,但这是可行的,毕竟 eth0.7 和 eth0.9 不在同一个 vlan!你可以想象成它们的网线接在不同的局域网中。好了,问题出来了,现在我们在另外一台机器,物理上连接着 eth0,上面分别发送 vlan tag 是 7 和 9 的两个 arp request,结果是只有先被 ifup 起来的那个网卡回应!为什么?

我一开始的想法这可能是内核的bug,毕竟 vlan 那一部分经常出现一些问题。但经过人肉跟踪 vlan tag 的处理流程,发现基本上不太可能是内核的问题,至少不是内核 vlan 处理代码的问题。其实,这部分内核代码经过重写之后还是很清晰的,推荐你有时间阅读一下。

所以问题一定是在 arp 处理的代码中,所以最后锁定到了 arp_process()。分析一下里面的代码你不难看到里面调用了 ip_route_input_noref(),所以路由有可能是其中一个因素。所以我们看一下路由表:

ip r s

default via 192.168.122.1 dev eth0
192.168.122.0/24 dev eth0.7 proto kernel scope link src 192.168.122.74
192.168.122.0/24 dev eth0.9 proto kernel scope link src 192.168.122.74

然后尝试换一个顺序对 eth0.7 和 eth0.9 进行 ifup,你会发现其实是路由的顺序决定了你能得到哪个 arp reply!这时你应该能明白了,是 rp_filter 在起作用。查看一下它们的 /proc/sys/net/ipv4/conf/X/rp_filter 设置,果然都是1,那么在这种情况下,eth0.9 因为不是最佳路由,因此发送给它的 arp request 就被丢弃了。我们也可以把 /proc/sys/net/ipv4/conf/eth0.9/log_martians 打开,很容易看到下面的log:

[87317.980514] IPv4: martian source 192.168.122.74 from 192.168.122.1, on dev eth0.9
[87317.998162] ll header: 00000000: ff ff ff ff ff ff 52 54 00 2e 23 92 08 06 00 01 ……RT..#…..
[87318.015159] ll header: 00000010: 08 00 ..

另外,分析过程中用到的两条相关的 tcpdump 命令:

tcpdump arp -xx -ni eth0

tcpdump -xx -ni eth0 vlan