循序漸進學WinPcap

去年開始學習winpcap,當時翻譯了一點,現在打算把這個工作完成了。我的水平比較差,翻譯的很不到位,不過對於初次接觸winpcap的人應該還是有點幫助吧。不過不知道我這樣來翻譯是不是侵犯了人家的版權?如果有這個嫌疑,請大家告訴我,我對這方面的法律不是很瞭解。建議對這方面有興趣的人還是去http://www.winpcap.org 下載文檔和資料看。

下面開始吧:

WinPcap tutorial: a step by step guide to using WinPcap
詳細說明
 這部分展示了怎樣使用WinPcap API。這個教程通過一系列的課程,從基本的函數(取得網卡列表,開始抓包,等等)到最高級的應用(處理數據包包發送隊列和統計網絡流量),一步一步地教會讀者如何用WinPcap來編程。
 這裏提供了幾個雖然簡單但卻完整的程序段作爲參考:所有的源代碼都有其餘部分的鏈接,只需要點擊一下函數和數據結構就可以跳轉到相應的文檔。
 這些例子都是使用c語言寫的,所以在讀本教程前要了解一些基本的c語言的知識。而且,這是一個關於處理原始網絡包的庫的教程,所以假定讀者具有良好的網絡和網絡協議方面的知識。

WinPcap tutorial: a step by step guide to using WinPcap(1)
獲取網絡設備列表
 基本上所有基於Winpcap的應用程序所做的第一件事情都是獲取一個已經綁定的網卡列表。爲此,libcap和winpcap都提供了pcap_findalldevs_ex()函數:這個函數返回一個指向pcap_if結構的鏈表,其中的每一項都包含了一個已經綁定的適配器的全部信息。其中name和description這兩項分別包含了相應設備的名稱和描述。
 下面的代碼取得適配器列表並在屏幕上顯示出來,如果適配器沒有被發現就把顯示錯誤。
#i nclude "pcap.h"

main()
{
    pcap_if_t *alldevs;
    pcap_if_t *d;
    int i=0;
    char errbuf[PCAP_ERRBUF_SIZE];
   
    /* 取得本機的網絡設備列表 */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL /* 這個參數在這裏不需要 */, &alldevs, errbuf) == -1)
    {
        fprintf(stderr,"Error in pcap_findalldevs_ex: %s/n", errbuf);
        exit(1);
    }
   
    /* 顯示列表 */
    for(d= alldevs; d != NULL; d= d->next)
    {
        printf("%d. %s", ++i, d->name);
        if (d->description)
            printf(" (%s)/n", d->description);
        else
            printf(" (No description available)/n");
    }
   
    if (i == 0)
    {
        printf("/nNo interfaces found! Make sure WinPcap is installed./n");
        return;
    }

    /* We don't need any more the device list. Free it */
    pcap_freealldevs(alldevs);
}
關於這段代碼的說明。
 首先,就象其他的libpcap函數,pcap_findalldevs_ex()有一個errbuf參數。如果發生錯誤,libcap就把一個錯誤說明放到這個參數指向的字符串中。
 其次,並不是所有的操作系統都支持libpcap提供的網絡接口描述,因此如果我們想寫一個可移植的程序,我們必須考慮description爲null的情況:這個時候我們就輸出字符串"No description available" 。
 最後要提醒一下:一旦我們完成了這些動作,就應該釋放用pcap_freealldevs()列表。
讓我們編譯並運行這段簡單的代碼。在unix或者cygwin中編譯的話,打入下面的命令:
gcc -o testaprog testprog.c -lpcap
 在windows中,你需要創建一個project,照着手冊中的Using WinPcap in your programs 那一章做就可以了。但是,我們建議你使用the WinPcap developer's pack(可以在http://www.winpcap.org 下載),因爲這個開發包提供了許多教程中使用的代碼示例,這些示例都已經配置好了,其中包含了編譯執行例子所需要的include文件和lib文件。
 編譯好了程序後,在我的winxp工作站上運行的結果:
1. {4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS) Ethernet Adapter)
2. {5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)
 就象你看到的一樣,在windows系統中網絡適配器的名稱(在打開網絡適配器時將被傳遞給libcap)根本沒有辦法理解,所以附加說明可能是非常有幫助的。

WinPcap tutorial: a step by step guide to using WinPcap(2)
Obtaining advanced information about installed devices
 課程1(Obtaining the device list)說明了怎樣得到可用適配器的基本信息(比如設備名和說明)。實際上,winpcap也提供其他的高級信息。每個由pcap_findalldevs_ex()返回的pcap_if 結構體都包含了一個pcap_addr結構列表,裏面包含的內容有:
一個接口的地址列表。
一個子網掩碼列表(每一個都與地址列表中的條目一一對應)。
一個廣播地址列表(每一個都與地址列表中的條目一一對應)。
一個目的地址列表(每一個都與地址列表中的條目一一對應)。
 除此之外,pcap_findalldevs_ex() 也能返回遠程的適配器和任給的本地文件夾的pcap文件列表。
 下面的例子提供了一個ifprint() 函數來打印出一個pcap_if 結構中的所有內容。程序對每一個pcap_findalldevs_ex()返回的條目都調用一次這個函數。

/*
 * Copyright (c) 1999 - 2003
 * NetGroup, Politecnico di Torino (Italy)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Politecnico di Torino nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */


#i nclude "pcap.h"

#ifndef WIN32
    #i nclude <sys/socket.h>
    #i nclude <netinet/in.h>
#else
    #i nclude <winsock.h>
#endif


// Function prototypes
void ifprint(pcap_if_t *d);
char *iptos(u_long in);
char* ip6tos(struct sockaddr *sockaddr, char *address, int addrlen);


int main()
{
  pcap_if_t *alldevs;
  pcap_if_t *d;
  char errbuf[PCAP_ERRBUF_SIZE+1];
  char source[PCAP_ERRBUF_SIZE+1];

  printf("Enter the device you want to list:/n"
            "rpcap://              ==> lists interfaces in the local machine/n"
            "rpcap://hostname:port ==> lists interfaces in a remote machine/n"
            "                          (rpcapd daemon must be up and running/n"
            "                           and it must accept 'null' authentication)/n"
            "file://foldername     ==> lists all pcap files in the give folder/n/n"
            "Enter your choice: ");

  fgets(source, PCAP_ERRBUF_SIZE, stdin);
  source[PCAP_ERRBUF_SIZE] = '/0';

  /* Retrieve the interfaces list */
  if (pcap_findalldevs_ex(source, NULL, &alldevs, errbuf) == -1)
  {
    fprintf(stderr,"Error in pcap_findalldevs: %s/n",errbuf);
    exit(1);
  }

  /* Scan the list printing every entry */
  for(d=alldevs;d;d=d->next)
  {
    ifprint(d);
  }

  pcap_freealldevs(alldevs);

  return 1;
}

 

/* Print all the available information on the given interface */
void ifprint(pcap_if_t *d)
{
  pcap_addr_t *a;
  char ip6str[128];

  /* Name */
  printf("%s/n",d->name);

  /* Description */
  if (d->description)
    printf("/tDescription: %s/n",d->description);

  /* Loopback Address*/
  printf("/tLoopback: %s/n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");

  /* IP addresses */
  for(a=d->addresses;a;a=a->next) {
    printf("/tAddress Family: #%d/n",a->addr->sa_family);
 
    switch(a->addr->sa_family)
    {
      case AF_INET:
        printf("/tAddress Family Name: AF_INET/n");
        if (a->addr)
          printf("/tAddress: %s/n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
        if (a->netmask)
          printf("/tNetmask: %s/n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
        if (a->broadaddr)
          printf("/tBroadcast Address: %s/n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
        if (a->dstaddr)
          printf("/tDestination Address: %s/n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
        break;

      case AF_INET6:
        printf("/tAddress Family Name: AF_INET6/n");
        if (a->addr)
          printf("/tAddress: %s/n", ip6tos(a->addr, ip6str, sizeof(ip6str)));
       break;

      default:
        printf("/tAddress Family Name: Unknown/n");
        break;
    }
  }
  printf("/n");
}

 

/* From tcptraceroute, convert a numeric IP address to a string */
#define IPTOSBUFFERS    12
char *iptos(u_long in)
{
    static char output[IPTOSBUFFERS][3*4+3+1];
    static short which;
    u_char *p;

    p = (u_char *)&in;
    which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
    sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
    return output[which];
}

char* ip6tos(struct sockaddr *sockaddr, char *address, int addrlen)
{
    socklen_t sockaddrlen;

    #ifdef WIN32
    sockaddrlen = sizeof(struct sockaddr_in6);
    #else
    sockaddrlen = sizeof(struct sockaddr_storage);
    #endif


    if(getnameinfo(sockaddr,
        sockaddrlen,
        address,
        addrlen,
        NULL,
        0,
        NI_NUMERICHOST) != 0) address = NULL;

    return address;
}

WinPcap tutorial: a step by step guide to using WinPcap(3)
Opening an adapter and capturing the packets
打開一個適配器開始抓取
 現在我們已經知道了怎樣去獲取一個適配器並使用它,讓我們開始真正的工作-----開始抓取網絡數據包吧。在這一課中我們將寫一個程序,這個程序將在我們選擇的適配器上監聽,並抓取通過這個適配器上的每一個數據包,打印其中的一些信息。
 我們主要使用的函數是pcap_open(),這個函數的功能是打開一個抓取設備。在這裏有必要對其中的幾個參數snaplen, flags and to_ms作一下說明。
 snaplen指定了我們所要抓取的包的長度(譯者:也就是我們想抓多長就設置多長)。在一些操作系統(如xBSD和Win32)中,底層驅動可以通過配置只抓取數據包的開始部分:這樣就減少了拷貝給應用程序的數據量,因此可以提高抓取效率。在這個例子裏我們使用65536這個比我們所能遇到的最大的MTU還大的數字。這樣我們就能確保我們的程序可以抓到整個數據包。
 flags:最重要的標誌是一個指示適配器是否工作在混雜模式下的。在正常狀況下,一個適配器僅僅抓取網絡中目的地是它自己的數據包;因此其他主機交換的數據包都被忽略。相反,當適配器處在混雜模式下的時候它就會抓取所有的數據包而不管是不是發給它的。這就意味着在共享媒體(如非交換的以太網)上,WinPcap將能夠抓取其他主機的數據包。混雜模式是大部分抓取程序的默認模式,所以在下面的例子中我們就開啓它。
 to_ms以豪秒爲單位指定了讀取操作的超時界限。在適配器上一個讀取操作(比如,pcap_dispatch() 或者 pcap_next_ex())將總是在to_ms豪秒後返回,即使網絡中沒有數據包可供抓取。如果適配器工作在統計模式(如果對此不瞭解,請看課程9),to_ms還定義了統計報告之間的間隔。把tm_ms設置爲0意味着沒有時間限制,如果沒有數據包到達適配器,讀取操作將永遠不會返回。反過來,把tm_ms設置爲-1將使讀取操作總是立即返回。
#include "pcap.h"

/* 數據包處理程序,回調函數 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);

main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
   
    /* Retrieve the device list on the local machine */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
    {
        fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
        exit(1);
    }
   
    /* Print the list */
    for(d=alldevs; d; d=d->next)
    {
        printf("%d. %s", ++i, d->name);
        if (d->description)
            printf(" (%s)/n", d->description);
        else
            printf(" (No description available)/n");
    }
   
    if(i==0)
    {
        printf("/nNo interfaces found! Make sure WinPcap is installed./n");
        return -1;
    }
   
    printf("Enter the interface number (1-%d):",i);
    scanf("%d", &inum);
   
    if(inum < 1 || inum > i)
    {
        printf("/nInterface number out of range./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
   
    /* Jump to the selected adapter */
    for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
   
    /* Open the device */
    if ( (adhandle= pcap_open(d->name,          // name of the device
                              65536,            // portion of the packet to capture
                                                // 65536 guarantees that the whole packet will be captured on all the link layers
                              PCAP_OPENFLAG_PROMISCUOUS,    // promiscuous mode
                              1000,             // read timeout
                              NULL,             // authentication on the remote machine
                              errbuf            // error buffer
                              ) ) == NULL)
    {
        fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n", d->name);
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
   
    printf("/nlistening on %s.../n", d->description);
   
    /* At this point, we don't need any more the device list. Free it */
    pcap_freealldevs(alldevs);
   
    /* start the capture */
    pcap_loop(adhandle, 0, packet_handler, NULL);
   
    return 0;
}


/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
    struct tm *ltime;
    char timestr[16];
   
    /* convert the timestamp to readable format */
    ltime=localtime(&header->ts.tv_sec);
    strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
   
    printf("%s,%.6d len:%d/n", timestr, header->ts.tv_usec, header->len);
   
}

 一旦打開了適配器,就由pcap_dispatch() 或者pcap_loop()開始抓取。這兩個函數都非常慢,所不同的是pcap_ dispatch()一旦超時就可以返回(儘管不能保證)而pcap_loop() 會一直等到cnt包被抓到纔會返回,所以這個函數在沒有數據包的網絡中會阻塞任意的時間。在這個例子中pcap_loop()就足夠了,而pcap_dispatch() 則可以在一個更復雜的程序中使用。
 這兩個函數都有一個回調函數作爲參數,packet_handler,這個參數指定的函數將收到數據包。這個函數在每一個新的數據包從網絡中到達時都會被libpcap調用,並且會收到一個反映pcap_loop() 和 pcap_dispatch()函數的生成狀態,和一個結構體header。這個header中帶有一些數據包中的信息,比如時間戳和長度、包括所有協議頭的實際數據包。注意結構體中是沒有CRC的,因爲在數據確認後已經被適配器去掉了。也要注意大部分適配器丟棄了CRC錯誤的數據包,因此Winpcap不能抓取這些包。
 上面的例子從pcap_pkthdr中提取每個數據包的時間戳和長度並顯示它們。
 請注意使用pcap_loop()可能有一個缺點,就是使用這個函數時包處理函數要被包抓取驅動程序來調用;因此應用程序不能直接控制它。另一種方法(並且更容易理解)是使用函數pcap_next_ex(),這個函數我們將在下一個例子中使用。

4.
Capturing the packets without the callback
 這節課程中的例子程序完成的功能和上節課的一樣,但是使用的是pcap_next_ex()而不是pcap_loop().
 基於回調捕獲機制的 pcap_loop()是非常優雅的,在很多情況下都是一個不錯的選擇。不過,有時候處理一個回調函數顯得不太現實 --- 通常這會使程序更加複雜,在使用多線程或者c++類的時候尤其如此。
 在這種情況下,可以直接調用 pcap_next_ex() 來返回一個數據包 -- 這樣程序員可以在僅僅想使用它們的時候再處理 pcap_next_ex() 返回的數據包。
 這個函數的參數和回調函數 pcap_loop() 的一樣 -- 由一個網絡適配器描述符作爲入口參數和兩個指針作爲出口參數,這兩個指針將在函數中被初始化,然後再返回給用戶(一個指向pcap_pkthdr 結構,另一個指向一個用作數據緩衝區的內存區域)。
 在下面的程序中,我們繼續使用上一節課中的例子的數據處理部分的代碼,把這些代碼拷貝到main()函數中pcap_next_ex()的後面。
#include "pcap.h"


main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
int res;
char errbuf[PCAP_ERRBUF_SIZE];
struct tm *ltime;
char timestr[16];
struct pcap_pkthdr *header;
u_char *pkt_data;
   
   
    /* Retrieve the device list on the local machine */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
    {
        fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
        exit(1);
    }
   
    /* Print the list */
    for(d=alldevs; d; d=d->next)
    {
        printf("%d. %s", ++i, d->name);
        if (d->description)
            printf(" (%s)/n", d->description);
        else
            printf(" (No description available)/n");
    }
   
    if(i==0)
    {
        printf("/nNo interfaces found! Make sure WinPcap is installed./n");
        return -1;
    }
   
    printf("Enter the interface number (1-%d):",i);
    scanf("%d", &inum);
   
    if(inum < 1 || inum > i)
    {
        printf("/nInterface number out of range./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
   
    /* Jump to the selected adapter */
    for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
   
    /* Open the device */
    if ( (adhandle= pcap_open(d->name,          // name of the device
                              65536,            // portion of the packet to capture.
                                                // 65536 guarantees that the whole packet will be captured on all the link layers
                              PCAP_OPENFLAG_PROMISCUOUS,    // promiscuous mode
                              1000,             // read timeout
                              NULL,             // authentication on the remote machine
                              errbuf            // error buffer
                              ) ) == NULL)
    {
        fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n", d->name);
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
   
    printf("/nlistening on %s.../n", d->description);
   
    /* At this point, we don't need any more the device list. Free it */
    pcap_freealldevs(alldevs);
   
    /* Retrieve the packets */
    while((res = pcap_next_ex( adhandle, &header, &pkt_data)) >= 0){
       
        if(res == 0)
            /* Timeout elapsed */
            continue;
       
        /* convert the timestamp to readable format */
        ltime=localtime(&header->ts.tv_sec);
        strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
       
        printf("%s,%.6d len:%d/n", timestr, header->ts.tv_usec, header->len);
    }
   
    if(res == -1){
        printf("Error reading the packets: %s/n", pcap_geterr(adhandle));
        return -1;
    }
   
    return 0;
 爲什麼我們使用 pcap_next_ex() 而不是 pcap_next()?因爲 pcap_next() 有一些缺陷。首先,它的效率不高,因爲它雖然隱藏了回調模式但是仍然依賴於 pcap_dispatch()。其次,它不能檢測EOF(譯者注:end of file,意思是文件結束標誌),所以當我們從一個文件中收集包的時候不是很有用(譯者注:winpcap可以把捕獲的數據包以很高的效率存在文件中,留待以後分析,這一點以後的課程中也會講到)。
 也要注意 pcap_next_ex() 對於成功調用、超時、錯誤和EOF狀態會返回不同的值。

5.Filtering the traffic
 WinPcap提供的最強大的特性之一就是過濾引擎。它是被集成到了winpcap的捕獲機制中的,提供了一種非常高效的方法來獲取部分網絡數據。被用來過濾數據包的函數是 pcap_compile() 和 pcap_setfilter()。
 pcap_compile() 接受一個包含布爾表達式的字符串,生成可以被捕獲包驅動中的過濾引擎解釋的代碼。布爾表達式的語法在這個文檔的Filtering expression syntax 那一節(譯者注:其實和tcpdump的一樣,如果瞭解tcpdump,可以直接按照tcpdump的語法來寫)。
 pcap_setfilter() 綁定一個過濾器到一個在覈心驅動中的捕獲進程中。一旦 pcap_setfilter() 被調用,這個過濾器就會對網絡來的所有數據包進行過濾,所有符合條件的數據包(按照布爾表達式來計算出結果是真的數據包)都會被拷貝給進行捕獲的應用程序。
 下面的代碼說明了怎樣編譯和設置一個過濾器。注意我們必須得到說明適配器的 pcap_if 結構中的子網掩碼,因爲一些被 pcap_compile() 生成的過濾器需要它。這個過濾器中傳遞給 pcap_compile() 的字符串是 "ip and tcp",意思是“僅僅把IPv4 and TCP 數據包保存下來並交付給應用程序”。
if (d->addresses != NULL)
        /* Retrieve the mask of the first address of the interface */
        netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
    else
        /* If the interface is without an address we suppose to be in a C class network */
        netmask=0xffffff;


    //compile the filter
    if (pcap_compile(adhandle, &fcode, "ip and tcp", 1, netmask) < 0)
    {
        fprintf(stderr,"/nUnable to compile the packet filter. Check the syntax./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
   
    //set the filter
    if (pcap_setfilter(adhandle, &fcode) < 0)
    {
        fprintf(stderr,"/nError setting the filter./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
 如果你想看一些在這節課中講述的使用過濾功能的代碼,請看下節課中的例子,Interpreting the packets.

6.Interpreting the packets.
 現在我們已經能夠捕獲並且過濾網絡數據包,下面我們就把我們的知識運用到一個簡單的“真實的”應用程序中去。
 在這節課中我們將從前面的課程中拷貝代碼並用它們來構造出一個更有用途的程序。這個程序主要的目的就是說明怎樣分析和解釋我們已經捕獲的數據包的協議結構。最終的應用程序,叫做UDPdump,會打印出一個在我們的網絡中的UDP數據包的概要。
 在開始階段我們選擇分析並顯示UDP協議,因爲UDP協議比其他的協議比如TCP協議更容易理解,從而非常適合作爲初始階段的例子。還是讓我們開始看代碼吧:
/*
 * Copyright (c) 1999 - 2003
 * NetGroup, Politecnico di Torino (Italy)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Politecnico di Torino nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */


#include "pcap.h"

/* 4 bytes IP address */
typedef struct ip_address{
    u_char byte1;
    u_char byte2;
    u_char byte3;
    u_char byte4;
}ip_address;

/* IPv4 header */
typedef struct ip_header{
    u_char  ver_ihl;        // Version (4 bits) + Internet header length (4 bits)
    u_char  tos;            // Type of service
    u_short tlen;           // Total length
    u_short identification; // Identification
    u_short flags_fo;       // Flags (3 bits) + Fragment offset (13 bits)
    u_char  ttl;            // Time to live
    u_char  proto;          // Protocol
    u_short crc;            // Header checksum
    ip_address  saddr;      // Source address
    ip_address  daddr;      // Destination address
    u_int   op_pad;         // Option + Padding
}ip_header;

/* UDP header*/
typedef struct udp_header{
    u_short sport;          // Source port
    u_short dport;          // Destination port
    u_short len;            // Datagram length
    u_short crc;            // Checksum
}udp_header;

/* prototype of the packet handler */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);


main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
u_int netmask;
char packet_filter[] = "ip and udp";
struct bpf_program fcode;

    /* Retrieve the device list */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
    {
        fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
        exit(1);
    }
   
    /* Print the list */
    for(d=alldevs; d; d=d->next)
    {
        printf("%d. %s", ++i, d->name);
        if (d->description)
            printf(" (%s)/n", d->description);
        else
            printf(" (No description available)/n");
    }

    if(i==0)
    {
        printf("/nNo interfaces found! Make sure WinPcap is installed./n");
        return -1;
    }
   
    printf("Enter the interface number (1-%d):",i);
    scanf("%d", &inum);
   
    if(inum < 1 || inum > i)
    {
        printf("/nInterface number out of range./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    /* Jump to the selected adapter */
    for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
   
    /* Open the adapter */
    if ( (adhandle= pcap_open(d->name,  // name of the device
                             65536,     // portion of the packet to capture.
                                        // 65536 grants that the whole packet will be captured on all the MACs.
                             PCAP_OPENFLAG_PROMISCUOUS,         // promiscuous mode
                             1000,      // read timeout
                             NULL,      // remote authentication
                             errbuf     // error buffer
                             ) ) == NULL)
    {
        fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
   
    /* Check the link layer. We support only Ethernet for simplicity. */
    if(pcap_datalink(adhandle) != DLT_EN10MB)
    {
        fprintf(stderr,"/nThis program works only on Ethernet networks./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
   
    if(d->addresses != NULL)
        /* Retrieve the mask of the first address of the interface */
        netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
    else
        /* If the interface is without addresses we suppose to be in a C class network */
        netmask=0xffffff;


    //compile the filter
    if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) <0 )
    {
        fprintf(stderr,"/nUnable to compile the packet filter. Check the syntax./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
   
    //set the filter
    if (pcap_setfilter(adhandle, &fcode)<0)
    {
        fprintf(stderr,"/nError setting the filter./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
   
    printf("/nlistening on %s.../n", d->description);
   
    /* At this point, we don't need any more the device list. Free it */
    pcap_freealldevs(alldevs);
   
    /* start the capture */
    pcap_loop(adhandle, 0, packet_handler, NULL);
   
    return 0;
}

/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
    struct tm *ltime;
    char timestr[16];
    ip_header *ih;
    udp_header *uh;
    u_int ip_len;
    u_short sport,dport;

    /* convert the timestamp to readable format */
    ltime=localtime(&header->ts.tv_sec);
    strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);

    /* print timestamp and length of the packet */
    printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len);

    /* retireve the position of the ip header */
    ih = (ip_header *) (pkt_data +
        14); //length of ethernet header

    /* retireve the position of the udp header */
    ip_len = (ih->ver_ihl & 0xf) * 4;
    uh = (udp_header *) ((u_char*)ih + ip_len);

    /* convert from network byte order to host byte order */
    sport = ntohs( uh->sport );
    dport = ntohs( uh->dport );

    /* print ip addresses and udp ports */
    printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d/n",
        ih->saddr.byte1,
        ih->saddr.byte2,
        ih->saddr.byte3,
        ih->saddr.byte4,
        sport,
        ih->daddr.byte1,
        ih->daddr.byte2,
        ih->daddr.byte3,
        ih->daddr.byte4,
        dport);
}
 首先,我們設定過濾器的過濾規則爲"ip and udp"。這樣我們就能夠確保 packet_handler() 函數只返回IPv4的UDP數據包:這可以簡化分析工作,改善程序的效率。
 在程序開始部分,我們已經建立了兩個結構體,分別用來描述IP和UDP頭。這兩個結構體被 packet_handler() 函數用來正確定位IP和UDP頭字段。
 packet_handler(),雖然被限制爲只能解析一個協議(IPv4的UDP),但是仍然可以說明那些象 tcpdump/WinDump 一樣複雜的嗅探器怎樣解析網絡數據包的。因爲我們對MAC頭不感興趣,所以我們就忽略它。爲了簡單起見,在開始捕獲之前,我們用 pcap_datalink() 函數來檢查MAC層,以確保我們正在處理的是以太網楨。這樣我們就能夠確保MAC頭正好是14字節。(譯者注:這裏因爲是直接把以太網楨加14來獲得IP包的,所以要確保的卻是以太網楨,不然就會產生錯誤。)
 IP頭就緊跟在MAC頭後面。我們將從IP頭中取得IP源地址和目的地址。
 到達UDP頭複雜了一點,因爲IP頭的長度是不固定的。因此,我們使用IP頭的長度字段來確定它的大小。於是我們就知道了UDP頭的位置,然後就可以取得源端口和目的端口。
 我們把獲取的數據打印到屏幕上,結果如下:
1. {A7FD048A-5D4B-478E-B3C1-34401AC3B72F} (Xircom t 10/100 Adapter)
Enter the interface number (1-2):1


listening on Xircom CardBus Ethernet 10/100 Adapter...
16:13:15.312784 len:87 130.192.31.67.2682 -> 130.192.3.21.53
16:13:15.314796 len:137 130.192.3.21.53 -> 130.192.31.67.2682
16:13:15.322101 len:78 130.192.31.67.2683 -> 130.192.3.21.53

後三行的每一行都代表了一個不同的數據包。

7.Handling offline dump files
 在這節課中我們來學習怎樣將數據包保存到一個文件中。Winpcap提供了一系列保存網絡數據包到一個文件和從文件中讀取保存內容的函數 -- 這節課就是講述怎樣使用這些函數的。同時也會展示怎樣winpcap核心中的保存特性來獲得高性能的存儲(注意:現在,由於新的內核緩存的一些問題,這個特性已經不能使用了)。
 dump文件的格式和libpcap的是一樣的。在這個格式裏捕獲的數據包是用二進制的形式來保存的,現在已經作爲標準被許多網絡工具使用,其中就包括WinDump, Ethereal 和 Snort。
保存數據包到一個dump文件:
 首先,讓我們看看怎樣用libpcap格式來寫數據包。下面的例子從選擇的網絡接口中捕獲數據並保存到一個由用戶提供名字的文件中。
#include "pcap.h"

/* prototype of the packet handler */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);

main(int argc, char **argv)
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_dumper_t *dumpfile;


   
    /* Check command line */
    if(argc != 2)
    {
        printf("usage: %s filename", argv[0]);
        return -1;
    }
   
    /* Retrieve the device list on the local machine */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
    {
        fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
        exit(1);
    }
   
    /* Print the list */
    for(d=alldevs; d; d=d->next)
    {
        printf("%d. %s", ++i, d->name);
        if (d->description)
            printf(" (%s)/n", d->description);
        else
            printf(" (No description available)/n");
    }

    if(i==0)
    {
        printf("/nNo interfaces found! Make sure WinPcap is installed./n");
        return -1;
    }
   
    printf("Enter the interface number (1-%d):",i);
    scanf("%d", &inum);
   
    if(inum < 1 || inum > i)
    {
        printf("/nInterface number out of range./n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
       
    /* Jump to the selected adapter */
    for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
   
   
    /* Open the device */
    if ( (adhandle= pcap_open(d->name,          // name of the device
                              65536,            // portion of the packet to capture
                                                // 65536 guarantees that the whole packet will be captured on all the link layers
                              PCAP_OPENFLAG_PROMISCUOUS,    // promiscuous mode
                              1000,             // read timeout
                              NULL,             // authentication on the remote machine
                              errbuf            // error buffer
                              ) ) == NULL)
    {
        fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n", d->name);
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    /* Open the dump file */
    dumpfile = pcap_dump_open(adhandle, argv[1]);

    if(dumpfile==NULL)
    {
        fprintf(stderr,"/nError opening output file/n");
        return -1;
    }
   
    printf("/nlistening on %s... Press Ctrl+C to stop.../n", d->description);
   
    /* At this point, we no longer need the device list. Free it */
    pcap_freealldevs(alldevs);
   
    /* start the capture */
    pcap_loop(adhandle, 0, packet_handler, (unsigned char *)dumpfile);

    return 0;
}

/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *dumpfile, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
    /* save the packet on the dump file */
    pcap_dump(dumpfile, header, pkt_data);
}
現在你也看到了,這個程序的結構和我們以前學的程序的結構非常類似。只有兩點不同:
 打開網絡接口後跟着就調用pcap_dump_open() ,這個調用打開一個dump文件並把它和網絡接口綁定到一起。
 在 packet_handler() 回調函數中數據包被 pcap_dump() 函數寫到打開的文件中去。pcap_dump() 的參數與 packet_handler() 中的參數一一對應。
從一個dump文件中讀取數據包:
 現在我們已經有了一個有效的dump文件,我們可以嘗試來讀取它的內容。下面的代碼打開一個dump文件並顯示出裏面包含的每一個數據包。文件打開是用的 pcap_open_offline() 函數,然後一般是用 pcap_loop() 來按照順序來讀取數據包。就象你看到的一樣,從一個dump文件中讀取數據包和從一個物理接口來捕獲數據包基本上是一樣的。
 這個例子還介紹了另一個函數:pcap_createsrcsrc()。這個函數生成一個以一個標記開始的數據源字符串,這個標記用來告訴winpcap數據源的類型,比如"rpcap://" 代表一個適配器,"file://" 代表一個文件。如果已經使用了 pcap_findalldevs_ex() (這個函數的返回值已經包含了數據源字符串),那麼就不需要再使用 pcap_createsrcsrc() 了。不過,因爲文件名字是用戶輸入的,所以在這個例子裏我們還是要使用它的。
#include <stdio.h>
#include <pcap.h>

#define LINE_LEN 16

void dispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *);

main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];

    if(argc != 2){

        printf("usage: %s filename", argv[0]);
        return -1;

    }

    /* Create the source string according to the new WinPcap syntax */
    if ( pcap_createsrcstr( source,         // variable that will keep the source string
                            PCAP_SRC_FILE,  // we want to open a file
                            NULL,           // remote host
                            NULL,           // port on the remote host
                            argv[1],        // name of the file we want to open
                            errbuf          // error buffer
                            ) != 0)
    {
        fprintf(stderr,"/nError creating a source string/n");
        return -1;
    }
   
    /* Open the capture file */
    if ( (fp= pcap_open(source,         // name of the device
                        65536,          // portion of the packet to capture
                                        // 65536 guarantees that the whole packet will be captured on all the link layers
                         PCAP_OPENFLAG_PROMISCUOUS,     // promiscuous mode
                         1000,              // read timeout
                         NULL,              // authentication on the remote machine
                         errbuf         // error buffer
                         ) ) == NULL)
{
        fprintf(stderr,"/nUnable to open the file %s./n", source);
        return -1;
    }

    // read and dispatch packets until EOF is reached
    pcap_loop(fp, 0, dispatcher_handler, NULL);

    return 0;
}

 

void dispatcher_handler(u_char *temp1,
                        const struct pcap_pkthdr *header, const u_char *pkt_data)
{
    u_int i=0;
   
    /* print pkt timestamp and pkt len */
    printf("%ld:%ld (%ld)/n", header->ts.tv_sec, header->ts.tv_usec, header->len);         
   
    /* Print the packet */
    for (i=1; (i < header->caplen + 1 ) ; i++)
    {
        printf("%.2x ", pkt_data[i-1]);
        if ( (i % LINE_LEN) == 0) printf("/n");
    }
   
    printf("/n/n");    
   
}
下面這個例子的功能和上一個例子的一樣,不過用 pcap_next_ex() 代替了 pcap_loop() 的回調模式。
#include <stdio.h>
#include <pcap.h>

#define LINE_LEN 16

main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
struct pcap_pkthdr *header;
u_char *pkt_data;
u_int i=0;
int res;

    if(argc != 2)
    {
        printf("usage: %s filename", argv[0]);
        return -1;
    }
   
    /* Create the source string according to the new WinPcap syntax */
    if ( pcap_createsrcstr( source,         // variable that will keep the source string
                            PCAP_SRC_FILE,  // we want to open a file
                            NULL,           // remote host
                            NULL,           // port on the remote host
                            argv[1],        // name of the file we want to open
                            errbuf          // error buffer
                            ) != 0)
    {
        fprintf(stderr,"/nError creating a source string/n");
        return -1;
    }
   
    /* Open the capture file */
    if ( (fp= pcap_open(source,         // name of the device
                        65536,          // portion of the packet to capture
                                        // 65536 guarantees that the whole packet will be captured on all the link layers
                         PCAP_OPENFLAG_PROMISCUOUS,     // promiscuous mode
                         1000,              // read timeout
                         NULL,              // authentication on the remote machine
                         errbuf         // error buffer
                         ) ) == NULL)
    {
        fprintf(stderr,"/nUnable to open the file %s./n", source);
        return -1;
    }
   
    /* Retrieve the packets from the file */
    while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
    {
        /* print pkt timestamp and pkt len */
        printf("%ld:%ld (%ld)/n", header->ts.tv_sec, header->ts.tv_usec, header->len);         
       
        /* Print the packet */
        for (i=1; (i < header->caplen + 1 ) ; i++)
        {
            printf("%.2x ", pkt_data[i-1]);
            if ( (i % LINE_LEN) == 0) printf("/n");
        }
       
        printf("/n/n");    
    }
   
   
    if (res == -1)
    {
        printf("Error reading the packets: %s/n", pcap_geterr(fp));
    }
   
    return 0;
}
用pcap_live_dump()來往dump文件中寫數據包:
注意:現在,由於新的內核緩存的一些問題,這個特性已經不能使用了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章