在Ubuntu 14.04 64位上使用libpcap進行抓包和解包

爲了開發須要,我決定使用最新libpcap源碼包安裝。在Unix環境下安裝libpcap庫,須要
c編譯器,flex,bison等,安裝Ubuntu系統時,沒有這些包。安裝flex須要m4編譯環境,不然會提示「GNU M4 is required」錯誤。


1.安裝系統依賴包
sudo apt-get install gcc libc6-dev
sudo apt-get install m4
sudo apt-get install flex bison


2.下載libpcap源碼並安裝
從官網http://www.tcpdump.org/下載最新的libpcap版本
cd /usr/local/src
wget http://www.tcpdump.org/release/libpcap-1.5.3.tar.gz
tar zxvf libpcap-1.5.3.tar.gz
cd libpcap-1.5.3
./configure
make
sudo make install


3.安裝開發須要用到的依賴庫
sudo apt-get install libpcap-dev


4.測試libpcap的小程序,命名爲pcap_demo.c,以檢驗環境是否配置正確
#include <pcap.h>
#include <stdio.h>


int main(int argc, char *argv[])
{
pcap_t *handle; /* Session handle */
char *dev; /* The device to sniff on */
char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */
struct bpf_program fp; /* The compiled filter */
char filter_exp[] = "port 80"; /* The filter expression */
bpf_u_int32 mask; /* Our netmask */
bpf_u_int32 net; /* Our IP */
struct pcap_pkthdr header; /* The header that pcap gives us */
const u_char *packet; /* The actual packet */


/* Define the device */
dev = pcap_lookupdev(errbuf);
if (dev == NULL) {
fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
return(2);
}
/* Find the properties for the device */
if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
fprintf(stderr, "Couldn't get netmask for device %s: %s\n", dev, errbuf);
net = 0;
mask = 0;
}
/* Open the session in promiscuous mode */
handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return(2);
}
/* Compile and apply the filter */
if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
return(2);
}
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
return(2);
}
/* Grab a packet */
packet = pcap_next(handle, &header);
/* Print its length */
printf("Jacked a packet with length of [%d]\n", header.len);
/* And close the session */
pcap_close(handle);
return(0);
}
開始編譯:
gcc -g pcap_demo.c -o pcap_demo -lpcap 
開始執行
./pcap_demo


5.注意的問題
5.1.注意使用root用戶來執行,或者對普通用戶使用sudo來提高權限
sudo pcap_demo 
5.2.對一些PCAP API函數要有全面地理解,並時刻更新文檔,好比pcap_loop這個函數,下面是官網的man page地址 
http://www.tcpdump.org/manpages/pcap.3pcap.html
5.3,對一些函數的理解:
pcap_loop和pcap_dispatch的區別,前者不會超時返回,而是會盡可能去讀取更多的包,直至發生一個錯誤才返回。正常返回的值是0,不然就是負值。我目前須要接二連三地抓包,因此應該使用pcap_loop。pcap_dispatch內部會調用pcap_loop。pcap_loop中的第四個參數在某些應用中頗有用,可是在一般狀況下被設爲NULL。這種定義頗有用,好比假如你想給pcap_loop的回調函數再額外傳遞一些參數,你可使用u_char類型的指針(字符串),傳人到回調函數中再作具體地處理。pcap_dispatch只會處理它收到的第一批包。
從pcap_loop返回後,咱們應該顯式調用pcap_close來關閉pcap以便釋放資源。爲了表示連續不間斷的抓包,在pcap_loop中要儘可能使用-1而不是0.關於其中回調函數的說明,裏面有3個參數。第一個參數user是pcap_loop中最後一個參數,它表示某個不過重要的信息,能夠忽略,第二個參數是包頭信息,含有包的時間戳和長度,第三個參數是包的數據,從鏈路層頭開始,它在該回調函數中並不會被釋放,可是當該回調返回時不包裝這些數據再合法,因此要再次使用它們,請事先複製出來。

下面再深刻研究一下pcap_loop這個回調函數的定義:
首先注意函數的返回值是void類型,這徹底符合邏輯,由於pcap_loop根本不知道如何處理回調函數的返回值,就是給他傳出返回值也沒有意義。而後就是回調函數的第一個參數對應pcap_loop中的最後一個參數,無論你給pcap_loop的最後一個參數傳遞什麼值,當回調函數被調用時,它都會做爲第一個參數傳遞給回調函數,第二個參數是pcap頭,它包含一些信息:抓包時間,包多大,等等,這個結構在pcap.h中定義,以下
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) */
};

抓包方式有兩種,一種是每次抓一個包,另外一種是循環抓包,分爲循環抓有限個數的包,或者是無限循環抓包。其中每次抓一個包使用pcap_next函數。pcap_next()表示只抓取一個數據包,它的第一個參數是session handle,第二個參數是包頭,返回值是包數據。函數原型以下
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
另外一種循環抓包的方法,就是使用pcap_loop和回調函數,代碼樣例參見我整理出的兩個例子。


5.4.網卡設備的設置有兩種方法,第一種方法是用戶直接指定網卡名稱(必需要真實可用),從命令行參數傳遞進去,好比
#include <stdio.h>
#include <pcap.h>


int main(int argc, char *argv[])
{
char *dev = argv[1];


printf("Device: %s\n", dev);
return(0);
}
另外一種方法,是讓pcap本身去探測網卡設備,若是出錯,會給出出錯信息,可是這種方法不少狀況下不靠譜,對此的改進是,讓pcap探測出全部的網卡設備,讓用戶區選擇使用那個網絡設備抓包,參見下面的代碼
#include <stdio.h>
#include <pcap.h>


int main(int argc, char *argv[])
{
char *dev, errbuf[PCAP_ERRBUF_SIZE];


dev = pcap_lookupdev(errbuf);
if (dev == NULL) {
fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
return(2);
}
printf("Device: %s\n", dev);
return(0);
}


5.5.對咱們抓到的數據包,它的結構,大致分爲Ethernet header,IP header,TCP header等等,分別對應14,20,20個字節,IP header至少20個字節,TCP頭也至少20個字節。
data=recv_data[42:]這樣作的緣由是,recv_data 抓到的都是raw packet, tcp/ip是分了5層,一個udp包,會帶有 14Bytes的Ethenet_II Frame的頭,而後是20個字節的ip包頭,而udp包頭有8個字節,因此偏移量42以後的纔是udp的實際內容
tcp包的包頭大小爲20Bytes,因此其實際內容在偏移量54以後


5.6.對數據包的解包和組包,須要用到Ethernet header,IP header,TCP header,UDP header等經常使用數據結構的定義,咱們並不須要從新定義這些頭,直接引用相關的頭文件定義就能夠了。
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>

5.6.對咱們目前的程序,一般須要從命令行傳人以下參數信息:
wlan0 待抓包的網卡名稱
tcp port 80 過濾表達式
10000 or -1 抓包模式,是抓指定個數的包(這裏是10000個包),仍是無限抓包下去
固然,對這些參數,你也能夠寫死在程序裏。

參考文獻
[1].http://www.tcpdump.org/pcap.htmlhtml

相關文章
相關標籤/搜索