libpcap讲解与API接口函数讲解
ibpcap(Packet Capture Library),即數(shù)據(jù)包捕獲函數(shù)庫(kù),是Unix/Linux平臺(tái)下的網(wǎng)絡(luò)數(shù)據(jù)包捕獲函數(shù)庫(kù)。它是一個(gè)獨(dú)立于系統(tǒng)的用戶層包捕獲的API接口,為底層網(wǎng)絡(luò)監(jiān)測(cè)提供了一個(gè)可移植的框架。
?
一、libpcap工作原理
libpcap主要由兩部份組成:網(wǎng)絡(luò)分接頭(Network Tap)和數(shù)據(jù)過(guò)濾器(Packet Filter)。網(wǎng)絡(luò)分接頭從網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序中收集數(shù)據(jù)拷貝,過(guò)濾器決定是否接收該數(shù)據(jù)包。Libpcap利用BSD Packet Filter(BPF)算法對(duì)網(wǎng)卡接收到的鏈路層數(shù)據(jù)包進(jìn)行過(guò)濾。BPF算法的基本思想是在有BPF監(jiān)聽的網(wǎng)絡(luò)中,網(wǎng)卡驅(qū)動(dòng)將接收到的數(shù)據(jù)包復(fù)制一份交給BPF過(guò)濾器,過(guò)濾器根據(jù)用戶定義的規(guī)則決定是否接收此數(shù)據(jù)包以及需要拷貝該數(shù)據(jù)包的那些內(nèi)容,然后將過(guò)濾后的數(shù)據(jù)給與過(guò)濾器相關(guān)聯(lián)的上層應(yīng)用程序。
libpcap的包捕獲機(jī)制就是在數(shù)據(jù)鏈路層加一個(gè)旁路處理。當(dāng)一個(gè)數(shù)據(jù)包到達(dá)網(wǎng)絡(luò)接口時(shí),libpcap首先利用已經(jīng)創(chuàng)建的Socket從鏈路層驅(qū)動(dòng)程序中獲得該數(shù)據(jù)包的拷貝,再通過(guò)Tap函數(shù)將數(shù)據(jù)包發(fā)給BPF過(guò)濾器。BPF過(guò)濾器根據(jù)用戶已經(jīng)定義好的過(guò)濾規(guī)則對(duì)數(shù)據(jù)包進(jìn)行逐一匹配,匹配成功則放入內(nèi)核緩沖區(qū),并傳遞給用戶緩沖區(qū),匹配失敗則直接丟棄。如果沒有設(shè)置過(guò)濾規(guī)則,所有數(shù)據(jù)包都將放入內(nèi)核緩沖區(qū),并傳遞給用戶層緩沖區(qū)。
?
二、libpcap的抓包框架
pcap_lookupdev()函數(shù)用于查找網(wǎng)絡(luò)設(shè)備,返回可被pcap_open_live()函數(shù)調(diào)用的網(wǎng)絡(luò)設(shè)備名指針。
pcap_open_live()函數(shù)用于打開網(wǎng)絡(luò)設(shè)備,并且返回用于捕獲網(wǎng)絡(luò)數(shù)據(jù)包的數(shù)據(jù)包捕獲描述字。對(duì)于此網(wǎng)絡(luò)設(shè)備的操作都要基于此網(wǎng)絡(luò)設(shè)備描述字。
pcap_lookupnet()函數(shù)獲得指定網(wǎng)絡(luò)設(shè)備的網(wǎng)絡(luò)號(hào)和掩碼。
pcap_compile()函數(shù)用于將用戶制定的過(guò)濾策略編譯到過(guò)濾程序中。
pcap_setfilter()函數(shù)用于設(shè)置過(guò)濾器。
pcap_loop()函數(shù)pcap_dispatch()函數(shù)用于捕獲數(shù)據(jù)包,捕獲后還可以進(jìn)行處理,此外pcap_next()和pcap_next_ex()兩個(gè)函數(shù)也可以用來(lái)捕獲數(shù)據(jù)包。
pcap_close()函數(shù)用于關(guān)閉網(wǎng)絡(luò)設(shè)備,釋放資源。
?
其實(shí)pcap的應(yīng)用程序格式很簡(jiǎn)單,總的來(lái)說(shuō)可以可以分為以下5部分,相信看了以下的5部分,大概能對(duì)pcap的總體布局有個(gè)大概的了解了吧:
1.我們從決定用哪一個(gè)接口進(jìn)行嗅探開始。在Linux中,這可能是eth0,而在BSD系統(tǒng)中則可能是xl1等等。我們也可以用一個(gè)字符串來(lái)定義這個(gè)設(shè)備,或者采用pcap提供的接口名來(lái)工作。
2.初始化pcap。在這里我們要告訴pcap對(duì)什么設(shè)備進(jìn)行嗅探。假如愿意的話,我們還可以嗅探多個(gè)設(shè)備。怎樣區(qū)分它們呢?使用 文件句柄。就像打開一個(gè)文件進(jìn)行讀寫一樣,必須命名我們的嗅探“會(huì)話”,以此使它們各自區(qū)別開來(lái)。
3.假如我們只想嗅探特定的傳輸(如TCP/IP包,發(fā)往端口23的包等等),我們必須創(chuàng)建一個(gè)規(guī)則集合,編譯并且使用它。這個(gè)過(guò)程分為三個(gè)相互緊密關(guān)聯(lián)的階段。規(guī)則集合被置于一個(gè)字符串內(nèi),并且被轉(zhuǎn)換成能被pcap讀的格式(因此編譯它)。編譯實(shí)際上就是在我們的程序里調(diào)用一個(gè)不被外部程序使用的函數(shù)。接下來(lái)我們要告訴 pcap使用它來(lái)過(guò)濾出我們想要的那一個(gè)會(huì)話。
4.最后,我們告訴pcap進(jìn)入它的主體執(zhí)行循環(huán)。在這個(gè)階段內(nèi)pcap一直工作到它接收了所有我們想要的包為止。每當(dāng)它收到一個(gè)包就調(diào)用另一個(gè)已經(jīng)定義好的函數(shù),這個(gè)函數(shù)可以做我們想要的任何工作,它可以剖析所部獲的包并給用戶打印出結(jié)果,它可以將結(jié)果保存為一個(gè)文件,或者什么也不作。
5.在嗅探到所需的數(shù)據(jù)后,我們要關(guān)閉會(huì)話并結(jié)束。
?
三、實(shí)現(xiàn)libpcap的每一個(gè)步驟
(1)設(shè)置設(shè)備
這是很簡(jiǎn)單的。有兩種方法設(shè)置想要嗅探的設(shè)備。
第一種,我們可以簡(jiǎn)單的讓用戶告訴我們。考察下面的程序:
#include
#include
int main(int argc, char *argv[])
{
char *dev = argv[1];
printf("Device: %s", dev);
return(0);
}
用戶通過(guò)傳遞給程序的第一個(gè)參數(shù)來(lái)指定設(shè)備。字符串“dev”以pcap能“理解”的格式保存了我們要嗅探的接口的名字(當(dāng)然,用戶必須給了我們一個(gè)真正存在的接口)。
另一種也是同樣的簡(jiǎn)單。來(lái)看這段程序:
#include
#include
int main()
{
char *dev, errbuf[PCAP_ERRBUF_SIZE];
dev = pcap_lookupdev(errbuf);
printf("Device: %s", dev);
return(0);
}
(2)打開設(shè)備進(jìn)行嗅探
創(chuàng)建一個(gè)嗅探會(huì)話的任務(wù)真的非常簡(jiǎn)單。為此,我們使用pcap_open_live()函數(shù)。此函數(shù)的原型(根據(jù)pcap的手冊(cè)頁(yè))如下:
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
其第一個(gè)參數(shù)是我們?cè)谏弦还?jié)中指定的設(shè)備,snaplen是整形的,它定義了將被pcap捕捉的最大字節(jié)數(shù)。當(dāng)promisc設(shè)為true時(shí)將置指定接口為混雜模式(然而,當(dāng)它置為false時(shí)接口仍處于混雜模式的非凡情況也是有可能的)。to_ms是讀取時(shí)的超時(shí)值,單位是毫秒(假如為0則一直嗅探直到錯(cuò)誤發(fā)生,為-1則不確定)。最后,ebuf是一個(gè)我們可以存入任何錯(cuò)誤信息的字符串(就像上面的errbuf)。此函數(shù)返回其會(huì)話句柄。
混雜模式與非混雜模式的區(qū)別:這兩種方式區(qū)別很大。一般來(lái)說(shuō),非混雜模式的嗅探器中,主機(jī)僅嗅探那些跟它直接有關(guān)的通信,如發(fā)向它的,從它發(fā)出的,或經(jīng)它路由的等都會(huì)被嗅探器捕捉。而在混雜模式中則嗅探傳輸線路上的所有通信。在非交換式網(wǎng)絡(luò)中,這將是整個(gè)網(wǎng)絡(luò)的通信。這樣做最明顯的優(yōu)點(diǎn)就是使更多的包被嗅探到,它們因你嗅探網(wǎng)絡(luò)的原因或者對(duì)你有幫助,或者沒有。但是,混雜模式是可被探測(cè)到的。一個(gè)主機(jī)可以通過(guò)高強(qiáng)度的測(cè)試判定另一臺(tái)主機(jī)是否正在進(jìn)行混雜模式的嗅探。其次,它僅在非交換式的網(wǎng)絡(luò)環(huán)境中有效工作(如集線器,或者交換中的ARP層面)。再次,在高負(fù)荷的網(wǎng)絡(luò)中,主機(jī)的系統(tǒng)資源將消耗的非常嚴(yán)重。
(3)過(guò)濾通信
實(shí)現(xiàn)這一過(guò)程由pcap_compile()與pcap_setfilter()這兩個(gè)函數(shù)完成。
在使用我們自己的過(guò)濾器前必須編譯它。過(guò)濾表達(dá)式被保存在一個(gè)字符串中(字符數(shù)組)。其句法在tcpdump的手冊(cè)頁(yè)中被證實(shí)非常好。我建議你親自閱讀它。但是我們將使用簡(jiǎn)單的測(cè)試表達(dá)式,這樣你可能很輕易理解我的例子。
我們調(diào)用pcap_compile()來(lái)編譯它,其原型是這樣定義的:
int pcap_compile(pcap_t *p, strUCt bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
第一個(gè)參數(shù)是會(huì)話句柄。接下來(lái)的是我們存儲(chǔ)被編譯的過(guò)濾器版本的地址的引用。再接下來(lái)的則是表達(dá)式本身,存儲(chǔ)在規(guī)定的字符串格式里。再下邊是一個(gè)定義表達(dá)式是否被優(yōu)化的整形量(0為false,1為true,標(biāo)準(zhǔn)規(guī)定)。最后,我們必須指定應(yīng)用此過(guò)濾器的網(wǎng)絡(luò)掩碼。函數(shù)返回-1為失敗,其他的任何值都表明是成功的。
表達(dá)式被編譯之后就可以使用了。現(xiàn)在進(jìn)入pcap_setfilter()。仿照我們介紹pcap的格式,先來(lái)看一看pcap_setfilter()的原型:
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
這非常直觀,第一個(gè)參數(shù)是會(huì)話句柄,第二個(gè)參數(shù)是被編譯表達(dá)式版本的引用(可推測(cè)出它與pcap_compile()的第二個(gè)參數(shù)相同)。
下面的代碼示例可能能使你更好的理解:
#include
pcap_t *handle; /* 會(huì)話的句柄 */
char dev[] = "eth0"; /* 執(zhí)行嗅探的設(shè)備 */
char errbuf[PCAP_ERRBUF_SIZE]; /* 存儲(chǔ)錯(cuò)誤 信息的字符串 */
struct bpf_program filter; /*已經(jīng)編譯好的過(guò)濾表達(dá)式*/
char filter_app[] = "port 23"; /* 過(guò)濾表達(dá)式*/
bpf_u_int32 mask; /* 執(zhí)行嗅探的設(shè)備的網(wǎng)絡(luò)掩碼 */
bpf_u_int32 net; /* 執(zhí)行嗅探的設(shè)備的IP地址 */
pcap_lookupnet(dev, &net, &mask, errbuf);
handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
pcap_compile(handle, &filter, filter_app, 0, net);
pcap_setfilter(handle, &filter);
這個(gè)程序使嗅探器嗅探經(jīng)由端口23的所有通信,使用混雜模式,設(shè)備是eth0。
(4)實(shí)際的嗅探
有兩種手段捕捉包。我們可以一次只捕捉一個(gè)包,也可以進(jìn)入一個(gè)循環(huán),等捕捉到多個(gè)包再進(jìn)行處理。我們將先看看怎樣去捕捉單個(gè)包,然后再看看使用循環(huán)的方法。為此,我們使用函數(shù)pcap_next()。
pcap_next()的原型及其簡(jiǎn)單:
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
第一個(gè)參數(shù)是會(huì)話句柄,第二個(gè)參數(shù)是指向一個(gè)包括了當(dāng)前數(shù)據(jù)包總體信息(被捕捉時(shí)的時(shí)間,包的長(zhǎng)度,其被指定的部分長(zhǎng)度)的結(jié)構(gòu)體的指針(在這里只有一個(gè)片斷,只作為一個(gè)示例)。pcap_next()返回一個(gè)u_char指針給被這個(gè)結(jié)構(gòu)體描述的包。我們將稍后討論這種實(shí)際讀取包本身的手段。
這里有一個(gè)演示怎樣使用pcap_next()來(lái)嗅探一個(gè)包的例子:
#include
#include
int main()
{
pcap_t *handle; /* 會(huì)話句柄 */
char *dev; /* 執(zhí)行嗅探的設(shè)備 */
char errbuf[PCAP_ERRBUF_SIZE]; /* 存儲(chǔ)錯(cuò)誤信息的字符串 */
struct bpf_program filter; /* 已經(jīng)編譯好的過(guò)濾器 */
char filter_app[] = "port 23"; /* 過(guò)濾表達(dá)式 */
bpf_u_int32 mask; /* 所在網(wǎng)絡(luò)的掩碼 */
bpf_u_int32 net; /* 主機(jī)的IP地址 */
struct pcap_pkthdr header; /* 由pcap.h定義 */
const u_char *packet; /* 實(shí)際的包 */
/* Define the device */
dev = pcap_lookupdev(errbuf);
/* 探查設(shè)備屬性 */
pcap_lookupnet(dev, &net, &mask, errbuf);
/* 以混雜模式打開會(huì)話 */
handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
/* 編譯并應(yīng)用過(guò)濾器 */
pcap_compile(handle, &filter, filter_app, 0, net);
pcap_setfilter(handle, &filter);
/* 截獲一個(gè)包 */
packet = pcap_next(handle, &header);
/* 打印它的長(zhǎng)度 */
printf("Jacked a packet with length of [%d]
", header.len);
/* 關(guān)閉會(huì)話 */
pcap_close(handle);
return(0);
}
這個(gè)程序嗅探被pcap_lookupdev()返回的設(shè)備并將它置為混雜模式。它發(fā)現(xiàn)第一個(gè)包經(jīng)過(guò)端口23(telnet)并且告訴用戶此包的大小(以字 節(jié)為單位)。這個(gè)程序又包含了一個(gè)新的調(diào)用pcap_close(),我們將在后面討論(盡管它的名字就足夠證實(shí)它自己的作用)。
實(shí)際上很少有嗅探程序會(huì)真正的使用pcap_next()。通常,它們使用pcap_loop()或者 pcap_dispatch()(它就是用了pcap_loop())。
pcap_loop()的原型如下:
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
第一個(gè)參數(shù)是會(huì)話句柄,接下來(lái)是一個(gè)整型,它告訴pcap_loop()在返回前應(yīng)捕捉多少個(gè)數(shù)據(jù)包(若為負(fù)值則表示應(yīng)該一直工作直至錯(cuò)誤發(fā)生)。第三個(gè)參數(shù)是回調(diào)函數(shù)的名稱(正像其標(biāo)識(shí)符所指,無(wú)括號(hào))。最后一個(gè)參數(shù)在有些應(yīng)用里有用,但更多時(shí)候則置為NULL。假設(shè)我們有我們自己的想送往回調(diào)函數(shù)的參數(shù),另外還有pcap_loop()發(fā)送的參數(shù),這就需要用到它。很明顯,必須是一個(gè)u_char類型的指針以確保結(jié)果正確;正像我們稍后見到的,pcap使用了很有意思的方法以u(píng)_char指針的形勢(shì)傳遞信息。pcap_dispatch()的用法幾乎相同。唯一不同的是它們?nèi)绾翁幚沓瑫r(shí)(還記得在調(diào)用pcap_open_live()時(shí)怎樣設(shè)置超時(shí)嗎?這就是它起作用的地方)。Pcap_loop()忽略超時(shí)而pcap_dispatch()則不。關(guān)于它們之間區(qū)別的更深入的討論請(qǐng)參見pcap的手冊(cè)頁(yè)。
回調(diào)函數(shù)的原型:
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
讓我們更細(xì)致的考察它。首先,你會(huì)注重到該函數(shù)返回void類型,這是符合邏輯的,因?yàn)閜cap_loop()不知道如何去處理一個(gè)回調(diào)返回值。第一個(gè)參數(shù)相應(yīng)于pcap_loop()的最后一個(gè)參數(shù)。每當(dāng)回調(diào)函數(shù)被老婆 調(diào)用時(shí),無(wú)論最后一個(gè)參數(shù)傳給pcap_loop()什么值,這個(gè)值都會(huì)傳給我們回調(diào)函數(shù)的第一個(gè)參數(shù)。第二個(gè)參數(shù)是pcap頭文件定義的,它包括數(shù)據(jù)包被嗅探的時(shí)間、大小等信息。結(jié)構(gòu)體pcap_pkhdr在pcap.h中定義如下:
struct pcap_pkthdr {
struct timeval ts; /* 時(shí)間戳 */
bpf_u_int32 caplen; /* 已捕捉部分的長(zhǎng)度 */
bpf_u_int32 len; /* 該包的脫機(jī)長(zhǎng)度 */
};
這些量都相當(dāng)明了。最后一個(gè)參數(shù)在它們中是最有意思的,也最讓pcap程序新手感到迷惑。這又是一個(gè)u_char指針,它包含了被pcap_loop()嗅探到的所有包。
但是你怎樣使用這個(gè)我們?cè)谠屠锓Q為packet的變量呢?一個(gè)數(shù)據(jù)包包含許多屬性,因此你可以想象它不只是一個(gè)字符串,而實(shí)質(zhì)上是一個(gè)結(jié)構(gòu)體的集合(比如,一個(gè)TCP/IP包會(huì)有一個(gè)以太網(wǎng)的頭部,一個(gè)IP頭部,一個(gè)TCP頭部,還有此包的有效載荷)。這個(gè)u_char就是這些結(jié)構(gòu)體的串聯(lián)版本。為了使用它,我們必須作一些有趣的匹配工作。
下面這些是一些數(shù)據(jù)包的結(jié)構(gòu)體:
/* 以太網(wǎng)幀頭部 */
struct sniff_ethernet {
u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主機(jī)的地址 */
u_char ether_shost[ETHER_ADDR_LEN]; /* 源主機(jī)的地址 */
u_short ether_type; /* IP? ARP? RARP? etc */
};
/* IP數(shù)據(jù)包的頭部 */
struct sniff_ip {
#if BYTE_ORDER == LITTLE_ENDIAN
u_int ip_hl:4, /* 頭部長(zhǎng)度 */
ip_v:4; /* 版本號(hào) */
#if BYTE_ORDER == BIG_ENDIAN
u_int ip_v:4, /* 版本號(hào) */
ip_hl:4; /* 頭部長(zhǎng)度 */
#endif
#endif /* not _IP_VHL */
u_char ip_tos; /* 服務(wù)的類型 */
u_short ip_len; /* 總長(zhǎng)度 */
u_short ip_id; /*包標(biāo)志號(hào) */
u_short ip_off; /* 碎片偏移 */
#define IP_RF 0x8000 /* 保留的碎片標(biāo)志 */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* 多碎片標(biāo)志*/
#define IP_OFFMASK 0x1fff /*分段位 */
u_char ip_ttl; /* 數(shù)據(jù)包的生存時(shí)間 */
u_char ip_p; /* 所使用的協(xié)議 */
u_short ip_sum; /* 校驗(yàn)和 */
struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
};
/* TCP 數(shù)據(jù)包的頭部 */
struct sniff_tcp {
u_short th_sport; /* 源端口 */
u_short th_dport; /* 目的端口 */
tcp_seq th_seq; /* 包序號(hào) */
tcp_seq th_ack; /* 確認(rèn)序號(hào) */
#if BYTE_ORDER == LITTLE_ENDIAN
u_int th_x2:4, /* 還沒有用到 */
th_off:4; /* 數(shù)據(jù)偏移 */
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_int th_off:4, /* 數(shù)據(jù)偏移*/
th_x2:4; /*還沒有用到 */
#endif
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
u_short th_win; /* TCP滑動(dòng)窗口 */
u_short th_sum; /* 頭部校驗(yàn)和 */
u_short th_urp; /* 緊急服務(wù)位 */
};
pcap嗅探數(shù)據(jù)包時(shí)正是使用的這些結(jié)構(gòu)。接下來(lái),它簡(jiǎn)單的創(chuàng)建一個(gè)u_char字符串并且將這些結(jié)構(gòu)體填入。那么我們?cè)鯓硬拍軈^(qū)分它們呢?預(yù)備好見證指針最實(shí)用的好處之一吧。
我們?cè)僖淮渭俣ㄒ獙?duì)以太網(wǎng)上的TCP/IP包進(jìn)行處理。同樣的手段可以應(yīng)用于任何數(shù)據(jù)包,唯一的區(qū)別是你實(shí)際所使用的結(jié)構(gòu)體的類型。讓我們從聲明分解u_char包的變量開始:
const struct sniff_ethernet *ethernet; /* 以太網(wǎng)幀頭部*/
const struct sniff_ip *ip; /* IP包頭部 */
const struct sniff_tcp *tcp; /* TCP包頭部 */
const char *payload; /* 數(shù)據(jù)包的有效載荷*/
/*為了讓它的可讀性好,我們計(jì)算每個(gè)結(jié)構(gòu)體中的變量大小*/
int size_ethernet = sizeof(struct sniff_ethernet);
int size_ip = sizeof(struct sniff_ip);
int size_tcp = sizeof(struct sniff_tcp);
現(xiàn)在我們開始讓人感到有些神秘的匹配:
ethernet = (struct sniff_ethernet*)(packet);
ip = (struct sniff_ip*)(packet + size_ethernet);
tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);
payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp);
此處如何工作?考慮u_char在內(nèi)存中的層次。基本的,當(dāng)pcap將這些結(jié)構(gòu)體填入u_char的時(shí)候是將這些數(shù)據(jù)存入一個(gè)字符串中,那個(gè)字符串將被送入我們的回調(diào)函數(shù)中。反向轉(zhuǎn)換是這樣的,不考慮這些結(jié)構(gòu)體制中的值,它們的大小將是一致的。例如在我的平臺(tái)上,一個(gè)sniff_ethernet結(jié)構(gòu)體的大小是14字節(jié)。一個(gè)sniff_ip結(jié)構(gòu)體是20字節(jié),一個(gè)sniff_tcp結(jié)構(gòu)體也是20字節(jié)。 u_char指針正是包含了內(nèi)存地址的一個(gè)變量,這也是指針的實(shí)質(zhì),它指向內(nèi)存的一個(gè)區(qū)域。簡(jiǎn)單而言,我們說(shuō)指針指向的地址為x,假如三個(gè)結(jié)構(gòu)體恰好線性排列,第一個(gè)(sniff_ethernet)被裝載到內(nèi)存地址的x處則我們很輕易的發(fā)現(xiàn)其他結(jié)構(gòu)體的地址,讓我們以表格顯示之:
Variable Location (in bytes)
sniff_ethernet X
sniff_ip X + 14
sniff_tcp X + 14 + 20
payload X + 14 + 20 + 20
結(jié)構(gòu)體sniff_ethernet正好在x處,緊接著它的sniff_ip則位于x加上它本身占用的空間(此例為14字節(jié)),依此類推可得全部地址。
注重:你沒有假定你的變量也是同樣大小是很重要的。你應(yīng)該總是使用sizeof()來(lái)確保尺寸的正確。這是因?yàn)檫@些結(jié)構(gòu)體中的每個(gè)成員在不同平臺(tái)下可以有不同的尺寸。
?
下面是主要函數(shù)接口:
pcap_t *pcap_open_live(char *device, int snaplen,
?? int promisc, int to_ms, char *ebuf)
?? 獲得用于捕獲網(wǎng)絡(luò)數(shù)據(jù)包的數(shù)據(jù)包捕獲描述字。device參數(shù)為指定打開
?? 的網(wǎng)絡(luò)設(shè)備名。snaplen參數(shù)定義捕獲數(shù)據(jù)的最大字節(jié)數(shù)。promisc指定
?? 是否將網(wǎng)絡(luò)接口置于混雜模式。to_ms參數(shù)指定超時(shí)時(shí)間(毫秒)。
?? ebuf參數(shù)則僅在pcap_open_live()函數(shù)出錯(cuò)返回NULL時(shí)用于傳遞錯(cuò)誤消
?? 息。
pcap_t *pcap_open_offline(char *fname, char *ebuf)
?? 打開以前保存捕獲數(shù)據(jù)包的文件,用于讀取。fname參數(shù)指定打開的文
?? 件名。該文件中的數(shù)據(jù)格式與tcpdump和tcpslice兼容。"-"為標(biāo)準(zhǔn)輸
?? 入。ebuf參數(shù)則僅在pcap_open_offline()函數(shù)出錯(cuò)返回NULL時(shí)用于傳
?? 遞錯(cuò)誤消息。
pcap_dumper_t *pcap_dump_open(pcap_t *p, char *fname)
?? 打開用于保存捕獲數(shù)據(jù)包的文件,用于寫入。fname參數(shù)為"-"時(shí)表示
?? 標(biāo)準(zhǔn)輸出。出錯(cuò)時(shí)返回NULL。p參數(shù)為調(diào)用pcap_open_offline()或
?? pcap_open_live()函數(shù)后返回的pcap結(jié)構(gòu)指針。fname參數(shù)指定打開
?? 的文件名。如果返回NULL,則可調(diào)用pcap_geterr()函數(shù)獲取錯(cuò)誤消
?? 息。
?
char *pcap_lookupdev(char *errbuf)
?? 用于返回可被pcap_open_live()或pcap_lookupnet()函數(shù)調(diào)用的網(wǎng)絡(luò)
?? 設(shè)備名指針。如果函數(shù)出錯(cuò),則返回NULL,同時(shí)errbuf中存放相關(guān)的
?? 錯(cuò)誤消息。
int pcap_lookupnet(char *device, bpf_u_int32 *netp,
?? bpf_u_int32 *maskp, char *errbuf)
?? 獲得指定網(wǎng)絡(luò)設(shè)備的網(wǎng)絡(luò)號(hào)和掩碼。netp參數(shù)和maskp參數(shù)都是
?? bpf_u_int32指針。如果函數(shù)出錯(cuò),則返回-1,同時(shí)errbuf中存放相
?? 關(guān)的錯(cuò)誤消息。
int pcap_dispatch(pcap_t *p, int cnt,
?? pcap_handler callback, u_char *user)
?? 捕獲并處理數(shù)據(jù)包。cnt參數(shù)指定函數(shù)返回前所處理數(shù)據(jù)包的最大值。
?? cnt=-1表示在一個(gè)緩沖區(qū)中處理所有的數(shù)據(jù)包。cnt=0表示處理所有
?? 數(shù)據(jù)包,直到產(chǎn)生以下錯(cuò)誤之一:讀取到EOF;超時(shí)讀取。callback
?? 參數(shù)指定一個(gè)帶有三個(gè)參數(shù)的回調(diào)函數(shù),這三個(gè)參數(shù)為:一個(gè)從
?? pcap_dispatch()函數(shù)傳遞過(guò)來(lái)的u_char指針,一個(gè)pcap_pkthdr結(jié)構(gòu)
?? 的指針,和一個(gè)數(shù)據(jù)包大小的u_char指針。如果成功則返回讀取到的
?? 字節(jié)數(shù)。讀取到EOF時(shí)則返回零值。出錯(cuò)時(shí)則返回-1,此時(shí)可調(diào)用
?? pcap_perror()或pcap_geterr()函數(shù)獲取錯(cuò)誤消息。
int pcap_loop(pcap_t *p, int cnt,
?? pcap_handler callback, u_char *user)
?? 功能基本與pcap_dispatch()函數(shù)相同,只不過(guò)此函數(shù)在cnt個(gè)數(shù)據(jù)包
?? 被處理或出現(xiàn)錯(cuò)誤時(shí)才返回,但讀取超時(shí)不會(huì)返回。而如果為
?? pcap_open_live()函數(shù)指定了一個(gè)非零值的超時(shí)設(shè)置,然后調(diào)用
?? pcap_dispatch()函數(shù),則當(dāng)超時(shí)發(fā)生時(shí)pcap_dispatch()函數(shù)會(huì)返回。
?? cnt參數(shù)為負(fù)值時(shí)pcap_loop()函數(shù)將始終循環(huán)運(yùn)行,除非出現(xiàn)錯(cuò)誤。
void pcap_dump(u_char *user, struct pcap_pkthdr *h,
?? u_char *sp)
?? 向調(diào)用pcap_dump_open()函數(shù)打開的文件輸出一個(gè)數(shù)據(jù)包。該函數(shù)可
?? 作為pcap_dispatch()函數(shù)的回調(diào)函數(shù)。
int pcap_compile(pcap_t *p, struct bpf_program *fp,
?? char *str, int optimize, bpf_u_int32 netmask)
?? 將str參數(shù)指定的字符串編譯到過(guò)濾程序中。fp是一個(gè)bpf_program結(jié)
?? 構(gòu)的指針,在pcap_compile()函數(shù)中被賦值。optimize參數(shù)控制結(jié)果
?? 代碼的優(yōu)化。netmask參數(shù)指定本地網(wǎng)絡(luò)的網(wǎng)絡(luò)掩碼。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
?? 指定一個(gè)過(guò)濾程序。fp參數(shù)是bpf_program結(jié)構(gòu)指針,通常取自
?? pcap_compile()函數(shù)調(diào)用。出錯(cuò)時(shí)返回-1;成功時(shí)返回0。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
?? 返回指向下一個(gè)數(shù)據(jù)包的u_char指針。
int pcap_datalink(pcap_t *p)
?? 返回?cái)?shù)據(jù)鏈路層類型,例如DLT_EN10MB。
int pcap_snapshot(pcap_t *p)
?? 返回pcap_open_live被調(diào)用后的snapshot參數(shù)值。
int pcap_is_swapped(pcap_t *p)
?? 返回當(dāng)前系統(tǒng)主機(jī)字節(jié)與被打開文件的字節(jié)順序是否不同。
int pcap_major_version(pcap_t *p)
?? 返回寫入被打開文件所使用的pcap函數(shù)的主版本號(hào)。
int pcap_minor_version(pcap_t *p)
?? 返回寫入被打開文件所使用的pcap函數(shù)的輔版本號(hào)。
int pcap_stats(pcap_t *p, struct pcap_stat *ps)
?? 向pcap_stat結(jié)構(gòu)賦值。成功時(shí)返回0。這些數(shù)值包括了從開始
?? 捕獲數(shù)據(jù)以來(lái)至今共捕獲到的數(shù)據(jù)包統(tǒng)計(jì)。如果出錯(cuò)或不支持
?? 數(shù)據(jù)包統(tǒng)計(jì),則返回-1,且可調(diào)用pcap_perror()或
?? pcap_geterr()函數(shù)來(lái)獲取錯(cuò)誤消息。
FILE *pcap_file(pcap_t *p)
?? 返回被打開文件的文件名。
int pcap_fileno(pcap_t *p)
?? 返回被打開文件的文件描述字號(hào)碼。
void pcap_perror(pcap_t *p, char *prefix)
?? 在標(biāo)準(zhǔn)輸出設(shè)備上顯示最后一個(gè)pcap庫(kù)錯(cuò)誤消息。以prefix參
?? 數(shù)指定的字符串為消息頭。
char *pcap_geterr(pcap_t *p)
?? 返回最后一個(gè)pcap庫(kù)錯(cuò)誤消息。
char *pcap_strerror(int error)
?? 如果strerror()函數(shù)不可用,則可調(diào)用pcap_strerror函數(shù)替代。
void pcap_close(pcap_t *p)
?? 關(guān)閉p參數(shù)相應(yīng)的文件,并釋放資源。
libpcap(Packet Capture Library),即數(shù)據(jù)包捕獲函數(shù)庫(kù),是Unix/Linux平臺(tái)下的網(wǎng)絡(luò)數(shù)據(jù)包捕獲函數(shù)庫(kù)。它是一個(gè)獨(dú)立于系統(tǒng)的用戶層包捕獲的API接口,為底層網(wǎng)絡(luò)監(jiān)測(cè)提供了一個(gè)可移植的框架。
?
一、libpcap工作原理
libpcap主要由兩部份組成:網(wǎng)絡(luò)分接頭(Network Tap)和數(shù)據(jù)過(guò)濾器(Packet Filter)。網(wǎng)絡(luò)分接頭從網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序中收集數(shù)據(jù)拷貝,過(guò)濾器決定是否接收該數(shù)據(jù)包。Libpcap利用BSD Packet Filter(BPF)算法對(duì)網(wǎng)卡接收到的鏈路層數(shù)據(jù)包進(jìn)行過(guò)濾。BPF算法的基本思想是在有BPF監(jiān)聽的網(wǎng)絡(luò)中,網(wǎng)卡驅(qū)動(dòng)將接收到的數(shù)據(jù)包復(fù)制一份交給BPF過(guò)濾器,過(guò)濾器根據(jù)用戶定義的規(guī)則決定是否接收此數(shù)據(jù)包以及需要拷貝該數(shù)據(jù)包的那些內(nèi)容,然后將過(guò)濾后的數(shù)據(jù)給與過(guò)濾器相關(guān)聯(lián)的上層應(yīng)用程序。
libpcap的包捕獲機(jī)制就是在數(shù)據(jù)鏈路層加一個(gè)旁路處理。當(dāng)一個(gè)數(shù)據(jù)包到達(dá)網(wǎng)絡(luò)接口時(shí),libpcap首先利用已經(jīng)創(chuàng)建的Socket從鏈路層驅(qū)動(dòng)程序中獲得該數(shù)據(jù)包的拷貝,再通過(guò)Tap函數(shù)將數(shù)據(jù)包發(fā)給BPF過(guò)濾器。BPF過(guò)濾器根據(jù)用戶已經(jīng)定義好的過(guò)濾規(guī)則對(duì)數(shù)據(jù)包進(jìn)行逐一匹配,匹配成功則放入內(nèi)核緩沖區(qū),并傳遞給用戶緩沖區(qū),匹配失敗則直接丟棄。如果沒有設(shè)置過(guò)濾規(guī)則,所有數(shù)據(jù)包都將放入內(nèi)核緩沖區(qū),并傳遞給用戶層緩沖區(qū)。
?
二、libpcap的抓包框架
pcap_lookupdev()函數(shù)用于查找網(wǎng)絡(luò)設(shè)備,返回可被pcap_open_live()函數(shù)調(diào)用的網(wǎng)絡(luò)設(shè)備名指針。
pcap_open_live()函數(shù)用于打開網(wǎng)絡(luò)設(shè)備,并且返回用于捕獲網(wǎng)絡(luò)數(shù)據(jù)包的數(shù)據(jù)包捕獲描述字。對(duì)于此網(wǎng)絡(luò)設(shè)備的操作都要基于此網(wǎng)絡(luò)設(shè)備描述字。
pcap_lookupnet()函數(shù)獲得指定網(wǎng)絡(luò)設(shè)備的網(wǎng)絡(luò)號(hào)和掩碼。
pcap_compile()函數(shù)用于將用戶制定的過(guò)濾策略編譯到過(guò)濾程序中。
pcap_setfilter()函數(shù)用于設(shè)置過(guò)濾器。
pcap_loop()函數(shù)pcap_dispatch()函數(shù)用于捕獲數(shù)據(jù)包,捕獲后還可以進(jìn)行處理,此外pcap_next()和pcap_next_ex()兩個(gè)函數(shù)也可以用來(lái)捕獲數(shù)據(jù)包。
pcap_close()函數(shù)用于關(guān)閉網(wǎng)絡(luò)設(shè)備,釋放資源。
?
其實(shí)pcap的應(yīng)用程序格式很簡(jiǎn)單,總的來(lái)說(shuō)可以可以分為以下5部分,相信看了以下的5部分,大概能對(duì)pcap的總體布局有個(gè)大概的了解了吧:
1.我們從決定用哪一個(gè)接口進(jìn)行嗅探開始。在Linux中,這可能是eth0,而在BSD系統(tǒng)中則可能是xl1等等。我們也可以用一個(gè)字符串來(lái)定義這個(gè)設(shè)備,或者采用pcap提供的接口名來(lái)工作。
2.初始化pcap。在這里我們要告訴pcap對(duì)什么設(shè)備進(jìn)行嗅探。假如愿意的話,我們還可以嗅探多個(gè)設(shè)備。怎樣區(qū)分它們呢?使用 文件句柄。就像打開一個(gè)文件進(jìn)行讀寫一樣,必須命名我們的嗅探“會(huì)話”,以此使它們各自區(qū)別開來(lái)。
3.假如我們只想嗅探特定的傳輸(如TCP/IP包,發(fā)往端口23的包等等),我們必須創(chuàng)建一個(gè)規(guī)則集合,編譯并且使用它。這個(gè)過(guò)程分為三個(gè)相互緊密關(guān)聯(lián)的階段。規(guī)則集合被置于一個(gè)字符串內(nèi),并且被轉(zhuǎn)換成能被pcap讀的格式(因此編譯它)。編譯實(shí)際上就是在我們的程序里調(diào)用一個(gè)不被外部程序使用的函數(shù)。接下來(lái)我們要告訴 pcap使用它來(lái)過(guò)濾出我們想要的那一個(gè)會(huì)話。
4.最后,我們告訴pcap進(jìn)入它的主體執(zhí)行循環(huán)。在這個(gè)階段內(nèi)pcap一直工作到它接收了所有我們想要的包為止。每當(dāng)它收到一個(gè)包就調(diào)用另一個(gè)已經(jīng)定義好的函數(shù),這個(gè)函數(shù)可以做我們想要的任何工作,它可以剖析所部獲的包并給用戶打印出結(jié)果,它可以將結(jié)果保存為一個(gè)文件,或者什么也不作。
5.在嗅探到所需的數(shù)據(jù)后,我們要關(guān)閉會(huì)話并結(jié)束。
?
三、實(shí)現(xiàn)libpcap的每一個(gè)步驟
(1)設(shè)置設(shè)備
這是很簡(jiǎn)單的。有兩種方法設(shè)置想要嗅探的設(shè)備。
第一種,我們可以簡(jiǎn)單的讓用戶告訴我們。考察下面的程序:
#include
#include
int main(int argc, char *argv[])
{
char *dev = argv[1];
printf("Device: %s", dev);
return(0);
}
用戶通過(guò)傳遞給程序的第一個(gè)參數(shù)來(lái)指定設(shè)備。字符串“dev”以pcap能“理解”的格式保存了我們要嗅探的接口的名字(當(dāng)然,用戶必須給了我們一個(gè)真正存在的接口)。
另一種也是同樣的簡(jiǎn)單。來(lái)看這段程序:
#include
#include
int main()
{
char *dev, errbuf[PCAP_ERRBUF_SIZE];
dev = pcap_lookupdev(errbuf);
printf("Device: %s", dev);
return(0);
}
(2)打開設(shè)備進(jìn)行嗅探
創(chuàng)建一個(gè)嗅探會(huì)話的任務(wù)真的非常簡(jiǎn)單。為此,我們使用pcap_open_live()函數(shù)。此函數(shù)的原型(根據(jù)pcap的手冊(cè)頁(yè))如下:
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
其第一個(gè)參數(shù)是我們?cè)谏弦还?jié)中指定的設(shè)備,snaplen是整形的,它定義了將被pcap捕捉的最大字節(jié)數(shù)。當(dāng)promisc設(shè)為true時(shí)將置指定接口為混雜模式(然而,當(dāng)它置為false時(shí)接口仍處于混雜模式的非凡情況也是有可能的)。to_ms是讀取時(shí)的超時(shí)值,單位是毫秒(假如為0則一直嗅探直到錯(cuò)誤發(fā)生,為-1則不確定)。最后,ebuf是一個(gè)我們可以存入任何錯(cuò)誤信息的字符串(就像上面的errbuf)。此函數(shù)返回其會(huì)話句柄。
混雜模式與非混雜模式的區(qū)別:這兩種方式區(qū)別很大。一般來(lái)說(shuō),非混雜模式的嗅探器中,主機(jī)僅嗅探那些跟它直接有關(guān)的通信,如發(fā)向它的,從它發(fā)出的,或經(jīng)它路由的等都會(huì)被嗅探器捕捉。而在混雜模式中則嗅探傳輸線路上的所有通信。在非交換式網(wǎng)絡(luò)中,這將是整個(gè)網(wǎng)絡(luò)的通信。這樣做最明顯的優(yōu)點(diǎn)就是使更多的包被嗅探到,它們因你嗅探網(wǎng)絡(luò)的原因或者對(duì)你有幫助,或者沒有。但是,混雜模式是可被探測(cè)到的。一個(gè)主機(jī)可以通過(guò)高強(qiáng)度的測(cè)試判定另一臺(tái)主機(jī)是否正在進(jìn)行混雜模式的嗅探。其次,它僅在非交換式的網(wǎng)絡(luò)環(huán)境中有效工作(如集線器,或者交換中的ARP層面)。再次,在高負(fù)荷的網(wǎng)絡(luò)中,主機(jī)的系統(tǒng)資源將消耗的非常嚴(yán)重。
(3)過(guò)濾通信
實(shí)現(xiàn)這一過(guò)程由pcap_compile()與pcap_setfilter()這兩個(gè)函數(shù)完成。
在使用我們自己的過(guò)濾器前必須編譯它。過(guò)濾表達(dá)式被保存在一個(gè)字符串中(字符數(shù)組)。其句法在tcpdump的手冊(cè)頁(yè)中被證實(shí)非常好。我建議你親自閱讀它。但是我們將使用簡(jiǎn)單的測(cè)試表達(dá)式,這樣你可能很輕易理解我的例子。
我們調(diào)用pcap_compile()來(lái)編譯它,其原型是這樣定義的:
int pcap_compile(pcap_t *p, strUCt bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
第一個(gè)參數(shù)是會(huì)話句柄。接下來(lái)的是我們存儲(chǔ)被編譯的過(guò)濾器版本的地址的引用。再接下來(lái)的則是表達(dá)式本身,存儲(chǔ)在規(guī)定的字符串格式里。再下邊是一個(gè)定義表達(dá)式是否被優(yōu)化的整形量(0為false,1為true,標(biāo)準(zhǔn)規(guī)定)。最后,我們必須指定應(yīng)用此過(guò)濾器的網(wǎng)絡(luò)掩碼。函數(shù)返回-1為失敗,其他的任何值都表明是成功的。
表達(dá)式被編譯之后就可以使用了。現(xiàn)在進(jìn)入pcap_setfilter()。仿照我們介紹pcap的格式,先來(lái)看一看pcap_setfilter()的原型:
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
這非常直觀,第一個(gè)參數(shù)是會(huì)話句柄,第二個(gè)參數(shù)是被編譯表達(dá)式版本的引用(可推測(cè)出它與pcap_compile()的第二個(gè)參數(shù)相同)。
下面的代碼示例可能能使你更好的理解:
#include
pcap_t *handle; /* 會(huì)話的句柄 */
char dev[] = "eth0"; /* 執(zhí)行嗅探的設(shè)備 */
char errbuf[PCAP_ERRBUF_SIZE]; /* 存儲(chǔ)錯(cuò)誤 信息的字符串 */
struct bpf_program filter; /*已經(jīng)編譯好的過(guò)濾表達(dá)式*/
char filter_app[] = "port 23"; /* 過(guò)濾表達(dá)式*/
bpf_u_int32 mask; /* 執(zhí)行嗅探的設(shè)備的網(wǎng)絡(luò)掩碼 */
bpf_u_int32 net; /* 執(zhí)行嗅探的設(shè)備的IP地址 */
pcap_lookupnet(dev, &net, &mask, errbuf);
handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
pcap_compile(handle, &filter, filter_app, 0, net);
pcap_setfilter(handle, &filter);
這個(gè)程序使嗅探器嗅探經(jīng)由端口23的所有通信,使用混雜模式,設(shè)備是eth0。
(4)實(shí)際的嗅探
有兩種手段捕捉包。我們可以一次只捕捉一個(gè)包,也可以進(jìn)入一個(gè)循環(huán),等捕捉到多個(gè)包再進(jìn)行處理。我們將先看看怎樣去捕捉單個(gè)包,然后再看看使用循環(huán)的方法。為此,我們使用函數(shù)pcap_next()。
pcap_next()的原型及其簡(jiǎn)單:
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
第一個(gè)參數(shù)是會(huì)話句柄,第二個(gè)參數(shù)是指向一個(gè)包括了當(dāng)前數(shù)據(jù)包總體信息(被捕捉時(shí)的時(shí)間,包的長(zhǎng)度,其被指定的部分長(zhǎng)度)的結(jié)構(gòu)體的指針(在這里只有一個(gè)片斷,只作為一個(gè)示例)。pcap_next()返回一個(gè)u_char指針給被這個(gè)結(jié)構(gòu)體描述的包。我們將稍后討論這種實(shí)際讀取包本身的手段。
這里有一個(gè)演示怎樣使用pcap_next()來(lái)嗅探一個(gè)包的例子:
#include
#include
int main()
{
pcap_t *handle; /* 會(huì)話句柄 */
char *dev; /* 執(zhí)行嗅探的設(shè)備 */
char errbuf[PCAP_ERRBUF_SIZE]; /* 存儲(chǔ)錯(cuò)誤信息的字符串 */
struct bpf_program filter; /* 已經(jīng)編譯好的過(guò)濾器 */
char filter_app[] = "port 23"; /* 過(guò)濾表達(dá)式 */
bpf_u_int32 mask; /* 所在網(wǎng)絡(luò)的掩碼 */
bpf_u_int32 net; /* 主機(jī)的IP地址 */
struct pcap_pkthdr header; /* 由pcap.h定義 */
const u_char *packet; /* 實(shí)際的包 */
/* Define the device */
dev = pcap_lookupdev(errbuf);
/* 探查設(shè)備屬性 */
pcap_lookupnet(dev, &net, &mask, errbuf);
/* 以混雜模式打開會(huì)話 */
handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
/* 編譯并應(yīng)用過(guò)濾器 */
pcap_compile(handle, &filter, filter_app, 0, net);
pcap_setfilter(handle, &filter);
/* 截獲一個(gè)包 */
packet = pcap_next(handle, &header);
/* 打印它的長(zhǎng)度 */
printf("Jacked a packet with length of [%d]
", header.len);
/* 關(guān)閉會(huì)話 */
pcap_close(handle);
return(0);
}
這個(gè)程序嗅探被pcap_lookupdev()返回的設(shè)備并將它置為混雜模式。它發(fā)現(xiàn)第一個(gè)包經(jīng)過(guò)端口23(telnet)并且告訴用戶此包的大小(以字 節(jié)為單位)。這個(gè)程序又包含了一個(gè)新的調(diào)用pcap_close(),我們將在后面討論(盡管它的名字就足夠證實(shí)它自己的作用)。
實(shí)際上很少有嗅探程序會(huì)真正的使用pcap_next()。通常,它們使用pcap_loop()或者 pcap_dispatch()(它就是用了pcap_loop())。
pcap_loop()的原型如下:
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
第一個(gè)參數(shù)是會(huì)話句柄,接下來(lái)是一個(gè)整型,它告訴pcap_loop()在返回前應(yīng)捕捉多少個(gè)數(shù)據(jù)包(若為負(fù)值則表示應(yīng)該一直工作直至錯(cuò)誤發(fā)生)。第三個(gè)參數(shù)是回調(diào)函數(shù)的名稱(正像其標(biāo)識(shí)符所指,無(wú)括號(hào))。最后一個(gè)參數(shù)在有些應(yīng)用里有用,但更多時(shí)候則置為NULL。假設(shè)我們有我們自己的想送往回調(diào)函數(shù)的參數(shù),另外還有pcap_loop()發(fā)送的參數(shù),這就需要用到它。很明顯,必須是一個(gè)u_char類型的指針以確保結(jié)果正確;正像我們稍后見到的,pcap使用了很有意思的方法以u(píng)_char指針的形勢(shì)傳遞信息。pcap_dispatch()的用法幾乎相同。唯一不同的是它們?nèi)绾翁幚沓瑫r(shí)(還記得在調(diào)用pcap_open_live()時(shí)怎樣設(shè)置超時(shí)嗎?這就是它起作用的地方)。Pcap_loop()忽略超時(shí)而pcap_dispatch()則不。關(guān)于它們之間區(qū)別的更深入的討論請(qǐng)參見pcap的手冊(cè)頁(yè)。
回調(diào)函數(shù)的原型:
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
讓我們更細(xì)致的考察它。首先,你會(huì)注重到該函數(shù)返回void類型,這是符合邏輯的,因?yàn)閜cap_loop()不知道如何去處理一個(gè)回調(diào)返回值。第一個(gè)參數(shù)相應(yīng)于pcap_loop()的最后一個(gè)參數(shù)。每當(dāng)回調(diào)函數(shù)被老婆 調(diào)用時(shí),無(wú)論最后一個(gè)參數(shù)傳給pcap_loop()什么值,這個(gè)值都會(huì)傳給我們回調(diào)函數(shù)的第一個(gè)參數(shù)。第二個(gè)參數(shù)是pcap頭文件定義的,它包括數(shù)據(jù)包被嗅探的時(shí)間、大小等信息。結(jié)構(gòu)體pcap_pkhdr在pcap.h中定義如下:
struct pcap_pkthdr {
struct timeval ts; /* 時(shí)間戳 */
bpf_u_int32 caplen; /* 已捕捉部分的長(zhǎng)度 */
bpf_u_int32 len; /* 該包的脫機(jī)長(zhǎng)度 */
};
這些量都相當(dāng)明了。最后一個(gè)參數(shù)在它們中是最有意思的,也最讓pcap程序新手感到迷惑。這又是一個(gè)u_char指針,它包含了被pcap_loop()嗅探到的所有包。
但是你怎樣使用這個(gè)我們?cè)谠屠锓Q為packet的變量呢?一個(gè)數(shù)據(jù)包包含許多屬性,因此你可以想象它不只是一個(gè)字符串,而實(shí)質(zhì)上是一個(gè)結(jié)構(gòu)體的集合(比如,一個(gè)TCP/IP包會(huì)有一個(gè)以太網(wǎng)的頭部,一個(gè)IP頭部,一個(gè)TCP頭部,還有此包的有效載荷)。這個(gè)u_char就是這些結(jié)構(gòu)體的串聯(lián)版本。為了使用它,我們必須作一些有趣的匹配工作。
下面這些是一些數(shù)據(jù)包的結(jié)構(gòu)體:
/* 以太網(wǎng)幀頭部 */
struct sniff_ethernet {
u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主機(jī)的地址 */
u_char ether_shost[ETHER_ADDR_LEN]; /* 源主機(jī)的地址 */
u_short ether_type; /* IP? ARP? RARP? etc */
};
/* IP數(shù)據(jù)包的頭部 */
struct sniff_ip {
#if BYTE_ORDER == LITTLE_ENDIAN
u_int ip_hl:4, /* 頭部長(zhǎng)度 */
ip_v:4; /* 版本號(hào) */
#if BYTE_ORDER == BIG_ENDIAN
u_int ip_v:4, /* 版本號(hào) */
ip_hl:4; /* 頭部長(zhǎng)度 */
#endif
#endif /* not _IP_VHL */
u_char ip_tos; /* 服務(wù)的類型 */
u_short ip_len; /* 總長(zhǎng)度 */
u_short ip_id; /*包標(biāo)志號(hào) */
u_short ip_off; /* 碎片偏移 */
#define IP_RF 0x8000 /* 保留的碎片標(biāo)志 */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* 多碎片標(biāo)志*/
#define IP_OFFMASK 0x1fff /*分段位 */
u_char ip_ttl; /* 數(shù)據(jù)包的生存時(shí)間 */
u_char ip_p; /* 所使用的協(xié)議 */
u_short ip_sum; /* 校驗(yàn)和 */
struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
};
/* TCP 數(shù)據(jù)包的頭部 */
struct sniff_tcp {
u_short th_sport; /* 源端口 */
u_short th_dport; /* 目的端口 */
tcp_seq th_seq; /* 包序號(hào) */
tcp_seq th_ack; /* 確認(rèn)序號(hào) */
#if BYTE_ORDER == LITTLE_ENDIAN
u_int th_x2:4, /* 還沒有用到 */
th_off:4; /* 數(shù)據(jù)偏移 */
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_int th_off:4, /* 數(shù)據(jù)偏移*/
th_x2:4; /*還沒有用到 */
#endif
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
u_short th_win; /* TCP滑動(dòng)窗口 */
u_short th_sum; /* 頭部校驗(yàn)和 */
u_short th_urp; /* 緊急服務(wù)位 */
};
pcap嗅探數(shù)據(jù)包時(shí)正是使用的這些結(jié)構(gòu)。接下來(lái),它簡(jiǎn)單的創(chuàng)建一個(gè)u_char字符串并且將這些結(jié)構(gòu)體填入。那么我們?cè)鯓硬拍軈^(qū)分它們呢?預(yù)備好見證指針最實(shí)用的好處之一吧。
我們?cè)僖淮渭俣ㄒ獙?duì)以太網(wǎng)上的TCP/IP包進(jìn)行處理。同樣的手段可以應(yīng)用于任何數(shù)據(jù)包,唯一的區(qū)別是你實(shí)際所使用的結(jié)構(gòu)體的類型。讓我們從聲明分解u_char包的變量開始:
const struct sniff_ethernet *ethernet; /* 以太網(wǎng)幀頭部*/
const struct sniff_ip *ip; /* IP包頭部 */
const struct sniff_tcp *tcp; /* TCP包頭部 */
const char *payload; /* 數(shù)據(jù)包的有效載荷*/
/*為了讓它的可讀性好,我們計(jì)算每個(gè)結(jié)構(gòu)體中的變量大小*/
int size_ethernet = sizeof(struct sniff_ethernet);
int size_ip = sizeof(struct sniff_ip);
int size_tcp = sizeof(struct sniff_tcp);
現(xiàn)在我們開始讓人感到有些神秘的匹配:
ethernet = (struct sniff_ethernet*)(packet);
ip = (struct sniff_ip*)(packet + size_ethernet);
tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);
payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp);
此處如何工作?考慮u_char在內(nèi)存中的層次。基本的,當(dāng)pcap將這些結(jié)構(gòu)體填入u_char的時(shí)候是將這些數(shù)據(jù)存入一個(gè)字符串中,那個(gè)字符串將被送入我們的回調(diào)函數(shù)中。反向轉(zhuǎn)換是這樣的,不考慮這些結(jié)構(gòu)體制中的值,它們的大小將是一致的。例如在我的平臺(tái)上,一個(gè)sniff_ethernet結(jié)構(gòu)體的大小是14字節(jié)。一個(gè)sniff_ip結(jié)構(gòu)體是20字節(jié),一個(gè)sniff_tcp結(jié)構(gòu)體也是20字節(jié)。 u_char指針正是包含了內(nèi)存地址的一個(gè)變量,這也是指針的實(shí)質(zhì),它指向內(nèi)存的一個(gè)區(qū)域。簡(jiǎn)單而言,我們說(shuō)指針指向的地址為x,假如三個(gè)結(jié)構(gòu)體恰好線性排列,第一個(gè)(sniff_ethernet)被裝載到內(nèi)存地址的x處則我們很輕易的發(fā)現(xiàn)其他結(jié)構(gòu)體的地址,讓我們以表格顯示之:
Variable Location (in bytes)
sniff_ethernet X
sniff_ip X + 14
sniff_tcp X + 14 + 20
payload X + 14 + 20 + 20
結(jié)構(gòu)體sniff_ethernet正好在x處,緊接著它的sniff_ip則位于x加上它本身占用的空間(此例為14字節(jié)),依此類推可得全部地址。
注重:你沒有假定你的變量也是同樣大小是很重要的。你應(yīng)該總是使用sizeof()來(lái)確保尺寸的正確。這是因?yàn)檫@些結(jié)構(gòu)體中的每個(gè)成員在不同平臺(tái)下可以有不同的尺寸。
?
下面是主要函數(shù)接口:
pcap_t *pcap_open_live(char *device, int snaplen,
?? int promisc, int to_ms, char *ebuf)
?? 獲得用于捕獲網(wǎng)絡(luò)數(shù)據(jù)包的數(shù)據(jù)包捕獲描述字。device參數(shù)為指定打開
?? 的網(wǎng)絡(luò)設(shè)備名。snaplen參數(shù)定義捕獲數(shù)據(jù)的最大字節(jié)數(shù)。promisc指定
?? 是否將網(wǎng)絡(luò)接口置于混雜模式。to_ms參數(shù)指定超時(shí)時(shí)間(毫秒)。
?? ebuf參數(shù)則僅在pcap_open_live()函數(shù)出錯(cuò)返回NULL時(shí)用于傳遞錯(cuò)誤消
?? 息。
pcap_t *pcap_open_offline(char *fname, char *ebuf)
?? 打開以前保存捕獲數(shù)據(jù)包的文件,用于讀取。fname參數(shù)指定打開的文
?? 件名。該文件中的數(shù)據(jù)格式與tcpdump和tcpslice兼容。"-"為標(biāo)準(zhǔn)輸
?? 入。ebuf參數(shù)則僅在pcap_open_offline()函數(shù)出錯(cuò)返回NULL時(shí)用于傳
?? 遞錯(cuò)誤消息。
pcap_dumper_t *pcap_dump_open(pcap_t *p, char *fname)
?? 打開用于保存捕獲數(shù)據(jù)包的文件,用于寫入。fname參數(shù)為"-"時(shí)表示
?? 標(biāo)準(zhǔn)輸出。出錯(cuò)時(shí)返回NULL。p參數(shù)為調(diào)用pcap_open_offline()或
?? pcap_open_live()函數(shù)后返回的pcap結(jié)構(gòu)指針。fname參數(shù)指定打開
?? 的文件名。如果返回NULL,則可調(diào)用pcap_geterr()函數(shù)獲取錯(cuò)誤消
?? 息。
?
char *pcap_lookupdev(char *errbuf)
?? 用于返回可被pcap_open_live()或pcap_lookupnet()函數(shù)調(diào)用的網(wǎng)絡(luò)
?? 設(shè)備名指針。如果函數(shù)出錯(cuò),則返回NULL,同時(shí)errbuf中存放相關(guān)的
?? 錯(cuò)誤消息。
int pcap_lookupnet(char *device, bpf_u_int32 *netp,
?? bpf_u_int32 *maskp, char *errbuf)
?? 獲得指定網(wǎng)絡(luò)設(shè)備的網(wǎng)絡(luò)號(hào)和掩碼。netp參數(shù)和maskp參數(shù)都是
?? bpf_u_int32指針。如果函數(shù)出錯(cuò),則返回-1,同時(shí)errbuf中存放相
?? 關(guān)的錯(cuò)誤消息。
int pcap_dispatch(pcap_t *p, int cnt,
?? pcap_handler callback, u_char *user)
?? 捕獲并處理數(shù)據(jù)包。cnt參數(shù)指定函數(shù)返回前所處理數(shù)據(jù)包的最大值。
?? cnt=-1表示在一個(gè)緩沖區(qū)中處理所有的數(shù)據(jù)包。cnt=0表示處理所有
?? 數(shù)據(jù)包,直到產(chǎn)生以下錯(cuò)誤之一:讀取到EOF;超時(shí)讀取。callback
?? 參數(shù)指定一個(gè)帶有三個(gè)參數(shù)的回調(diào)函數(shù),這三個(gè)參數(shù)為:一個(gè)從
?? pcap_dispatch()函數(shù)傳遞過(guò)來(lái)的u_char指針,一個(gè)pcap_pkthdr結(jié)構(gòu)
?? 的指針,和一個(gè)數(shù)據(jù)包大小的u_char指針。如果成功則返回讀取到的
?? 字節(jié)數(shù)。讀取到EOF時(shí)則返回零值。出錯(cuò)時(shí)則返回-1,此時(shí)可調(diào)用
?? pcap_perror()或pcap_geterr()函數(shù)獲取錯(cuò)誤消息。
int pcap_loop(pcap_t *p, int cnt,
?? pcap_handler callback, u_char *user)
?? 功能基本與pcap_dispatch()函數(shù)相同,只不過(guò)此函數(shù)在cnt個(gè)數(shù)據(jù)包
?? 被處理或出現(xiàn)錯(cuò)誤時(shí)才返回,但讀取超時(shí)不會(huì)返回。而如果為
?? pcap_open_live()函數(shù)指定了一個(gè)非零值的超時(shí)設(shè)置,然后調(diào)用
?? pcap_dispatch()函數(shù),則當(dāng)超時(shí)發(fā)生時(shí)pcap_dispatch()函數(shù)會(huì)返回。
?? cnt參數(shù)為負(fù)值時(shí)pcap_loop()函數(shù)將始終循環(huán)運(yùn)行,除非出現(xiàn)錯(cuò)誤。
void pcap_dump(u_char *user, struct pcap_pkthdr *h,
?? u_char *sp)
?? 向調(diào)用pcap_dump_open()函數(shù)打開的文件輸出一個(gè)數(shù)據(jù)包。該函數(shù)可
?? 作為pcap_dispatch()函數(shù)的回調(diào)函數(shù)。
int pcap_compile(pcap_t *p, struct bpf_program *fp,
?? char *str, int optimize, bpf_u_int32 netmask)
?? 將str參數(shù)指定的字符串編譯到過(guò)濾程序中。fp是一個(gè)bpf_program結(jié)
?? 構(gòu)的指針,在pcap_compile()函數(shù)中被賦值。optimize參數(shù)控制結(jié)果
?? 代碼的優(yōu)化。netmask參數(shù)指定本地網(wǎng)絡(luò)的網(wǎng)絡(luò)掩碼。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
?? 指定一個(gè)過(guò)濾程序。fp參數(shù)是bpf_program結(jié)構(gòu)指針,通常取自
?? pcap_compile()函數(shù)調(diào)用。出錯(cuò)時(shí)返回-1;成功時(shí)返回0。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
?? 返回指向下一個(gè)數(shù)據(jù)包的u_char指針。
int pcap_datalink(pcap_t *p)
?? 返回?cái)?shù)據(jù)鏈路層類型,例如DLT_EN10MB。
int pcap_snapshot(pcap_t *p)
?? 返回pcap_open_live被調(diào)用后的snapshot參數(shù)值。
int pcap_is_swapped(pcap_t *p)
?? 返回當(dāng)前系統(tǒng)主機(jī)字節(jié)與被打開文件的字節(jié)順序是否不同。
int pcap_major_version(pcap_t *p)
?? 返回寫入被打開文件所使用的pcap函數(shù)的主版本號(hào)。
int pcap_minor_version(pcap_t *p)
?? 返回寫入被打開文件所使用的pcap函數(shù)的輔版本號(hào)。
int pcap_stats(pcap_t *p, struct pcap_stat *ps)
?? 向pcap_stat結(jié)構(gòu)賦值。成功時(shí)返回0。這些數(shù)值包括了從開始
?? 捕獲數(shù)據(jù)以來(lái)至今共捕獲到的數(shù)據(jù)包統(tǒng)計(jì)。如果出錯(cuò)或不支持
?? 數(shù)據(jù)包統(tǒng)計(jì),則返回-1,且可調(diào)用pcap_perror()或
?? pcap_geterr()函數(shù)來(lái)獲取錯(cuò)誤消息。
FILE *pcap_file(pcap_t *p)
?? 返回被打開文件的文件名。
int pcap_fileno(pcap_t *p)
?? 返回被打開文件的文件描述字號(hào)碼。
void pcap_perror(pcap_t *p, char *prefix)
?? 在標(biāo)準(zhǔn)輸出設(shè)備上顯示最后一個(gè)pcap庫(kù)錯(cuò)誤消息。以prefix參
?? 數(shù)指定的字符串為消息頭。
char *pcap_geterr(pcap_t *p)
?? 返回最后一個(gè)pcap庫(kù)錯(cuò)誤消息。
char *pcap_strerror(int error)
?? 如果strerror()函數(shù)不可用,則可調(diào)用pcap_strerror函數(shù)替代。
void pcap_close(pcap_t *p)
?? 關(guān)閉p參數(shù)相應(yīng)的文件,并釋放資源。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的libpcap讲解与API接口函数讲解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: iptables:tproxy做透明代理
- 下一篇: iptables详解和练习