一、 pcap簡介
封裝了OS提供的底層抓包技術,對外提供一些統一的抓包(及發送)接口。實現這些功能的其他技術包括:BPF(Berkeley Packet Filter),DLPI(Data Link Provider Interface),NIT ,Linux專用的SOCKET_PACKET或PF_PACKET等。
二、 pcap Linux安裝
參考《INSTALL.txt》。
進入pcap源碼目錄,執行./configure,這將檢測系統環境,並生成Makefile文件;執行make;執行make install,這將安裝開發頭文件、庫、手冊等;注意這不會安裝動態庫。
三、 pcap 開發介紹
2.1 API介紹
本部分介紹API,並對主要的API進行詳細的說明。
pcap_open_live,打開由dev指定的設備,
pcap_open_dead,只是建立一個pcap_t結構體,用處不大;
pcap_open_offline,打開一個tcpdump/libpcap 格式的文件,從中讀取數據;
pcap_dump_open
pcap_setnonblock
pcap_getnonblock
pcap_findalldevs,獲取設備列表
pcap_freealldevs,關閉查詢的設備
pcap_lookupdev,獲得設備信息,如eth0,只是獲得找到的第一個設備
pcap_lookupnet,獲得IP/Mask信息
pcap_dispatch,抓包引擎,需循環調用
pcap_loop,抓包引擎,與pcap_dispatch的不同處在於它少一個超時返回參數;
pcap_dump
pcap_compile,編譯過濾語法
pcap_setfilter,綁定過濾器
pcap_freecode
pcap_next,輪詢方式抓包
pcap_datalink
pcap_snapshot
pcap_is_swapped
pcap_major_version
pcap_minor_version
pcap_stats,獲取當前捕獲的統計信息
pcap_file
pcap_fileno
pcap_perror
pcap_geterr
pcap_strerror
pcap_close,關閉設備
pcap_dump_close
pcap_sendpacket,發送一個原始數據包
說明:
1, 函數的返回值,0表示成功,-1表示錯誤;
2, 參數errbuf用於接收錯誤信息,不小於PCAP_ERRBUF_SIZE;
2.2使用pcap的一般步驟
Ø pcap_lookupdev等獲得設備信息,網卡設備名、設備所在網絡地址;
Ø pcap_open_live打開設備,設置網卡成混雜模式;
Ø 循環調用pcap_loop中實現包捕獲引擎,編寫包分析程序;
2.3回調函數定義
typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *);
2.4設置過濾條件
首先使用pcap_compile編譯一個filter字符串,然后使用pcap_setfilter將編譯結果綁定到一個設備;
char* filter = "udp port 5060";
bpf_program fp;
if(-1 == pcap_compile(cap_des, &fp, filter, 0, netp))
{
cout<<"compile err: "<<pcap_geterr(cap_des)<<endl;
return 6;
}
if(-1 == pcap_setfilter(cap_des, &fp))
{
cout<<"set filter err: "<<pcap_geterr(cap_des)<<endl;
return 7;
}
2.5 錯誤返回
存在兩種獲取錯誤原因的方式,一是通過函數參數的errbuf;如果函數沒有該參數,則使用pcap_geterr獲得,函數執行錯誤時會將錯誤信息寫入結構體中的預分配的errbuf(其中一些是基於errno),該函數返回該errbuf的地址;
四、 pcap Linux實現
4.1函數
本部分介紹某些關鍵函數的實現:
1, pcap_findalldevs,首先使用socket()獲得一個socket的句柄,然后使用ioctl獲得所有網卡信息;該函數會嘗試打開找到的設備(add_or_find_if),它只返回能夠用於live capture的設備;
2, pcap_lookupdev,調用pcap_findalldevs,將找到的第一個device返回。
3, pcap_open_live,
a) 參數device賦空(NULL)或“any”時將抓取所有網卡的數據包(這種情況下將不支持混雜模式?);
b) 嘗試使用live_open_new打開設備(PF_PACKET),失敗將使用live_open_old(SOCK_PACKET);
c) live_open_new,對捕獲單塊網卡,調用socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))(數據帶鏈路層頭),如果需捕獲所有網卡,調用socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL))(不帶鏈路層頭);調用setsockopt設置混雜模式;
d) 設置pcap_t對象,設置操作系統相關的處理函數的指針,及初始化buffer(大小由參數snaplen確定),
4, pcap_close,調用pcap_close_linux,
5, pcap_lookupnet,先使用socket()獲得一個socket句柄,然后調用ioctl獲得設備相關的參數;
6, pcap_loop,判斷open方式,循環調用pcap_offline_read讀取文件或read_op(pcap_read_linux)讀取socket,讀取cnt個packet,並對每個packet調用callback函數;將參數user傳給callbask函數;函數返回已處理的packet數;
a) pcap_read_linux調用pcap_read_packet,后者調用recvfrom將數據接收到bufsize;如果kernel filter沒有起作用,調用bpf_filter進行處理;最后調用callback函數;
7, pcap_dispatch,僅調用一次read_op,相對pcap_loop,不能用於讀取文件,及不循環;這樣它的處理少一些;在Linux下,每次調用只抓一個packet;
8, pcap_next,調用pcap_dispatch實現,每次只抓一個packet,將packet作為函數返回值;
9, pcap_next_ex,提供了讀取文件的能力,其他處理與pcap_next相仿;
10, pcap_compile,調用了lex_init等函數——沒看到這些函數的實現;
11, pcap_setfilter,調用了pcap_setfilter_linux(#ifdef SO_ATTACH_FILTER);filter分內核filter及pcap自己實現的filter兩種,pcap會優先使用內核filter;如果filter語法過於復雜(#ifdef USHRT_MAX),會使用或經檢查filter不能在內核執行時,
a) 調用fix_program,
b) 調用set_kernel_filter設置內核filter,使用setsockopt(SO_ATTACH_FILTER);
12, pcap_inject,調用p->inject_op,pcap_inject_linux,send發送數據;
13, pcap_sendpacket,與pcap_inject實現一樣,只是更改了接口;
14, pcap_stats,調用stats_op(pcap_stats_linux)函數,內核版本需2.4 以上,調用getsockopt獲得數據;可統計數據包括:經過filter到達pcap的packet數量、通過了filter但是因為buffer不足等原因而沒有到達pcap的packet數量;
15, pcap_setnonblock,將socket設置成阻塞或非阻塞模式;
16, pcap_setdirection,設置要抓取的packet的方向,發出還是收到?;
4.1數據結構
本部分為pcap的關鍵數據結構:
struct pcap_if {
struct pcap_if *next;
char *name;
char *description;
struct pcap_addr *addresses;
bpf_u_int32 flags; PCAP_IF_LOOPBACK
};
struct pcap_pkthdr {
struct timeval ts; //獲得packet的時間
bpf_u_int32 caplen; //抓取到的packet長度
bpf_u_int32 len; //packet的真實長度
};
len可能大於caplen
pcap_t,摘出了Linux相關部分:
struct pcap {
int fd;
int selectable_fd;
int send_fd;
int snapshot;
int linktype;
int tzoff;
int offset;
int break_loop;
#ifdef PCAP_FDDIPAD
int fddipad;
#endif
struct pcap_sf sf;
struct pcap_md md;
int bufsize;
u_char *buffer;
u_char *bp;
int cc;
u_char *pkt;
pcap_direction_t direction;
int (*read_op)(pcap_t *, int cnt, pcap_handler, u_char *);
int (*inject_op)(pcap_t *, const void *, size_t);
int (*setfilter_op)(pcap_t *, struct bpf_program *);
int (*setdirection_op)(pcap_t *, pcap_direction_t);
int (*set_datalink_op)(pcap_t *, int);
int (*getnonblock_op)(pcap_t *, char *);
int (*setnonblock_op)(pcap_t *, int, char *);
int (*stats_op)(pcap_t *, struct pcap_stat *);
void (*close_op)(pcap_t *);
struct bpf_program fcode;
char errbuf[PCAP_ERRBUF_SIZE + 1];
int dlt_count;
u_int *dlt_list;
struct pcap_pkthdr pcap_header;
};
五、 一些問題
1,c代碼與c++代碼風格比較
1, C++使用繼承結構區分共性與個性,將代表個性的數據結構放到子類中,這樣區別能集中到子類中;C中使用大量的條件編譯,如#ifdef HAVE_PF_PACKET_SOCKETS,代碼混雜;
2, C中實現多態的方式,結構體中定義函數指針,不同的實現賦不同的值;
六、 一些測試數據
1,基於Winpcap,使用filter;
程序執行環境:Windows XP sp2,無線網卡;
測試方式:向10.130.24.158拷貝一個超過1G的文件,檢查程序的性能情況;
測試數據:
|
描述 |
CPU(%) |
程序消耗(%) |
其他 |
1 |
不設置filter,抓取所有數據 |
65~85 |
20 |
|
2 |
設置filter(udp)使得不抓取數據 |
15~25 |
0 |
|
3 |
設置filter(tcp)抓取所有數據 |
65~80 |
20 |
|
2,Winpcap的發送速度
說明:本次測試只測試了發送函數的執行耗時,未檢查接受端的情況,即不能保證數據真的通過網卡發出。
測試方式:每次發送300B大小的數據包,每循環執行100000或500000次發送,記錄每循環的耗時,取多次循環的折中值;另外24.158機器上安裝有兩塊千兆網卡,一塊接在千兆交換機上,另一塊接在百兆交換機上。
1, pcap_sendpacket與pcap_sendqueue_transmit的發送速度比較:
每循環執行100000次pcap_sendpacket發送,耗時約6秒,流量約40Mb/s,且100Mb網絡稍快於1000Mb網絡;
使用pcap_sendqueue_transmit,積累到100個數據包時發送一次;千兆網絡每循環耗時0.7秒,流量342Mb/s,百兆網絡每循環耗時2.6秒,流量92Mb/s。
結論:pcap_sendqueue_transmit比pcap_sendpacket發送速度快得多。
2, 用戶buffer、系統buffer、每次發送數量對發送速度的影響
本部分測試用戶buffer、系統buffer、每次發送數量對pcap_sendqueue_transmit的發送速度的影響;本測試每循環發送500000個包,每個包300B;
關於pcap_sendqueue_alloc的說明:該函數用於分配一塊用戶空間存儲,應設置得足夠大以容納數據;測試發現它會影響到程序占用的內存,但對發送速度沒有影響。
用戶buffer 1M,系統buffer1M
每次發包數 |
50 |
100 |
1200 |
1 |
1000Mb網絡(秒) |
3 |
4 |
3 |
32 |
100Mb網絡(秒) |
13 |
13 |
13 |
30 |
設置用戶buffer為8M,系統buffer 1M;
每次發包數 |
|
100 |
1200 |
|
1000Mb網絡(秒) |
|
2.7 |
3 |
|
100Mb網絡(秒) |
|
13 |
13.3 |
|
設置用戶buffer為64M,系統buffer 1M;
每次發包數 |
|
100 |
1200 |
|
1000Mb網絡(秒) |
|
2.7 |
3 |
|
100Mb網絡(秒) |
|
13 |
13.4 |
|
設置用戶buffer為1M,系統buffer 1M;
每次發包數 |
|
100 |
1200 |
|
1000Mb網絡(秒) |
|
2.7 |
3 |
|
100Mb網絡(秒) |
|
13 |
13.4 |
|
設置用戶buffer為1M,系統buffer 64M;
每次發包數 |
|
100 |
1200 |
|
1000Mb網絡(秒) |
|
3.7 |
3.1 |
|
100Mb網絡(秒) |
|
13 |
13.3 |
|
設置用戶buffer為8M,系統buffer 8M;
每次發包數 |
|
100 |
1200 |
12000 |
1000Mb網絡(秒) |
|
3.7 |
3 |
2.9 |
100Mb網絡(秒) |
|
13 |
13.4 |
13.6 |
設置用戶buffer為8M,系統buffer 64M;
每次發包數 |
|
100 |
1200 |
|
1000Mb網絡(秒) |
|
2.7 |
3 |
|
100Mb網絡(秒) |
|
13 |
13.4 |
|