不用loopback捕獲數(shù)據(jù)報
這節(jié)的例子很象先前的一章(獲得網(wǎng)卡的高級信息)但是這一節(jié)中是用pcap_next_ex()來代替pcap_loop()來捕獲數(shù)據(jù)包。基于回調(diào)包捕獲機(jī)制
的pcap_loop()在某些情況下是不錯的選擇。但是在一些情況下處理回調(diào)并不特別好:這會使程序變的復(fù)雜并且在象多線程或C++類這些情況下
它看起來到象一塊絆腳石。
在這些情況下pcap_next_ex()允許直接調(diào)用來接收包,它的參數(shù)和pcap_loop()相同:有一個網(wǎng)卡描述副,和兩個指針,這兩個指針會被初始化
并返回給用戶,一個是pcap_pkthdr結(jié)構(gòu),另一個是接收數(shù)據(jù)的緩沖區(qū)。下面的程序我門將循環(huán)調(diào)用前一節(jié)的例子中的回掉部分,只是把它移到
了main里面了。
程序代碼: [ 復(fù)制代碼到剪貼板 ]
#include "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 */
if (pcap_findalldevs(&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_live(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.
1, // promiscuous mode
1000, // read timeout
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;
}
printf("\nlistening on %s...\n", d->description);
/* At this point, we don‘t need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* 此處循環(huán)調(diào)用 pcap_next_ex來接受數(shù)據(jù)報*/
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()只在Win32環(huán)境下才行因?yàn)樗皇窃糽ibpcap API中的一部分,這就意味著依賴于這個函數(shù)的代碼不會在UNIX下工作。那
么為什么我們用pcap_next_ex()而不用pcap_next()?因?yàn)閜cap_next()有許多限制在很多情況下并不鼓勵用它。首先它的效率很低因?yàn)樗[藏
了回掉方法并且還依賴于pcap_dispatch()這個函數(shù)。再次它不能夠識別文件結(jié)束標(biāo)志EOF所以對來自文件的數(shù)據(jù)流它幾乎無能為力。
注意當(dāng)pcap_next_ex()在成功,超時,出錯和文件結(jié)束的情況下會返回不同的值
五)數(shù)據(jù)流的過濾
WinPcap或libpca最強(qiáng)大的特點(diǎn)之一就是數(shù)據(jù)流的過濾引擎。它提供一種高效的方法來只捕獲網(wǎng)絡(luò)數(shù)據(jù)流的某些數(shù)據(jù)而且常常和系統(tǒng)的捕獲機(jī)制
相集成。過濾數(shù)據(jù)的函數(shù)是pcap_compile() 和 pcap_setfilter()來實(shí)現(xiàn)的。
pcap_compile()來編譯一個過濾設(shè)備,它通過一個高層的boolean型變量和字串產(chǎn)生一系列的能夠被底層驅(qū)動所解釋的二進(jìn)制編碼。boolean表
示語法能夠在這個文件的過濾表示語法中找到。
pcap_setfilter() 用來聯(lián)系一個在內(nèi)核驅(qū)動上過濾的過濾器,這時所有網(wǎng)絡(luò)數(shù)據(jù)包都將流經(jīng)過濾器,并拷貝到應(yīng)用程序中。
下面的代碼展示了如何編譯并社定一個過濾設(shè)備。注意我們必須從pcap_if結(jié)構(gòu)中獲得掩碼,因?yàn)橐恍┻^濾器的創(chuàng)建需要這個參數(shù)。
下面的代碼段中的pcap_compile()的"ip and tcp"參數(shù)說明只有IPV4和TCP數(shù)據(jù)才會被內(nèi)核保存并被傳遞到應(yīng)用程序。
程序代碼: [ 復(fù)制代碼到剪貼板 ]
if(d->addresses != NULL)
/* 獲得第一個接口地址的掩碼 */
netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* 如果這個接口沒有地址那么我們假設(shè)他為C類地址 */
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;
}
六)解析數(shù)據(jù)包
現(xiàn)在經(jīng)過上幾節(jié)的學(xué)習(xí)能夠進(jìn)行數(shù)據(jù)報的捕獲和過濾了,我們想用一個簡單的"real world"程序?qū)⑽覀兯鶎W(xué)的
知識應(yīng)用于實(shí)際。
這一節(jié)里我們將利用以前的代碼并將其引申從而建立一個更實(shí)用的程序。該程序的主要目的是如何顯示出所捕
獲的數(shù)據(jù)報的內(nèi)容,尤其是對它的協(xié)議頭的分析和說明。這個程序名叫UDPdump它將在屏幕上顯示出我們網(wǎng)絡(luò)上
UDP數(shù)據(jù)的信息。
在此我們選擇解析UDP而不用TCP因?yàn)樗萒CP簡單更加的直觀明了。下面讓我們來看看原代碼。
程序代碼: [ 復(fù)制代碼到剪貼板 ]
/*
* Copyright (c) 1999 - 2002
* Politecnico di Torino. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the Politecnico
* di Torino, and its contributors.‘‘ Neither the name of
* the University 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 ``AS IS‘‘ AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "pcap.h"
/* 4 BIT的IP頭定義 */
typedef struct ip_address{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/* IPv4 頭的定義 */
typedef struct ip_header{
u_char ver_ihl; // 4 bit的版本信息 + 4 bits的頭長
u_char tos; // TOS類型
u_short tlen; // 總長度
u_short identification; // Identification
u_short flags_fo; // Flags (3 bits) + Fragment offset (13 bits)
u_char ttl; // 生存期
u_char proto; // 后面的協(xié)議信息
u_short crc; // 校驗(yàn)和
ip_address saddr; // 源IP
ip_address daddr; // 目的IP
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;
/* 定義處理包的函數(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];
u_int netmask;
char packet_filter[] = "ip and udp";
struct bpf_program fcode;
/* Retrieve the device list */
if (pcap_findalldevs(&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_live(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.
1, // promiscuous mode
1000, // read timeout
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);
/* 找到IP頭的位置 */
ih = (ip_header *) (pkt_data +
14); //14為以太頭的長度
/* 找到UDP的位置 */
ip_len = (ih->ver_ihl & 0xf) * 4;
uh = (udp_header *) ((u_char*)ih + ip_len);
/* 將端口信息從網(wǎng)絡(luò)型轉(zhuǎn)變?yōu)橹鳈C(jī)順序 */
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);
}
首先我門設(shè)置UDP過濾,用這種方法我們確保packet_handler()只接受到基于IPV4的UDP數(shù)據(jù)。我們同樣定義了
兩個數(shù)據(jù)結(jié)構(gòu)來描述IP 和UDP的頭部信息,packet_handler()用這兩個結(jié)構(gòu)來定位頭部的各種字段。
packet_handler()雖然只是限于處理一些UDP數(shù)據(jù)但卻顯示了復(fù)雜的嗅探器如tcpdump/WinDump的工作原理。
首先我們對MAC地址的頭部并不感興趣所以我們跳過它。不過在開始捕獲之前我們用pcap_datalink()來檢查MAC
層,所以以上的程序只能夠工作在Ethernet networks上,再次我們確保MAC頭為14 bytes。
MAC頭之后是IP頭,我們從中提取出了目的地址。IP之后是UDP,在確定UDP的位置時有點(diǎn)復(fù)雜,因?yàn)镮P的長度以
為版本的不同而不同,所以我們用頭長字段來定位UDP,一旦 我們確定了UDP的起始位置,我們就可以解析出原
和目的端口。
下面是我們打印出來的一些結(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
上面每一行都顯示出不同的數(shù)據(jù)包的內(nèi)容.
(七)處理脫機(jī)的堆文件
通過以前的學(xué)習(xí)我門已經(jīng)熟悉了從網(wǎng)卡上捕獲數(shù)據(jù)包,現(xiàn)在我門將學(xué)習(xí)如何處理數(shù)據(jù)包。WINPCAP為我們提供了很多API來將流經(jīng)網(wǎng)絡(luò)的數(shù)據(jù)包
保存到一個堆文件并讀取堆的內(nèi)容。這一節(jié)將講述如何使用所有的這些API。
這種文件的格式很簡單,但包含了所捕獲的數(shù)據(jù)報的二進(jìn)制內(nèi)容,這種文件格式也是很多網(wǎng)絡(luò)工具的標(biāo)準(zhǔn)如WinDump, Ethereal 還有 Snort等.
關(guān)于如何將數(shù)據(jù)包保存到文件:
首先我們看看如何以LIBPCAP的格式寫數(shù)據(jù)包。
下面的例子演示了如何從指定的接口上捕獲數(shù)據(jù)包并將它們存儲到一個指定的文件。
程序代碼: [ 復(fù)制代碼到剪貼板 ]
#include "pcap.h"
/* 定義處理數(shù)據(jù)的函數(shù)原形 */
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;
/* 檢查命令行參數(shù) 是否帶有文件名*/
if(argc != 2){
printf("usage: %s filename", argv[0]);
return -1;
}
/* 獲得驅(qū)動列表 */
if (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 打印 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;
}
/* 跳轉(zhuǎn)到指定的網(wǎng)卡 */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the adapter */
if ( (adhandle = pcap_open_live(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.
1, // promiscuous mode
1000, // read timeout
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;
}
/* 打開文件 */
dumpfile = pcap_dump_open(adhandle, argv[1]);
if(dumpfile==NULL){
fprintf(stderr,"\nError opening output file\n");
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);
/* 循環(huán)捕獲數(shù)據(jù)并調(diào)用packet_handler函數(shù)把數(shù)據(jù)存儲到堆文件 */
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)
{
/* 此函數(shù)功能將數(shù)據(jù)報存儲到堆文件 */
pcap_dump(dumpfile, header, pkt_data);
}
正如你看到的那樣該程序的結(jié)構(gòu)非常類似與以前的例子,區(qū)別是:
一旦打開網(wǎng)卡就調(diào)用pcap_dump_open()來打開一個文件,該調(diào)用將文件和某個網(wǎng)卡相關(guān)聯(lián)。
packet_handler()內(nèi)部通過調(diào)用pcap_dump()來將捕獲的數(shù)據(jù)報存儲到文件。pcap_dump()的參數(shù)和 packet_handler()一樣,所以用起來比較方
便。
從文件讀數(shù)據(jù)包:
下面我們來看如何從文件讀取數(shù)據(jù)內(nèi)容。下面的代碼打開了 一個堆文件并打印了其中的每個包內(nèi)容。
pcap_open_offline()用來打開一個堆文件,之后用pcap_loop()來循環(huán)從文件中讀取數(shù)據(jù)。你能發(fā)現(xiàn)讀取脫機(jī)的數(shù)據(jù)幾乎和實(shí)時的從網(wǎng)卡上讀
取一摸一樣。
程序代碼: [ 復(fù)制代碼到剪貼板 ]
#include <stdio.h>
#include <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];
if(argc != 2){
printf("usage: %s filename", argv[0]);
return -1;
}
/* 打開一個存儲有數(shù)據(jù)的堆文件 */
if ( (fp = pcap_open_offline(argv[1], errbuf) ) == NULL)
{
fprintf(stderr,"\nError opening dump file\n");
return -1;
}
// 讀取數(shù)據(jù)直到遇到 EOF標(biāo)志。
pcap_loop(fp, 0, dispatcher_handler, NULL);
return 0;
}
void dispatcher_handler(u_char *temp1,
const struct pcap_pkthdr *header, const u_char *pkt_data)
{
u_int i=0;
/* print pkt timestamp and pkt len */
printf("%ld:%ld (%ld)\n", header->ts.tv_sec, header->ts.tv_usec, header->len);
/* Print the packet */
for (i=1; (i < header->caplen + 1 ) ; i++)
{
printf("%.2x ", pkt_data[i-1]);
if ( (i % LINE_LEN) == 0) printf("\n");
}
printf("\n\n");
}
下面的代碼具有一樣的作用,只不過是用pcap_next_ex()來代替pcap_loop()循環(huán)讀取數(shù)據(jù)而已。
程序代碼: [ 復(fù)制代碼到剪貼板 ]
#include <stdio.h>
#include <pcap.h>
#define LINE_LEN 16
main(int argc, char **argv) {
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
struct pcap_pkthdr *header;
u_char *pkt_data;
u_int i=0;
int res;
if(argc != 2){
printf("usage: %s filename", argv[0]);
return -1;
}
/* Open a capture file */
if ( (fp = pcap_open_offline(argv[1], errbuf) ) == NULL)
{
fprintf(stderr,"\nError opening dump file\n");
return -1;
}
/* Retrieve the packets from the file */
while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0){
/* print pkt timestamp and pkt len */
printf("%ld:%ld (%ld)\n", header->ts.tv_sec, header->ts.tv_usec, header->len);
/* Print the packet */
for (i=1; (i < header->caplen + 1 ) ; i++)
{
printf("%.2x ", pkt_data[i-1]);
if ( (i % LINE_LEN) == 0) printf("\n");
}
printf("\n\n");
}
if(res == -1){
printf("Error reading the packets: %s\n", pcap_geterr(fp));
}
return 0;
}
用pcap_live_dump將數(shù)據(jù)寫到文件:
WinPcap的最新版本提供了一個進(jìn)一步的方法來將數(shù)據(jù)包存儲到磁盤,就是使用pcap_live_dump()函數(shù)。他需要三個參數(shù):一個文件名,和一個
該文件允許的最大長度還有一個參數(shù)是該文件所允許的最大包的數(shù)量。對這些參數(shù)來說 0 意味著沒有最大限制。注:我們可以在調(diào)用
pcap_live_dump()前設(shè)置一個過濾器來定義哪些數(shù)據(jù)報需要存儲。
pcap_live_dump() 是非阻塞的,所以他會立刻返回:數(shù)據(jù)的存儲過程將會異步的進(jìn)行,直到文件到達(dá)了指定的最大長度或最大數(shù)據(jù)報的數(shù)目為
止。
應(yīng)用程序能夠用pcap_live_dump_ended()來等檢查是否數(shù)據(jù)存儲完畢,如果你指定的最大長度參數(shù)和數(shù)據(jù)報數(shù)量為0,那么該操作將永遠(yuǎn)阻塞。
pcap_live_dump() 和 pcap_dump()的不同從設(shè)置的最大極限來說就是性能的問題。pcap_live_dump()采用WinPcap NPF驅(qū)動來從內(nèi)核級的層次
上向文件中寫數(shù)據(jù),從而使內(nèi)存拷貝最小化。
顯然,這些特點(diǎn)當(dāng)前在其他的操作系統(tǒng)下是不能夠?qū)崿F(xiàn)的,pcap_live_dump()是WinPcap所特有的,而且只能夠應(yīng)用于Win32環(huán)境.
八)發(fā)送數(shù)據(jù)包
盡管WinPcap從名字上來看表明他的主要目的是捕獲數(shù)據(jù)包,但是他還為原始網(wǎng)絡(luò)提供了一些其他的功能,其中
之一就是用戶可以發(fā)送數(shù)據(jù)包,這也就是本節(jié)的主要內(nèi)容。需要指出的是原來的libpcap并不提供數(shù)據(jù)包 的發(fā)
送功能,這里所說的功能都是WinPcap的擴(kuò)展功能,所以并不能夠工作在UNIX下。
用pcap_sendpacket來發(fā)送一個數(shù)據(jù)包:
下面的代碼是一個最簡單的發(fā)送數(shù)據(jù)的方法。打開一個適配器后就可以用 pcap_sendpacket()來手工發(fā)送一
個數(shù)據(jù)包了。這個函數(shù)需要的參數(shù):一個裝有要發(fā)送數(shù)據(jù)的緩沖區(qū),要發(fā)送的長度,和一個適配器。注意緩沖
區(qū)中的數(shù)據(jù)將不被內(nèi)核協(xié)議處理,只是作為最原始的數(shù)據(jù)流被發(fā)送,所以我門必須填充好正確的協(xié)議頭以便正
確的將數(shù)據(jù)發(fā)送。
程序代碼: [ 復(fù)制代碼到剪貼板 ]
#include <stdlib.h>
#include <stdio.h>
#include <pcap.h>
void usage();
void main(int argc, char **argv) {
pcap_t *fp;
char error[PCAP_ERRBUF_SIZE];
u_char packet[100];
int i;
/* Check the validity of the command line */
if (argc != 2)
{
printf("usage: %s inerface", argv[0]);
return;
}
/* 打開指定網(wǎng)卡 */
if((fp = pcap_open_live(argv[1], 100, 1, 1000, error) ) == NULL)
{
fprintf(stderr,"\nError opening adapter: %s\n", error);
return;
}
/* 假設(shè)網(wǎng)絡(luò)環(huán)境為ethernet,我門把目的MAC設(shè)為1:1:1:1:1:1*/
packet[0]=1;
packet[1]=1;
packet[2]=1;
packet[3]=1;
packet[4]=1;
packet[5]=1;
/* 假設(shè)源MAC為 2:2:2:2:2:2 */
packet[6]=2;
packet[7]=2;
packet[8]=2;
packet[9]=2;
packet[10]=2;
packet[11]=2;
/* 填充發(fā)送包的剩余部分 */
for(i=12;i<100;i++){
packet[i]=i%256;
}
/* 發(fā)送包 */
pcap_sendpacket(fp,
packet,
100);
return;
}
發(fā)送隊(duì)列:
pcap_sendpacket()只是提供一個簡單的直接的發(fā)送數(shù)據(jù)的方法,而發(fā)送隊(duì)列提供一個高級的強(qiáng)大的和最優(yōu)的機(jī)
制來發(fā)送一組數(shù)據(jù)包,隊(duì)列實(shí)際上是一個裝有要發(fā)送數(shù)據(jù)的一個容器,他有一個最大值來表明他所能夠 容納的
最大比特數(shù)。
pcap_sendqueue_alloc()用來創(chuàng)建一個隊(duì)列,并指定該隊(duì)列的大小。
一旦隊(duì)列被創(chuàng)建就可以調(diào)用pcap_sendqueue_queue()來將數(shù)據(jù)存儲到隊(duì)列中,這個函數(shù)接受一個帶有時間戳和
長度的pcap_pkthdr結(jié)構(gòu)和一個裝有數(shù)據(jù)報的緩沖區(qū)。這些參數(shù)同樣也應(yīng)用于pcap_next_ex() 和
pcap_handler()中,所以給要捕獲的數(shù)據(jù)包或要從文件讀取的數(shù)據(jù)包排隊(duì)就是pcap_sendqueue_queue()的事情
了。
WinPcap調(diào)用pcap_sendqueue_transmit()來發(fā)送數(shù)據(jù)包,注意,第三個參數(shù)如果非零,那么發(fā)送將是同步的,
這將站用很大的CPU資源,因?yàn)榘l(fā)生在內(nèi)核驅(qū)動的同步發(fā)送是通過"brute force"loops的,但是一般情況下能夠
精確到微秒。
需要指出的是用pcap_sendqueue_transmit()來發(fā)送比用pcap_sendpacket()來發(fā)送一系列的數(shù)據(jù)要高效的多,
因?yàn)樗臄?shù)據(jù)是在內(nèi)核級上被緩沖。
當(dāng)不再需要隊(duì)列時可以用pcap_sendqueue_destroy()來釋放掉所有的隊(duì)列資源。
下面的代碼演示了如何用發(fā)送隊(duì)列來發(fā)送數(shù)據(jù),該示例用pcap_open_offline()打開了一個文件,然后將數(shù)據(jù)
從文件移動到已分配的隊(duì)列,這時就同步地傳送隊(duì)列(如果用戶指定為同步的話)。
程序代碼: [ 復(fù)制代碼到剪貼板 ]
/*
* Copyright (c) 1999 - 2002
* Politecnico di Torino. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the Politecnico
* di Torino, and its contributors.‘‘ Neither the name of
* the University 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 ``AS IS‘‘ AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <pcap.h>
void usage();
void main(int argc, char **argv) {
pcap_t *indesc,*outdesc;
char error[PCAP_ERRBUF_SIZE];
FILE *capfile;
int caplen,
sync;
u_int res;
pcap_send_queue *squeue;
struct pcap_pkthdr *pktheader;
u_char *pktdata;
/* Check the validity of the command line */
if (argc <= 2 || argc >= 5)
{
usage();
return;
}
/* 得到文件長度 */
capfile=fopen(argv[1],"rb");
if(!capfile){
printf("Capture file not found!\n");
return;
}
fseek(capfile , 0, SEEK_END);
caplen= ftell(capfile)- sizeof(struct pcap_file_header);
fclose(capfile);
/* 檢查確保時間戳被忽略 */
if(argc == 4 && argv[3][0] == ‘s‘)
sync = TRUE;
else
sync = FALSE;
/* Open the capture */
if((indesc = pcap_open_offline(argv[1], error)) == NULL){
fprintf(stderr,"\nError opening the input file: %s\n", error);
return;
}
/* Open the output adapter */
if((outdesc = pcap_open_live(argv[2], 100, 1, 1000, error) ) == NULL)
{
fprintf(stderr,"\nError opening adapter: %s\n", error);
return;
}
/* 檢測MAC類型 */
if(pcap_datalink(indesc) != pcap_datalink(outdesc)){
printf("Warning: the datalink of the capture differs from the one of the selected
interface.\n");
printf("Press a key to continue, or CTRL+C to stop.\n");
getchar();
}
/* 給對列分配空間 */
squeue = pcap_sendqueue_alloc(caplen);
/* 從文件獲得包來填充隊(duì)列 */
while((res = pcap_next_ex( indesc, &pktheader, &pktdata)) == 1){
if(pcap_sendqueue_queue(squeue, pktheader, pktdata) == -1){
printf("Warning: packet buffer too small, not all the packets will be sent.\n");
break;
}
}
if(res == -1){
printf("Corrupted input file.\n");
pcap_sendqueue_destroy(squeue);
return;
}
/* 傳送隊(duì)列數(shù)據(jù) */
if((res = pcap_sendqueue_transmit(outdesc, squeue, sync)) < squeue->len)
{
printf("An error occurred sending the packets: %s. Only %d bytes were sent\n", error,
res);
}
/* free the send queue */
pcap_sendqueue_destroy(squeue);
return;
}
void usage()
{
printf("\nSendcap, sends a libpcap/tcpdump capture file to the net. Copyright (C) 2002 Loris
Degioanni.\n");
printf("\nUsage:\n");
printf("\t sendcap file_name adapter [s]\n");
printf("\nParameters:\n");
printf("\nfile_name: the name of the dump file that will be sent to the network\n");
printf("\nadapter: the device to use. Use \"WinDump -D\" for a list of valid devices\n");
printf("\ns: if present, forces the packets to be sent synchronously, i.e. respecting the
timestamps in the dump file. This option will work only under Windows NTx.\n\n");
exit(0);
}
九)收集并統(tǒng)計(jì)網(wǎng)絡(luò)流量
這一節(jié)將展示W(wǎng)inPcap的另一高級功能:收集網(wǎng)絡(luò)流量的統(tǒng)計(jì)信息。WinPcap的統(tǒng)計(jì)引擎在內(nèi)核層次上對到來的數(shù)據(jù)進(jìn)行分類。如果你想了解更
多的細(xì)節(jié)請查看NPF驅(qū)動指南。
為了利用這個功能來監(jiān)視網(wǎng)絡(luò),我門的程序必須打開一個網(wǎng)卡并用pcap_setmode()將其設(shè)置為統(tǒng)計(jì)模式。注意pcap_setmode()要用 MODE_STAT
來將網(wǎng)卡設(shè)置為統(tǒng)計(jì)模式。
在統(tǒng)計(jì)模式下編寫一個程序來監(jiān)視TCP流量只是幾行代碼的事情,下面的例子說明了如何來實(shí)現(xiàn)該功能的。
程序代碼: [ 復(fù)制代碼到剪貼板 ]
/*
* Copyright (c) 1999 - 2002
* Politecnico di Torino. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the Politecnico
* di Torino, and its contributors.‘‘ Neither the name of
* the University 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 ``AS IS‘‘ AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <pcap.h>
void usage();
void dispatcher_handler(u_char *,
const struct pcap_pkthdr *, const u_char *);
void main(int argc, char **argv) {
pcap_t *fp;
char error[PCAP_ERRBUF_SIZE];
struct timeval st_ts;
u_int netmask;
struct bpf_program fcode;
/* Check the validity of the command line */
if (argc != 2)
{
usage();
return;
}
/* Open the output adapter */
if((fp = pcap_open_live(argv[1], 100, 1, 1000, error) ) == NULL)
{
fprintf(stderr,"\nError opening adapter: %s\n", error);
return;
}
/* Don‘t care about netmask, it won‘t be used for this filter */
netmask=0xffffff;
//compile the filter
if(pcap_compile(fp, &fcode, "tcp", 1, netmask) <0 ){
fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");
/* Free the device list */
return;
}
//set the filter
if(pcap_setfilter(fp, &fcode)<0){
fprintf(stderr,"\nError setting the filter.\n");
/* Free the device list */
return;
}
/* 將網(wǎng)卡設(shè)置為統(tǒng)計(jì)模式 */
pcap_setmode(fp, MODE_STAT);
printf("TCP traffic summary:\n");
/* Start the main loop */
pcap_loop(fp, 0, dispatcher_handler, (PUCHAR)&st_ts);
return;
}
void dispatcher_handler(u_char *state, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
struct timeval *old_ts = (struct timeval *)state;
u_int delay;
LARGE_INTEGER Bps,Pps;
struct tm *ltime;
char timestr[16];
/* 從最近一次的采樣以微秒計(jì)算延遲時間 */
/* This value is obtained from the timestamp that the associated with the sample. */
delay=(header->ts.tv_sec - old_ts->tv_sec) * 1000000 - old_ts->tv_usec + header->ts.tv_usec;
/* 獲得每秒的比特數(shù) */
Bps.QuadPart=(((*(LONGLONG*)(pkt_data + 8)) * 8 * 1000000) / (delay));
/* ^ ^
| |
| |
| |
converts bytes in bits -- |
|
delay is expressed in microseconds --
*/
/* 獲得每秒的數(shù)據(jù)包數(shù) */
Pps.QuadPart=(((*(LONGLONG*)(pkt_data)) * 1000000) / (delay));
/* 將時間戳轉(zhuǎn)變?yōu)榭勺x的標(biāo)準(zhǔn)格式 */
ltime=localtime(&header->ts.tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
/* Print timestamp*/
printf("%s ", timestr);
/* Print the samples */
printf("BPS=%I64u ", Bps.QuadPart);
printf("PPS=%I64u\n", Pps.QuadPart);
//store current timestamp
old_ts->tv_sec=header->ts.tv_sec;
old_ts->tv_usec=header->ts.tv_usec;
}
void usage()
{
printf("\nShows the TCP traffic load, in bits per second and packets per second.\nCopyright (C) 2002 Loris Degioanni.\n");
printf("\nUsage:\n");
printf("\t tcptop adapter\n");
printf("\t You can use \"WinDump -D\" if you don‘t know the name of your adapters.\n");
exit(0);
}
在設(shè)置為統(tǒng)計(jì)模式前可以設(shè)置一個過濾器來指定要捕獲的協(xié)議包。如果沒有設(shè)置過濾器那么整個網(wǎng)絡(luò)數(shù)據(jù)都將被監(jiān)視。一旦設(shè)置了 過濾器就可
以調(diào)用pcap_setmode()來設(shè)置為統(tǒng)計(jì)模式,之后網(wǎng)卡開始工作在統(tǒng)計(jì)模式下。
需要指出的是pcap_open_live()的第四個參數(shù)(to_ms)定義了采樣的間隔,回調(diào)函數(shù)pcap_loop()每隔一定間隔就獲取一次采樣統(tǒng)計(jì),這個采樣
被裝入pcap_loop()的第二和第三個參數(shù),過程如下圖所示:
______________
|struct timeval ts |
|_____________|
|bpf_u_int32 |
|caplen=16 | struct pcap_pkthdr*
|_____________| (參數(shù)2)
| bpf_u_int32 |
| len=16 |
|_____________|
__________________________
|large_integer Accepted packet |
|_________________________| uchar *
| large_integer Accepted bits | (參數(shù)3)
|_________________________|
用兩個64位的計(jì)數(shù)器分別記錄最近一次間隔數(shù)據(jù)包數(shù)量和比特數(shù)量。
本例子中,網(wǎng)卡打開時設(shè)置超時為1000毫秒,也就是說dispatcher_handler()每隔1秒就被調(diào)用一次。過濾器也
設(shè)置為只監(jiān)視TCP包,然后pcap_setmode() and pcap_loop()被調(diào)用,注意一個指向timeval的指針 作為參數(shù)傳
送到pcap_loop()。這個timeval結(jié)構(gòu)將用來存儲個時間戳以計(jì)算兩次采樣的時間間隔。
dispatcher_handler()用該間隔來獲取每秒的比特數(shù)和數(shù)據(jù)包數(shù),并把著兩個數(shù)顯示在顯示器上。
最后指出的是目前這個例子是比任何一個利用傳統(tǒng)方法在用戶層統(tǒng)計(jì)的包捕獲程序都高效。因?yàn)榻y(tǒng)計(jì)模式需要
最小數(shù)量的數(shù)據(jù)拷貝和上下環(huán)境交換,同時還有最小的內(nèi)存需求,所以CPU是最優(yōu)的。