初識函數庫libpcap

因爲工做上的須要,最近簡單學習了抓包函數庫libpcap,順便記下筆記,方便之後查看linux

1、libpcap簡介
    libpcap(Packet Capture Library),即數據包捕獲函數庫,是Unix/Linux平臺下的網絡數據包捕獲函數庫。它是一個獨立於系統的用戶層包捕獲的API接口,爲底層網絡監測提供了一個可移植的框架.
Libpcap能夠在絕大多數類unix平臺下工做,libpcap庫安裝也很簡單,Libpcap 軟件包可從 http://www.tcpdump.org/ 下載,而後依此執行下列三條命令便可安裝
./configure
make
make install
2、pcap基本工做流程
(1)肯定將要嗅探的接口,在linux下是相似eth0的東西。在BSD下是相似xll的東西。能夠在一個字符串中聲明設備,也可讓pcap提供備選接口(咱們想要嗅探的接口)的名字。
(2)初始化pcap,此時才真正告訴pcap咱們要嗅探的具體接口,只要咱們願意,咱們能夠嗅探多個接口。可是如何區分多個接口呢,使用文件句柄。就像讀寫文件時使用文件句柄同樣。咱們必須給嗅探任務命名,以致於區分不一樣的嗅探任務。
(3)指定過濾規則,當咱們只想嗅探特殊的流量時(例如,僅僅嗅探TCP/IP包、僅僅嗅探通過端口80的包,等等)咱們必須設定一個規則集,「編譯」並應用它。這是一個三相的而且緊密聯繫的過程,規則集存儲與字符串中,在「編譯」以後會轉換成pcap能夠讀取的格式。「編譯過程」其實是調用自定義的函數完成的,不涉及外部的函數。而後咱們能夠告訴pcap在咱們想要過濾的任何任務上實施。
(4)抓包,最後,告訴pcap進入主要的執行循環中,在此階段,在接收到任何咱們想要的包以前pcap將一直循環等待。在每次抓取到一個新的數據包時,它將調用另外一個自定義的函數,咱們能夠在這個函數中肆意妄爲,例如,解析數據包並顯示數據內容、保存到文件或者什麼都不作等等。
 當嗅探完美任務完成時,記得關掉任務。正則表達式

下面是pcap工做流程圖(摘自官網)express


下面咱們看一下具體的步驟實施:
(1)肯定咱們將要嗅探的接口
這一步操做咱們能夠手動指定接口或者調用pcap庫提供的接口來查找網絡設備
手動指定:網絡

 1 #include <stdio.h>  
 2 #include <string.h>
 3 #include <stdlib.h>
 4   
 5 int main(int argc, char **argv)  
 6 {  
 7     char *dev = argv[1];
 8 
 9     printf("Device: %s\n", dev);
10  
11   return 0;  
12 }

使用pcap API查找網絡設備session

pcap_lookupdev()函數用於查找網絡設備app

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <pcap.h>
 5 
 6 int main(int argc, char *argv[])
 7 {
 8     char *dev, errbuf[PCAP_ERRBUF_SIZE];
 9 
10     dev = pcap_lookupdev(errbuf);
11     if (dev == NULL) {
12         fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
13         return(2);
14     }   
15 
16     printf("Device: %s\n", dev);
17     return(0);
18 }

編譯時須要鏈接pcap庫 -lpcap
(2)打開嗅探設備框架

pcap_open_live()函數用於打開網絡設備,而且返回用於捕獲網絡數據包的數據包捕獲描述字。對於此網絡設備的操做都要基於此網絡設備描述字。tcp

函數原型以下:函數

1     pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *errbuf);
2     
3 /*函數說明:該函數用於打開一個嗅探設備
4     參數:device 須要打開的設備
5           snaplen int型,表示pcap能夠捕獲的最大字節數(最大爲65535)
6           promisc 是否開啓混雜模式(1打開,0關閉),設置開啓混雜模式,須要對應的網卡也開啓混雜模式
7           to_ms 是讀取時間溢出,單位爲毫秒(ms), 0表示沒有時間溢出
8           errbuf 保存錯誤的返回值
9 */    

下面是具體實現:oop

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <pcap.h>
 5 
 6 int main(int argc, char *argv[])
 7 {
 8     char *dev, errbuf[PCAP_ERRBUF_SIZE];
 9     pcap_t *handle;
10 
11     dev = pcap_lookupdev(errbuf);
12     if (dev == NULL) {
13         fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
14         return(2);
15     }   
16     printf("Device: %s\n", dev);
17     
18     handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
19     if (handle == NULL) {
20         fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
21         return(2);
22     }   
23     printf("Open Device success!\n");   
24 
25     return(0);
26 }

 (3)過濾指定流量
      不少時候,我只須要咱們指定的流量,好比咱們須要劫持http請求(80端口),劫持DNS服務(53端口),所以,咱們大多數時候都不會盲目的抓取所有的報文。
    相關過濾函數pcap_compile()and pcap_setfilter(),當咱們調用pcap_open_live()後,咱們會獲得一個創建的嗅探會話,此時咱們就能夠開始過濾咱們想要流量了;
    過濾器表達式是基於正則表達式來編寫的,官網tcpdump有詳細的規則說明,在使用過濾器以前咱們必須」編譯「.

函數原型以下:

1     int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
2 /*函數說明:將str參數指定的字符串編譯到過濾程序中。
3     參數:  p是嗅探器回話句柄
4             fp是一個bpf_program結構的指針,在pcap_compile()函數中被賦值。o
5             ptimize參數控制結果代碼的優化。
6             netmask參數指定本地網絡的網絡掩碼。
7 */

編譯完過濾表達式後,咱們就能夠應用它了,下面是int pcap_setfilter(),具體用法看man手冊:

1     int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
2 //第一個參數嗅探器回話句柄,第二參數是存儲過濾器編譯版本的結構體指針(跟pcap_compile 一個參數同樣)

下面是簡單實例:

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <pcap.h>
 5 
 6 int main(int argc, char *argv[])
 7 {
 8     char *dev, errbuf[PCAP_ERRBUF_SIZE];
 9     struct bpf_program fp;      /* The compiled filter expression */
10     char filter_exp[] = "port 53";  /* The filter expression (filter 53 port)*/
11     pcap_t *handle;
12     bpf_u_int32 mask;       /* The netmask of our sniffing device */
13     bpf_u_int32 net;        /* The IP of our sniffing device */
14 
15     dev = pcap_lookupdev(errbuf);
16     if (dev == NULL) {
17         fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
18         return(2);
19     }   
20     printf("Device: %s\n", dev);
21 
22     /*get network mask*/    
23     if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
24         fprintf(stderr, "Can't get netmask for device %s\n", dev);
25         net = 0;
26         mask = 0;
27     }   
28     /*Open the session in promiscuous mode*/
29     handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
30     if (handle == NULL) {
31         fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
32         return(2);
33     }   
34     /* Compile and apply the filter */
35     if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
36         fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
37         return(2);
38     }   
39     if (pcap_setfilter(handle, &fp) == -1) {
40         fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
41         return(2);
42     }   
43 
44     return(0);
45 }

以上代碼中的pcap_lookupnet()函數得到指定網絡設備的網絡號和掩碼。函數原型以下:

1 int pcap_lookupnet(const char *device, bpf_u_int32 *netp,
2                bpf_u_int32 *maskp, char *errbuf);
3 /*    參數:device 指定的嗅探設備名稱
4           netp 指定設備的網絡號
5           maskp 掩碼
6           errbuf 保存錯誤信息
7 */

下面是具體實例:

 1 #include <stdio.h>  
 2 #include <string.h>
 3 #include <netinet/in.h>
 4 #include <arpa/inet.h>
 5 #include <pcap.h> 
 6 
 7 #define DEVICE "enp0s3" 
 8  
 9 int main()  
10 {  
11     char errBuf[PCAP_ERRBUF_SIZE];
12     struct pcap_pkthdr packet;  
13     pcap_t *dev;
14     bpf_u_int32 netp, maskp;
15     char *net, *mask;
16     struct in_addr addr;
17     int ret;
18 
19     if(pcap_lookupnet(DEVICE, &netp, &maskp, errBuf)) {
20         printf("get net failure\n");
21         return -1; 
22     }   
23     addr.s_addr = netp;
24     net = inet_ntoa(addr);
25     printf("network: %s\n", net);
26     
27     addr.s_addr = maskp;
28     mask = inet_ntoa(addr);
29     printf("mask: %s\n", mask);
30 
31     return 0;
32 } 
33 //運行結果
34 [root@localhost pacp_1st]# ./pacp 
35 network: 192.168.16.0
36 mask: 255.255.255.0

(4)進行抓包處理  

  經過以上內容,咱們已經知道了如何指定獲取以及初始化一個嗅探器設備,如何編譯及使用過濾器;下面咱們就開始進行抓包,抓包程序有抓一次包(pcap_next())和循環一直抓包幾個函數;
下面咱們咱們先用pcap_next()進行一次抓包
函數原型:

 1     const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h);
 2     
 3 /*參數:p是嗅探器會話句柄
 4         h是一個指向存儲數據包概略信息結構體的指針
 5     */
 6 struct pcap_pkthdr {
 7     struct timeval ts;  /* time stamp */ 
 8     bpf_u_int32 caplen; /* length of portion present */
 9     bpf_u_int32 len;    /* length this packet (off wire) */
10 };
11 //ts——時間戳
12 //caplen——真正實際捕獲的包的長度
13 //len——這個包的長度
14 
15 由於在某些狀況下你不能保證捕獲的包是完整的,例如一個包長1480,可是你捕獲到1000的時候,
16 可能由於某些緣由就停止捕獲了,因此caplen是記錄實際捕獲的包長,也就是1000,而len就是1480。

下面是使用pcap_next()抓包程序

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <pcap.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 53";  /* 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, 100, 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 header info */
    printf("Packet length: %d\n", header.len);  
    printf("Number of bytes: %ud\n", header.caplen);  
    printf("Recieved time: %s\n", ctime((const time_t *)&header.ts.tv_sec)); 
    /* And close the session */
    pcap_close(handle);

    return(0);
}
//運行結果
[root@localhost pacp_5th]# ./pacp     
Packet length: 32603
Number of bytes: 3372236960d
Recieved time: Sat Aug 16 07:45:20 4461732

     上面的代碼在promisc模式下嗅探全部由pcap_lookupdev()返回的設備。它發現第一個通過端口53(DNS)的數據包並打印包的相關信息。
在大多數狀況下咱們不多的嗅探器使用pcap_next(),更多的是使用pcap_loop()或者pcap_dispatch()(pcap_dispatch()內部調用pcap_next())
pcap_loop()及pcap_dispatch()的具體使用在下篇博客中介紹

參考:http://www.tcpdump.org/ libpcap 官方有不少關於libpcap的說明文檔,講的很是詳細;

相關文章
相關標籤/搜索