當設備找到後,下一步工作就是打開設備以準備捕獲數據包。libpcap的包捕獲是建立在具體的操作系統所提供的捕獲機制上,而Linux系統隨着版本的不同,所支持的捕獲機制也有所不同。
2.0 及以前的內核版本使用一個特殊的socket類型SOCK_PACKET,調用形式是socket(PF_INET, SOCK_PACKET, int protocol),但 Linux 內核開發者明確指出這種方式已過時。Linux 在 2.2及以後的版本中提供了一種新的協議簇 PF_PACKET 來實現捕獲機制。PF_PACKET 的調用形式爲 socket(PF_PACKET, int socket_type, int protocol),其中socket類型可以是 SOCK_RAW和SOCK_DGRAM。SOCK_RAW 類型使得數據包從數據鏈路層取得後,不做任何修改直接傳遞給用戶程序,而 SOCK_DRRAM 則要對數據包進行加工(cooked),把數據包的數據鏈路層頭部去掉,而使用一個通用結構 sockaddr_ll 來保存鏈路信息。
使用 2.0 版本內核捕獲數據包存在多個問題:首先,SOCK_PACKET 方式使用結構 sockaddr_pkt來保存數據鏈路層信息,但該結構缺乏包類型信息;其次,如果參數 MSG_TRUNC 傳遞給讀包函數 recvmsg()、recv()、recvfrom() 等,則函數返回的數據包長度是實際讀到的包數據長度,而不是數據包真正的長度。libpcap 的開發者在源代碼中明確建議不使用 2.0 版本進行捕獲。
相對2.0版本SOCK_PACKET方式,2.2版本的PF_PACKET方式則不存在上述兩個問題。在實際應用中,用戶程序顯然希望直接得到"原始"的數據包,因此使用 SOCK_RAW 類型最好。但在下面兩種情況下,libpcap 不得不使用SOCK_DGRAM類型,從而也必須爲數據包合成一個"僞"鏈路層頭部(sockaddr_ll)。
某些類型的設備數據鏈路層頭部不可用:例如 Linux 內核的 PPP 協議實現代碼對 PPP 數據包頭部的支持不可靠。
在捕獲設備爲"any"時:所有設備意味着libpcap對所有接口進行捕獲,爲了使包過濾機制能在所有類型的數據包上正常工作,要求所有的數據包有相同的數據鏈路頭部。
打開網絡設備的主函數是 pcap_open_live()[pcap-Linux.c],其任務就是通過給定的接口設備名,獲得一個捕獲句柄:結構 pcap_t。pcap_t 是大多數libpcap函數都要用到的參數,其中最重要的屬性則是上面討論到的三種 socket方式中的某一種。首先我們看看pcap_t的具體構成。
|
函數pcap_open_live()的調用形式是 pcap_t * pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *ebuf),其中如果 device 爲 NULL 或"any",則對所有接口捕獲,snaplen 代表用戶期望的捕獲數據包最大長度,promisc 代表設置接口爲混雜模式(捕獲所有到達接口的數據包,但只有在設備給定的情況下有意義),to_ms 代表函數超時返回的時間。本函數的代碼比較簡單,其執行步驟如下:
* 爲結構pcap_t分配空間並根據函數入參對其部分屬性進行初試化。
* 分別利用函數 live_open_new() 或 live_open_old() 嘗試創建 PF_PACKET 方式或 SOCK_PACKET 方式的socket,注意函數名中一個爲"new",另一個爲"old"。 * 根據 socket 的方式,設置捕獲句柄的讀緩衝區長度,並分配空間。 * 爲捕獲句柄pcap_t設置Linux系統下的特定函數,其中最重要的是讀數據包函數和設置過濾器函數。(注意到這種從抽象模式到具體模式的設計思想在 Linux 源代碼中也多次出現,如VFS文件系統) handle->read_op = pcap_read_Linux; handle->setfilter_op = pcap_setfilter_Linux;下面我們依次分析 2.2 和 2.0 內核版本下的socket創建函數。
|
比較上面兩個函數的代碼,還有兩個細節上的區別。首先是 socket 與接口綁定所使用的結構:老式的綁定使用了結構 sockaddr,而新式的則使用了 2.2 內核中定義的通用鏈路頭部層結構sockaddr_ll。
|
第二個是在 2.2 版本中設置設備爲混雜模式時,使用了函數 setsockopt(),以及新的標誌 PACKET_ADD_MEMBERSHIP 和結構 packet_mreq。我估計這種方式主要是希望提供一個統一的調用接口,以代替傳統的(混亂的)ioctl 調用。
|
第二個是在 2.2 版本中設置設備爲混雜模式時,使用了函數 setsockopt(),以及新的標誌 PACKET_ADD_MEMBERSHIP 和結構 packet_mreq。我估計這種方式主要是希望提供一個統一的調用接口,以代替傳統的(混亂的)ioctl 調用。
|
libpcap 提供的用戶程序接口比較簡單,通過反覆調用函數pcap_next()[pcap.c]則可獲得捕獲到的數據包。下面是一些使用到的數據結構:
|
pcap_dispatch() 簡單的調用捕獲句柄 pcap_t 中定義的特定操作系統的讀數據函數:return p->read_op(p, cnt, callback, user)。在 Linux 系統下,對應的讀函數爲 pcap_read_Linux()(在創建捕獲句柄時已定義 [pcap-Linux.c]),而pcap_read_Linux() 則是直接調用 pcap_read_packet()([pcap-Linux.c])。
pcap_read_packet() 的中心任務是利用了 recvfrom() 從已創建的 socket 上讀數據包數據,但是考慮到 socket 可能爲前面討論到的三種方式中的某一種,因此對數據緩衝區的結構有相應的處理,主要表現在加工模式下對僞鏈路層頭部的合成。具體代碼分析如下:
|