1. 程式人生 > >《Linux網路程式設計》: libpcap 詳解

《Linux網路程式設計》: libpcap 詳解

1.概述

libpcap (Packet Capture Library)是一個網路資料包捕獲函式庫,是Unix/Linux平臺下的網路資料包捕獲函式庫。它是一個獨立於系統的使用者層包捕獲的API介面,為底層網路監測提供了一個可移植的框架。功能非常強大,Linux 下著名的 tcpdump 就是以它為基礎的。該庫提供的C函式介面用於捕獲經過指定網路介面(通過將網絡卡設定為混雜模式,可以捕獲所有經過該網路介面的資料包)的資料包。

2.libpcap主要的作用

Libpcap面向上層應用,提供了使用者級別的網路資料包捕獲介面,在系統部署時充分考慮到應用程式的可以移植性。

Libpcap主要有如下功能:

1.資料包捕獲

捕獲流經本網絡卡的所有原始資料包,甚至對交換裝置中的資料包也能夠進行捕獲,本功能是嗅探器的基礎。

2. 自定義資料包傳送

構造任意格式的原始資料包,併發送到目標網路,本功能是新協議驗證、甚至攻擊驗證的基礎。

3. 流量採集與統計

對所採集到的網路中的流量資訊進行按照新規則分類,按指標進行統計,並輸出到指定終端。利用這項功能可以分析目標網路的流量特性。

4. 規則過濾

Libpcap自帶規則過濾功能,並提供指令碼程式設計介面,能夠按照使用者程式設計的方式對已經採集到的資料包進行過濾,以便提高分析的效能。

3. Libpcap的應用範圍

由於擁有強大的功能,當前基於Libpcap的應用比較廣泛,有很多Unix上的流量相關的網路系統都是基於Libpcap的,它的一些典型應用如下:

1. 網路協議分析器

Libpcap應用最多的就是網路協議分析器,也可以稱之為網路嗅探。

2. 網路流量發生器

網路流量發生器也是Libpcap的一大應用,它是基於Libpcap的資料構造與傳送功能,可以有針對性的構造各種形式的資料包,並執行傳送工作,這樣的組合便構成了網路流量的產生工具。

3. 網路入侵檢測系統

網路入侵檢測系統(IDS)是發現網路入侵行為的關鍵,利用Libpcap所提供的資料包捕獲功能,可以進一步開發出IDS。

4. 網路掃描器

這是基於Libpacp的資料包構造與傳送功能。當前最著名的網路掃描器Tcpdump就是基於Libpcap開發而成的。

5. 其他安全工具

利用Libpacp可以設計出其他的網路安全工具。

不過有一點要提醒:Libpacp只是提供網路資料包的監測以及傳送功能,並不提供控制層面的服務,類似於防火牆那種資料包過濾的功能,Libpcap是無法實現的。

4. libpcap工作原理

libpcap主要由兩部份組成:網路分介面(Network Tap)和資料過濾器(Packet Filter)。

libpcap的包捕獲機制就是在資料鏈路層加一個旁路處理。當一個數據包到達網路介面時,libpcap首先利用鏈路層PF_PACKET原始套接字從鏈路層驅動程式中獲得該資料包的拷貝,再通過Tap函式將資料包發給BPF過濾器。BPF過濾器根據使用者已經定義好的過濾規則對資料包進行逐一匹配,匹配成功則放入核心緩衝區,並傳遞給使用者緩衝區,匹配失敗則直接丟棄。如果沒有設定過濾規則,所有資料包都將放入核心緩衝區,並傳遞給使用者層緩衝區。所以整個過程並不干擾系統自身的網路協議棧的處理。


如圖所示一個數據包的捕捉分為三個主要部分: 

- 面向底層包捕獲、 
- 面向中間層的資料包過濾 
- 面向應用層的使用者介面

5. libpcap 庫介紹

Libpcap庫主要由三個部分組成,網路分接頭、資料包過濾器和使用者API

1. 網路分接頭

網路分接頭(Network Tap)是一種鏈路層旁路機制,負責採集網絡卡資料包

2. 資料包過濾器

資料包過濾器(Packet Filter)是針對資料包的一種過濾機制,在Libpcap中採用BPF(BSD Packet Filter)演算法對資料包執行過濾操作,這種演算法的基本思想就是基於規則匹配,對伊符合條件的額資料包進行放行。

3. 使用者API

使用者API是Libpcap面向上層應用程式提供的程式設計介面,使用者通過呼叫相關的函式實現資料包的捕獲或者傳送。

6. Libpcap函式庫開發應用程式的基本步驟

1. 網路裝置查詢

網路裝置查詢的目的就是發現可用的網絡卡,它的實現函式是pcap_lookupdev(),如果當前有多個網絡卡,它會返回一個網路裝置名指標列表。

2. 開啟網路裝置

利用第一步的返回值,使用者可以決定Libpcap實用哪個網絡卡,當然開啟這個網路裝置的函式是pcap_open_live(),它返回用於捕獲網路資料包的資料包捕獲描述字。對於此網路裝置的任何操作都要基於這個描述字。

3. 獲取網路引數

利用pcap_lookupnet()函式,可以獲得指定網路裝置的IP地址和子網掩碼,當然這個步驟並不是必須,但是為了顯示的完整性,有時候還是需要標記這些引數。

4. 編譯過濾策略

Libpcap的一個功能就是提供資料包過濾,即只採集符合規則的資料包,此項功能由函式pcap_compile()來實現,這個函式的作用即使將使用者指定的過濾策略編譯到過濾程式中。

5. 設定過濾器

這是上步驟的繼續,pcap_setfilter()函式用於設定上一步驟配置好的過濾器,當然要註明一點,步驟4和步驟5也不是必須的,如果不採用過濾,意味著程式會抓到所有的網路資料包。

6. 利用回撥函式捕獲資料包

Libpcap提供的是一種回撥的機制來獲取資料包,可以採用pcap_loop()和pcap_dispatch()函式來抓取資料包,當然,也可以呼叫pcap_next和pcap_next_ex()函式來完成同樣的工作。

若資料包捕獲到之後,應用程式就可以採用相應的方式進行資料包解析,分析其中感興趣的資訊,當然分析工作往往是整個程式的關鍵。

7.  關閉網路裝置

當應用程式工作完畢時,可以呼叫pcap_close()函式關閉網路裝置,釋放資源。

7. libpcap 的安裝

8. Libpcap庫主要函式說明

首先要使用 libpcap,我們必須包含 pcap.h 標頭檔案,可以在 /usr/local/include/pcap/pcap.h 找到,其中包含了每個型別定義的詳細說明。

1、獲取網路介面裝置名

char *pcap_lookupdev(char *errbuf);

功能:得到可用的網路裝置名指標

引數:

errbuf:存放出錯資訊字串,裡面有個巨集定義:PCAP_ERRBUF_SIZE,為錯誤緩衝區大小。

返回值:

成功返回裝置名指標(第一個合適的網路介面的字串指標);

失敗返回 NULL,同時,errbuf 存放出錯資訊字串。

例項如下:

char error_content[PCAP_ERRBUF_SIZE] = {0};	// 出錯資訊
char *dev = pcap_lookupdev(error_content);
if(NULL == dev)
{
	printf(error_content);
	exit(-1);
}

2、獲取網路號(ip 地址)和掩碼

int pcap_lookupnet(    char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp,  char *errbuf  );

功能:獲取指定網絡卡的 ip 地址,子網掩碼

引數:

device:網路裝置名,為第一步獲取的網路介面字串(pcap_lookupdev() 的返回值 ),也可人為指定,如“eth0”。

netp:存放 ip 地址的指標,bpf_u_int32 為 32 位無符號整型

maskp:存放子網掩碼的指標,bpf_u_int32 為 32 位無符號整型

errbuf:存放出錯資訊

返回值:

成功返回 0,失敗返回 -1

例項如下:

char error_content[PCAP_ERRBUF_SIZE] = {0};	// 出錯資訊
char *dev = pcap_lookupdev(error_content);
if(NULL == dev)
{
	printf(error_content);
	exit(-1);
}
 
 
bpf_u_int32 netp = 0, maskp = 0;
pcap_t * pcap_handle = NULL;
int ret = 0;
 
//獲得網路號和掩碼
ret = pcap_lookupnet(dev, &netp, &maskp, error_content);
if(ret == -1)
{
	printf(error_content);
	exit(-1);
}

3、開啟網路介面

pcap_t *pcap_open_live(  const char *device,int snaplen,int promisc,int to_ms,char *ebuf );

功能:開啟一個用於捕獲資料的網路介面

引數:

device:網路介面的名字,為第一步獲取的網路介面字串(pcap_lookupdev() 的返回值 ),也可人為指定,如“eth0”。

snaplen:捕獲資料包的長度,長度不能大於 65535 個位元組。

promise:“1” 代表混雜模式,其它非混雜模式。什麼為混雜模式,請看《Linux網路程式設計》: 原始套接字介紹

to_ms:指定需要等待的毫秒數,超過這個數值後,獲取資料包的函式就會立即返回(這個函式不會阻塞,後面的抓包函式才會阻塞)。0 表示一直等待直到有資料包到來。

ebuf:儲存錯誤資訊。

返回值:

返回 pcap_t 型別指標,後面的所有操作都要使用這個指標。

例項如下:

char error_content[PCAP_ERRBUF_SIZE] = {0};	// 出錯資訊
char *dev = pcap_lookupdev(error_content);	// 獲取網路介面
if(NULL == dev)
{
	printf(error_content);
	exit(-1);
}
 
// 開啟網路介面
pcap_t * pcap_handle = pcap_open_live(dev, 1024, 1, 0, error_content);
if(NULL == pcap_handle)
{
	printf(error_content);
	exit(-1);
}

4、獲取資料包

const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h);

功能:捕獲一個網路資料包,收到一個數據包立即返回

引數:

p:pcap_open_live()返回的 pcap_t 型別的指標

h:資料包頭

返回值: 成功返回捕獲資料包的地址,失敗返回 NULL

pcap_pkthdr 型別的定義如下:

struct pcap_pkthdr
{
	struct timeval ts; // 抓到包的時間
	bpf_u_int32 caplen; // 表示抓到的資料長度
	bpf_u_int32 len; // 表示資料包的實際長度
}

len 和 caplen的區別:
因為在某些情況下你不能保證捕獲的包是完整的,例如一個包長 1480,但是你捕獲到 1000 的時候,可能因為某些原因就中止捕獲了,所以 caplen 是記錄實際捕獲的包長,也就是 1000,而 len 就是 1480。 

例項如下:

const unsigned char *p_packet_content = NULL; // 儲存接收到的資料包的起始地址
pcap_t *pcap_handle = NULL;
struct pcap_pkthdr protocol_header;
 
pcap_handle = pcap_open_live("eth0", 1024, 1, 0,NULL);
 
p_packet_content = pcap_next(pcap_handle, &protocol_header); 
//p_packet_content  所捕獲資料包的地址
		
printf("Capture Time is :%s",ctime((const time_t *)&protocol_header.ts.tv_sec)); // 時間
printf("Packet Lenght is :%d\n",protocol_header.len);	// 資料包的實際長度
 
// 分析乙太網中的 源mac、目的mac
struct ether_header *ethernet_protocol = NULL;
unsigned char *p_mac_string = NULL;			// 儲存mac的地址,臨時變數
 
ethernet_protocol = (struct ether_header *)p_packet_content;  //struct ether_header 乙太網幀頭部
 
p_mac_string = (unsigned char *)ethernet_protocol->ether_shost;//獲取源mac
printf("Mac Source Address is %02x:%02x:%02x:%02x:%02x:%02x\n",*(p_mac_string+0),* \
(p_mac_string+1),*(p_mac_string+2),*(p_mac_string+3),*(p_mac_string+4),*(p_mac_string+5));
 
p_mac_string = (unsigned char *)ethernet_protocol->ether_dhost;//獲取目的mac
printf("Mac Destination Address is %02x:%02x:%02x:%02x:%02x:%02x\n",*(p_mac_string+0),* \
(p_mac_string+1),*(p_mac_string+2),*(p_mac_string+3),*(p_mac_string+4),*(p_mac_string+5));

int pcap_loop( pcap_t *p,int cnt,pcap_handler callback,u_char *user );

功能:

迴圈捕獲網路資料包,直到遇到錯誤或者滿足退出條件。每次捕獲一個數據包就會呼叫 callback 指定的回撥函式,所以,可以在回撥函式中進行資料包的處理操作。

引數:

p:pcap_open_live()返回的 pcap_t 型別的指標。

cnt:指定捕獲資料包的個數,一旦抓到了 cnt 個數據包,pcap_loop 立即返回。如果是 -1,就會永無休止的捕獲,直到出現錯誤。

callback:回撥函式,名字任意,根據需要自行起名。

user:向回撥函式中傳遞的引數。

callback 回撥函式的定義:

void callback(  u_char *userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet )

userarg:pcap_loop() 的最後一個引數,當收到足夠數量的包後 pcap_loop 會呼叫callback 回撥函式,同時將pcap_loop()的user引數傳遞給它

pkthdr:是收到資料包的 pcap_pkthdr 型別的指標,和 pcap_next() 第二個引數是一樣的。

packet :收到的資料包資料


返回值: 成功返回0,失敗返回負數

例項如下:

if( pcap_loop(pcap_handle, -1, ethernet_protocol_callback, NULL) < 0 )
{
	perror("pcap_loop");
}
 
/*******************************回撥函式************************************/
void ethernet_protocol_callback(unsigned char *argument,const struct pcap_pkthdr\
 *packet_heaher,const unsigned char *packet_content)
{
	unsigned char *mac_string;				//
	struct ether_header *ethernet_protocol;
	unsigned short ethernet_type;			//乙太網型別
	printf("----------------------------------------------------\n");
	printf("%s\n", ctime((time_t *)&(packet_heaher->ts.tv_sec))); //轉換時間
	ethernet_protocol = (struct ether_header *)packet_content;
	
	mac_string = (unsigned char *)ethernet_protocol->ether_shost;//獲取源mac地址
	printf("Mac Source Address is %02x:%02x:%02x:%02x:%02x:%02x\n",*(mac_string+0),\
*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
	
	mac_string = (unsigned char *)ethernet_protocol->ether_dhost;//獲取目的mac
	printf("Mac Destination Address is %02x:%02x:%02x:%02x:%02x:%02x\n",\
   *(mac_string+0),*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*\
(mac_string+5));
	
	ethernet_type = ntohs(ethernet_protocol->ether_type);//獲得乙太網的型別
	printf("Ethernet type is :%04x\n",ethernet_type);
	switch(ethernet_type)
	{
		case 0x0800:printf("The network layer is IP protocol\n");break;//ip
		case 0x0806:printf("The network layer is ARP protocol\n");break;//arp
		case 0x0835:printf("The network layer is RARP protocol\n");break;//rarp
		default:break;
	}
	usleep(800*1000);
}

int pcap_dispatch(pcap_t * p, int cnt, pcap_handler callback, u_char * user);

這個函式和 pcap_loop() 非常類似,只是在超過 to_ms 毫秒後就會返回( to_ms 是pcap_open_live() 的第4個引數 )

5、釋放網路介面

void pcap_close(pcap_t *p);

功能:

關閉 pcap_open_live() 開啟的網路介面(即其返回值,pcap_t 型別指標),並釋放相關資源。注意,操作完網路介面,應該釋放其資源。

引數:

p:需要關閉的網路介面,pcap_open_live() 的返回值(pcap_t 型別指標)

返回值:

例項如下:

// 開啟網路介面
pcap_t * pcap_handle = pcap_open_live("eth0", 1024, 1, 0, error_content);
if(NULL == pcap_handle)
{
	printf(error_content);
	exit(-1);
}
 
//// ……
//// ……
 
pcap_close(pcap_handle); //釋放網路介面

例子1(接收一個數據包):

#include <stdio.h>
#include <pcap.h>
#include <arpa/inet.h>
#include <time.h>
#include <stdlib.h>
struct ether_header
{
	unsigned char ether_dhost[6];	//目的mac
	unsigned char ether_shost[6];	//源mac
	unsigned short ether_type;		//乙太網型別
};
#define BUFSIZE 1514
 
int main(int argc,char *argv[])
{
	pcap_t * pcap_handle = NULL;
	char error_content[100] = "";	// 出錯資訊
	const unsigned char *p_packet_content = NULL;		// 儲存接收到的資料包的起始地址
	unsigned char *p_mac_string = NULL;			// 儲存mac的地址,臨時變數
	unsigned short ethernet_type = 0;			// 乙太網型別
	char *p_net_interface_name = NULL;		// 介面名字
	struct pcap_pkthdr protocol_header;
	struct ether_header *ethernet_protocol;
 
	//獲得介面名
	p_net_interface_name = pcap_lookupdev(error_content);
	if(NULL == p_net_interface_name)
	{
		perror("pcap_lookupdev");
		exit(-1);
	}
	
	//開啟網路介面
	pcap_handle = pcap_open_live(p_net_interface_name,BUFSIZE,1,0,error_content);
	p_packet_content = pcap_next(pcap_handle,&protocol_header);
	
	printf("------------------------------------------------------------------------\n");
	printf("capture a Packet from p_net_interface_name :%s\n",p_net_interface_name);
	printf("Capture Time is :%s",ctime((const time_t *)&protocol_header.ts.tv_sec));
	printf("Packet Lenght is :%d\n",protocol_header.len);
	
	/*
	*分析乙太網中的 源mac、目的mac
	*/
	ethernet_protocol = (struct ether_header *)p_packet_content;
	p_mac_string = (unsigned char *)ethernet_protocol->ether_shost;//獲取源mac
	printf("Mac Source Address is %02x:%02x:%02x:%02x:%02x:%02x\n",*(p_mac_string+0),*\
(p_mac_string+1),*(p_mac_string+2),*(p_mac_string+3),*(p_mac_string+4),*(p_mac_string+5));
	p_mac_string = (unsigned char *)ethernet_protocol->ether_dhost;//獲取目的mac
	printf("Mac Destination Address is %02x:%02x:%02x:%02x:%02x:%02x\n",*\
(p_mac_string+0),*(p_mac_string+1),*(p_mac_string+2),*(p_mac_string+3),*(p_mac_string+4),*(p_mac_string+5));
 
	/*
	*獲得乙太網的資料包的地址,然後分析出上層網路協議的型別
	*/
	ethernet_type = ntohs(ethernet_protocol->ether_type);
	printf("Ethernet type is :%04x\t",ethernet_type);
	switch(ethernet_type)
	{
		case 0x0800:printf("The network layer is IP protocol\n");break;//ip
		case 0x0806:printf("The network layer is ARP protocol\n");break;//arp
		case 0x0835:printf("The network layer is RARP protocol\n");break;//rarp
		default:printf("The network layer unknow!\n");break;
	}
	
	pcap_close(pcap_handle);
	return 0;
}

注意:gcc 編譯時需要加上 -lpcap,執行時需要使用超級許可權

例子2(接收多個數據包):

#include <stdio.h>
#include <pcap.h>
#include <arpa/inet.h>
#include <time.h>
#include <stdlib.h>
 
#define BUFSIZE 1514
 
struct ether_header
{
	unsigned char ether_dhost[6];	//目的mac
	unsigned char ether_shost[6];	//源mac
	unsigned short ether_type;		//乙太網型別
};
 
/*******************************回撥函式************************************/
void ethernet_protocol_callback(unsigned char *argument,const struct pcap_pkthdr \
 *packet_heaher,const unsigned char *packet_content)
{
	unsigned char *mac_string;				//
	struct ether_header *ethernet_protocol;
	unsigned short ethernet_type;			//乙太網型別
	printf("----------------------------------------------------\n");
	printf("%s\n", ctime((time_t *)&(packet_heaher->ts.tv_sec))); //轉換時間
	ethernet_protocol = (struct ether_header *)packet_content;
	
	mac_string = (unsigned char *)ethernet_protocol->ether_shost;//獲取源mac地址
	printf("Mac Source Address is %02x:%02x:%02x:%02x:%02x:%02x\n",*(mac_string+0),\
*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
	mac_string = (unsigned char *)ethernet_protocol->ether_dhost;//獲取目的mac
	printf("Mac Destination Address is %02x:%02x:%02x:%02x:%02x:%02x\n",\
*(mac_string+0),*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*\
(mac_string+5));
	
	ethernet_type = ntohs(ethernet_protocol->ether_type);//獲得乙太網的型別
	printf("Ethernet type is :%04x\n",ethernet_type);
	switch(ethernet_type)
	{
		case 0x0800:printf("The network layer is IP protocol\n");break;//ip
		case 0x0806:printf("The network layer is ARP protocol\n");break;//arp
		case 0x0835:printf("The network layer is RARP protocol\n");break;//rarp
		default:break;
	}
	usleep(800*1000);
}
 
int main(int argc, char *argv[])
{
	char error_content[100];	//出錯資訊
	pcap_t * pcap_handle;
	unsigned char *mac_string;				
	unsigned short ethernet_type;			//乙太網型別
	char *net_interface = NULL;					//介面名字
	struct pcap_pkthdr protocol_header;
	struct ether_header *ethernet_protocol;
	
	//獲取網路介面
	net_interface = pcap_lookupdev(error_content);
	if(NULL == net_interface)
	{
		perror("pcap_lookupdev");
		exit(-1);
	}
 
	pcap_handle = pcap_open_live(net_interface,BUFSIZE,1,0,error_content);//開啟網路介面
		
	if(pcap_loop(pcap_handle,-1,ethernet_protocol_callback,NULL) < 0)
	{
		perror("pcap_loop");
	}
	
	pcap_close(pcap_handle);
	return 0;
}

執行情況如下:

6. 過濾資料包

我們抓到的資料包往往很多,如何過濾掉我們不感興趣的資料包呢?

幾乎所有的作業系統( BSD, AIX, Mac OS, Linux 等)都會在核心中提供過濾資料包的方法,主要都是基於 BSD Packet Filter( BPF ) 結構的。libpcap 利用 BPF 來過濾資料包。

1)設定過濾條件

BPF 使用一種類似於組合語言的語法書寫過濾表示式,不過 libpcap 和 tcpdump 都把它封裝成更高階且更容易的語法了,具體可以通過 man tcpdump檢視:

以下是一些例子

tcpdump src host 192.168.1.177

只接收源 ip 地址是 192.168.1.177 的資料包

tcpdump dst port 80

只接收 tcp/udp 的目的埠是 80 的資料包

tcpdump not tcp

只接收不使用 tcp 協議的資料包

tcpdump tcp[13] == 0x02 and (dst port 22 or dst port 23)

只接收 SYN 標誌位置位且目標埠是 22 或 23 的資料包( tcp 首部開始的第 13 個位元組)

tcpdump icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo

只接收 icmp 的 ping 請求和 ping 響應的資料包

tcpdump ehter dst 00:e0:09:c1:0e:82

只接收乙太網 mac 地址是 00:e0:09:c1:0e:82 的資料包

tcpdump ip[8] == 5

只接收 ip 的 ttl=5 的資料包(ip首部開始的第8個位元組)

2)編譯 BPF 過濾規則

int pcap_compile(  pcap_t *p,struct bpf_program *fp,char *buf,int optimize,bpf_u_int32 mask );

功能:編譯 BPF 過濾規則

引數:

p:pcap_open_live() 返回的 pcap_t 型別的指標

fp:存放編譯後的 bpf,應用過濾規則時需要用到這個指標

buf:過濾條件

optimize:是否需要優化過濾表示式

mask:指定本地網路的網路掩碼,不需要時可寫 0

返回值:

成功返回 0,失敗返回 -1

3)應用 BPF 過濾規則

int pcap_setfilter( pcap_t * p,  struct bpf_program * fp );

功能:應用 BPF 過濾規則,簡單理解為讓過濾生效

引數:

p:pcap_open_live() 返回的 pcap_t 型別的指標

fp:pcap_compile() 的第二個引數

返回值:

成功返回 0,失敗返回 -1

這個編譯應用過程,有點類似於,我們寫一個 C 程式,先編譯,後執行的過程。

應用完過濾表示式之後我們便可以使用 pcap_loop() 或 pcap_next() 等抓包函式來抓包了。

下面的程式演示瞭如何過濾資料包,我們只接收目的埠是 80 的資料包:

#include <pcap.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
 
void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
{
  int * id = (int *)arg;
  
  printf("id: %d\n", ++(*id));
  printf("Packet length: %d\n", pkthdr->len);
  printf("Number of bytes: %d\n", pkthdr->caplen);
  printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec)); 
  
  int i;
  for(i=0; i<pkthdr->len; ++i)
  {
    printf(" %02x", packet[i]);
    if( (i + 1) % 16 == 0 )
    {
      printf("\n");
    }
  }
  
  printf("\n\n");
}
 
int main()
{
  char errBuf[PCAP_ERRBUF_SIZE], * devStr;
  
  /* get a device */
  devStr = pcap_lookupdev(errBuf);
  
  if(devStr)
  {
    printf("success: device: %s\n", devStr);
  }
  else
  {
    printf("error: %s\n", errBuf);
    exit(1);
  }
  
  /* open a device, wait until a packet arrives */
  pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);
  
  if(!device)
  {
    printf("error: pcap_open_live(): %s\n", errBuf);
    exit(1);
  }
  
  /* construct a filter */
  struct bpf_program filter;
  pcap_compile(device, &filter, "dst port 80", 1, 0);
  pcap_setfilter(device, &filter);
  
  /* wait loop forever */
  int id = 0;
  pcap_loop(device, -1, getPacket, (u_char*)&id);
  
  pcap_close(device);
 
  return 0;
}