Pcap程序設計
摘要
Pcap程序設計(2004-08-17 09:14:50)
By 阿美
Tim Carstens
此文的最近更新見於 http://broker.dhs.org/pcap.htm
好, 讓咱們從看看這篇文章寫給誰開始。顯而易見的,須要一些C語言基礎知識,除非你只想瞭解基本的理論。你沒必要是一個編碼專家,由於這個領域只有經驗豐富的程 序員涉足,而我將盡量詳細的描述這些概念。另外,考慮到這是有關一個包嗅探器的,因此對網絡基礎知識的理解是有幫助的。全部在此出現的代碼示例都已在 FreeBSD 4.3平臺上測試經過。
開始:pcap應用程序的格式
咱們所要理解的第一件事情是一個基於pcap的嗅探器程序的整體佈局。流程以下:
1.咱們從決定用哪個接口進行嗅探開始。在Linux中,這多是eth0,而在BSD系統中則多是xl1等等。咱們也能夠用一個字符串來定義這個設備,或者採用pcap提供的接口名來工做。
2.初始化pcap。在這裏咱們要告訴pcap對什麼設備進行嗅探。假如願意的話,咱們還能夠嗅探多個設備。怎樣區分它們呢?使用 文件句柄。就像打開一個文件進行讀寫同樣,必須命名咱們的嗅探「會話」,以此使它們各自區別開來。
3. 若是咱們只想嗅探特定的傳輸(如TCP/IP包,發往端口23的包等等),咱們必須建立一個規則集合,編譯而且使用它。這個過程分爲三個相互緊密關聯的階 段。規則集合被置於一個字符串內,而且被轉換成能被pcap讀的格式(所以編譯它)。編譯實際上就是在咱們的程序裏調用一個不被外部程序使用的函數。接下 來咱們要告訴 pcap使用它來過濾出咱們想要的那一個會話。
4.最後,咱們告訴pcap進入它的主體執行循環。在這個階段內pcap一直工做到 它接收了全部咱們想要的包爲止。每當它收到一個包就調用另外一個已經定義好的函數,這個函數能夠作咱們想要的任何工做,它能夠剖析所部獲的包並給用戶打印出 結果,它能夠將結果保存爲一個文件,或者什麼也不做。
5.在嗅探到所需的數據後,咱們要關閉會話並結束。
這是實際上一個很簡單的過程。一共五個步驟,其中一個(第3個)是可選的。咱們爲何不看一看是怎樣實現每個步驟呢?
設置設備
這是很簡單的。有兩種方法設置想要嗅探的設備。
第一種,咱們能夠簡單的讓用戶告訴咱們。考察下面的程序:
#include <stdio.h>
#include <pcap.h>
int main(int argc, char *argv[])
{
char *dev = argv[1];
printf("Device: %s", dev);
return(0);
}
用戶經過傳遞給程序的第一個參數來指定設備。字符串「dev」以pcap能「理解」的格式保存了咱們要嗅探的接口的名字(固然,用戶必須給了咱們一個真正存在的接口)。
另外一種也是一樣的簡單。來看這段程序:
#include <stdio.h>
#include <pcap.h>
int main()
{
char *dev, errbuf[PCAP_ERRBUF_SIZE];
dev = pcap_lookupdev(errbuf);
printf("Device: %s", dev);
return(0);
}
在 這個例子裏,pcap就本身設置設備。「可是,等一下,Tim」,你會說,「字符串errbuf是作什麼的?」大多數的pcap命令容許咱們向它們傳遞字 符串做爲參數。這個字符串的目的是什麼呢?若是命令失敗,它將傳給這個字符串關於錯誤的描述。這樣,若是pcap_lookupdev()失敗,它將 在 errbuf存儲錯誤信息。很好,是否是?這就是咱們怎樣去設置設備。
打開設備進行嗅探
建立一個嗅探會話的任務真的很是簡單。爲此,咱們使用pcap_open_live()函數。此函數的原型(根據pcap的手冊頁)以下:
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
其 第一個參數是咱們在上一節中指定的設備,snaplen是整形的,它定義了將被pcap捕獲的最大字節數。當promisc設爲true時將置指定接口爲 混雜模式(然而,當它置爲false時接口仍處於混雜模式的特殊狀況也是有可能的)。to_ms是讀取時的超時值,單位是毫秒(若是爲0則一直嗅探直到錯 誤發生,爲-1則不肯定)。最後,ebuf是一個咱們能夠存入任何錯誤信息的字符串(就像上面的errbuf)。此函數返回其會話句柄。
舉個例子,考察如下代碼片段:
#include <pcap.h>
...
pcap_t *handle;
handle = pcap_open_live(somedev, BUFSIZ, 1, 0, errbuf);
這個代碼片段打開字符串somedev的設備,告訴它讀取被BUFSIZ指定的字節數(BUFSIZ在pcap.h裏定義)。咱們告訴它將設備置爲混雜模式,一直嗅探到錯誤發生,若是有了錯誤,把它存放在字符串errbuf中。
混 雜模式與非混雜模式的區別:這兩種方式區別很大。通常來講,非混雜模式的嗅探器中,主機僅嗅探那些跟它直接有關的通訊,如發向它的,從它發出的,或經它路 由的等都會被嗅探器捕獲。而在混雜模式中則嗅探傳輸線路上的全部通訊。在非交換式網絡中,這將是整個網絡的通訊。這樣作最明顯的優勢就是使更多的包被嗅探 到,它們因你嗅探網絡的緣由或者對你有幫助,或者沒有。可是,混雜模式是可被探測到的。一個主機能夠經過高強度的測試斷定另外一臺主機是否正在進行混雜模式 的嗅探。其次,它僅在非交換式的網絡環境中有效工做(如集線器,或者交換中的ARP層面)。再次,在高負荷的網絡中,主機的系統資源將消耗的很是嚴重。
過濾通訊
通 常,咱們的嗅探器僅對某特定的通訊感興趣。例如,有時咱們想嗅探到端口23(telnet)的包以得到密碼;或者咱們想截獲一個正經過端口 21 (FTP)傳送的文件;可能咱們僅想要獲得DNS的通訊(端口53,UDP)。不管哪一種狀況,咱們都不多盲目的嗅探整個網絡的通訊。下面討論 pcap_compile()與pcap_setfilter()。
這個過程很是簡單。當咱們已經調用了pcap_open_live()從而建 立了一個嗅探會話以後就能夠應用咱們本身的過濾器了。爲何要用咱們本身的過濾器呢?有兩個緣由。第一,pcap的過濾器太強大了,由於它直接使 用 BPF過濾器,咱們經過使用BPF驅動直接過濾跳過了不少的關節。第二,這樣作要容易的多。
在使用咱們本身的過濾器前必須編譯它。過濾表達式被保存在一個字符串中(字符數組)。其句法在tcpdump的手冊頁中被證實很是好。我建議你親自閱讀它。可是咱們將使用簡單的測試表達式,這樣你可能很容易理解個人例子。
咱們調用pcap_compile()來編譯它,其原型是這樣定義的:
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
第 一 個參數是會話句柄(pcap_t *handle在前一節的示例中)。接下來的是咱們存儲被編譯的過濾器版本的地址的引用。再接下來的則是表達式自己,存 儲在規定的字符串格式裏。再下邊是一個定義表達式是否被優化的整形量(0爲false,1爲true,標準規定)。最後,咱們必須指定應用此過濾器的網絡 掩碼。函數返回-1爲失敗,其餘的任何值都代表是成功的。
表達式被編譯以後就可使用了。如今進入pcap_setfilter()。仿照咱們介紹pcap的格式,先來看一看pcap_setfilter()的原型:
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
這很是直觀,第一個參數是會話句柄,第二個參數是被編譯表達式版本的引用(可推測出它與pcap_compile()的第二個參數相同)。
下面的代碼示例可能能使你更好的理解:
#include <pcap.h>
pcap_t *handle; /* 會話的句柄 */
char dev[] = "rl0"; /* 執行嗅探的設備 */
char errbuf[PCAP_ERRBUF_SIZE]; /* 存儲錯誤 信息的字符串 */
struct bpf_program filter; /*已經編譯好的過濾表達式*/
char filter_app[] = "port 23"; /* 過濾表達式*/
bpf_u_int32 mask; /* 執行嗅探的設備的網絡掩碼 */
bpf_u_int32 net; /* 執行嗅探的設備的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);
這個程序使嗅探器嗅探經由端口23的全部通訊,使用混雜模式,設備是rl0。
你可能注意到前面的示例包含一個咱們還沒提到的函數:pcap_lookupnet(),向這個函數提供設備接口名,它將返回其IP和網絡掩碼,這是很基本的,由於咱們須要知道網絡掩碼以便應用過濾器。此函數在此文最後的miscellaneous一節裏還有描述。
據個人經驗,這個過濾器在全部的操做系統下都不會工做。在個人測試環境裏,我發現OpenBSD 2.9默認內核支持這種過濾器,但FreeBSD 4.3默認內核則不支持。你的狀況可能會有變化。
實際的嗅探
到 此爲止,咱們已經學習瞭如何定義一個設備,讓它準備嗅探,還有應用過濾器使咱們嗅談到什麼或者不嗅探到什麼。如今到了真正去捕獲一些數據包的時候了。有兩 種手段捕獲包。咱們能夠一次只捕獲一個包,也能夠進入一個循環,等捕獲到多個包再進行處理。咱們將先看看怎樣去捕獲單個包,而後再看看使用循環的方法。爲 此,咱們使用函數pcap_next()。
Pcap_next()的原型及其簡單:
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
第 一個參數是會話句柄,第二個參數是指向一個包括了當前數據包整體信息(被捕獲時的時間,包的長度,其被指定的部分長度)的結構體的指針(在這裏只有一個片 斷,只做爲一個示例)。Pcap_next()返回一個u_char指針給被這個結構體描述的包。咱們將稍後討論這種實際讀取包自己的手段。
這裏有一個演示怎樣使用pcap_next()來嗅探一個包的例子:
#include <pcap.h>
#include <stdio.h>
int main()
{
pcap_t *handle; /* 會話句柄 */
char *dev; /* 執行嗅探的設備 */
char errbuf[PCAP_ERRBUF_SIZE]; /* 存儲錯誤信息的字符串 */
struct bpf_program filter; /* 已經編譯好的過濾器 */
char filter_app[] = "port 23"; /* 過濾表達式 */
bpf_u_int32 mask; /* 所在網絡的掩碼 */
bpf_u_int32 net; /* 主機的IP地址 */
struct pcap_pkthdr header; /* 由pcap.h定義 */
const u_char *packet; /* 實際的包 */
/* Define the device */
dev = pcap_lookupdev(errbuf);
/* 探查設備屬性 */
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);
/* 截獲一個包 */
packet = pcap_next(handle, &header);
/* 打印它的長度 */
printf("Jacked a packet with length of [%d]
", header.len);
/* 關閉會話 */
pcap_close(handle);
return(0);
}
這 個程序嗅探被pcap_lookupdev()返回的設備並將它置爲混雜模式。它發現第一個包通過端口23(telnet)而且告訴用戶此包的大小(以 字 節爲單位)。這個程序又包含了一個新的調用pcap_close(),咱們將在後面討論(儘管它的名字就足夠證實它本身的做用)。
咱們可使 用的另外一種手段則要複雜的多,而且可能也更爲有用。不多有(若是有的話)嗅探器真正的使用pcap_next()。一般,它們使用pcap_loop() 或者 pcap_dispatch()(它就是用了pcap_loop())。爲了理解這兩個函數的用法,你必須理解回調函數的思想。
回調函數並 不是什麼新東西,它在許多API裏面很是廣泛。回調函數的概念極其簡單。設想我有一個程序正等待某種排序的事件。爲了達到這個例子的目的,讓咱們假象個人 程序想讓用戶在鍵盤上按下一個鍵,每當他們按下了一個鍵,我就想調用一個做相應處理的函數。我所用的函數就是一個回調函數。用戶每按一個鍵一次,個人程序 就調用回調函數一次。回調函數在應用在pcap裏,取代當用戶按下鍵時被調用的函數的是當pcap嗅探到一個數據包時所調用的函數。能夠定義它們的回調函 數的兩個函數就是pcap_loop()和pcap_dispatch()。此兩者在它們的回調函數的使用上很是的類似。它們都是每當捕獲到一個符合咱們 過濾器的包時調用器回調函數(固然是存在一個過濾器時,若是不存在則全部被嗅探到的包都被送到會調函數處理)。
Pcap_loop()的原型以下:
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
第 一個參數是會話句柄,接下來是一個整型,它告訴pcap_loop()在返回前應捕獲多少個數據包(若爲負值則表示應該一直工做直至錯誤發生)。第三個參 數是回調函數的名稱(正像其標識符所指,無括號)。最後一個參數在有些應用裏有用,但更多時候則置爲NULL。假設咱們有咱們本身的想送往回調函數的參 數,另外還有pcap_loop()發送的參數,這就須要用到它。很明顯,必須是一個u_char類型的指針以確保結果正確;正像咱們稍後見到 的, pcap使用了頗有意思的方法以u_char指針的形勢傳遞信息。在咱們展現了一個pcap是怎樣作的例子以後就很容易去作了。如果還不行就參考你 的本地的C引用文本,做爲一個指針的解釋那就超出了本文的範圍。 Pcap_dispatch()的用法幾乎相同。惟一不一樣的是它們如何處理超時(還記得 在調用pcap_open_live()時怎樣設置超時嗎?這就是它起做用的地方)。Pcap_loop()忽略超時而pcap_dispatch()則 不。關於它們之間區別的更深刻的討論請參見pcap的手冊頁。
在提供使用pcap_loop()的示例以前,咱們必須檢查咱們的回調函數的格式。咱們不能武斷的定義回調函數的原型,不然pcap_loop()將會不知道如何去使用它。所以咱們使用這樣的格式做爲咱們的回調函數的原型:
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
讓 咱們更細緻的考察它。首先,你會注意到該函數返回void類型,這是符合邏輯的,由於pcap_loop()不知道如何去處理一個回調返回值。第一個參數 相應於pcap_loop()的最後一個參數。每當回調函數被調用時,不管最後一個參數傳給pcap_loop()什麼值,這個值都會傳給咱們回調函數的 第一個參數。第二個參數是pcap頭文件定義的,它包括數據包被嗅探的時間、大小等信息。結構體pcap_pkhdr在pcap.h中定義以下:
struct pcap_pkthdr {
struct timeval ts; /* 時間戳 */
bpf_u_int32 caplen; /* 已捕獲部分的長度 */
bpf_u_int32 len; /* 該包的脫機長度 */
};
這些量都至關明瞭。最後一個參數在它們中是最有意思的,也最讓pcap程序新手感到迷惑。這又是一個u_char指針,它包含了被pcap_loop()嗅探到的全部包。
但 是你怎樣使用這個咱們在原型裏稱爲packet的變量呢?一個數據包包含許多屬性,所以你能夠想象它不僅是一個字符串,而實質上是一個結構體的集合(比 如,一個TCP/IP包會有一個以太網的頭部,一個IP頭部,一個TCP頭部,還有此包的有效載荷)。這個u_char就是這些結構體的串聯版本。爲了使 用它,咱們必須做一些有趣的匹配工做。
首先,在匹配它們以前必須定義這些實際的結構體。下面就是我用來描述一個經過以太網的TCP/IP包的結構 體的定義。我使用的全部這些定義都是直接從POSIX庫中提取的。一般,我只簡單的使用那些庫中的定義便可,但據個人經驗不一樣平臺的庫之間有輕微的差異, 這使得它實現起來變得混亂。所以,爲達到示例的目的,我就避免那些混亂而簡單的複製這些有關的結構體。全部這些都能在你的本地unix系統中的 include/netinet中找到。下面就是這些結構體:
/* 以太網幀頭部 */
struct sniff_ethernet {
u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主機的地址 */
u_char ether_shost[ETHER_ADDR_LEN]; /* 源主機的地址 */
u_short ether_type; /* IP? ARP? RARP? etc */
};
/* IP數據包的頭部 */
struct sniff_ip {
#if BYTE_ORDER == LITTLE_ENDIAN
u_int ip_hl:4, /* 頭部長度 */
ip_v:4; /* 版本號 */
#if BYTE_ORDER == BIG_ENDIAN
u_int ip_v:4, /* 版本號 */
ip_hl:4; /* 頭部長度 */
#endif
#endif /* not _IP_VHL */
u_char ip_tos; /* 服務的類型 */
u_short ip_len; /* 總長度 */
u_short ip_id; /*包標誌號 */
u_short ip_off; /* 碎片偏移 */
#define IP_RF 0x8000 /* 保留的碎片標誌 */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* 多碎片標誌*/
#define IP_OFFMASK 0x1fff /*分段位 */
u_char ip_ttl; /* 數據包的生存時間 */
u_char ip_p; /* 所使用的協議 */
u_short ip_sum; /* 校驗和 */
struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
};
/* TCP 數據包的頭部 */
struct sniff_tcp {
u_short th_sport; /* 源端口 */
u_short th_dport; /* 目的端口 */
tcp_seq th_seq; /* 包序號 */
tcp_seq th_ack; /* 確認序號 */
#if BYTE_ORDER == LITTLE_ENDIAN
u_int th_x2:4, /* 尚未用到 */
th_off:4; /* 數據偏移 */
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_int th_off:4, /* 數據偏移*/
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_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
u_short th_win; /* TCP滑動窗口 */
u_short th_sum; /* 頭部校驗和 */
u_short th_urp; /* 緊急服務位 */
};
注: 在Slackware Linux 8(內核版本2.2.19)上我發現使用以上結構體的代碼將不能經過編譯。後來證實問題在於 include/fearures.h,它只實現了一個 POSIX接口,除非定義BSD_SOURCE。若是它沒有被定義,我就只能使用一個不一樣的結構 體去定義TCP頭部。使它們工做在FreeBSD或 OpenBSD系統上的更爲通用的解決方法以下:
#define _BSD_SOURCE 1
事先要包含你本身的全部頭文件。這將確保正常使用BSD風格的API。若是不想這樣作,那你能夠改變TCP頭結構(點此連接便可,內含註釋)。
那 麼全部這些與pcap還有神祕的u_char是怎麼關聯的呢?看,幸運的是pcap嗅探數據包時正是使用的這些結構。接下來,它簡單的建立一 個 u_char字符串而且將這些結構體填入。那麼咱們怎樣才能區分它們呢?準備好見證指針最實用的好處之一吧(在此,我可要刺激刺激那些堅持說指針無用 的C 程序新手了)。
咱們再一次假定要對以太網上的TCP/IP包進行處理。一樣的手段能夠應用於任何數據包,惟一的區別是你實際所使用的結構體的類型。讓咱們從聲明分解u_char包的變量開始:
const struct sniff_ethernet *ethernet; /* 以太網幀頭部*/
const struct sniff_ip *ip; /* IP包頭部 */
const struct sniff_tcp *tcp; /* TCP包頭部 */
const char *payload; /* 數據包的有效載荷*/
/*爲了讓它的可讀性好,咱們計算每一個結構體中的變量大小*/
int size_ethernet = sizeof(struct sniff_ethernet);
int size_ip = sizeof(struct sniff_ip);
int size_tcp = sizeof(struct sniff_tcp);
如今咱們開始讓人感到有些神祕的匹配:
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在內存中的層次。基本的,當pcap將這些結構體填入u_char的時候是將這些數據存入一個字符串中,那個字符串將被送入 咱們的會調函數中。反向轉換是這樣的,不考慮這些結構體制中的值,它們的大小將是一致的。例如在個人平臺上,一個sniff_ethernet結構體的大 小是14字節。一個sniff_ip結構體是20字節,一個sniff_tcp結構體也是20字節。 u_char指針正是包含了內存地址的一個變量,這 也是指針的實質,它指向內存的一個區域。簡單而言,咱們說指針指向的地址爲x,若是三個結構體剛好線性排列,第一個(sniff_ethernet)被裝 載到內存地址的x處則咱們很容易的發現其餘結構體的地址,讓咱們以表格顯示之:
Variable Location (in bytes)
sniff_ethernet X
sniff_ip X + 14
sniff_tcp X + 14 + 20
payload X + 14 + 20 + 20
結構體sniff_ethernet正好在x處,緊接着它的sniff_ip則位於x加上它自己佔用的空間(此例爲14字節),依此類推可得所有地址。
注意:你沒有假定你的變量也是一樣大小是很重要的。你應該老是使用sizeof()來確保尺寸的正確。這是由於這些結構體中的每一個成員在不一樣平臺下能夠有不一樣的尺寸。
到如今,咱們已經知道了怎樣設置回調函數,調用它,弄清被嗅探到的數據包的屬性。你可能正期待着寫出一個可用的包嗅探器。由於代碼的長度關係,我不想列在這篇文章裏。你能夠點擊這裏下載並測試它。
結束語
到此爲止,你應該能夠寫出一個基於pcap的包嗅探器了。你已經學習了基本的概念:打開一個pcap會話,有關它的全體屬性,嗅探數據包,使用過濾器,使用回調函數,等等。如今是進行數據包嗅探的時候了。
做者Blog:http://blog.csdn.net/plowboy/
相關文章Programming with pcap
libpcap使用總結!
libpcap 是一個開發sniffer的工具包。pcap:packet capture!
libpcap的數據類型定義:
struct pcap_addr:網卡地址描述
{
pcap_addr * next;
sockaddr * addr;
sockaddr * netmask;
sockaddr *broadaddr;
sockaddr *dstaddr;
};
pcap_addr * next;
若是非空,指向鏈表中一個元素的指針;空表示鏈表中的最後一個元素。
sockaddr * addr;
指向包含一個地址的sockaddr的結構的指針。
sockaddr * netmask;
若是非空,指向包含相對於addr指向的地址的一個網絡掩碼的結構。
sockaddr * broadaddr;
若是非空,指向包含相對於addr指向的地址的一個廣播地址,若是網絡不支持廣播可能爲空。
sockaddr * dstaddr;
若是非空,指向一個相對於addr指向的源地址的目的地址,若是網絡不支持點對點通信,則爲空。
struct pcap_file_header {
bpf_u_int32 magic;
u_short version_major;
u_short version_minor;
bpf_int32 thiszone; /* gmt to local correction */
bpf_u_int32 sigfigs; /* accuracy of timestamps */
bpf_u_int32 snaplen; /* max length saved portion of each pkt */
bpf_u_int32 linktype; /* data link type (LINKTYPE_*) */
};
bpf_u_int32 magic;
????????????????????????????????
u_short version_major;
Libpcap的主版本號。
u_shart version_minor;
Libpcap的從版本號。
bpf_u_int32 sigfigs;
時間戳描述。
bpf_u_int32 snaplen;
保存的每一個pkt的分片號的最大值。
bpf_u_int32 linktype;
數據鏈的類型。
細節說明:
libpcap dump文件頭;
libpcap dump文件中的第一個記錄包含了一些標誌的保存值,這些標誌在打印階段用到。這兒的不少域都是32位的int,因此compilers不用進行轉化;這些文件須要具備跨層次的可交換性。
不管如何不要改變結構的層次(包括僅僅改變這個結構中域的長度);
struct pcap_if { /*網卡數據鏈的一個元素*/
struct pcap_if *next;
char *name; /* name to hand to "pcap_open_live()" */
char *description; /* textual description of interface, or NULL */
struct pcap_addr *addresses;
u_int flags; /* PCAP_IF_ interface flags */
};
pcap_if *next;
若是非空,指向鏈的下一個元素。若是爲空是鏈的最後一個元素。
char * name;
指向一個字符串,該字符串是傳給pcap_open_live()函數的設備名;
char * description;
若是非空,指向一個對設備的人性化的描述字符串。
pcap_addr *addresses;
指向網卡地址鏈中的第一個元素。
u_int flags;
PCAP_IF_ 網卡的標誌。如今惟一可用的標識是PCAP_IF_LOOKBACK,它被用來標識網卡是否是lookback網卡。
struct pcap_pkthdr { /*dump 文件中的數據包頭*/
struct timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
timeval ts;
數據報時間戳;
bpf_u_int32 caplen;
當前分片的長度;
dpf_u_int32 len;
這個數據報的長度;
細節描述:
在dump文件中的每一個數據報都有這樣一個報頭。它用來處理不一樣數據報網卡的不一樣報頭問題。
struct pcap_stat { /*用來保存網卡靜態變量的結構*/
u_int ps_recv; /* number of packets received */
u_int ps_drop; /* number of packets dropped */
u_int ps_ifdrop; /* drops by interface XXX not yet supported */
};
u_int ps_recv;
接受數據報的數目;
u_int ps_drop;
被驅動程序丟棄的數據報的數目;
u_int ps_ifdrop;
被網卡丟棄的數據報的數目;
lipcap的聲明:
#define PCAP_VERSION_MAJOR 2
libpcap dump文件的主版本號;
#define PCAP_VERSION_MINOR 4
libpcap dump文件的從版本號;
#define PCAP_ERRBUF_SIZE 256
用來存放libpcap出錯信息的緩衝區的大小;
#define PCAP_IF_LOOPBACK 0x00000001
網卡是迴環網卡;
#define MODE_CAPT 0
抓報模式,在調用pcap_setmode()時使用;
#define MODE_STAT 1
靜態模式,在調用pcap_setmode()時使用;
libpcap的類型定義:
typedef int bpf_int32
32bit 的整形;
typedef u_int bpf_u_int32
32bit 的無類型整形;
typedef pcap pcap_t
Descriptor of an open capture instance(一個打開的捕獲實例的描述符?)這個結構對用戶是不透明的。
typedef pcap_dumper pcap_dumper_t
libpcap保存文件的描述符。
typedef pcap_if pcap_if_t
網卡鏈表的一個元素;
typedef pcap_addr pcap_addr_t
網卡地址的表示;
libpcap函數描述:
char *pcap_lookupdev(char * errbuf);
描 述:這個函數用於獲取一個合適的網卡描述,以供pcap_open_liver函數和pcap_lookupnet函數使用。若是找不到網卡或者全部網卡 爲 off,則返回null。若是一個系統中有多個網卡,那麼該函數返回找到的第一個on的網卡。最後纔是迴環接口。迴環網卡一直被忽略;
參數:
char * errbuf 存放pcap_lookupdev函數的出錯信息,只有在pcap_lookup失敗是纔有值。
返回值: 若是函數執行成功,則返回一個用於描述系統上的一個網卡的描述符的指針。若是失敗,返回null,errbuf中存放出錯信息。
int pcap_lookupnet(char * device, bpf_u_int32 * netp, bpf_u_int32 * maskp,char * errbuf);
描述:該函數用於監測網卡所在網絡的網絡地址和子網掩碼。
參數:
char *devic:網卡的描述符指針,由pcap_looupdev函數獲取;
bpf_u_int32 *netp:存放網絡地址;
bpf_u_int32 *maskp:存放子網掩碼;
char * errbuf: 存放出錯信息;
返回值:若是函數執行成功,則返回值爲0,不然返回值爲-1,並在errbuf中存放出錯信息。
pcap_t *pcap_open_live(char * device, int snaplen,int promisc, int to_ms, char * ebuf);
描 述:該函數用於打開網卡用於捕獲數據報。單詞live的意思就是表示一個運行的網卡(相對於offline而言)被打開了,如同一個保存有被抓數據報的文 件被打開同樣。在捕獲數據報以前這個函數必須被執行。全部的其餘的用於處理數據報捕獲的函數用到的捕獲數據報的描述符由該函數產生。查 看 pcap_open_offlin()函數的定義,瞭解如何打開一個預先保存的包含數據報的文件的細節。
參數:
char *device:網卡的描述符指針,由pcap_looupdev函數獲取;
int snaplen:規定捕獲的每一個數據報的最大字節數;
int promisc:1爲混雜模式;0爲非混雜模式;
int to_ms:規定讀超時的微秒(milliseconds)數;
char *ebuf:存放錯誤信息,只有在pcap_open_live失敗時才被設置;
返回值:若是函數成功執行,則返回一個指向數據報捕獲的指針;若是錯誤,返回null,ebuf存放出錯信息;
int pcap_compile(pcap_t * p, struct bpf_ program *fp, char * str,int optimize, bpf_u_int32 netmask);
描述:該函數用於將str指定的規則整合到fp過濾程序中去,並生成過濾程序入口地址,用於過濾選擇指望的數據報;
參數:
pcap_t *p:pcap_open_live返回的數據報捕獲的指針;
struct bpf_program *fp:指向一個子函數用於過濾,在pcap_compile()函數中被賦值;
char *str:該字符串規定過濾規則;
int optimize:規定了在結果代碼上的選擇是否被執行;
bpf_u_int32 netmask:該網卡的子網掩碼,能夠經過pcap_lookupnet()獲取;
返回值:
若是成功執行,返回0,不然返回-1;
int pcap_loop(pcap_t * p, int cnt, pcap_handler callback,u_char * user);
描述:
該函數用於讀取和處理數據報。既能夠用來處理事先捕獲的保存在文件中的數據報,也能夠用來處理實時捕獲的數據報;
這 個函數相似於pcap_dispatch函數,除了它繼續讀取數據報直至完成cnt個報的處理,或者文件處理完(在offline狀況下),或者有錯誤發 生爲止。它不會在實時讀超時時返回(而若是爲pcap_open_live()函數指定了一個非零值的超時設置,而後調用
pcap_dispatch()函數,則當超時發生時pcap_dispatch()函數會返回。)
注意第三個參數,callback是pcap_handler類型的變量。這是一個用戶提供的有着三個參數的子函數。定義爲:
void user_routine(u_char *user, struct pcap_pkthdr *phrd, u_char *pdata)
這三個參數中,user,是傳遞給pcap_dispatch()的那個參數;phdr,是個pcap_pkthdr類型的指針,是savefile中的數據報的頭指針,pdata,指向數據報數據;這個函數容許用戶定義子集的數據報過濾程序;
參數:
pcap_t * pcap_open_live返回的數據報捕獲的指針;
int cnt:規定了函數返回前應處理的數據報數目;
pcap_handler callback:指向一個用戶自定義的函數,在處理每一個報後自動調用該函數進行再處理;
u_char *user:該指針用於傳遞給callback.(不知道有什麼用?)
返回值:
若是函數成功執行(包括讀文件時讀到EOF),則返回0.不然返回-1,那麼錯誤信息將由函數pcap_geterr或pcap_perror給出;
===============================>?-----------------------------------(to be continued!!)
libpcap使用舉例(1)
發佈日期:2001-01-23
文章內容:
做者:小四 < mailto: scz@nsfocus.com >
主頁:http://www.nsfocus.com
日期:2000-12-16
咱們曾經提供過<<libnet使用舉例(1-12)>>,比較詳細地介紹了報文發送編程。始終
沒有介紹libpcap報文捕捉編程的緣由不少,tcpdump、snort等著名軟件包都是基於
libpcap,加上W.Richard.Stevens的<<Unix Network Programming Vol I>>第26章推
波助瀾,實在以爲沒有必要繼續介紹libpcap編程。更真實的緣由多是BPF、DLPI、
SOCK_PACKET三種接口編程已經被演練得太多太濫。
今天討論的不是效率,而是可能的移植性要求。沒辦法,第一次使用libpcap庫,舉
例能深刻到什麼地步,不知道。若是你也是第一次用這個庫,跟我來,第N次使用?
那仍是忙你的去吧,別來看這篇無聊的灌水,:-P。我無聊是由於有程序要普遍可移
植,你無聊是爲何。
char * pcap_lookupdev ( char * errbuf );
該函數返回一個網絡設備接口名,相似libnet_select_device(),對於Linux就是
"eth0"一類的名字。pcap_open_live()、pcap_lookupnet()等函數將用到這個網絡設
備接口名。失敗時返回NULL,errbuf包含了失敗緣由。errbuf通常定義以下:
/usr/include/pcap.h
#define PCAP_ERRBUF_SIZE 256
char errbuf[ PCAP_ERRBUF_SIZE ];
pcap_t * pcap_open_live ( char * device, int snaplen, int promisc,
int to_ms, char * errbuf );
該函數用於獲取一個抽象的包捕捉句柄,後續不少libpcap函數將使用該句柄,相似
文件操做函數頻繁使用文件句柄。device指定網絡接口設備名,好比"eth0。snaplen
指定單包最大捕捉字節數,爲了保證包捕捉不至於過低效率,snaplen儘可能適中,以
恰能得到所需協議層數據爲準。promisc指定網絡接口是否進入混雜模式,注意即便
該參數爲false(0),網絡接口仍然有可能由於其餘緣由處在混雜模式。to_ms指定毫
秒級讀超時,man手冊上並無指明什麼值意味着永不超時,測試下來的結論,0可能
表明永不超時。若是調用失敗返回NULL,errbuf包含失敗緣由。
--------------------------------------------------------------------------
/usr/include/pcap.h
typedef struct pcap pcap_t;
pcap-int.h裏定義了struct pcap {}
struct pcap
{
int fd;
int snapshot;
int linktype;
int tzoff; /* timezone offset */
int offset; /* offset for proper alignment */
struct pcap_sf sf;
struct pcap_md md;
int bufsize; /* Read buffer */
u_char * buffer;
u_char * bp;
int cc;
u_char * pkt; /* Place holder for pcap_next() */
struct bpf_program fcode; /* Placeholder for filter code if bpf not in kernel. */
char errbuf[PCAP_ERRBUF_SIZE];
};
--------------------------------------------------------------------------
int pcap_lookupnet ( char * device, bpf_u_int32 * netp,
bpf_u_int32 * maskp, char * errbuf );
該函數用於獲取指定網絡接口的IP地址、子網掩碼。不要被netp的名字所迷惑,它對
應的就是IP地址,maskp對應子網掩碼。
/usr/include/pcap.h
typedef u_int bpf_u_int32;
顯然簡單理解成32-bit便可。若是調用失敗則返回-1,errbuf包含失敗緣由。
int pcap_compile ( pcap_t * p, struct bpf_program * fp, char * str,
int optimize, bpf_u_int32 netmask );
該函數用於解析過濾規則串,填寫bpf_program結構。str指向過濾規則串,格式參看
tcpdump的man手冊,好比:
tcpdump -x -vv -n -t ip proto \\tcp and dst 192.168.8.90 and tcp[13] \& 2 = 2
這條過濾規則將捕捉全部攜帶SYN標誌的到192.168.8.90的TCP報文。過濾規則串能夠
是空串(""),表示抓取全部過路的報文。
optimize爲1表示對過濾規則進行優化處理。netmask指定子網掩碼,通常從
pcap_lookupnet()調用中獲取。返回值小於零表示調用失敗。
這個函數可能比較難於理解,涉及的概念源自BPF,Linux系統沒有這種概念,可是
libpcap採用pcap_compile()和pcap_setfilter()結合的辦法屏蔽了各類鏈路層支持
的不一樣,不管是SOCK_PACKET、DLPI。曾在華中Security版上寫過一篇
<<內核包捕獲過濾機制介紹>>,參看該文增強理解。
--------------------------------------------------------------------------
# tcpdump -d ip proto \\tcp and dst 192.168.8.90 and tcp[13] \& 2 = 2
(000) ldh [-4096]
(001) jeq #0x800 jt 2 jf 13
(002) ldb [9]
(003) jeq #0x6 jt 4 jf 13
(004) ld [16]
(005) jeq #0xc0a8085a jt 6 jf 13
(006) ldh [6]
(007) jset #0x1fff jt 13 jf 8
(008) ldxb 4*([0]&0xf)
(009) ldb [x + 13]
(010) and #0x2
(011) jeq #0x2 jt 12 jf 13
(012) ret #65535
(013) ret #0
#
/usr/include/net/bpf.h
/* Structure for BIOCSETF. */
struct bpf_program
{
u_int bf_len;
struct bpf_insn * bf_insns;
};
/*
* The instruction data structure.
*/
struct bpf_insn
{
u_short code;
u_char jt;
u_char jf;
bpf_int32 k;
};
/*
* Macros for insn array initializers.
*/
#define BPF_STMT(code, k) { (u_short)(code), 0, 0, k }
#define BPF_JUMP(code, k, jt, jf) { (u_short)(code), jt, jf, k }
--------------------------------------------------------------------------
int pcap_setfilter ( pcap_t * p, struct bpf_program * fp );
該函數用於設置pcap_compile()解析完畢的過濾規則,若是你足夠聰明(愚公?),完
全能夠本身提供過濾規則,無須pcap_compile()介入,就象你寫
Password Sniffer For I386/FreeBSD時常作的那樣。成功返回0,失敗返回-1。
int pcap_dispatch ( pcap_t * p, int cnt, pcap_handler callback, u_char * user );
該函數用於捕捉報文、分發報文到預先指定好的處理函數(回調函數)。
pcap_dispatch()接收夠cnt個報文便返回,若是cnt爲-1意味着全部報文集中在一個
緩衝區中。若是cnt爲0,僅當發生錯誤、讀取到EOF或者讀超時到了(pcap_open_live
中指定)才中止捕捉報文並返回。callback指定以下類型的回調函數,用於處理
pcap_dispatch()所捕獲的報文:
typedef void ( *pcap_handler ) ( u_char *, const struct pcap_pkthdr *, const u_char * );
pcap_dispatch()返回捕捉到的報文個數,若是在讀取靜態文件(之前包捕捉過程當中存
儲下來的)時碰到EOF則返回0。返回-1表示發生錯誤,此時能夠用pcap_perror()、
pcap_geterr()顯示錯誤信息。
下面來看看那個回調函數,總共有三個參數,第一個形參來自pcap_dispatch()的第
三個形參,通常咱們本身的包捕捉程序不須要提供它,老是爲NULL。第二個形參指向
pcap_pkthdr結構,該結構位於真正的物理幀前面,用於消除不一樣鏈路層支持的差別。
最後的形參指向所捕獲報文的物理幀。
--------------------------------------------------------------------------
/usr/include/pcap.h
/*
* Each packet in the dump file is prepended with this generic header.
* This gets around the problem of different headers for different
* packet interfaces.
*/
struct pcap_pkthdr
{
struct timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
/usr/include/net/bpf.h
/*
* Structure prepended to each packet.
*/
struct bpf_hdr
{
struct timeval bh_tstamp; /* time stamp */
bpf_u_int32 bh_caplen; /* length of captured portion */
bpf_u_int32 bh_datalen; /* original length of packet */
u_short bh_hdrlen; /* length of bpf header (this struct
plus alignment padding) */
};
/*
* Because the structure above is not a multiple of 4 bytes, some compilers
* will insist on inserting padding; hence, sizeof(struct bpf_hdr) won't work.
* Only the kernel needs to know about it; applications use bh_hdrlen.
*/
#ifdef KERNEL
#define SIZEOF_BPF_HDR 18
#endif
--------------------------------------------------------------------------
void pcap_close ( pcap_t * p );
該函數用於關閉pcap_open_live()獲取的包捕捉句柄,釋放相關資源。
void pcap_perror ( pcap_t * p, char * prefix );
第一形參來自pcap_open_live(),第二行參的做用相似perror()的形參,指定錯誤信
息的前綴,與perror()同樣,結尾自動輸出一個換行。
pcap_perror( p, "pcap_compile" )的輸出相似這個效果:
pcap_compile: unknown ip proto ...
pcap_perror並不自動exit(),與perror()同樣,若是須要,應該顯式調用exit()。
介紹到這裏,已經能夠寫簡單的sniffer。出於完整演示目的,提供這樣一個sample
code。請勿詢問任何關於該代碼的問題,煩了。
--------------------------------------------------------------------------
/*
* File : sniffer program for I386/Linux using libpcap
* Version: 0.01 aleph
* Author : Anonymous ( Don't ask anything about this program, please. )
* Complie: gcc -O3 -o pcap pcap_sniffer.c -lpcap `libnet-config --defines --cflags` -Wall
* : strip pcap
* Usage : ./pcap -h
* Date : 2000-12-15 16:35
*/
/*******************************************************************
* *
* Head File *
* *
*******************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <pcap.h>
#include <libnet.h> /* for LIBNET_TCP_H */
/*******************************************************************
* *
* Macro *
* *
*******************************************************************/
#define SUCCESS 0
#define FAILURE -1
typedef void Sigfunc ( int ); /* for signal handlers */
/*******************************************************************
* *
* Static Global Var *
* *
*******************************************************************/
static pcap_t * pcap_fd = NULL; /* 抽象的包捕捉句柄 */
/*******************************************************************
* *
* Function Prototype *
* *
*******************************************************************/
static void Atexit ( void ( * func ) ( void ) );
static void bpf_dump ( struct bpf_program * p, int option );
char * bpf_image ( struct bpf_insn * p, int n );
static void outputBinary ( const u_char * byteArray, const size_t byteArrayLen );
static void pcap_callback ( u_char * none, const struct pcap_pkthdr * pcap_head, const u_char * packet );
static pcap_t * pcap_init ( char * dev, char * filter, int snaplen, int timeout, int dumplevel );
static void pcap_read ( pcap_t * p );
static void sig_end ( int signo );
Sigfunc * signal ( int signo, Sigfunc * func );
static Sigfunc * Signal ( int signo, Sigfunc * func ); /* for our signal() function */
static void terminate ( void );
static void usage ( char * arg );
/*----------------------------------------------------------------------*/
static void Atexit ( void ( * func ) ( void ) )
{
if ( atexit( func ) != 0 )
{
exit( FAILURE );
}
return;
} /* end of Atexit */
static void bpf_dump ( struct bpf_program * p, int option )
{
struct bpf_insn * insn;
int i;
int n = p->bf_len;
insn = p->bf_insns;
if ( option > 2 )
{
fprintf( stderr, "%d\n", n );
for ( i = 0; i < n; ++insn, ++i )
{
fprintf( stderr, "%u %u %u %u\n", insn->code,
insn->jt, insn->jf, insn->k );
}
return;
}
if ( option > 1 )
{
for ( i = 0; i < n; ++insn, ++i )
{
fprintf( stderr, "{ 0x%x, %d, %d, 0x%08x },\n",
insn->code, insn->jt, insn->jf, insn->k );
}
return;
}
for ( i = 0; i < n; ++insn, ++i )
{
puts( bpf_image( insn, i ) );
}
} /* end of bpf_dump */
char * bpf_image ( struct bpf_insn * p, int n )
{
int v;
char * fmt;
char * op;
static char image[256];
char operand[64];
v = p->k;
switch ( p->code )
{
default:
op = "unimp";
fmt = "0x%x";
v = p->code;
break;
case BPF_RET|BPF_K:
op = "ret";
fmt = "#%d";
break;
case BPF_RET|BPF_A:
op = "ret";
fmt = "";
break;
case BPF_LD|BPF_W|BPF_ABS:
op = "ld";
fmt = "[%d]";
break;
case BPF_LD|BPF_H|BPF_ABS:
op = "ldh";
fmt = "[%d]";
break;
case BPF_LD|BPF_B|BPF_ABS:
op = "ldb";
fmt = "[%d]";
break;
case BPF_LD|BPF_W|BPF_LEN:
op = "ld";
fmt = "#pktlen";
break;
case BPF_LD|BPF_W|BPF_IND:
op = "ld";
fmt = "[x + %d]";
break;
case BPF_LD|BPF_H|BPF_IND:
op = "ldh";
fmt = "[x + %d]";
break;
case BPF_LD|BPF_B|BPF_IND:
op = "ldb";
fmt = "[x + %d]";
break;
case BPF_LD|BPF_IMM:
op = "ld";
fmt = "#0x%x";
break;
case BPF_LDX|BPF_IMM:
op = "ldx";
fmt = "#0x%x";
break;
case BPF_LDX|BPF_MSH|BPF_B:
op = "ldxb";
fmt = "4*([%d]&0xf)";
break;
case BPF_LD|BPF_MEM:
op = "ld";
fmt = "M[%d]";
break;
case BPF_LDX|BPF_MEM:
op = "ldx";
fmt = "M[%d]";
break;
case BPF_ST:
op = "st";
fmt = "M[%d]";
break;
case BPF_STX:
op = "stx";
fmt = "M[%d]";
break;
case BPF_JMP|BPF_JA:
op = "ja";
fmt = "%d";
v = n + 1 + p->k;
break;
case BPF_JMP|BPF_JGT|BPF_K:
op = "jgt";
fmt = "#0x%x";
break;
case BPF_JMP|BPF_JGE|BPF_K:
op = "jge";
fmt = "#0x%x";
break;
case BPF_JMP|BPF_JEQ|BPF_K:
op = "jeq";
fmt = "#0x%x";
break;
case BPF_JMP|BPF_JSET|BPF_K:
op = "jset";
fmt = "#0x%x";
break;
case BPF_JMP|BPF_JGT|BPF_X:
op = "jgt";
fmt = "x";
break;
case BPF_JMP|BPF_JGE|BPF_X:
op = "jge";
fmt = "x";
break;
case BPF_JMP|BPF_JEQ|BPF_X:
op = "jeq";
fmt = "x";
break;
case BPF_JMP|BPF_JSET|BPF_X:
op = "jset";
fmt = "x";
break;
case BPF_ALU|BPF_ADD|BPF_X:
op = "add";
fmt = "x";
break;
case BPF_ALU|BPF_SUB|BPF_X:
op = "sub";
fmt = "x";
break;
case BPF_ALU|BPF_MUL|BPF_X:
op = "mul";
fmt = "x";
break;
case BPF_ALU|BPF_DIV|BPF_X:
op = "div";
fmt = "x";
break;
case BPF_ALU|BPF_AND|BPF_X:
op = "and";
fmt = "x";
break;
case BPF_ALU|BPF_OR|BPF_X:
op = "or";
fmt = "x";
break;
case BPF_ALU|BPF_LSH|BPF_X:
op = "lsh";
fmt = "x";
break;
case BPF_ALU|BPF_RSH|BPF_X:
op = "rsh";
fmt = "x";
break;
case BPF_ALU|BPF_ADD|BPF_K:
op = "add";
fmt = "#%d";
break;
case BPF_ALU|BPF_SUB|BPF_K:
op = "sub";
fmt = "#%d";
break;
case BPF_ALU|BPF_MUL|BPF_K:
op = "mul";
fmt = "#%d";
break;
case BPF_ALU|BPF_DIV|BPF_K:
op = "div";
fmt = "#%d";
break;
case BPF_ALU|BPF_AND|BPF_K:
op = "and";
fmt = "#0x%x";
break;
case BPF_ALU|BPF_OR|BPF_K:
op = "or";
fmt = "#0x%x";
break;
case BPF_ALU|BPF_LSH|BPF_K:
op = "lsh";
fmt = "#%d";
break;
case BPF_ALU|BPF_RSH|BPF_K:
op = "rsh";
fmt = "#%d";
break;
case BPF_ALU|BPF_NEG:
op = "neg";
fmt = "";
break;
case BPF_MISC|BPF_TAX:
op = "tax";
fmt = "";
break;
case BPF_MISC|BPF_TXA:
op = "txa";
fmt = "";
break;
} /* end of switch */
( void )sprintf( operand, fmt, v );
( void )sprintf( image, ( BPF_CLASS( p->code ) == BPF_JMP && BPF_OP( p->code ) != BPF_JA ) ?
"(%03d) %-8s %-16s jt %d\tjf %d" : "(%03d) %-8s %s",
n, op, operand, n + 1 + p->jt, n + 1 + p->jf );
return image;
} /* end of bpf_image */
static void outputBinary ( const u_char * byteArray, const size_t byteArrayLen )
{
u_long offset;
int i, j, k;
fprintf( stderr, "byteArray [ %lu bytes ] ----> \n", ( long unsigned int )byteArrayLen );
if ( byteArrayLen <= 0 )
{
return;
}
i = 0;
offset = 0;
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
{
fprintf( stderr, "%08X ", ( unsigned int )offset );
for ( j = 0; j < 16; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
fprintf( stderr, " " );
i -= 16;
for ( j = 0; j < 16; j++, i++ )
{
/* if ( isprint( (int)byteArray[i] ) ) */
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "\n" );
} /* end of for */
k = byteArrayLen - i;
if ( k <= 0 )
{
return;
}
fprintf( stderr, "%08X ", ( unsigned int )offset );
for ( j = 0 ; j < k; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
i -= k;
for ( j = 16 - k; j > 0; j-- )
{
fprintf( stderr, " " );
}
fprintf( stderr, " " );
for ( j = 0; j < k; j++, i++ )
{
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "\n" );
return;
} /* end of outputBinary */
static void pcap_callback ( u_char * none, const struct pcap_pkthdr * pcap_head, const u_char * packet )
{
outputBinary( ( u_char * )packet, ( size_t )( pcap_head->caplen ) );
return;
} /* end of pcap_callback */
static pcap_t * pcap_init ( char * dev, char * filter, int snaplen, int timeout, int dumplevel )
{
pcap_t * p = NULL;
char errbuf[ PCAP_ERRBUF_SIZE ];
struct bpf_program bpf;
bpf_u_int32 ip, mask;
if ( dev == NULL )
{
if ( ( dev = pcap_lookupdev( errbuf ) ) == NULL )
{
fprintf( stderr, "%s\n", errbuf );
exit( FAILURE );
}
}
fprintf( stderr, "[ device --> %s ]\n", dev );
/* 1表示進入混雜模式 */
if ( ( p = pcap_open_live( dev, snaplen, 1, timeout, errbuf ) ) == NULL )
{
fprintf( stderr, "%s\n", errbuf );
exit( FAILURE );
}
if ( pcap_lookupnet( dev, &ip, &mask, errbuf ) == -1 )
{
exit( FAILURE );
}
/* 1表示優化過濾規則 */
if ( pcap_compile( p, &bpf, filter, 1, mask ) < 0 )
{
/* for example, pcap_compile: unknown ip proto ... */
pcap_perror( p, "pcap_compile" );
exit( FAILURE );
}
if ( dumplevel >= 0 )
{
bpf_dump( &bpf, dumplevel );
exit( SUCCESS );
}
else if ( pcap_setfilter( p, &bpf ) == -1 )
{
exit( FAILURE );
}
return( p );
} /* end of pcap_init */
static void pcap_read ( pcap_t * p )
{
// static u_long count = 0;
while ( 1 )
{
pcap_dispatch( p, 1, pcap_callback, NULL );
// fprintf( stderr, "count = %lu\n", ( long unsigned int )count );
// count++;
} /* end of while */
return;
} /* end of pcap_read */
static void sig_end ( int signo )
{
fprintf( stderr, "\n\nsig_end = %d\n", signo );
exit( SUCCESS );
} /* end of sig_end */
Sigfunc * signal ( int signo, Sigfunc * func )
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc * func ) /* for our signal() function */
{
Sigfunc * sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
exit( FAILURE );
}
return( sigfunc );
} /* end of Signal */
static void terminate ( void )
{
if ( pcap_fd != NULL )
{
pcap_close( pcap_fd );
}
fprintf( stderr, "\n" );
return;
} /* end of terminate */
static void usage ( char * arg )
{
fprintf( stderr, " Usage: %s [-h] [-d dumplevel] [-i interface] [-s snaplen] [-t timeout]\n", arg );
exit( FAILURE );
} /* end of usage */
int main ( int argc, char * argv[] )
{
char * dev = NULL;
char filter[300] = ""; /* "ip proto \\tcp and dst 192.168.8.90 and tcp[13] & 2 = 2" */
int snaplen = LIBNET_ETH_H + LIBNET_IP_H + LIBNET_TCP_H;
int timeout = 0; /* 值爲0是否表示不設置讀超時 */
int dumplevel = -1;
int c, i;
opterr = 0; /* don't want getopt() writing to stderr */
while ( ( c = getopt( argc, argv, "d:hi:s:t:" ) ) != EOF )
{
switch ( c )
{
case 'd':
dumplevel = atoi( optarg );
break;
case 'i':
dev = optarg; /* 指定網絡接口設備 */
break;
case 's':
snaplen = atoi( optarg );
case 't':
timeout = atoi( optarg );
break;
case 'h':
case '?':
usage( argv[0] );
break;
} /* end of switch */
} /* end of while */
argc -= optind;
argv += optind;
if ( argc > 0 )
{
for ( i = 0; i < argc; i++ )
{
if ( ( strlen( filter ) + strlen( argv[i] ) ) > 256 )
{
fprintf( stderr, "Checking your filter.\n" );
return( FAILURE );
}
strcat( filter, argv[i] );
strcat( filter, " " );
}
}
fprintf( stderr, "[ filter --> %s ]\n", filter );
Atexit( terminate );
for ( i = 1; i < 9; i++ )
{
Signal( i, sig_end );
}
Signal( SIGTERM, sig_end );
pcap_fd = pcap_init( dev, filter, snaplen, timeout, dumplevel );
pcap_read( pcap_fd );
return( SUCCESS );
} /* end of main */
/*----------------------------------------------------------------------*/
--------------------------------------------------------------------------
Usage: ./pcap [-h] [-d dumplevel] [-i interface] [-s snaplen] [-t timeout]
libpcap的好處仍是不少,好比不須要爲解析過濾規則耗費精力。這個程序再次演示
了不少經典Unix編程技巧,好比getopt()、signal()、atexit(),回調函數部分沒有
作什麼實際工做,看你本身發揮了。順便提一句,即便是個小程序,也應該保持良好
的風格,在華中看到太多不負責任的提問中的垃圾代碼,實在是有辱C語言的傳奇。
<待續>