歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 在Ubuntu 14.04 64位上使用libpcap進行抓包和解包

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

日期:2017/2/28 13:56:03   编辑:Linux教程

為了開發需要,我決定使用最新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.html

更多Ubuntu相關信息見Ubuntu 專題頁面 http://www.linuxidc.com/topicnews.aspx?tid=2

Copyright © Linux教程網 All Rights Reserved