2021-05-12 14:32:11
在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: %sn", 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: %sn", 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: %sn", 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: %sn", filter_exp, pcap_geterr(handle));
return(2);
}
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %sn", 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: %sn", 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: %sn", errbuf);
return(2);
}
printf("Device: %sn", 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.html
相關文章