天天看點

網絡抓包工具wireshark and tcpdump 及其實作基于的libpcap

最近無意中看到部落格園中一篇 介紹wireshark的文章

,寫得不錯,它簡單清楚介紹了wireshark的使用

簡介

wireshark 以前叫做Ethereal, 在大學時候的網絡課程中就常看到它,它是世界上最流行的網絡抓包分析工具(world's most popular network protocol analyzer),它是基于圖形界面的,官網有介紹wireshark是1998年的一個項目衍生出來的,它有比較強大的 特性 ,可以用來分析數百種網絡協定。wireshark是在 GNU General Public Lisence

下釋出的。

以下是我自己使用wireshark的一個截圖, 照着上面的部落格中去抓取和檢視一次http請求前的tcp三次握手,下面詳細顯示的那行及其上下行就是我從本機浏覽器通路自己部落格園部落格的時候産生的tcp三次握手,可以看到是先握手成功才傳輸的http封包

網絡抓包工具wireshark and tcpdump 及其實作基于的libpcap
可以看到wireshark提供了一個非常友好和詳細的界面,可以看到一個包從鍊路層ethernet,網絡層ip,傳輸層tcp的包資訊。詳細的使用參加上面提到的部落格 tcpdump 其實和wireshark很像,差別隻是tcpdump是指令行界面的,wireshark和tcpdump都共同使用 libpcap作為其底層抓包的庫,  tcpdump最早是由  Van Jacobson 于1987年開發的,後來在1999年 http://www.tcpdump.org/

創立,這上面有關于tcpdump和pcap詳細的文檔。

tcpdump的輸出

tcpdump的輸出格式是和協定相關的,在協定棧的同一層會有不同的協定,比如在Transport Layer會有TCP,UDP協定。下圖摘自wikipedia

網絡抓包工具wireshark and tcpdump 及其實作基于的libpcap
對于tcpdump,-e表示輸出link level header,以下是一個例子   tcpdump -i eth2 -e -n 
網絡抓包工具wireshark and tcpdump 及其實作基于的libpcap
要以看到用了-e列印出了網卡MAC位址,鍊路層的協定(ethertype), 網絡層的協定(IPv4), 第一個length是表示鍊路層包的長度。然後打出的是裡面的tcp packet的資訊,發送端和接收端的ip位址,tcp中的Flags等,可以看到後三個包都是廣播的包。最後一個是一個 ARP

查詢的包

用tcpdump來看一下三次握手

用這樣的指令形式,sudo tcpdump -i eth2 -n '(tcp[13] & 2 == 2 or tcp[13] & 16 ==16)'  , 注意tcpdump是需要superviser的權限的, 輸出很多,從中提了一個三次握手

網絡抓包工具wireshark and tcpdump 及其實作基于的libpcap
上面的 -n 表示輸出結果全用數字表示而不用域名和端口代表的服務名,而後面那個是傳遞給tcpdump中的libpcap子產品的過濾expression, 關于這個過濾expression的文法,在man pcap-filter中有詳細的說明,上面這個表達式的意思是 , tcp[13]是包中的tcp子包的第13個位元組的值,位元組數是從第0個位元組開始的。而這個位元組正好是Flags這個位元組。 而上面Flags的字段中 S表示SYN,   .(一個點)表示ACK
網絡抓包工具wireshark and tcpdump 及其實作基于的libpcap

上圖是TCP的結構,從圖中可以看出,對于Flags這個位元組,2表示隻有SYN這一位為1, 16則是隻有ACK那一位為1, 而上面的tcp[13] & 2 == 2表示SYN這一位為1, 其他位不管。在tcp協定中,隻有建立連接配接的兩個端口發的第一個包才會設定SYN位,表示起始的sequence number。從上面可以看到起始的seq num是一個随機值。

libpcap , 以及基于libpcap實作一個簡單的抓包程式

安裝

libpcap

是一個c庫,用于網絡抓包和過濾,源于tcpdump項目,是從最開始tcpdump中剝離出來的一個庫, tcpdump中抓包,過濾,capture file的讀寫的代碼被提取出來成了libpcap。現在也是由tcpdump項目的開發者維護。

從tcpdump的官網上下載下傳下來後,包裡面有一個INSTALL.txt檔案,也就是三步的内容,./configure;   make;   make install;在這個過程中我安裝了flex(一個lexical analyzer generator)和yacc才成功了

寫的一個簡單程式

pcap實際上是從鍊路層抓包的,是以可以從中提出取出從鍊路層開始的包資訊,官網裡(

這裡 )有詳細的基于pcap的程式設計文檔。這個文檔中有提到基于libpcap程式設計的基本步驟, 如何應用過濾條件,如何拿到一個包後回調,以及在回調函數中(下面的call_back)怎樣提取包的詳細資訊,因為是得到這個鍊路層包的實際内容的(以字串的形式),是以是可以提取出從鍊路層開始,網絡程ip, 傳輸層如tcp的所有資訊的,  基本上不同的基于libpcap的軟體也就是這裡不同了,怎樣提取和展示包的資訊。基于這個文檔我寫了一個簡單的程式
網絡抓包工具wireshark and tcpdump 及其實作基于的libpcap

1 #include<stdio.h>
 2 #include<pcap.h>
 3 #include<string>
 4 
 5 using namespace std;
 6 
 7 static const unsigned int ETHER_ADDR_LEN = 6;    
 8 void call_back(u_char * args, const struct pcap_pkthdr * header, const u_char * packet);
 9 string generate_mac_address(char macChars[ETHER_ADDR_LEN]);
10 
11 int main(){
12     pcap_t * handle; // Sesion handle
13     char dev[] = "eth2";  //device to sniff on
14     char errbuf[PCAP_ERRBUF_SIZE]; // error string
15     char filter_exp[] = "";  //filter expression
16     bpf_u_int32 mask;  //The netmask of our sniffing device
17     bpf_u_int32 net;   //The IP of our sniffing device
18 
19     struct bpf_program fp;  //the compiled filter expression
20     
21     //查詢device的mask和ip    
22     if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1){
23         fprintf(stderr, "Can't get netmask for device %s\n", dev);
24         net = 0;
25         mask = 0;
26     }
27 
28     //obtaining packet capture descriptor
29     handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
30     if(handle == NULL){
31         fprintf(stderr, "Can't open device %s\n", dev);
32         return 2;
33     }
34     
35     // before apply filter exp, compile it 
36     if(pcap_compile(handle, &fp, filter_exp, 0, net) == -1){
37         fprintf(stderr, "can't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
38         return 2;
39     }    
40     //apply filter to this session
41     if(pcap_setfilter(handle, &fp) == -1){
42         fprintf(stderr, "can't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
43         return 2;
44     }
45 
46     // now the device is prepared to sniff under the filter condition
47     struct pcap_pkthdr header; // packet header struct
48     const u_char * packet;  // actual packet
49     
50     //5表示積累5個包pcap_loop才傳回,但每個包都會調一次call_back
51     while(!pcap_loop(handle, 5, call_back, NULL)){
52         printf("-------\n");  //每8行才會輸出一次這個
53     }
54     pcap_close(handle);
55 }
56 
57 struct sniff_ethernet {
58         char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */
59         char ether_shost[ETHER_ADDR_LEN]; /* Source host address */
60         u_short ether_type; /* IP? ARP? RARP? etc */
61 };
62 
63 // call_back function的統一原型
64 void call_back(u_char * args, const struct pcap_pkthdr * header, const u_char * packet){
65     static int count = 0;
66     struct sniff_ethernet * ethernet;  //ethernet header
67     ethernet = (struct sniff_ethernet*)(packet);
68     
69     //把6位元組的字元串轉換成mac位址的表示形式
70     std::string source_mac_address = generate_mac_address(ethernet->ether_shost);
71     std::string dst_mac_address = generate_mac_address(ethernet->ether_dhost);
72 
73     printf("wy: call_back called %d,  %s->%s,   packet length:%d\n", count++, source_mac_address.c_str(), dst_mac_address.c_str(),  header->len);
74 }
75 
76 //由位元組為機關字元串生成mac位址,16進制數的字串
77 string generate_mac_address(char macChars[ETHER_ADDR_LEN]){
78     string macAddr;
79     char temp[2];
80     for(int i = 0; i < ETHER_ADDR_LEN; i++){
81         //把一個位元組轉化成16進制表示形式
82         sprintf(temp, "%x", macChars[i]);
83         if(i != 0){
84             macAddr.append(":");
85         }
86         macAddr.append(temp, 2);
87     }
88     return macAddr;
89 }      
網絡抓包工具wireshark and tcpdump 及其實作基于的libpcap

這個程式上面有比較詳細的注釋,就是把通用的基于libpcap程式設計的流程走了一遍,最後列印出每個包鍊路層from和to的MAC位址,以及每個包的長度.

對于call_back的第二個參數 struct pcap_pkthdr, 這是pcap.h中定義的一個結構體,包含了這個包的一些資訊,捕獲時間,包長度, 可以看到程式中的包長度就是從中提取的,定義如下

1 struct pcap_pkthdr {
2         struct timeval ts; /* time stamp */
3         bpf_u_int32 caplen; /* length of portion present */
4         bpf_u_int32 len; /* length this packet (off wire) */
5     };      

上面的參數char * packet實際上是整個包在記憶體在的位址,為了從這當中提取出資訊,必須要自己定義相應的資料結構從這個純字元串中去提, 可以看到我照着文檔中去定義了一個 struct  sniff_ethernet,這個是需要自己定義的,pcap.h中是沒有的,然後可以看到我如何寫了一個函數 generate_mac_address把這個6位元組的字元串轉換成mac位址标準的表達形式。以下是程式運作輸出

網絡抓包工具wireshark and tcpdump 及其實作基于的libpcap

比較奇怪的是,我本機網卡 eth2的MAC位址是  bc:30:5b:a4:40:40, 但是程式的輸出是 ff:30:5b:ff:40:40

本來想選擇就用c來寫這個程式,但是寫到字串轉換那裡,對字元串的操作用c确實比較麻煩和難看,是以我還是用的c++, 我想到了陳皓的

一篇文章

,他感慨c的編譯器gcc已經開始用c++來實作了,他列舉出了c++比c優雅的地方,就我的感覺是很贊同的。

一個連結錯誤

很奇怪的是我編譯程式遇到了一個錯誤, g++ -o test_pcap test_pcap.cpp -L/usr/local/lib -lpcap

錯誤提示是 

/usr/local/lib/libpcap.so: undefined reference to `pcap_parse'

collect2: ld returned 1 exit status

google了一下,發現這個問題很普遍卻都沒給出一個明确的原因解釋,有篇文章提到把libpcap重裝也一遍,也就是cd到下載下傳下來的包目錄,make clean ; ./configure; make ; make install ;  我這樣試了之後竟然好了,沒有明白是為什麼,網上也沒有找到,在這裡做一個記錄吧

繼續閱讀