我們都知道 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