循序漸進(jìn)學(xué)WinPcap
風(fēng)兒 發(fā)表于 2006-4-19 10:48:00 WinPcap tutorial: a step by step guide to using WinPcap 詳細(xì)說(shuō)明 這部分展示了怎樣使用WinPcap API。這個(gè)教程通過(guò)一系列的課程,從基本的函數(shù)(取得網(wǎng)卡列表,開(kāi)始抓包,等等)到最高級(jí)的應(yīng)用(處理數(shù)據(jù)包包發(fā)送隊(duì)列和統(tǒng)計(jì)網(wǎng)絡(luò)流量),一步一步地教會(huì)讀者如何用WinPcap來(lái)編程。 這里提供了幾個(gè)雖然簡(jiǎn)單但卻完整的程序段作為參考:所有的源代碼都有其余部分的鏈接,,只需要點(diǎn)擊一下函數(shù)和數(shù)據(jù)結(jié)構(gòu)就可以跳轉(zhuǎn)到相應(yīng)的文檔。 這些例子都是使用c語(yǔ)言寫(xiě)的,所以在讀本教程前要了解一些基本的c語(yǔ)言的知識(shí),。而且,,這是一個(gè)關(guān)于處理原始網(wǎng)絡(luò)包的庫(kù)的教程,所以假定讀者具有良好的網(wǎng)絡(luò)和網(wǎng)絡(luò)協(xié)議方面的知識(shí),。 WinPcap tutorial: a step by step guide to using WinPcap(1)
獲取網(wǎng)絡(luò)設(shè)備列表 基本上所有基于Winpcap的應(yīng)用程序所做的第一件事情都是獲取一個(gè)已經(jīng)綁定的網(wǎng)卡列表,。為此,libcap和winpcap都提供了pcap_findalldevs_ex()函數(shù):這個(gè)函數(shù)返回一個(gè)指向pcap_if結(jié)構(gòu)的鏈表,,其中的每一項(xiàng)都包含了一個(gè)已經(jīng)綁定的適配器的全部信息,。其中name和description這兩項(xiàng)分別包含了相應(yīng)設(shè)備的名稱(chēng)和描述。 下面的代碼取得適配器列表并在屏幕上顯示出來(lái),,如果適配器沒(méi)有被發(fā)現(xiàn)就把顯示錯(cuò)誤,。 #i nclude "pcap.h" main()
{ pcap_if_t *alldevs; pcap_if_t *d; int i=0; char errbuf[PCAP_ERRBUF_SIZE]; /* 取得本機(jī)的網(wǎng)絡(luò)設(shè)備列表 */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL /* 這個(gè)參數(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); } 關(guān)于這段代碼的說(shuō)明。 首先,,就象其他的libpcap函數(shù),,pcap_findalldevs_ex()有一個(gè)errbuf參數(shù),。如果發(fā)生錯(cuò)誤,,libcap就把一個(gè)錯(cuò)誤說(shuō)明放到這個(gè)參數(shù)指向的字符串中。 其次,,并不是所有的操作系統(tǒng)都支持libpcap提供的網(wǎng)絡(luò)接口描述,,因此如果我們想寫(xiě)一個(gè)可移植的程序,我們必須考慮description為null的情況:這個(gè)時(shí)候我們就輸出字符串"No description available" ,。 最后要提醒一下:一旦我們完成了這些動(dòng)作,,就應(yīng)該釋放用pcap_freealldevs()列表。 讓我們編譯并運(yùn)行這段簡(jiǎn)單的代碼,。在unix或者cygwin中編譯的話,,打入下面的命令: gcc -o testaprog testprog.c -lpcap 在windows中,你需要?jiǎng)?chuàng)建一個(gè)project,,照著手冊(cè)中的Using WinPcap in your programs 那一章做就可以了,。但是,我們建議你使用the WinPcap developer's pack(可以在http://www. 下載),,因?yàn)檫@個(gè)開(kāi)發(fā)包提供了許多教程中使用的代碼示例,,這些示例都已經(jīng)配置好了,其中包含了編譯執(zhí)行例子所需要的include文件和lib文件,。 編譯好了程序后,,在我的winxp工作站上運(yùn)行的結(jié)果: 1. {4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS) Ethernet Adapter) 2. {5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI) 就象你看到的一樣,在windows系統(tǒng)中網(wǎng)絡(luò)適配器的名稱(chēng)(在打開(kāi)網(wǎng)絡(luò)適配器時(shí)將被傳遞給libcap)根本沒(méi)有辦法理解,,所以附加說(shuō)明可能是非常有幫助的,。 WinPcap tutorial: a step by step guide to using WinPcap(2)
Obtaining advanced information about installed devices 課程1(Obtaining the device list)說(shuō)明了怎樣得到可用適配器的基本信息(比如設(shè)備名和說(shuō)明)。實(shí)際上,winpcap也提供其他的高級(jí)信息,。每個(gè)由pcap_findalldevs_ex()返回的pcap_if 結(jié)構(gòu)體都包含了一個(gè)pcap_addr結(jié)構(gòu)列表,,里面包含的內(nèi)容有: 一個(gè)接口的地址列表。 一個(gè)子網(wǎng)掩碼列表(每一個(gè)都與地址列表中的條目一一對(duì)應(yīng)),。 一個(gè)廣播地址列表(每一個(gè)都與地址列表中的條目一一對(duì)應(yīng)),。 一個(gè)目的地址列表(每一個(gè)都與地址列表中的條目一一對(duì)應(yīng))。 除此之外,,pcap_findalldevs_ex() 也能返回遠(yuǎn)程的適配器和任給的本地文件夾的pcap文件列表,。 下面的例子提供了一個(gè)ifprint() 函數(shù)來(lái)打印出一個(gè)pcap_if 結(jié)構(gòu)中的所有內(nèi)容。程序?qū)γ恳粋€(gè)pcap_findalldevs_ex()返回的條目都調(diào)用一次這個(gè)函數(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 打開(kāi)一個(gè)適配器開(kāi)始抓取 現(xiàn)在我們已經(jīng)知道了怎樣去獲取一個(gè)適配器并使用它,,讓我們開(kāi)始真正的工作-----開(kāi)始抓取網(wǎng)絡(luò)數(shù)據(jù)包吧。在這一課中我們將寫(xiě)一個(gè)程序,,這個(gè)程序?qū)⒃谖覀冞x擇的適配器上監(jiān)聽(tīng),,并抓取通過(guò)這個(gè)適配器上的每一個(gè)數(shù)據(jù)包,打印其中的一些信息,。 我們主要使用的函數(shù)是pcap_open(),,這個(gè)函數(shù)的功能是打開(kāi)一個(gè)抓取設(shè)備。在這里有必要對(duì)其中的幾個(gè)參數(shù)snaplen, flags and to_ms作一下說(shuō)明,。 snaplen指定了我們所要抓取的包的長(zhǎng)度(譯者:也就是我們想抓多長(zhǎng)就設(shè)置多長(zhǎng)),。在一些操作系統(tǒng)(如xBSD和Win32)中,底層驅(qū)動(dòng)可以通過(guò)配置只抓取數(shù)據(jù)包的開(kāi)始部分:這樣就減少了拷貝給應(yīng)用程序的數(shù)據(jù)量,,因此可以提高抓取效率,。在這個(gè)例子里我們使用65536這個(gè)比我們所能遇到的最大的MTU還大的數(shù)字。這樣我們就能確保我們的程序可以抓到整個(gè)數(shù)據(jù)包,。 flags:最重要的標(biāo)志是一個(gè)指示適配器是否工作在混雜模式下的,。在正常狀況下,一個(gè)適配器僅僅抓取網(wǎng)絡(luò)中目的地是它自己的數(shù)據(jù)包,;因此其他主機(jī)交換的數(shù)據(jù)包都被忽略,。相反,當(dāng)適配器處在混雜模式下的時(shí)候它就會(huì)抓取所有的數(shù)據(jù)包而不管是不是發(fā)給它的,。這就意味著在共享媒體(如非交換的以太網(wǎng))上,,WinPcap將能夠抓取其他主機(jī)的數(shù)據(jù)包?;祀s模式是大部分抓取程序的默認(rèn)模式,,所以在下面的例子中我們就開(kāi)啟它。 to_ms以豪秒為單位指定了讀取操作的超時(shí)界限,。在適配器上一個(gè)讀取操作(比如,,pcap_dispatch() 或者 pcap_next_ex())將總是在to_ms豪秒后返回,即使網(wǎng)絡(luò)中沒(méi)有數(shù)據(jù)包可供抓取。如果適配器工作在統(tǒng)計(jì)模式(如果對(duì)此不了解,,請(qǐng)看課程9),,to_ms還定義了統(tǒng)計(jì)報(bào)告之間的間隔。把tm_ms設(shè)置為0意味著沒(méi)有時(shí)間限制,,如果沒(méi)有數(shù)據(jù)包到達(dá)適配器,,讀取操作將永遠(yuǎn)不會(huì)返回。反過(guò)來(lái),,把tm_ms設(shè)置為-1將使讀取操作總是立即返回,。 #i nclude "pcap.h" /* 數(shù)據(jù)包處理程序,回調(diào)函數(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); } 一旦打開(kāi)了適配器,,就由pcap_dispatch() 或者pcap_loop()開(kāi)始抓取,。這兩個(gè)函數(shù)都非常慢,所不同的是pcap_ dispatch()一旦超時(shí)就可以返回(盡管不能保證)而pcap_loop() 會(huì)一直等到cnt包被抓到才會(huì)返回,,所以這個(gè)函數(shù)在沒(méi)有數(shù)據(jù)包的網(wǎng)絡(luò)中會(huì)阻塞任意的時(shí)間,。在這個(gè)例子中pcap_loop()就足夠了,而pcap_dispatch() 則可以在一個(gè)更復(fù)雜的程序中使用,。
這兩個(gè)函數(shù)都有一個(gè)回調(diào)函數(shù)作為參數(shù),,packet_handler,這個(gè)參數(shù)指定的函數(shù)將收到數(shù)據(jù)包,。這個(gè)函數(shù)在每一個(gè)新的數(shù)據(jù)包從網(wǎng)絡(luò)中到達(dá)時(shí)都會(huì)被libpcap調(diào)用,,并且會(huì)收到一個(gè)反映pcap_loop() 和 pcap_dispatch()函數(shù)的生成狀態(tài),,和一個(gè)結(jié)構(gòu)體header,。這個(gè)header中帶有一些數(shù)據(jù)包中的信息,比如時(shí)間戳和長(zhǎng)度,、包括所有協(xié)議頭的實(shí)際數(shù)據(jù)包,。注意結(jié)構(gòu)體中是沒(méi)有CRC的,因?yàn)樵跀?shù)據(jù)確認(rèn)后已經(jīng)被適配器去掉了,。也要注意大部分適配器丟棄了CRC錯(cuò)誤的數(shù)據(jù)包,,因此Winpcap不能抓取這些包。 上面的例子從pcap_pkthdr中提取每個(gè)數(shù)據(jù)包的時(shí)間戳和長(zhǎng)度并顯示它們,。 請(qǐng)注意使用pcap_loop()可能有一個(gè)缺點(diǎn),,就是使用這個(gè)函數(shù)時(shí)包處理函數(shù)要被包抓取驅(qū)動(dòng)程序來(lái)調(diào)用;因此應(yīng)用程序不能直接控制它,。另一種方法(并且更容易理解)是使用函數(shù)pcap_next_ex(),,這個(gè)函數(shù)我們將在下一個(gè)例子中使用。 4.
Capturing the packets without the callback 這節(jié)課程中的例子程序完成的功能和上節(jié)課的一樣,,但是使用的是pcap_next_ex()而不是pcap_loop(). 基于回調(diào)捕獲機(jī)制的 pcap_loop()是非常優(yōu)雅的,,在很多情況下都是一個(gè)不錯(cuò)的選擇。不過(guò),有時(shí)候處理一個(gè)回調(diào)函數(shù)顯得不太現(xiàn)實(shí) --- 通常這會(huì)使程序更加復(fù)雜,,在使用多線程或者c++類(lèi)的時(shí)候尤其如此,。 在這種情況下,可以直接調(diào)用 pcap_next_ex() 來(lái)返回一個(gè)數(shù)據(jù)包 -- 這樣程序員可以在僅僅想使用它們的時(shí)候再處理 pcap_next_ex() 返回的數(shù)據(jù)包,。 這個(gè)函數(shù)的參數(shù)和回調(diào)函數(shù) pcap_loop() 的一樣 -- 由一個(gè)網(wǎng)絡(luò)適配器描述符作為入口參數(shù)和兩個(gè)指針作為出口參數(shù),,這兩個(gè)指針將在函數(shù)中被初始化,然后再返回給用戶(hù)(一個(gè)指向pcap_pkthdr 結(jié)構(gòu),,另一個(gè)指向一個(gè)用作數(shù)據(jù)緩沖區(qū)的內(nèi)存區(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()?因?yàn)?pcap_next() 有一些缺陷。首先,,它的效率不高,,因?yàn)樗m然隱藏了回調(diào)模式但是仍然依賴(lài)于 pcap_dispatch()。其次,,它不能檢測(cè)EOF(譯者注:end of file,,意思是文件結(jié)束標(biāo)志),所以當(dāng)我們從一個(gè)文件中收集包的時(shí)候不是很有用(譯者注:winpcap可以把捕獲的數(shù)據(jù)包以很高的效率存在文件中,,留待以后分析,,這一點(diǎn)以后的課程中也會(huì)講到)。 也要注意 pcap_next_ex() 對(duì)于成功調(diào)用,、超時(shí),、錯(cuò)誤和EOF狀態(tài)會(huì)返回不同的值。 5.Filtering the traffic
WinPcap提供的最強(qiáng)大的特性之一就是過(guò)濾引擎,。它是被集成到了winpcap的捕獲機(jī)制中的,,提供了一種非常高效的方法來(lái)獲取部分網(wǎng)絡(luò)數(shù)據(jù)。被用來(lái)過(guò)濾數(shù)據(jù)包的函數(shù)是 pcap_compile() 和 pcap_setfilter(),。 pcap_compile() 接受一個(gè)包含布爾表達(dá)式的字符串,,生成可以被捕獲包驅(qū)動(dòng)中的過(guò)濾引擎解釋的代碼。布爾表達(dá)式的語(yǔ)法在這個(gè)文檔的Filtering expression syntax 那一節(jié)(譯者注:其實(shí)和tcpdump的一樣,,如果了解tcpdump,,可以直接按照tcpdump的語(yǔ)法來(lái)寫(xiě))。 pcap_setfilter() 綁定一個(gè)過(guò)濾器到一個(gè)在核心驅(qū)動(dòng)中的捕獲進(jìn)程中,。一旦 pcap_setfilter() 被調(diào)用,,這個(gè)過(guò)濾器就會(huì)對(duì)網(wǎng)絡(luò)來(lái)的所有數(shù)據(jù)包進(jìn)行過(guò)濾,所有符合條件的數(shù)據(jù)包(按照布爾表達(dá)式來(lái)計(jì)算出結(jié)果是真的數(shù)據(jù)包)都會(huì)被拷貝給進(jìn)行捕獲的應(yīng)用程序,。 下面的代碼說(shuō)明了怎樣編譯和設(shè)置一個(gè)過(guò)濾器,。注意我們必須得到說(shuō)明適配器的 pcap_if 結(jié)構(gòu)中的子網(wǎng)掩碼,,因?yàn)橐恍┍?pcap_compile() 生成的過(guò)濾器需要它。這個(gè)過(guò)濾器中傳遞給 pcap_compile() 的字符串是 "ip and tcp",意思是“僅僅把IPv4 and TCP 數(shù)據(jù)包保存下來(lái)并交付給應(yīng)用程序”,。 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é)課中講述的使用過(guò)濾功能的代碼,,請(qǐng)看下節(jié)課中的例子,Interpreting the packets. 6.Interpreting the packets.
現(xiàn)在我們已經(jīng)能夠捕獲并且過(guò)濾網(wǎng)絡(luò)數(shù)據(jù)包,,下面我們就把我們的知識(shí)運(yùn)用到一個(gè)簡(jiǎn)單的“真實(shí)的”應(yīng)用程序中去,。 在這節(jié)課中我們將從前面的課程中拷貝代碼并用它們來(lái)構(gòu)造出一個(gè)更有用途的程序。這個(gè)程序主要的目的就是說(shuō)明怎樣分析和解釋我們已經(jīng)捕獲的數(shù)據(jù)包的協(xié)議結(jié)構(gòu),。最終的應(yīng)用程序,,叫做UDPdump,會(huì)打印出一個(gè)在我們的網(wǎng)絡(luò)中的UDP數(shù)據(jù)包的概要。 在開(kāi)始階段我們選擇分析并顯示UDP協(xié)議,,因?yàn)閁DP協(xié)議比其他的協(xié)議比如TCP協(xié)議更容易理解,,從而非常適合作為初始階段的例子。還是讓我們開(kāi)始看代碼吧: /* * 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); } 首先,,我們?cè)O(shè)定過(guò)濾器的過(guò)濾規(guī)則為"ip and udp",。這樣我們就能夠確保 packet_handler() 函數(shù)只返回IPv4的UDP數(shù)據(jù)包:這可以簡(jiǎn)化分析工作,改善程序的效率,。 在程序開(kāi)始部分,,我們已經(jīng)建立了兩個(gè)結(jié)構(gòu)體,分別用來(lái)描述IP和UDP頭,。這兩個(gè)結(jié)構(gòu)體被 packet_handler() 函數(shù)用來(lái)正確定位IP和UDP頭字段,。 packet_handler(),雖然被限制為只能解析一個(gè)協(xié)議(IPv4的UDP),,但是仍然可以說(shuō)明那些象 tcpdump/WinDump 一樣復(fù)雜的嗅探器怎樣解析網(wǎng)絡(luò)數(shù)據(jù)包的,。因?yàn)槲覀儗?duì)MAC頭不感興趣,所以我們就忽略它,。為了簡(jiǎn)單起見(jiàn),,在開(kāi)始捕獲之前,我們用 pcap_datalink() 函數(shù)來(lái)檢查MAC層,,以確保我們正在處理的是以太網(wǎng)楨。這樣我們就能夠確保MAC頭正好是14字節(jié),。(譯者注:這里因?yàn)槭侵苯影岩蕴W(wǎng)楨加14來(lái)獲得IP包的,,所以要確保的卻是以太網(wǎng)楨,不然就會(huì)產(chǎn)生錯(cuò)誤,。) IP頭就緊跟在MAC頭后面,。我們將從IP頭中取得IP源地址和目的地址。 到達(dá)UDP頭復(fù)雜了一點(diǎn),,因?yàn)镮P頭的長(zhǎng)度是不固定的,。因此,,我們使用IP頭的長(zhǎng)度字段來(lái)確定它的大小。于是我們就知道了UDP頭的位置,,然后就可以取得源端口和目的端口,。 我們把獲取的數(shù)據(jù)打印到屏幕上,結(jié)果如下: 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 后三行的每一行都代表了一個(gè)不同的數(shù)據(jù)包,。
7.Handling offline dump files
在這節(jié)課中我們來(lái)學(xué)習(xí)怎樣將數(shù)據(jù)包保存到一個(gè)文件中,。Winpcap提供了一系列保存網(wǎng)絡(luò)數(shù)據(jù)包到一個(gè)文件和從文件中讀取保存內(nèi)容的函數(shù) -- 這節(jié)課就是講述怎樣使用這些函數(shù)的。同時(shí)也會(huì)展示怎樣winpcap核心中的保存特性來(lái)獲得高性能的存儲(chǔ)(注意:現(xiàn)在,,由于新的內(nèi)核緩存的一些問(wèn)題,,這個(gè)特性已經(jīng)不能使用了)。 dump文件的格式和libpcap的是一樣的,。在這個(gè)格式里捕獲的數(shù)據(jù)包是用二進(jìn)制的形式來(lái)保存的,,現(xiàn)在已經(jīng)作為標(biāo)準(zhǔn)被許多網(wǎng)絡(luò)工具使用,其中就包括WinDump, Ethereal 和 Snort,。 保存數(shù)據(jù)包到一個(gè)dump文件: 首先,,讓我們看看怎樣用libpcap格式來(lái)寫(xiě)數(shù)據(jù)包。下面的例子從選擇的網(wǎng)絡(luò)接口中捕獲數(shù)據(jù)并保存到一個(gè)由用戶(hù)提供名字的文件中,。 #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)在你也看到了,,這個(gè)程序的結(jié)構(gòu)和我們以前學(xué)的程序的結(jié)構(gòu)非常類(lèi)似。只有兩點(diǎn)不同: 打開(kāi)網(wǎng)絡(luò)接口后跟著就調(diào)用pcap_dump_open() ,,這個(gè)調(diào)用打開(kāi)一個(gè)dump文件并把它和網(wǎng)絡(luò)接口綁定到一起,。 在 packet_handler() 回調(diào)函數(shù)中數(shù)據(jù)包被 pcap_dump() 函數(shù)寫(xiě)到打開(kāi)的文件中去。pcap_dump() 的參數(shù)與 packet_handler() 中的參數(shù)一一對(duì)應(yīng),。 從一個(gè)dump文件中讀取數(shù)據(jù)包: 現(xiàn)在我們已經(jīng)有了一個(gè)有效的dump文件,,我們可以嘗試來(lái)讀取它的內(nèi)容。下面的代碼打開(kāi)一個(gè)dump文件并顯示出里面包含的每一個(gè)數(shù)據(jù)包,。文件打開(kāi)是用的 pcap_open_offline() 函數(shù),,然后一般是用 pcap_loop() 來(lái)按照順序來(lái)讀取數(shù)據(jù)包。就象你看到的一樣,,從一個(gè)dump文件中讀取數(shù)據(jù)包和從一個(gè)物理接口來(lái)捕獲數(shù)據(jù)包基本上是一樣的,。 這個(gè)例子還介紹了另一個(gè)函數(shù):pcap_createsrcsrc()。這個(gè)函數(shù)生成一個(gè)以一個(gè)標(biāo)記開(kāi)始的數(shù)據(jù)源字符串,,這個(gè)標(biāo)記用來(lái)告訴winpcap數(shù)據(jù)源的類(lèi)型,,比如"rpcap://" 代表一個(gè)適配器,"file://" 代表一個(gè)文件,。如果已經(jīng)使用了 pcap_findalldevs_ex() (這個(gè)函數(shù)的返回值已經(jīng)包含了數(shù)據(jù)源字符串),,那么就不需要再使用 pcap_createsrcsrc() 了。不過(guò),,因?yàn)槲募质怯脩?hù)輸入的,,所以在這個(gè)例子里我們還是要使用它的,。 #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; }
|
|