循序漸進學WinPcap
風兒 發(fā)表于 2006-4-19 10:48:00
WinPcap tutorial: a step by step guide to using WinPcap
詳細說明
這部分展示了怎樣使用WinPcap API。這個教程通過一系列的課程,從基本的函數(shù)(取得網卡列表,開始抓包,等等)到最高級的應用(處理數(shù)據(jù)包包發(fā)送隊列和統(tǒng)計網絡流量),一步一步地教會讀者如何用WinPcap來編程。
這里提供了幾個雖然簡單但卻完整的程序段作為參考:所有的源代碼都有其余部分的鏈接,只需要點擊一下函數(shù)和數(shù)據(jù)結構就可以跳轉到相應的文檔。
這些例子都是使用c語言寫的,所以在讀本教程前要了解一些基本的c語言的知識。而且,這是一個關于處理原始網絡包的庫的教程,所以假定讀者具有良好的網絡和網絡協(xié)議方面的知識。
WinPcap tutorial: a step by step guide to using WinPcap(1)
獲取網絡設備列表
基本上所有基于Winpcap的應用程序所做的第一件事情都是獲取一個已經綁定的網卡列表。為此,libcap和winpcap都提供了pcap_findalldevs_ex()函數(shù):這個函數(shù)返回一個指向pcap_if結構的鏈表,其中的每一項都包含了一個已經綁定的適配器的全部信息。其中name和description這兩項分別包含了相應設備的名稱和描述。
下面的代碼取得適配器列表并在屏幕上顯示出來,如果適配器沒有被發(fā)現(xiàn)就把顯示錯誤。
#i nclude "pcap.h"
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int i=0;
char errbuf[PCAP_ERRBUF_SIZE];
/* 取得本機的網絡設備列表 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL /* 這個參數(shù)在這里不需要 */, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs_ex: %s\n", errbuf);
exit(1);
}
/* 顯示列表 */
for(d= alldevs; d != NULL; 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;
}
/* We don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
}
關于這段代碼的說明。
首先,就象其他的libpcap函數(shù),pcap_findalldevs_ex()有一個errbuf參數(shù)。如果發(fā)生錯誤,libcap就把一個錯誤說明放到這個參數(shù)指向的字符串中。
其次,并不是所有的操作系統(tǒng)都支持libpcap提供的網絡接口描述,因此如果我們想寫一個可移植的程序,我們必須考慮description為null的情況:這個時候我們就輸出字符串"No description available" 。
最后要提醒一下:一旦我們完成了這些動作,就應該釋放用pcap_freealldevs()列表。
讓我們編譯并運行這段簡單的代碼。在unix或者cygwin中編譯的話,打入下面的命令:
gcc -o testaprog testprog.c -lpcap
在windows中,你需要創(chuàng)建一個project,照著手冊中的Using WinPcap in your programs 那一章做就可以了。但是,我們建議你使用the WinPcap developer's pack(可以在
http://www.winpcap.org 下載),因為這個開發(fā)包提供了許多教程中使用的代碼示例,這些示例都已經配置好了,其中包含了編譯執(zhí)行例子所需要的include文件和lib文件。
編譯好了程序后,在我的winxp工作站上運行的結果:
1. {4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS) Ethernet Adapter)
2. {5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)
就象你看到的一樣,在windows系統(tǒng)中網絡適配器的名稱(在打開網絡適配器時將被傳遞給libcap)根本沒有辦法理解,所以附加說明可能是非常有幫助的。
WinPcap tutorial: a step by step guide to using WinPcap(2)
Obtaining advanced information about installed devices
課程1(Obtaining the device list)說明了怎樣得到可用適配器的基本信息(比如設備名和說明)。實際上,winpcap也提供其他的高級信息。每個由pcap_findalldevs_ex()返回的pcap_if 結構體都包含了一個pcap_addr結構列表,里面包含的內容有:
一個接口的地址列表。
一個子網掩碼列表(每一個都與地址列表中的條目一一對應)。
一個廣播地址列表(每一個都與地址列表中的條目一一對應)。
一個目的地址列表(每一個都與地址列表中的條目一一對應)。
除此之外,pcap_findalldevs_ex() 也能返回遠程的適配器和任給的本地文件夾的pcap文件列表。
下面的例子提供了一個ifprint() 函數(shù)來打印出一個pcap_if 結構中的所有內容。程序對每一個pcap_findalldevs_ex()返回的條目都調用一次這個函數(shù)。
/*
* Copyright (c) 1999 - 2003
* NetGroup, Politecnico di Torino (Italy)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Politecnico di Torino nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#i nclude "pcap.h"
#ifndef WIN32
#i nclude <sys/socket.h>
#i nclude <netinet/in.h>
#else
#i nclude <winsock.h>
#endif
// Function prototypes
void ifprint(pcap_if_t *d);
char *iptos(u_long in);
char* ip6tos(struct sockaddr *sockaddr, char *address, int addrlen);
int main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
char errbuf[PCAP_ERRBUF_SIZE+1];
char source[PCAP_ERRBUF_SIZE+1];
printf("Enter the device you want to list:\n"
"rpcap:// ==> lists interfaces in the local machine\n"
"rpcap://hostname:port ==> lists interfaces in a remote machine\n"
" (rpcapd daemon must be up and running\n"
" and it must accept 'null' authentication)\n"
"
file://foldername ==> lists all pcap files in the give folder\n\n"
"Enter your choice: ");
fgets(source, PCAP_ERRBUF_SIZE, stdin);
source[PCAP_ERRBUF_SIZE] = '\0';
/* Retrieve the interfaces list */
if (pcap_findalldevs_ex(source, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n",errbuf);
exit(1);
}
/* Scan the list printing every entry */
for(d=alldevs;d;d=d->next)
{
ifprint(d);
}
pcap_freealldevs(alldevs);
return 1;
}
/* Print all the available information on the given interface */
void ifprint(pcap_if_t *d)
{
pcap_addr_t *a;
char ip6str[128];
/* Name */
printf("%s\n",d->name);
/* Description */
if (d->description)
printf("\tDescription: %s\n",d->description);
/* Loopback Address*/
printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
/* IP addresses */
for(a=d->addresses;a;a=a->next) {
printf("\tAddress Family: #%d\n",a->addr->sa_family);
switch(a->addr->sa_family)
{
case AF_INET:
printf("\tAddress Family Name: AF_INET\n");
if (a->addr)
printf("\tAddress: %s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
if (a->netmask)
printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
if (a->broadaddr)
printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
if (a->dstaddr)
printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
break;
case AF_INET6:
printf("\tAddress Family Name: AF_INET6\n");
if (a->addr)
printf("\tAddress: %s\n", ip6tos(a->addr, ip6str, sizeof(ip6str)));
break;
default:
printf("\tAddress Family Name: Unknown\n");
break;
}
}
printf("\n");
}
/* From tcptraceroute, convert a numeric IP address to a string */
#define IPTOSBUFFERS 12
char *iptos(u_long in)
{
static char output[IPTOSBUFFERS][3*4+3+1];
static short which;
u_char *p;
p = (u_char *)∈
which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
return output[which];
}
char* ip6tos(struct sockaddr *sockaddr, char *address, int addrlen)
{
socklen_t sockaddrlen;
#ifdef WIN32
sockaddrlen = sizeof(struct sockaddr_in6);
#else
sockaddrlen = sizeof(struct sockaddr_storage);
#endif
if(getnameinfo(sockaddr,
sockaddrlen,
address,
addrlen,
NULL,
0,
NI_NUMERICHOST) != 0) address = NULL;
return address;
}
WinPcap tutorial: a step by step guide to using WinPcap(3)
Opening an adapter and capturing the packets
打開一個適配器開始抓取
現(xiàn)在我們已經知道了怎樣去獲取一個適配器并使用它,讓我們開始真正的工作-----開始抓取網絡數(shù)據(jù)包吧。在這一課中我們將寫一個程序,這個程序將在我們選擇的適配器上監(jiān)聽,并抓取通過這個適配器上的每一個數(shù)據(jù)包,打印其中的一些信息。
我們主要使用的函數(shù)是pcap_open(),這個函數(shù)的功能是打開一個抓取設備。在這里有必要對其中的幾個參數(shù)snaplen, flags and to_ms作一下說明。
snaplen指定了我們所要抓取的包的長度(譯者:也就是我們想抓多長就設置多長)。在一些操作系統(tǒng)(如xBSD和Win32)中,底層驅動可以通過配置只抓取數(shù)據(jù)包的開始部分:這樣就減少了拷貝給應用程序的數(shù)據(jù)量,因此可以提高抓取效率。在這個例子里我們使用65536這個比我們所能遇到的最大的MTU還大的數(shù)字。這樣我們就能確保我們的程序可以抓到整個數(shù)據(jù)包。
flags:最重要的標志是一個指示適配器是否工作在混雜模式下的。在正常狀況下,一個適配器僅僅抓取網絡中目的地是它自己的數(shù)據(jù)包;因此其他主機交換的數(shù)據(jù)包都被忽略。相反,當適配器處在混雜模式下的時候它就會抓取所有的數(shù)據(jù)包而不管是不是發(fā)給它的。這就意味著在共享媒體(如非交換的以太網)上,WinPcap將能夠抓取其他主機的數(shù)據(jù)包?;祀s模式是大部分抓取程序的默認模式,所以在下面的例子中我們就開啟它。
to_ms以豪秒為單位指定了讀取操作的超時界限。在適配器上一個讀取操作(比如,pcap_dispatch() 或者 pcap_next_ex())將總是在to_ms豪秒后返回,即使網絡中沒有數(shù)據(jù)包可供抓取。如果適配器工作在統(tǒng)計模式(如果對此不了解,請看課程9),to_ms還定義了統(tǒng)計報告之間的間隔。把tm_ms設置為0意味著沒有時間限制,如果沒有數(shù)據(jù)包到達適配器,讀取操作將永遠不會返回。反過來,把tm_ms設置為-1將使讀取操作總是立即返回。
#i nclude "pcap.h"
/* 數(shù)據(jù)包處理程序,回調函數(shù) */
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];
/* Retrieve the device list on the local machine */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* Print the list */
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the device */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
/* At this point, we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* start the capture */
pcap_loop(adhandle, 0, packet_handler, NULL);
return 0;
}
/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
struct tm *ltime;
char timestr[16];
/* convert the timestamp to readable format */
ltime=localtime(&header->ts.tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);
}
一旦打開了適配器,就由pcap_dispatch() 或者pcap_loop()開始抓取。這兩個函數(shù)都非常慢,所不同的是pcap_ dispatch()一旦超時就可以返回(盡管不能保證)而pcap_loop() 會一直等到cnt包被抓到才會返回,所以這個函數(shù)在沒有數(shù)據(jù)包的網絡中會阻塞任意的時間。在這個例子中pcap_loop()就足夠了,而pcap_dispatch() 則可以在一個更復雜的程序中使用。
這兩個函數(shù)都有一個回調函數(shù)作為參數(shù),packet_handler,這個參數(shù)指定的函數(shù)將收到數(shù)據(jù)包。這個函數(shù)在每一個新的數(shù)據(jù)包從網絡中到達時都會被libpcap調用,并且會收到一個反映pcap_loop() 和 pcap_dispatch()函數(shù)的生成狀態(tài),和一個結構體header。這個header中帶有一些數(shù)據(jù)包中的信息,比如時間戳和長度、包括所有協(xié)議頭的實際數(shù)據(jù)包。注意結構體中是沒有CRC的,因為在數(shù)據(jù)確認后已經被適配器去掉了。也要注意大部分適配器丟棄了CRC錯誤的數(shù)據(jù)包,因此Winpcap不能抓取這些包。
上面的例子從pcap_pkthdr中提取每個數(shù)據(jù)包的時間戳和長度并顯示它們。
請注意使用pcap_loop()可能有一個缺點,就是使用這個函數(shù)時包處理函數(shù)要被包抓取驅動程序來調用;因此應用程序不能直接控制它。另一種方法(并且更容易理解)是使用函數(shù)pcap_next_ex(),這個函數(shù)我們將在下一個例子中使用。
4.
Capturing the packets without the callback
這節(jié)課程中的例子程序完成的功能和上節(jié)課的一樣,但是使用的是pcap_next_ex()而不是pcap_loop().
基于回調捕獲機制的 pcap_loop()是非常優(yōu)雅的,在很多情況下都是一個不錯的選擇。不過,有時候處理一個回調函數(shù)顯得不太現(xiàn)實 --- 通常這會使程序更加復雜,在使用多線程或者c++類的時候尤其如此。
在這種情況下,可以直接調用 pcap_next_ex() 來返回一個數(shù)據(jù)包 -- 這樣程序員可以在僅僅想使用它們的時候再處理 pcap_next_ex() 返回的數(shù)據(jù)包。
這個函數(shù)的參數(shù)和回調函數(shù) pcap_loop() 的一樣 -- 由一個網絡適配器描述符作為入口參數(shù)和兩個指針作為出口參數(shù),這兩個指針將在函數(shù)中被初始化,然后再返回給用戶(一個指向pcap_pkthdr 結構,另一個指向一個用作數(shù)據(jù)緩沖區(qū)的內存區(qū)域)。
在下面的程序中,我們繼續(xù)使用上一節(jié)課中的例子的數(shù)據(jù)處理部分的代碼,把這些代碼拷貝到main()函數(shù)中pcap_next_ex()的后面。
#i nclude "pcap.h"
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
int res;
char errbuf[PCAP_ERRBUF_SIZE];
struct tm *ltime;
char timestr[16];
struct pcap_pkthdr *header;
u_char *pkt_data;
/* Retrieve the device list on the local machine */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* Print the list */
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the device */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture.
// 65536 guarantees that the whole packet will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
/* At this point, we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* Retrieve the packets */
while((res = pcap_next_ex( adhandle, &header, &pkt_data)) >= 0){
if(res == 0)
/* Timeout elapsed */
continue;
/* convert the timestamp to readable format */
ltime=localtime(&header->ts.tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);
}
if(res == -1){
printf("Error reading the packets: %s\n", pcap_geterr(adhandle));
return -1;
}
return 0;
為什么我們使用 pcap_next_ex() 而不是 pcap_next()?因為 pcap_next() 有一些缺陷。首先,它的效率不高,因為它雖然隱藏了回調模式但是仍然依賴于 pcap_dispatch()。其次,它不能檢測EOF(譯者注:end of file,意思是文件結束標志),所以當我們從一個文件中收集包的時候不是很有用(譯者注:winpcap可以把捕獲的數(shù)據(jù)包以很高的效率存在文件中,留待以后分析,這一點以后的課程中也會講到)。
也要注意 pcap_next_ex() 對于成功調用、超時、錯誤和EOF狀態(tài)會返回不同的值。
5.Filtering the traffic
WinPcap提供的最強大的特性之一就是過濾引擎。它是被集成到了winpcap的捕獲機制中的,提供了一種非常高效的方法來獲取部分網絡數(shù)據(jù)。被用來過濾數(shù)據(jù)包的函數(shù)是 pcap_compile() 和 pcap_setfilter()。
pcap_compile() 接受一個包含布爾表達式的字符串,生成可以被捕獲包驅動中的過濾引擎解釋的代碼。布爾表達式的語法在這個文檔的Filtering expression syntax 那一節(jié)(譯者注:其實和tcpdump的一樣,如果了解tcpdump,可以直接按照tcpdump的語法來寫)。
pcap_setfilter() 綁定一個過濾器到一個在核心驅動中的捕獲進程中。一旦 pcap_setfilter() 被調用,這個過濾器就會對網絡來的所有數(shù)據(jù)包進行過濾,所有符合條件的數(shù)據(jù)包(按照布爾表達式來計算出結果是真的數(shù)據(jù)包)都會被拷貝給進行捕獲的應用程序。
下面的代碼說明了怎樣編譯和設置一個過濾器。注意我們必須得到說明適配器的 pcap_if 結構中的子網掩碼,因為一些被 pcap_compile() 生成的過濾器需要它。這個過濾器中傳遞給 pcap_compile() 的字符串是 "ip and tcp",意思是“僅僅把IPv4 and TCP 數(shù)據(jù)包保存下來并交付給應用程序”。
if (d->addresses != NULL)
/* Retrieve the mask of the first address of the interface */
netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* If the interface is without an address we suppose to be in a C class network */
netmask=0xffffff;
//compile the filter
if (pcap_compile(adhandle, &fcode, "ip and tcp", 1, netmask) < 0)
{
fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
//set the filter
if (pcap_setfilter(adhandle, &fcode) < 0)
{
fprintf(stderr,"\nError setting the filter.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
如果你想看一些在這節(jié)課中講述的使用過濾功能的代碼,請看下節(jié)課中的例子,Interpreting the packets.
6.Interpreting the packets.
現(xiàn)在我們已經能夠捕獲并且過濾網絡數(shù)據(jù)包,下面我們就把我們的知識運用到一個簡單的“真實的”應用程序中去。
在這節(jié)課中我們將從前面的課程中拷貝代碼并用它們來構造出一個更有用途的程序。這個程序主要的目的就是說明怎樣分析和解釋我們已經捕獲的數(shù)據(jù)包的協(xié)議結構。最終的應用程序,叫做UDPdump,會打印出一個在我們的網絡中的UDP數(shù)據(jù)包的概要。
在開始階段我們選擇分析并顯示UDP協(xié)議,因為UDP協(xié)議比其他的協(xié)議比如TCP協(xié)議更容易理解,從而非常適合作為初始階段的例子。還是讓我們開始看代碼吧:
/*
* Copyright (c) 1999 - 2003
* NetGroup, Politecnico di Torino (Italy)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Politecnico di Torino nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#i nclude "pcap.h"
/* 4 bytes IP address */
typedef struct ip_address{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/* IPv4 header */
typedef struct ip_header{
u_char ver_ihl; // Version (4 bits) + Internet header length (4 bits)
u_char tos; // Type of service
u_short tlen; // Total length
u_short identification; // Identification
u_short flags_fo; // Flags (3 bits) + Fragment offset (13 bits)
u_char ttl; // Time to live
u_char proto; // Protocol
u_short crc; // Header checksum
ip_address saddr; // Source address
ip_address daddr; // Destination address
u_int op_pad; // Option + Padding
}ip_header;
/* UDP header*/
typedef struct udp_header{
u_short sport; // Source port
u_short dport; // Destination port
u_short len; // Datagram length
u_short crc; // Checksum
}udp_header;
/* prototype of the 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];
u_int netmask;
char packet_filter[] = "ip and udp";
struct bpf_program fcode;
/* Retrieve the device list */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* Print the list */
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the adapter */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // remote authentication
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Check the link layer. We support only Ethernet for simplicity. */
if(pcap_datalink(adhandle) != DLT_EN10MB)
{
fprintf(stderr,"\nThis program works only on Ethernet networks.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
if(d->addresses != NULL)
/* Retrieve the mask of the first address of the interface */
netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* If the interface is without addresses we suppose to be in a C class network */
netmask=0xffffff;
//compile the filter
if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) <0 )
{
fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
//set the filter
if (pcap_setfilter(adhandle, &fcode)<0)
{
fprintf(stderr,"\nError setting the filter.\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
/* At this point, we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* start the capture */
pcap_loop(adhandle, 0, packet_handler, NULL);
return 0;
}
/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
struct tm *ltime;
char timestr[16];
ip_header *ih;
udp_header *uh;
u_int ip_len;
u_short sport,dport;
/* convert the timestamp to readable format */
ltime=localtime(&header->ts.tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
/* print timestamp and length of the packet */
printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len);
/* retireve the position of the ip header */
ih = (ip_header *) (pkt_data +
14); //length of ethernet header
/* retireve the position of the udp header */
ip_len = (ih->ver_ihl & 0xf) * 4;
uh = (udp_header *) ((u_char*)ih + ip_len);
/* convert from network byte order to host byte order */
sport = ntohs( uh->sport );
dport = ntohs( uh->dport );
/* print ip addresses and udp ports */
printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n",
ih->saddr.byte1,
ih->saddr.byte2,
ih->saddr.byte3,
ih->saddr.byte4,
sport,
ih->daddr.byte1,
ih->daddr.byte2,
ih->daddr.byte3,
ih->daddr.byte4,
dport);
}
首先,我們設定過濾器的過濾規(guī)則為"ip and udp"。這樣我們就能夠確保 packet_handler() 函數(shù)只返回IPv4的UDP數(shù)據(jù)包:這可以簡化分析工作,改善程序的效率。
在程序開始部分,我們已經建立了兩個結構體,分別用來描述IP和UDP頭。這兩個結構體被 packet_handler() 函數(shù)用來正確定位IP和UDP頭字段。
packet_handler(),雖然被限制為只能解析一個協(xié)議(IPv4的UDP),但是仍然可以說明那些象 tcpdump/WinDump 一樣復雜的嗅探器怎樣解析網絡數(shù)據(jù)包的。因為我們對MAC頭不感興趣,所以我們就忽略它。為了簡單起見,在開始捕獲之前,我們用 pcap_datalink() 函數(shù)來檢查MAC層,以確保我們正在處理的是以太網楨。這樣我們就能夠確保MAC頭正好是14字節(jié)。(譯者注:這里因為是直接把以太網楨加14來獲得IP包的,所以要確保的卻是以太網楨,不然就會產生錯誤。)
IP頭就緊跟在MAC頭后面。我們將從IP頭中取得IP源地址和目的地址。
到達UDP頭復雜了一點,因為IP頭的長度是不固定的。因此,我們使用IP頭的長度字段來確定它的大小。于是我們就知道了UDP頭的位置,然后就可以取得源端口和目的端口。
我們把獲取的數(shù)據(jù)打印到屏幕上,結果如下:
1. {A7FD048A-5D4B-478E-B3C1-34401AC3B72F} (Xircom t 10/100 Adapter)
Enter the interface number (1-2):1
listening on Xircom CardBus Ethernet 10/100 Adapter...
16:13:15.312784 len:87 130.192.31.67.2682 -> 130.192.3.21.53
16:13:15.314796 len:137 130.192.3.21.53 -> 130.192.31.67.2682
16:13:15.322101 len:78 130.192.31.67.2683 -> 130.192.3.21.53
后三行的每一行都代表了一個不同的數(shù)據(jù)包。
7.Handling offline dump files
在這節(jié)課中我們來學習怎樣將數(shù)據(jù)包保存到一個文件中。Winpcap提供了一系列保存網絡數(shù)據(jù)包到一個文件和從文件中讀取保存內容的函數(shù) -- 這節(jié)課就是講述怎樣使用這些函數(shù)的。同時也會展示怎樣winpcap核心中的保存特性來獲得高性能的存儲(注意:現(xiàn)在,由于新的內核緩存的一些問題,這個特性已經不能使用了)。
dump文件的格式和libpcap的是一樣的。在這個格式里捕獲的數(shù)據(jù)包是用二進制的形式來保存的,現(xiàn)在已經作為標準被許多網絡工具使用,其中就包括WinDump, Ethereal 和 Snort。
保存數(shù)據(jù)包到一個dump文件:
首先,讓我們看看怎樣用libpcap格式來寫數(shù)據(jù)包。下面的例子從選擇的網絡接口中捕獲數(shù)據(jù)并保存到一個由用戶提供名字的文件中。
#i nclude "pcap.h"
/* prototype of the packet handler */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
main(int argc, char **argv)
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_dumper_t *dumpfile;
/* Check command line */
if(argc != 2)
{
printf("usage: %s filename", argv[0]);
return -1;
}
/* Retrieve the device list on the local machine */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* Print the list */
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the device */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Open the dump file */
dumpfile = pcap_dump_open(adhandle, argv[1]);
if(dumpfile==NULL)
{
fprintf(stderr,"\nError opening output file\n");
return -1;
}
printf("\nlistening on %s... Press Ctrl+C to stop...\n", d->description);
/* At this point, we no longer need the device list. Free it */
pcap_freealldevs(alldevs);
/* start the capture */
pcap_loop(adhandle, 0, packet_handler, (unsigned char *)dumpfile);
return 0;
}
/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *dumpfile, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
/* save the packet on the dump file */
pcap_dump(dumpfile, header, pkt_data);
}
現(xiàn)在你也看到了,這個程序的結構和我們以前學的程序的結構非常類似。只有兩點不同:
打開網絡接口后跟著就調用pcap_dump_open() ,這個調用打開一個dump文件并把它和網絡接口綁定到一起。
在 packet_handler() 回調函數(shù)中數(shù)據(jù)包被 pcap_dump() 函數(shù)寫到打開的文件中去。pcap_dump() 的參數(shù)與 packet_handler() 中的參數(shù)一一對應。
從一個dump文件中讀取數(shù)據(jù)包:
現(xiàn)在我們已經有了一個有效的dump文件,我們可以嘗試來讀取它的內容。下面的代碼打開一個dump文件并顯示出里面包含的每一個數(shù)據(jù)包。文件打開是用的 pcap_open_offline() 函數(shù),然后一般是用 pcap_loop() 來按照順序來讀取數(shù)據(jù)包。就象你看到的一樣,從一個dump文件中讀取數(shù)據(jù)包和從一個物理接口來捕獲數(shù)據(jù)包基本上是一樣的。
這個例子還介紹了另一個函數(shù):pcap_createsrcsrc()。這個函數(shù)生成一個以一個標記開始的數(shù)據(jù)源字符串,這個標記用來告訴winpcap數(shù)據(jù)源的類型,比如"rpcap://" 代表一個適配器,"file://" 代表一個文件。如果已經使用了 pcap_findalldevs_ex() (這個函數(shù)的返回值已經包含了數(shù)據(jù)源字符串),那么就不需要再使用 pcap_createsrcsrc() 了。不過,因為文件名字是用戶輸入的,所以在這個例子里我們還是要使用它的。
#i nclude <stdio.h>
#i nclude <pcap.h>
#define LINE_LEN 16
void dispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
if(argc != 2){
printf("usage: %s filename", argv[0]);
return -1;
}