libpcap是一個抓取網絡數據報文的C語言函數庫,使用這個庫可以非常方便的抓取網絡上的報文,方便我們分析經過我們設備上的各種報文;
使用libcap庫編譯時都要在後面加上-lpcap選項
使用pcap探測獲取網絡接口
char * pcap_lookupdev(char * errbuf)
這個函數就是用來探測網絡接口的,它會返回第一個合適的網絡接口字符串指針,如果出錯則在errbuf中返回,長度至少是PCAP_ERRBUF_SIZE。
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
char errBuf[PCAP_ERRBUF_SIZE], * devStr;
devStr = pcap_lookupdev(errBuf);
if (devStr)
printf("success: device: %s\n", devStr);
else
{
printf("error: %s\n", errBuf);
exit(1);
}
return 0;
}
注意這個函數是返回第一個合適的網絡接口字符串,我的主機第一個合適的網絡接口爲 vibir0端口,在這個虛擬端口下沒有辦法抓到包,因而要找到指定的端口要要循環遍歷端口後,進行選擇特定網卡端口
char * get(){
pcap_if_t *alldevs;
pcap_if_t *d;
int i=0;
char errbuf[PCAP_ERRBUF_SIZE];
/* 獲取本地機器設備列表 */
if (pcap_findalldevs( &alldevs, errbuf ) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs_ex: %s\n", errbuf);
exit(1);
}
for(d= alldevs; d != NULL; d= d->next)
{
printf("%d. %s\n", ++i, d->name);
}
//獲取指定的設備
char getPort[32];
gets(getPort);
for(d= alldevs; d != NULL; d= d->next)
{if(strcmp(d->name,getPort)==0)
{
printf("%d. %s", ++i, d->name);
break;
}
}
return d->name;
}
捕獲第一個報文
pcap_t * pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char * errbuf)
要捕獲報文,在探測到接口之後我們還要打開它,該函數會返回指定接口的pcap_t類型指針,後面的所有操作都要使用這個指針;
參數一:device是第一步探測到的接口的字符串;
參數二:snaplen是對於每個數據包,從開頭要抓多少個字節,我們可以設置這個值來只抓每個數據包的頭部,而不關心具體的內容。典型的以太網幀長度是1518字節,但其他的某些協議的數據包會更長一點,但任何一個協議的一個數據包長度都必然小於65535個字節;
參數三:promisc指定是否打開混雜模式(Promiscuous Mode),0表示非混雜模式,任何其他值表示混合模式。如果要打開混雜模式,那麼網卡必須也要打開混雜模式,可以使用如下的命令打開例如eth0混雜模式:
ifconfig eth0 promisc;
參數四:to_ms指定需要等待的毫秒數,超過這個數值後,第3步獲取數據包的這幾個函數就會立即返回。0表示一直等待直到有數據包到來;
參數五:用來返回錯誤信息;
void pcap_close(pcap_t * p)
釋放網絡接口,用於關閉pcap_open_live()獲取的pcap_t的網絡接口對象並釋放相關資源;
u_char * pcap_next(pcap_t * p, struct pcap_pkthdr * h)
該函數收到一個包就立刻返回,返回值爲NULL表示沒有收到包;
第一個參數是第2)個函數pcap_open_live返回的指針,第二個參數記錄了報文長度等,其結構也可以在pcap.h裏查看到;
循環捕獲報文
int pcap_loop(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
第一個參數是第2)個函數pcap_open_live返回的指針,第二個參數是需要抓的數據包的個數,一旦抓到了cnt個數據包,pcap_loop立即返回。負數的cnt表示pcap_loop永遠循環抓包,直到出現錯誤;第三個參數是一個回調函數指針,形式如下:void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet) 第一個參數是pcap_loop的最後一個參數,第二個參數是收到的數據包的pcap_pkthdr結構,第三個參數是數據包數據;
int pcap_dispatch(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
和pcap_loop()非常類似,它同時還受pcap_open_live()的第4個參數to_ms控制超時返回時間;
#include <pcap.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
void processPacket(u_char *arg, const struct pcap_pkthdr *pkthdr, const u_char *packet)
{
int *count = (int *)arg;
printf("Packet Count: %d\n", ++(*count));
printf("Received Packet Size: %d\n", pkthdr->len);
printf("Payload:\n");
for(int i=0; i < pkthdr->len; ++i) //print
{
printf("%02x ", packet[i]);
if ((i + 1) % 16 == 0)
{
printf("\n");
}
}
printf("\n\n");
return;
}
int main()
{
char errBuf[PCAP_ERRBUF_SIZE], * devStr;
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);
}
int count = 0;
/*Loop forever & call processPacket() for every received packet.*/
pcap_loop(device, -1, processPacket, (u_char *)&count);
pcap_close(device);
return 0;
}
過濾數據包函數
libpcap捕獲報文是把經過的報文複製一份,當接口上報文數量很大時,抓取報文非常的佔用系統資源,通過過濾數據包來捕獲數據包,既可以過濾掉我們不想要的報文,又可以提高效率;
設置過濾條件,就是過濾表達式;
舉例:
src host 192.168.1.177,dst port 80,not tcp等;
int pcap_compile(pcap_t * p, struct bpf_program fp, char \ str, int optimize, bpf_u_int32 netmask)
fp是一個傳出參數,存放編譯後的bpf,str是過濾表達式,optimize是否需要優化過濾表達式,metmask簡單設置爲0即可;
int pcap_setfilter(pcap_t p, struct bpf_program fp)
fp就是前一步pcap_compile()的第二個參數;
struct bpf_program filter;
pcap_compile(device, &filter, "tcp", 1, 0);
pcap_setfilter(device, &filter);
把數據包保存到文件
捕獲到數據包之後,通常就是對數據包的分析,具體的報文分析方法要依據網絡協議詳細分類並展開處理,這裏不做討論。我們可以暫時把捕獲到的數據包保存到文件中,稍後再由分析報文的程序或者工具來具體分析;libpcap庫提供了保存爲pcap類型的函數,非常方便,保存之後就可以用Wireshark直接打開了,如果想保存爲純粹的數據包,我們也可以用C語言的文件操作,直接把數據包以二進制的形式保存到文件中;
pcap_dumper_t *pcap_dump_open(pcap_t *p, const char *file)
函數返回pcap_dumper_t類型的指針,file是文件名,可以是絕對路徑,例如:/home/iona/packet.pcap;
void pcap_dump_close(pcap_dumper_t *p);
用來關閉pcap_dump_open打開的文件,入參是pcap_dump_open返回的指針;
int pcap_dump_flush(pcap_dumper_t *p)
刷新緩衝區,把捕獲的數據包從緩衝區真正拷貝到文件;
void pcap_dump(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
輸出數據到文件,與pcap_loop的第二個參數回調函數void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet) 形式完全相同,可以直接當pcap_loop的第二個參數;
#include <pcap.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
void processPacket(u_char *arg, const struct pcap_pkthdr *pkthdr, const u_char *packet)
{
pcap_dump(arg, pkthdr, packet);
printf("Received Packet Size: %d\n", pkthdr->len);
return;
}
int main()
{
char errBuf[PCAP_ERRBUF_SIZE], * devStr;
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);
}
/*open pcap write output file*/
pcap_dumper_t* out_pcap;
out_pcap = pcap_dump_open(device,"pack.pcap");
/*Loop forever & call processPacket() for every received packet.*/
pcap_loop(device, 20, processPacket, (u_char *)out_pcap);
/*flush buff*/
pcap_dump_flush(out_pcap);
pcap_dump_close(out_pcap);
pcap_close(device);
return 0;
}