使用 libpcap 实现网络转包
概述
libpcap 是一個網絡數據包捕獲函數庫,功能非常強大,Linux 下著名的 tcpdump 就是以它為基礎的。
libpcap主要的作用
1)捕獲各種數據包,列如:網絡流量統計。
2)過濾網絡數據包,列如:過濾掉本地上的一些數據,類似防火墻。
3)分析網絡數據包,列如:分析網絡協議,數據的采集。
4)存儲網絡數據包,列如:保存捕獲的數據以為將來進行分析。
libpcap 的安裝
libpcap 的抓包框架
pcap_lookupdev():函數用于查找網絡設備,返回可被 pcap_open_live() 函數調用的網絡設備名指針。
pcap_lookupnet():函數獲得指定網絡設備的網絡號和掩碼。
pcap_open_live(): 函數用于打開網絡設備,并且返回用于捕獲網絡數據包的數據包捕獲描述字。對于此網絡設備的操作都要基于此網絡設備描述字。
pcap_compile(): 函數用于將用戶制定的過濾策略編譯到過濾程序中。
pcap_setfilter():函數用于設置過濾器。
pcap_loop():函數 pcap_dispatch() 函數用于捕獲數據包,捕獲后還可以進行處理,此外 pcap_next() 和 pcap_next_ex() 兩個函數也可以用來捕獲數據包。
pcap_close():函數用于關閉網絡設備,釋放資源。
利用 libpcap 函數庫開發應用程序的基本步驟:
1、打開網絡設備
2、設置過濾規則
3、捕獲數據
4、關閉網絡設備
抓包詳細步驟
首先要使用 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” 代表混雜模式,其它非混雜模式。什么為混雜模式,請看《原始套接字編程》。
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、獲取數據包
a)
const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h);
功能:
捕獲一個網絡數據包,收到一個數據包立即返回
參數:
p:pcap_open_live()返回的?pcap_t?類型的指針
h:數據包頭
pcap_pkthdr 類型的定義如下:
struct?pcap_pkthdr {struct?timeval?ts;?//?抓到包的時間bpf_u_int32?caplen;?//?表示抓到的數據長度bpf_u_int32?len;?//?表示數據包的實際長度 }len 和 caplen的區別:
返回值:
成功返回捕獲數據包的地址,失敗返回 NULL
實例如下:
[cpp]
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));b)
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,失敗返回負數
實例如下:
[cpp]
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;//獲取目的macprintf("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;//ipcase?0x0806:printf("The?network?layer?is?ARP?protocol\n");break;//arpcase?0x0835:printf("The?network?layer?is?RARP?protocol\n");break;//rarpdefault:break;}usleep(800*1000); }c)
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]; //目的macunsigned?char?ether_shost[6]; //源macunsigned?short?ether_type; //以太網類型 }; #define?BUFSIZE?1514int?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;//獲取源macprintf("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;//獲取目的macprintf("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;//ipcase?0x0806:printf("The?network?layer?is?ARP?protocol\n");break;//arpcase?0x0835:printf("The?network?layer?is?RARP?protocol\n");break;//rarpdefault:printf("The?network?layer?unknow!\n");break;}pcap_close(pcap_handle);return?0; }注意:gcc 編譯時需要加上 -lpcap,運行時需要使用超級權限
例子2(接收多個數據包):
[cpp] #include?<stdio.h> #include?<pcap.h> #include?<arpa/inet.h> #include?<time.h> #include?<stdlib.h>#define?BUFSIZE?1514struct?ether_header {unsigned?char?ether_dhost[6]; //目的macunsigned?char?ether_shost[6]; //源macunsigned?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;//獲取目的macprintf("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;//ipcase?0x0806:printf("The?network?layer?is?ARP?protocol\n");break;//arpcase?0x0835:printf("The?network?layer?is?RARP?protocol\n");break;//rarpdefault: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; }
運行情況如下:
過濾數據包
我們抓到的數據包往往很多,如何過濾掉我們不感興趣的數據包呢?
幾乎所有的操作系統( BSD, AIX, Mac OS, Linux 等)都會在內核中提供過濾數據包的方法,主要都是基于 BSD Packet Filter( BPF ) 結構的。libpcap 利用 BPF 來過濾數據包。
1)設置過濾條件
BPF 使用一種類似于匯編語言的語法書寫過濾表達式,不過 libpcap 和 tcpdump 都把它封裝成更高級且更容易的語法了,具體可以通過 man tcpdump查看:
以下是一些例子:
src host 192.168.1.177
只接收源 ip 地址是 192.168.1.177 的數據包
dst port 80
只接收 tcp/udp 的目的端口是 80 的數據包
not tcp
只接收不使用 tcp 協議的數據包
tcp[13] == 0x02 and (dst port 22 or dst port 23)
只接收 SYN 標志位置位且目標端口是 22 或 23 的數據包( tcp 首部開始的第 13 個字節)
icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo
只接收 icmp 的 ping 請求和 ping 響應的數據包
ehter dst 00:e0:09:c1:0e:82
只接收以太網 mac 地址是 00:e0:09:c1:0e:82 的數據包
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 的數據包:
?[cpp]
#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; }轉載出處:http://blog.csdn.net/tennysonsky/article/details/44811899
轉載于:https://blog.51cto.com/aaroncao/1840193
總結
以上是生活随笔為你收集整理的使用 libpcap 实现网络转包的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android隐藏状态栏、导航栏
- 下一篇: android的m、mm、mmm编译命令