PCAP研究


 

一、  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_transmitpcap_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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM