winpcap程式中,捕獲資料包主要分為如下幾步:
- 擷取網路介面卡列表
- 開啟網路介面卡
- 捕獲資料
擷取網路介面卡列表在前文中已經講述,這裡就不再累述了。
開啟裝置的函數是pcap_open()。下面是參數 snaplen, flags 和 to_ms 的解釋說明
- snaplen 制定要捕獲資料包中的哪些部分。 在一些作業系統中 (比如 xBSD 和 Win32), 驅動可以被配置成只捕獲資料包的初始化部分: 這樣可以減少應用程式間複製資料的量,從而提高捕獲效率。本例中,我們將值定為65535,它比我們能遇到的最大的MTU還要大。因此,我們確信我們總能收到完整的資料包。
- flags: 最最重要的flag是用來指示適配器是否要被設定成混雜模式。 一般情況下,適配器只接收發給它自己的資料包, 而那些在其他機器之間通訊的資料包,將會被丟棄。 相反,如果適配器是混雜模式,那麼不管這個資料包是不是發給我的,我都會去捕獲。也就是說,我會去捕獲所有的資料包。 這意味著在一個共用媒介(比如匯流排型乙太網路),WinPcap能捕獲其他主機的所有的資料包。 大多數用於資料擷取的應用程式都會將適配器設定成混雜模式,所以,我們也會在下面的範例中,使用混雜模式。
- to_ms 指定讀取資料的逾時時間,以毫秒計(1s=1000ms)。在適配器上進行讀取操作(比如用pcap_dispatch() 或 pcap_next_ex()) 都會在 to_ms 毫秒時間內響應,即使在網路上沒有可用的資料包。 在統計模式下,to_ms 還可以用來定義統計的時間間隔。 將 to_ms 設定為0意味著沒有逾時,那麼如果沒有資料包到達的話,讀操作將永遠不會返回。 如果設定成-1,則情況恰好相反,無論有沒有資料包到達,讀操作都會立即返回。
開啟適配器後,捕獲工作就可以用 pcap_dispatch() 或 pcap_loop()進行。 這兩個函數非常的相似,區別就是 pcap_ dispatch() 當逾時時間到了(timeout expires)就返回 (儘管不能保證) ,而 pcap_loop() 不會因此而返回,只有當 cnt 資料包被捕獲,所以,pcap_loop()會在一小段時間內,阻塞網路的利用。pcap_loop()對於我們這個簡單的範例來說,可以滿足需求,不過, pcap_dispatch() 函數一般用於比較複雜的程式中。
這兩個函數都有一個回調參數, packet_handler指向一個可以接收資料包的函數。 這個函數會在收到每個新的資料包並收到一個通用狀態時被libpcap所調用 ( 與函數 pcap_loop() 和 pcap_dispatch() 中的 user 參數相似),資料包的首部一般有一些諸如時間戳記,資料包長度的資訊,還有包含了協議首部的實際資料。 注意:冗餘校正碼CRC不再支援,因為幀到達適配器,並經過校正確認以後,適配器就會將CRC刪除,與此同時,大部分適配器會直接丟棄CRC錯誤的資料包,所以,WinPcap沒法捕獲到它們。
請注意,使用pcap_loop()函數可能會遇到障礙,主要因為它直接由資料包捕獲驅動所調用。因此,使用者程式是不能直接控制它的。另一個實現方法(也是提高可讀性的方法),是使用 pcap_next_ex() 函數,從而可以以同步的方式實現資料擷取。
#include
"pcap.h"
/* packet handler 函數原型*/
void packet_handler(u_char *param, const
struct pcap_pkthdr *header, const u_char *pkt_data);
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
/* 擷取本機裝置列表*/
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 列印列表*/
for(d=alldevs; d; d=d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
}
if(i==0)
{
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
return -1;
}
printf("Enter the interface number (1-%d):",i);
scanf("%d", &inum);
if(inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
/* 釋放裝置列表*/
pcap_freealldevs(alldevs);
return -1;
}
/* 跳轉到選中的適配器*/
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* 開啟裝置*/
if ( (adhandle= pcap_open(d->name, // 裝置名稱
65536, // 65535保證能捕獲到不同資料連結層上的每個資料包的全部內容
PCAP_OPENFLAG_PROMISCUOUS, // 混雜模式
1000, // 讀取逾時時間
NULL, // 遠程機器驗證
errbuf // 錯誤緩衝池
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
/* 釋放裝置列表*/
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
/* 釋放裝置列表*/
pcap_freealldevs(alldevs);
/* 開始捕獲*/
pcap_loop(adhandle, 0, packet_handler, NULL);
return 0;
}
/* 每次捕獲到資料包時,會自動調用這個回呼函數*/
void packet_handler(u_char *param, const
struct pcap_pkthdr *header, const u_char *pkt_data)
{
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);
printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);
}