這段代碼對存儲了所有pcap條目信息的列表ptype_all進行遍歷, 判斷pcap條目的設備結構能否匹配skb所屬設備。
循環中對設備的檢查非常有意思:
1
|
if (!ptype->dev || ptype->dev == skb->dev) |
如果你試圖嗅探來自eth0的數據包,但是由于eth0已經是bond的一部分,因而這個檢查一定會失敗。原因是skb->dev已經被重寫為bond設備的dev結構了。
這就是為什么tcpdump和其他的測量工具嗅探bond相關的物理設備時看不到發送的數據包了!
只要簡單地把if語句修改成:
1
|
if (!ptype->dev || ptype->dev == skb->dev || ptype->dev == orig_dev) { |
因為加入了orig_dev檢查,這下pcap就能夠處理被dev指針被修改過的skb了。
讓我們來測試一下這個修改。
有意思的現象,第二輪觀察
接下來,重新構建并安裝修改后的內核(順便說一下,這里有一份非常有用的文檔),重新ping目標機器并開始嗅探發向物理設備的數據包:
1
2
3
4
5
|
% sudo tcpdump -i eth0 dst 172.16.209.136 and proto 1 ^C 0 packets captured 2 packets received by filter 0 packets dropped by kernel |
什么情況?
為什么修改以后還是沒有看到發送的數據包?
libpcap
讓我們快速檢查一下內核中負責處理AF_PACKET地址家族的libpcap接口。
AF_PACKET 在內核中是單獨實現的一個地址家族,相關的代碼位于net/packet/af_packet.c。libpcap通過調用socket系統調用建立一個socket,調用的第一個參數被設置為PACKET。libpcap接下來會使用bind系統調用把這個socket綁定想要嗅探的設備上。
現在有兩種方法可以從內核中拿到數據包:
• “以前的方法”: 對每個數據包的文件描述符調用recvfrom函數。在老版本的內核上,只有這一個函數可用。
• “新方法”:調用poll函數會通知libpcap有一組數據包到達,在內核與libpcap的共享內存中等待讀取。比起“老辦法”這種方法效率更高(使用的系統調用更少),在最近大多數的內核包括Debian Lenny都支持這種辦法。
結果是,盡管Debian Lenny的內核支持“新方法”實現的AF_PACKET,但是相應的libpcap卻不支持。這就意味著tcpdump(依賴于libpcap)只能逐次逐個地從內核中取得數據包。
更新版本的libpcap默認使用“新方法”從內核讀取數據包。因為Lenny支持這種用法,我試著構建了一個更新版本的libpcap并修改了tcpdump。在修改過的Lenny內核上測試這個修改,我看到當我在bond上的物理設備進行嗅探時,數據包從RX路徑流出。如果我把新的libpcap修改成使用“以前的方法”搜集數據包,沒有數據包從RX路徑流出。
這意味著在使用“以前的方法”時,要么AF_PACKET有bug,要么多版本的libpcap有bug。
if語句
經過數小時痛苦地閱讀代碼,我找了一條if語句可以控制libpcap使用“以前的方法”讀取數據包。
1
2
3
4
5
|
// From pcap_read_packet in pcap-linux.c: if (handle->md.ifindex != -1 && from.sll_ifindex != handle->md.ifindex) return 0; |
原文轉自:http://www.anti-gravitydesign.com