不用loopback捕獲數據報
這節的例子很象先前的一章(獲得網卡的高級信息)但是這一節中是用pcap_next_ex()來代替pcap_loop()來捕獲數據包。基于回調包捕獲機制
的pcap_loop()在某些情況下是不錯的選擇。但是在一些情況下處理回調并不特別好:這會使程序變的復雜并且在象多線程或C++類這些情況下
它看起來到象一塊絆腳石。
在這些情況下pcap_next_ex()允許直接調用來接收包,它的參數和pcap_loop()相同:有一個網卡描述副,和兩個指針,這兩個指針會被初始化
并返回給用戶,一個是pcap_pkthdr結構,另一個是接收數據的緩沖區。下面的程序我門將循環調用前一節的例子中的回掉部分,只是把它移到
了main里面了。
程序代碼: [ 復制代碼到剪貼板 ]
#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);
/* 此處循環調用 pcap_next_ex來接受數據報*/
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環境下才行因為它不是原始libpcap API中的一部分,這就意味著依賴于這個函數的代碼不會在UNIX下工作。那
么為什么我們用pcap_next_ex()而不用pcap_next()?因為pcap_next()有許多限制在很多情況下并不鼓勵用它。首先它的效率很低因為它隱藏
了回掉方法并且還依賴于pcap_dispatch()這個函數。再次它不能夠識別文件結束標志EOF所以對來自文件的數據流它幾乎無能為力。
注意當pcap_next_ex()在成功,超時,出錯和文件結束的情況下會返回不同的值
五)數據流的過濾
WinPcap或libpca最強大的特點之一就是數據流的過濾引擎。它提供一種高效的方法來只捕獲網絡數據流的某些數據而且常常和系統的捕獲機制
相集成。過濾數據的函數是pcap_compile() 和 pcap_setfilter()來實現的。
pcap_compile()來編譯一個過濾設備,它通過一個高層的boolean型變量和字串產生一系列的能夠被底層驅動所解釋的二進制編碼。boolean表
示語法能夠在這個文件的過濾表示語法中找到。
pcap_setfilter() 用來聯系一個在內核驅動上過濾的過濾器,這時所有網絡數據包都將流經過濾器,并拷貝到應用程序中。
下面的代碼展示了如何編譯并社定一個過濾設備。注意我們必須從pcap_if結構中獲得掩碼,因為一些過濾器的創建需要這個參數。
下面的代碼段中的pcap_compile()的"ip and tcp"參數說明只有IPV4和TCP數據才會被內核保存并被傳遞到應用程序。
程序代碼: [ 復制代碼到剪貼板 ]
if(d->addresses != NULL)
/* 獲得第一個接口地址的掩碼 */
netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* 如果這個接口沒有地址那么我們假設他為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;
}
六)解析數據包
現在經過上幾節的學習能夠進行數據報的捕獲和過濾了,我們想用一個簡單的"real world"程序將我們所學的
知識應用于實際。
這一節里我們將利用以前的代碼并將其引申從而建立一個更實用的程序。該程序的主要目的是如何顯示出所捕
獲的數據報的內容,尤其是對它的協議頭的分析和說明。這個程序名叫UDPdump它將在屏幕上顯示出我們網絡上
UDP數據的信息。
在此我們選擇解析UDP而不用TCP因為他比TCP簡單更加的直觀明了。下面讓我們來看看原代碼。
程序代碼: [ 復制代碼到剪貼板 ]
/*
* 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; // 后面的協議信息
u_short crc; // 校驗和
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;
/* 定義處理包的函數 */
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);
/* 將端口信息從網絡型轉變為主機順序 */
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);
}
首先我門設置UDP過濾,用這種方法我們確保packet_handler()只接受到基于IPV4的UDP數據。我們同樣定義了
兩個數據結構來描述IP 和UDP的頭部信息,packet_handler()用這兩個結構來定位頭部的各種字段。
packet_handler()雖然只是限于處理一些UDP數據但卻顯示了復雜的嗅探器如tcpdump/WinDump的工作原理。
首先我們對MAC地址的頭部并不感興趣所以我們跳過它。不過在開始捕獲之前我們用pcap_datalink()來檢查MAC
層,所以以上的程序只能夠工作在Ethernet networks上,再次我們確保MAC頭為14 bytes。
MAC頭之后是IP頭,我們從中提取出了目的地址。IP之后是UDP,在確定UDP的位置時有點復雜,因為IP的長度以
為版本的不同而不同,所以我們用頭長字段來定位UDP,一旦 我們確定了UDP的起始位置,我們就可以解析出原
和目的端口。
下面是我們打印出來的一些結果:
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
上面每一行都顯示出不同的數據包的內容.
(七)處理脫機的堆文件
通過以前的學習我門已經熟悉了從網卡上捕獲數據包,現在我門將學習如何處理數據包。WINPCAP為我們提供了很多API來將流經網絡的數據包
保存到一個堆文件并讀取堆的內容。這一節將講述如何使用所有的這些API。
這種文件的格式很簡單,但包含了所捕獲的數據報的二進制內容,這種文件格式也是很多網絡工具的標準如WinDump, Ethereal 還有 Snort等.
關于如何將數據包保存到文件:
首先我們看看如何以LIBPCAP的格式寫數據包。
下面的例子演示了如何從指定的接口上捕獲數據包并將它們存儲到一個指定的文件。
程序代碼: [ 復制代碼到剪貼板 ]
#include "pcap.h"
/* 定義處理數據的函數原形 */
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;
/* 檢查命令行參數 是否帶有文件名*/
if(argc != 2){
printf("usage: %s filename", argv[0]);
return -1;
}
/* 獲得驅動列表 */
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;
}
/* 跳轉到指定的網卡 */
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);
/* 循環捕獲數據并調用packet_handler函數把數據存儲到堆文件 */
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)
{
/* 此函數功能將數據報存儲到堆文件 */
pcap_dump(dumpfile, header, pkt_data);
}
正如你看到的那樣該程序的結構非常類似與以前的例子,區別是:
一旦打開網卡就調用pcap_dump_open()來打開一個文件,該調用將文件和某個網卡相關聯。
packet_handler()內部通過調用pcap_dump()來將捕獲的數據報存儲到文件。pcap_dump()的參數和 packet_handler()一樣,所以用起來比較方
便。
從文件讀數據包:
下面我們來看如何從文件讀取數據內容。下面的代碼打開了 一個堆文件并打印了其中的每個包內容。
pcap_open_offline()用來打開一個堆文件,之后用pcap_loop()來循環從文件中讀取數據。你能發現讀取脫機的數據幾乎和實時的從網卡上讀
取一摸一樣。
程序代碼: [ 復制代碼到剪貼板 ]
#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;
}
/* 打開一個存儲有數據的堆文件 */
if ( (fp = pcap_open_offline(argv[1], errbuf) ) == NULL)
{
fprintf(stderr,"\nError opening dump file\n");
return -1;
}
// 讀取數據直到遇到 EOF標志。
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()循環讀取數據而已。
程序代碼: [ 復制代碼到剪貼板 ]
#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將數據寫到文件:
WinPcap的最新版本提供了一個進一步的方法來將數據包存儲到磁盤,就是使用pcap_live_dump()函數。他需要三個參數:一個文件名,和一個
該文件允許的最大長度還有一個參數是該文件所允許的最大包的數量。對這些參數來說 0 意味著沒有最大限制。注:我們可以在調用
pcap_live_dump()前設置一個過濾器來定義哪些數據報需要存儲。
pcap_live_dump() 是非阻塞的,所以他會立刻返回:數據的存儲過程將會異步的進行,直到文件到達了指定的最大長度或最大數據報的數目為
止。
應用程序能夠用pcap_live_dump_ended()來等檢查是否數據存儲完畢,如果你指定的最大長度參數和數據報數量為0,那么該操作將永遠阻塞。
pcap_live_dump() 和 pcap_dump()的不同從設置的最大極限來說就是性能的問題。pcap_live_dump()采用WinPcap NPF驅動來從內核級的層次
上向文件中寫數據,從而使內存拷貝最小化。
顯然,這些特點當前在其他的操作系統下是不能夠實現的,pcap_live_dump()是WinPcap所特有的,而且只能夠應用于Win32環境.
八)發送數據包
盡管WinPcap從名字上來看表明他的主要目的是捕獲數據包,但是他還為原始網絡提供了一些其他的功能,其中
之一就是用戶可以發送數據包,這也就是本節的主要內容。需要指出的是原來的libpcap并不提供數據包 的發
送功能,這里所說的功能都是WinPcap的擴展功能,所以并不能夠工作在UNIX下。
用pcap_sendpacket來發送一個數據包:
下面的代碼是一個最簡單的發送數據的方法。打開一個適配器后就可以用 pcap_sendpacket()來手工發送一
個數據包了。這個函數需要的參數:一個裝有要發送數據的緩沖區,要發送的長度,和一個適配器。注意緩沖
區中的數據將不被內核協議處理,只是作為最原始的數據流被發送,所以我門必須填充好正確的協議頭以便正
確的將數據發送。
程序代碼: [ 復制代碼到剪貼板 ]
#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;
}
/* 打開指定網卡 */
if((fp = pcap_open_live(argv[1], 100, 1, 1000, error) ) == NULL)
{
fprintf(stderr,"\nError opening adapter: %s\n", error);
return;
}
/* 假設網絡環境為ethernet,我門把目的MAC設為1:1:1:1:1:1*/
packet[0]=1;
packet[1]=1;
packet[2]=1;
packet[3]=1;
packet[4]=1;
packet[5]=1;
/* 假設源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;
/* 填充發送包的剩余部分 */
for(i=12;i<100;i++){
packet[i]=i%256;
}
/* 發送包 */
pcap_sendpacket(fp,
packet,
100);
return;
}
發送隊列:
pcap_sendpacket()只是提供一個簡單的直接的發送數據的方法,而發送隊列提供一個高級的強大的和最優的機
制來發送一組數據包,隊列實際上是一個裝有要發送數據的一個容器,他有一個最大值來表明他所能夠 容納的
最大比特數。
pcap_sendqueue_alloc()用來創建一個隊列,并指定該隊列的大小。
一旦隊列被創建就可以調用pcap_sendqueue_queue()來將數據存儲到隊列中,這個函數接受一個帶有時間戳和
長度的pcap_pkthdr結構和一個裝有數據報的緩沖區。這些參數同樣也應用于pcap_next_ex() 和
pcap_handler()中,所以給要捕獲的數據包或要從文件讀取的數據包排隊就是pcap_sendqueue_queue()的事情
了。
WinPcap調用pcap_sendqueue_transmit()來發送數據包,注意,第三個參數如果非零,那么發送將是同步的,
這將站用很大的CPU資源,因為發生在內核驅動的同步發送是通過"brute force"loops的,但是一般情況下能夠
精確到微秒。
需要指出的是用pcap_sendqueue_transmit()來發送比用pcap_sendpacket()來發送一系列的數據要高效的多,
因為他的數據是在內核級上被緩沖。
當不再需要隊列時可以用pcap_sendqueue_destroy()來釋放掉所有的隊列資源。
下面的代碼演示了如何用發送隊列來發送數據,該示例用pcap_open_offline()打開了一個文件,然后將數據
從文件移動到已分配的隊列,這時就同步地傳送隊列(如果用戶指定為同步的話)。
程序代碼: [ 復制代碼到剪貼板 ]
/*
* 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);
/* 從文件獲得包來填充隊列 */
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;
}
/* 傳送隊列數據 */
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);
}
九)收集并統計網絡流量
這一節將展示WinPcap的另一高級功能:收集網絡流量的統計信息。WinPcap的統計引擎在內核層次上對到來的數據進行分類。如果你想了解更
多的細節請查看NPF驅動指南。
為了利用這個功能來監視網絡,我門的程序必須打開一個網卡并用pcap_setmode()將其設置為統計模式。注意pcap_setmode()要用 MODE_STAT
來將網卡設置為統計模式。
在統計模式下編寫一個程序來監視TCP流量只是幾行代碼的事情,下面的例子說明了如何來實現該功能的。
程序代碼: [ 復制代碼到剪貼板 ]
/*
* 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;
}
/* 將網卡設置為統計模式 */
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];
/* 從最近一次的采樣以微秒計算延遲時間 */
/* 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;
/* 獲得每秒的比特數 */
Bps.QuadPart=(((*(LONGLONG*)(pkt_data + 8)) * 8 * 1000000) / (delay));
/* ^ ^
| |
| |
| |
converts bytes in bits -- |
|
delay is expressed in microseconds --
*/
/* 獲得每秒的數據包數 */
Pps.QuadPart=(((*(LONGLONG*)(pkt_data)) * 1000000) / (delay));
/* 將時間戳轉變為可讀的標準格式 */
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);
}
在設置為統計模式前可以設置一個過濾器來指定要捕獲的協議包。如果沒有設置過濾器那么整個網絡數據都將被監視。一旦設置了 過濾器就可
以調用pcap_setmode()來設置為統計模式,之后網卡開始工作在統計模式下。
需要指出的是pcap_open_live()的第四個參數(to_ms)定義了采樣的間隔,回調函數pcap_loop()每隔一定間隔就獲取一次采樣統計,這個采樣
被裝入pcap_loop()的第二和第三個參數,過程如下圖所示:
______________
|struct timeval ts |
|_____________|
|bpf_u_int32 |
|caplen=16 | struct pcap_pkthdr*
|_____________| (參數2)
| bpf_u_int32 |
| len=16 |
|_____________|
__________________________
|large_integer Accepted packet |
|_________________________| uchar *
| large_integer Accepted bits | (參數3)
|_________________________|
用兩個64位的計數器分別記錄最近一次間隔數據包數量和比特數量。
本例子中,網卡打開時設置超時為1000毫秒,也就是說dispatcher_handler()每隔1秒就被調用一次。過濾器也
設置為只監視TCP包,然后pcap_setmode() and pcap_loop()被調用,注意一個指向timeval的指針 作為參數傳
送到pcap_loop()。這個timeval結構將用來存儲個時間戳以計算兩次采樣的時間間隔。
dispatcher_handler()用該間隔來獲取每秒的比特數和數據包數,并把著兩個數顯示在顯示器上。
最后指出的是目前這個例子是比任何一個利用傳統方法在用戶層統計的包捕獲程序都高效。因為統計模式需要
最小數量的數據拷貝和上下環境交換,同時還有最小的內存需求,所以CPU是最優的。