那麼就來教libpcap主要用途:抓封包,libpcap提供了四個函數抓封包:
pcap_loop()
pcap_dispatch()
pcap_next()
(最好別用)pcap_next_ex()
這篇先講
pcap_loop()
和pcap_dispatch()
因為這兩個參數是一樣的,只是功能不太一樣。先用函數
pcap_lookupdev()
取得預設device的名稱,我們要從這個device抓封包。char errbuf[PCAP_ERRBUF_SIZE]; char *device = NULL; //get default interface name device = pcap_lookupdev(errbuf); if(!device) { fprintf(stderr, "pcap_lookupdev(): %s\n", errbuf); exit(1); }//end if printf("Sniffing: %s\n", device);
接著就準備開始抓封包了。
pcap_t *handle = NULL; //open interface handle = pcap_open_live(device, 65535, 1, 1, errbuf); if(!handle) { fprintf(stderr, "pcap_open_live(): %s\n", errbuf); exit(1); }//end if
宣告的變數
pcap_t *
是libpcap主要的結構體,使用函數pcap_open_live()
開啟一個device,第一個參數就是要開啟的device;第二個參數填65535為一般IP封包最大值,表示不切割封包;第三個參數1表示使用混雜模式(promiscuous mode);第四個參數表示timeout時間,這個參數在(上面提到的)四種抓封包的函數都有不太一樣的結果。函數
pcap_open_live()
原型:pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf);
- 返回值:成功傳回一個libpcap handle,失敗傳回NULL,錯誤訊息在errbuf。
- 參數:device表示要開啟的device。snaplen表示要獲得的封包長度。例如原始封包有1000 bytes,如果設置500則會獲取前500 bytes,可以指定IP封包的最大值65535表示不切割(如果device設定了
Jumbo Frame
可能要更大)。promisc是否要設成混雜模式(promiscuous mode),1表示設置混雜模式。to_ms表示等待時間,在幾個抓封包的函數各有不同意思,之後再解釋,單位毫秒。errbuf錯誤訊息。 - 功能:打開一個device進行擷取封包。
函數
pcap_open_live()
第四個參數to_ms在pcap_dispatch()
作用是,當在to_ms時間內都沒有封包的話,就會退出函數,不管是否已經達到cnt數量,所以如果時間設置太短的話,很容易抓不到封包。處理時間跟pcap_loop()
一樣,到達to_ms時間才會處理,所以設置太大也會發生掉封包的情況。接著就直接開始抓封包了。
//start capture pcap_loop() if(0 > pcap_loop(handle, 10, pcap_callback1, NULL)) { fprintf(stderr, "pcap_loop(): %s\n", pcap_geterr(handle)); }//end if
函數
pcap_loop()
第二個參數10表示抓十個封包就return,第三個參數是當有封包進來的時候要呼叫的callback函數,第四個參數是要傳給第三個參數的callback function的參數。
函數
pcap_loop()
原型:int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user);
- 返回值:成功傳回0,失敗傳回-1,錯誤訊息從pcap_geterr()取得。
- 參數:p一個libpcap handle。cnt抓取封包數量,-1表示無限多。callback當抓取封包時要呼叫的函數。user要傳給callback的參數。
- 功能:該函數利用callback的方式抓取封包,當抓取到封包時會呼叫callback函數,只有發生錯誤時或抓到cnt數量的封包才會停止。
函數
pcap_open_live()
第四個參數to_ms在函數pcap_loop()
影響是,當達到to_ms時間時才會處理封包,所以如果設定3000則會每三秒(3000毫秒)處理一次封包,所以如果必須要一直處理封包就設置成1,則會每毫秒處理。時間不宜設置太大,否則當libpcap核心的封包環狀佇列(Circular Queue)滿溢時會丟掉在前端的封包,所以就會發生掉封包的情況。但是不能設置為0,在一些系統上會block住,講白話就是卡住在那邊。來看看第三個參數資料類型是
pcap_handler
來看一下它的定義:typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *);
也就是說這個函數如果要宣告的話,回傳值是
void
,參數分別是u_char *
、struct pcap_pkthdr *
和const u_char *
,所以我們的第一個callback宣告成:static void pcap_callback1(u_char *arg, const struct pcap_pkthdr *header, const u_char *content);
這個函數就是當抓到一個封包後,會呼叫的函數。
static void pcap_callback1(u_char *arg, const struct pcap_pkthdr *header, const u_char *content) { static int d = 0; printf("%3d: captured\n", ++d); }//end pcap_callback1
在呼叫函數
pcap_loop()
的時候第四個參數是NULL
,所以在這邊第一個參數arg也是NULL
;第二個參數header是這個封包的一些資訊,包含:抓到的時戳、抓到的大小跟實際大小;第三個參數content就是封包本身的資料的,長度多少要根據header裡面的資訊來解析。這邊先單純看看目前抓到第幾個封包。
當十個封包抓完後,函數
pcap_loop()
會傳回0,失敗的話可以用函數pcap_geterr()
取得某個handle儲存的錯誤訊息。函數
pcap_geterr()
原型:- 返回值:傳回錯誤訊息。
- 參數:p一個libpcap handle。
- 功能:當p使用函數時發生錯誤,可以用該函數傳回錯誤訊息。
接著換另外一個函數抓看看封包。
//start capture pcap_dispatch() int ret = pcap_dispatch(handle, -1, pcap_callback2, (u_char *)handle); if(0 > ret) { fprintf(stderr, "pcap_dispatch(): %s\n", pcap_geterr(handle)); }//end if else { printf("Captured: %d\n", ret); }//end else
參數跟函數
pcap_loop()
一樣,callback宣告方式也一樣,只是return條件不太一樣;這次把handle當成參數傳進去。static void pcap_callback2(u_char *arg, const struct pcap_pkthdr *header, const u_char *content);
函數
pcap_dispatch()
原型:int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user);
- 返回值:成功傳回抓取封包的數量,失敗傳回-1,錯誤訊息從pcap_geterr()取得。
- 參數:p一個libpcap handle。cnt抓取封包最多的數量,-1表示無限多。callback當抓取封包時要呼叫的函數。user要傳給callback的參數。
- 功能:該函數利用callback的方式抓取封包,當抓取到封包時會呼叫callback函數,只有發生錯誤、抓到cnt數量或是在達到to_ms時間內都沒有封包時才會停止。
一樣先列印出目前抓到幾個封包。
static void pcap_callback2(u_char *arg, const struct pcap_pkthdr *header, const u_char *content) { static int d = 0; printf("No. %3d\n", ++d);
接著來解析一下header。
//format timestamp struct tm *ltime; char timestr[16]; time_t local_tv_sec; local_tv_sec = header->ts.tv_sec; ltime = localtime(&local_tv_sec); strftime(timestr, sizeof timestr, "%H:%M:%S", ltime); //print header printf(" Time: %s.%.6d\n", timestr, (int)header->ts.tv_usec); printf(" Length: %d bytes\n", header->len); printf(" Capture length: %d bytes\n", header->caplen);
變數header的成員ts資料類型是
struct timeval
而這個結構的成員tv_sec代表秒數,成員tv_usec表示微秒(10-6秒)。先用變數local_tv_sec取得秒數,再用函數
localtime()
將秒數轉成目前時區(struct tm *
結構指標);最後再用函數strftime()
把struct tm *
結構指標轉成可辨識的字串。第一個
printf()
順便把微秒一起顯示出來;header的成員len表示封包原始大小(進入device時大小),caplen表示被libpcap截斷的大小(函數pcap_open_live()
第二個參數設定)。使用
localtime()
和strftime()
在unix-like、Mac OS X和Windows上都有點不同,我的作法剛好是三種系統都能通用,不會有問題的作法(至少我測試過的三個系統沒問題)。結構
struct pcap_pkthdr
宣告:/* * Generic per-packet information, as supplied by libpcap. * * The time stamp can and should be a "struct timeval", regardless of * whether your system supports 32-bit tv_sec in "struct timeval", * 64-bit tv_sec in "struct timeval", or both if it supports both 32-bit * and 64-bit applications. The on-disk format of savefiles uses 32-bit * tv_sec (and tv_usec); this structure is irrelevant to that. 32-bit * and 64-bit versions of libpcap, even if they're on the same platform, * should supply the appropriate version of "struct timeval", even if * that's not what the underlying packet capture mechanism supplies. */ struct pcap_pkthdr { struct timeval ts; /* time stamp */ bpf_u_int32 caplen; /* length of portion present */ bpf_u_int32 len; /* length this packet (off wire) */ };
封包內容就單純的以十六進位方式列印出來。(這邊講一下,指標跟陣列是不同的東西)
//print packet in hex dump for(int i = 0 ; i < header->caplen ; i++) { printf("%02x ", content[i]); }//end for printf("\n\n");
在呼叫函數
pcap_dispatch()
時候,第二個參數填入-1,表示抓無限多個(在timeout前),如果要停止的話,可以使用函數pcap_breakloop()
;當抓到二十個封包的時候,將變數arg(在函數pcap_dispatch()
第四個參數傳入的handle)轉成pcap_t *
再呼叫函數pcap_breakloop()
。//break when captured 20 frames if(d == 20) { pcap_t *handle = (pcap_t *)arg; pcap_breakloop(handle); }//end if
因為函數
pcap_dispatch()
特性,可能沒抓到二十個封包之前就發生timeout,所以結果最多就是二十個封包。函數
pcap_breakloop()
原型:void pcap_breakloop(pcap_t *p);
- 參數:p一個libpcap handle。
- 功能:退出抓封包的loop,可以用在pcap_loop()或pcap_dispatch()的callback內,或是用在多執行緒(Multithreading)程式內。
最後全部完成後,要關掉打開的handle。
//free pcap_close(handle);
函數
pcap_close()
原型:void pcap_close(pcap_t *p);
- 參數:p一個libpcap handle。
- 功能:關閉libpcap handle並釋放資源。
編譯:
libpcap % gcc -I/usr/local/opt/libpcap/include -Wall -std=gnu99 -L/usr/local/opt/libpcap/lib -lpcap capture-pcap_loop-and-pcap_dispatch.c -o capture-pcap_loop-and-pcap_dispatch
執行結果(Mac OS X):
libpcap % ./capture-pcap_loop-and-pcap_dispatch Sniffing: en0 1: captured 2: captured 3: captured 4: captured 5: captured 6: captured 7: captured 8: captured 9: captured 10: captured No. 1 Time: 23:04:00.293403 Length: 78 bytes Capture length: 78 bytes f8 1a 67 53 f5 dc 6c 40 08 bc ae 98 08 00 45 00 00 40 96 03 40 00 40 06 6d 96 c0 a8 00 64 1f 0d 57 05 dc db 01 bb 62 35 c0 c4 83 12 48 d2 b0 11 10 00 fe 4c 00 00 01 01 08 0a 21 3e 42 0d 0d 19 25 e5 01 01 05 0a 83 12 48 84 83 12 48 d1 No. 2 Time: 23:04:00.296157 Length: 68 bytes Capture length: 68 bytes 6c 40 08 bc ae 98 f8 1a 67 53 f5 dc 08 00 45 00 00 34 b8 44 40 00 57 06 34 61 1f 0d 57 05 c0 a8 00 64 01 bb dc db 83 12 48 d2 62 35 c0 c5 80 10 00 7e db 0a 00 00 01 01 08 0a 0d 19 26 46 21 3e 42 02 7e fd Captured: 2
執行結果(CentOS):
[root@tutu libpcap]# ./capture-pcap_loop-and-pcap_dispatch Sniffing: eth0 1: captured 2: captured 3: captured 4: captured 5: captured 6: captured 7: captured 8: captured 9: captured 10: captured Captured: 0
Source code on Github
沒有留言:
張貼留言