WinPcap - 捕獲數據、發送數據

1、WinPcap

WinPcap是一個基於Win32平臺的,用於捕獲網絡數據包並進行分析的開源庫。WinPcap提供了以下功能:

  • 捕獲原始數據包,無論它是發往某臺機器的,還是在其他設備(共享媒介)上進行交換的;
  • 在數據包發送給某應用程序前,根據用戶指定的規則過濾數據包;
  • 將原始數據包通過網絡發送出去;
  • 收集並統計網絡流量信息。

WinPcap的功能與LibPcap的功能很相似,但是WinPcap主要用在Win32平臺,且提供了發送網絡數據包的功能。

1.1、WinPcap的下載與安裝

要使用WinPcap開發程序需要下載兩部分:一部分是WinPcap的驅動和運行庫,一部分是WinPcap的開發包。WinPcap驅動和運行庫的下載可以訪問:https://www.winpcap.org/install/default.htm,如下所示: 

 

WinPcap開發包的下載可以訪問https://www.winpcap.org/devel.htm,如下圖所示:

 需要注意的是雖然下載的是4.1.2版本的開發包,但是仍然兼容4.1.3版本的WinPcap驅動和動態庫。

WinPcap程序的安裝很簡單,只需按照提示進行就可以,需要注意的是,需要保持如下圖中的複選框的選中狀態:

 然後點擊Install安裝即可。

下載WinPcap的開發包並解壓:

 需要用到的只有Include和Lib兩個文件夾。重新建立一個名爲WinPcap的文件夾,將這兩個文件夾拷貝到WinPcap中,然後將WinPcap拷貝到程序工程目錄下。

1.2、WinPcap開發環境配置

在進行開發前,需要進行一些配置:

1、在每一個使用了庫的源程序中,將 pcap.h 頭文件包含(include)進來:

 2、如果你的程序使用了WinPcap的遠程捕獲功能,那麼在預處理定義中加入HAVE_REMOTE不要直接把remote-ext.h直接加入到源文件中:

 3、設置VC++的鏈接器(Linker),把wpcap.lib庫文件和ws2_32.lib庫文件包含進來:

4、將頭文件目錄和庫文件目錄添加到工程目錄下:

 

 

 當配置完成之後就可以進行WinPcap程序的開發了。

2、捕獲數據包

2.1、獲取本地網絡設備列表

在WinPcap中,獲取本地網絡設備列表是通過函數pcap_findalldevs實現的,函數原型如下所示:

int pcap_findalldevs (pcap_if_t **alldevsp, char *errbuf);
// alldevsp: 存儲本地網絡設備列表
// errbuf: 存儲錯誤信息
// 返回值:int,如果發生錯誤將返回-1,執行成功將返回0

 具體實例如下所示:

pcap_if_t*  alldevs;
char		errbuf[PCAP_ERRBUF_SIZE];
// 獲取本地機器的設備列表
if( pcap_findalldevs(&alldevs, errbuf) == -1)
{
	printf(">>> Error - Error in pcap_findalldevs: %s\n", errbuf);
}

捕獲的網絡設備列表均保存在結構體鏈表alldevs中。

 2.2、打印本地網絡設備列表

要打印網絡設備的信息,首先要知道pcap_if_t結構體的具體組成,pcap_if_t結構體的原型如下所示:

struct pcap_if
{
pcap_if*    next, //指向下一個元素,爲NULL則表示該元素爲列表的最後一個元素
char*       name,//表示該網絡設備的名稱
char*       description,//表示該網絡設備的描述
pcap_addr* address,//該網絡設備中的地址信息,包括IPV4、IPV6、子網掩碼等
u_int        flags,//用於表示是否爲迴環端口(loopback interface)
};
typedef struct pcap_if pcap_if_t;

可以看出,pcap_if_t結構體是pcap_if結構體的另外一種表示。結構體內包含了網絡設備的名稱、描述、地址信息等。

2.3、打開要操作的網絡設備

打開網絡設備採用pcap_open函數,pcap_open函數原型如下所示:

pcap_t* pcap_open(const char* source, //接口設備名,pcap_if_t結構體中的name
int   snaplen,   //捕獲數據的保存長度,一本可以設置爲65535,65536保證能捕獲到不同數據鏈路層上的每個數據包的全部內容
int   flags,//是否設置爲混雜模式,爲0則表示不設置爲混雜模式
int   read_timeout, //捕獲數據時的超時時間,設置爲0表示不抓取到數據不會返回,設置爲-1表示不管有沒有抓到數據都會返回,設置爲1000表示,如果沒有抓取到數據則會等待1000ms再返回
struct pcap_rmtauth* auth, //遠程機器驗證,如果不是遠程捕獲,則設爲NULL
char* errbuf               //存儲錯誤信息
);
返回值爲pcap_t類型的指針,可以理解爲打開的接口設備的句柄。接下來的操作都是針對該句柄進行的。如果函數執行錯誤則返回NULL並在errbuf中存儲錯誤信息。

具體示例如下所示:

// 打開想要捕獲的網絡設備
pcap_t* g_Capture_Adhandle = NULL;
if((g_Capture_Adhandle = pcap_open(d->name,// 要捕獲的接口設備名
65536,	// 65536保證能捕獲到不同數據鏈路層上的每個數據包的全部內容
0,	// 不設置爲混雜模式
0,	// 讀取超時時間。設置爲0,表示如果沒有數據包到達的話,則讀取操作永遠不會返回。
NULL,			// 遠程機器驗證
errbuf)) == NULL)// 錯誤緩衝區
{
	printf("\nUnable to open the adpater. %s is not supported by Winpcap.\n", d->name);
	pcap_freealldevs(alldevs);//釋放所有的機器列表
	return;
}

2.4、設置捕獲過濾器

在網絡中傳輸的有各種各樣的數據,爲了準確抓取網絡中傳輸的數據,需要在抓取數據時設置捕獲過濾器,將我們不需要的數據過濾掉,之抓取我們想要的數據。設置過濾器時需要用到兩個函數:pcap_compile和pcap_setfilter。

pcap_compile函數是用來編譯過濾器,pcap_compile函數的原型如下所示:

int pcap_compile(pcap_t* p, //打開的接口設備的句柄,pcap_open函數的返回值
struct bpf_program*   fp,  //bpf_program結構體指針,函數執行成功後會填入內容
char*   str,                //存儲過濾規則的字符串
int   optimize, //用於控制是否會在結果代碼(resulting code)上執行優化,一般設置爲1
bpf_u_int32 netmask //用於指定要捕獲的網絡設備上的IPV4的掩碼
);
函數如果執行失敗,則會返回-1。

pcap_setfilter函數是用來設置過濾器的,是將一個過濾器與內核捕獲回話相關聯,pcap_setfilter函數原型如下所示:

int pcap_setfilter(pcap_t* p, //打開的接口設備的句柄,pcap_open函數的返回值
struct bpf_program*   fp,  //pcap_compile函數執行後生成的bpf_program結構體指針,已包含相關數據
);
函數如果執行失敗,則會返回-1;執行成功則會返回0。

設置過濾器的實例如下所示:

// 首先獲得掩碼
u_int netmask;
pcap_if_t* d;  //選中的網絡接口設備
pcap_t* g_Capture_Adhandle; //打開的網絡設備的句柄
if(d->addresses != NULL)
{
	// 獲得該接口的第一個地址的掩碼
	netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
}
else
	netmask = 0xffffffff;	// 如果接口沒有地址, 那麼就設置一個C類掩碼
struct bpf_program fcode;
char packet_filter[] = "ip and udp";//過濾器,表示只抓取UDP報文
if(pcap_compile(g_Capture_Adhandle, &fcode, packet_filter, 1, netmask) < 0)
{
	printf("\nUnable to compile the packet filter.Check the Syntax!\n");
	pcap_freealldevs(alldevs);
	return;
}
// 設置過濾器
if(pcap_setfilter(g_Capture_Adhandle, &fcode) < 0)
{
	fprintf(stderr, "\nError setting the filter!\n");
	pcap_freealldevs(alldevs);
	return;
}

2.5、開始捕獲數據

捕獲數據可以採取多種方式進行,可以採用pcap_loop通過回調函數的方式接收數據,也可以通過pcap_next_ex直接捕獲,下面主要介紹通過pcap_next_ex函數捕獲數據的方式。pcap_next_ex函數原型如下所示:

int pcap_next_ex(pcap_t* p, //要捕獲數據的接口設備的句柄,pcap_open函數的返回值
struct pcap_pkthdr**   pkt_header,  //捕獲數據的一些信息
const u_char** pkt_data    //捕獲的數據
);
函數的返回值:
1:函數執行成功
0:讀取超時
-1:發生錯誤
-2:讀取離線文件時,當讀取到EOF時會返回-2

結構體pkt_header存儲了關於捕獲到的數據一些基本信息,結構體原型如下所示:

struct pcap_pkthdr
{
timeval ts,         //時間戳
bpf_u_int32 caplen,//捕獲到的數據長度
bpf_u_int32 len    //數據長度
};

捕獲數據的具體實例如下所示:

int res;
struct pcap_pkthdr*	header;
const u_char*		pkt_data;
if( (res = pcap_next_ex(g_Capture_Adhandle, &header, &pkt_data)) >= 0)
{
	if(res == 0)
	{
		printf("\n>>> Time out.\n");
		continue;
	}
	else
	{
		int recv_data_length = header->len;//接收到的數據長度
		char msg[MAX_LENGTH] = {0}; // MAX_LENGTH > recv_data_length
		memcpy(msg, (void *)(pkt_data), recv_data_length);
	}
}

3、發送數據

winpcap另外一個很強大的功能就是數據包的發送功能,它能夠發送任何類型/協議的數據包,只需要將數據包的格式轉換爲對應協議的數據格式即可。

3.1 單個發送數據包

winpcap中發送單個數據包的函數是pcap_sendpacket,函數原型如下所示:

int pcap_sendpacket(pcap_t* p, //要發送數據的接口設備的句柄,pcap_open函數的返回值
u_char*   buf,  //要發送的數據
int        size    //要發送的數據長度
);
函數的返回值:
1:函數執行發送數據失敗
0:函數執行成功

具體實例如下所示:

pcap_t* g_Capture_Adhandle; // 要發送數據的網絡接口設備的句柄
int len; // 要發送的數據的長度
u_char* data; //要發送的數據,數據長度爲len
if( pcap_sendpacket(g_Capture_Adhandle, data, len) != 0 )
{
	printf("\nError - Error sending the packet;\n", pcap_geterr(g_Capture_Adhandle));
	return 0;
}

3.2、隊列發送數據包

pcap_sendpacket雖然能夠向目標網絡接口設備發送數據,但是隻能單個發送,且執行效率並不是很高。winpcap中有另外一個機制用於發送數據,那就是pcap_sendqueue_transmit,即通過隊列的方式進行發送,採用這種方式,會一次性向目標設備發送隊列內的數據,效率比較高。相關的函數有pcap_sendqueue_allocpcap_sendqueue_queuepcap_sendqueue_transmit

其中pcap_sendqueue_alloc是用來申請隊列的長度,單位爲字節,所以計算出來的長度需要精確到字節,函數原型:pcap_send_queue* pcap_sendqueue_alloc(u_int memsize);其中返回值爲該發送隊列的句柄。

pcap_sendqueue_queue是用來將單個數據添加到隊列中,函數原型爲:int pcap_sendqueue_queue(pcap_send_queue* queue, const struct pcap_pkthdr* pkt_header, const u_char* pkt_data);其中queue爲pcap_sendqueue_alloc函數創建的隊列句柄,pkt_header爲爲數據包添加的數據包頭,pkt_data爲要發送的數據。

pcap_sendqueue_transmit是用來將數據發送出去的,函數原型爲:u_int pcap_sendqueue_transmit(pcap_t* p, pcap_send_queue* queue, int sync);其中p爲要發送數據的網絡接口設備句柄,queue爲創建的發送隊列,sync爲同步標誌,如果非0表示發送過程將是同步進行,即只有時間戳相符的數據包纔會被處理。這個操作會消耗大量的CPU資源,但是數據包的處理很精確。

通過隊列發送數據包的具體實例如下所示:

pcap_t* g_Capture_Adhandle; // 要發送數據的網絡接口設備的句柄
pcap_send_queue* s_queue;
u_char	winpcap_send_data[DATA_LENGTH] = {0}; //要發送的數據,數據長度爲DATA_LENGTH
// 計算髮送隊列的大小= (包大小+包頭長度)* 隊列最大個數
u_int alloc_len		= (DATA_LENGTH + sizeof(struct pcap_pkthdr)) * QueueLen;
s_queue	= pcap_sendqueue_alloc(alloc_len);		// 分配長度爲alloc_len的發送隊列
struct pcap_pkthdr	pkt_header;
pkt_header.ts		= ts;
pkt_header.caplen	= packet_send_len;
pkt_header.len		= packet_send_len;
//會執行多次,將多有的數據都添加到隊列中
if(pcap_sendqueue_queue(s_queue, &pkt_header, send_data) == -1)
{
	printf(">>> Warning: Packet buffer is too small, not all the packets will be sent!\n");
}
int sent_len; //真實發送出去的數據隊列的長度
if( (sent_len = pcap_sendqueue_transmit(g_Capture_Adhandle, s_queue, FALSE)) < s_queue->len)
{
	printf(">>> Error: An error occurred sending the packets: %s, only %d bytes were sent.\n", pcap_geterr(g_Capture_Adhandle), sent_len);
	//continue;
}
cur_queue_len = 0;
s_queue->len = 0;	// 發送完一個隊列之後一定記得將隊列的長度置爲0

pcap_sendqueue_destroy(s_queue);// 隊列使用完之後一定記得要釋放隊列

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章