2016年11月4日 星期五

libpcap - Capture using pcap_loop and pcap_dispatch(4)


那麼就來教libpcap主要用途:抓封包,libpcap提供了四個函數抓封包:


  1. pcap_loop()
  2. pcap_dispatch()
  3. pcap_next()(最好別用)
  4. 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

沒有留言:

張貼留言