1. 程式人生 > >WinPcap 中文技術文件(4.1.2) 第三章

WinPcap 中文技術文件(4.1.2) 第三章

1.  WinPcap教程:循序漸進教您使用WinPcap

本節將向您顯示如何使用WinPcapAPI的一些特性。本教程被組織成一系列課程,以循序漸進的方式,讓讀者從最基本的部分(獲得裝置列表)到最複雜的部分(控制傳送佇列並收集和統計網路流量)來了解如何使用WinPcap進行程式開發。

我們會提供幾個簡單但是完整的程式(程式碼片斷)作為參考:所有的原始碼中都包含一些指向手冊其他地方的連結,這可以讓您很方便地通過點選函式和資料結構跳轉到相關的文件處。

範例程式都是使用純C語言編寫的, 所以掌握基本的C語言程式設計知識是必須的,而且這是一個關於處理“原始”網路資料包的教程,所以我們希望讀者擁有良好的網路及網路協議的基本知識。

1.1.  獲取裝置列表

通常,編寫基於WinPcap應用程式的第一件事情就是獲得已連線的網路介面卡列表。libpcap和WinPcap都提供了 pcap_findalldevs_ex() 函式來實現這個功能: 這個函式返回一個pcap_if結構的連結串列, 每個pcap_if結構都包含一個介面卡的詳細資訊。值得注意的是,資料域name和description分別表示一個介面卡名稱和一個人們可讀的描述。

下列程式碼用於獲取介面卡列表,並在螢幕上顯示出來,如果沒有找到介面卡,將列印一個錯誤資訊。

#include"pcap.h"

int 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 /* auth is not needed */, &alldevs, errbuf) == -1)

    {

        fprintf(stderr,"Error inpcap_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 descriptionavailable)\n");

    }

    if (i == 0)

    {

        printf("\nNo interfaces found!Make sure WinPcap is installed.\n");

        return;

    }

    /* 不再需要裝置列表了,釋放它 */

    pcap_freealldevs(alldevs);

}

有關這段程式碼的一些說明。

首先,與其他libpcap函式一樣,pcap_findalldevs_ex()有一個 errbuf 引數。一旦發生錯誤,由libpcap把錯誤描述寫入到該字串中。

第二,請記住不是所有的作業系統都支援libpcap提供的網路程式介面,因此,如果我們想編寫一個可移植的應用程式,我們就必須考慮在什麼情況下,description 是null。本程式中,我們遇到這種情況時,會列印一個提示語句“No description available”。

最後要記住,當我們完成了裝置列表的使用,我們要呼叫 pcap_freealldevs() 函式將其佔用的記憶體資源釋放掉。

讓我們編譯並執行我們的第一個示例程式吧!為了能在Unix或Cygwin平臺上編譯這段程式,需要簡單輸入:

  gcc -o testprog testprog.c -lpcap

在Windows平臺上,您需要建立一個工程,並按照“使用WinPcap程式設計”裡的步驟做。然而,我們建議您使用WinPcap developer's pack (詳情請訪問WinPcap網站,http://www.winpcap.org ), 因為它提供了很多已經配置好的範例,包括本教程中所有的示例程式碼,以及在編譯執行時需要的包含檔案(include) 和動態庫檔案(libraries)。

假設我們已經完成了對程式的編譯,那讓我們來執行它吧。在某臺WinXP的電腦上,獲得的結果是:

  1.\Device\NPF_{4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS)Ethernet Adapter)

  2.\Device\NPF_{5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)

正如您看到的,Windows平臺下的網路介面卡的名字(當開啟裝置的時候,把它傳遞給libpcap庫)是相當不可讀的,因此解釋性的描述是非常有幫助阿!

1.2.  獲取已安裝裝置的高階資訊

在第1講(獲取裝置列表) 中,我們展示瞭如何獲取介面卡的基本資訊 (如裝置的名稱和描述)。事實上,WinPcap提供了其他更高階的資訊。特別需要指出的是:由 pcap_findalldevs_ex() 返回的每一個pcap_if結構體,都包含一個pcap_addr的結構體,這個結構體由如下元素組成:

l  一個地址列表

l  一個掩碼列表 (eachof which corresponds to an entry in the addresses list).

l  一個廣播地址列表(each of which corresponds to an entry in the addresses list).

l  一個目的地址列表(each of which corresponds to an entry in the addresses list).

另外,函式pcap_findalldevs_ex()可以返回遠端介面卡資訊和一個位於給定本地目錄下pcap檔案列表。

下面的範例使用ifprint()函式打印出 pcap_if結構體中所有的內容。程式對每一個由 pcap_findalldevs_ex()函式返回的pcap_if都呼叫ifprint()函式來實現列印。

/*

 * Copyright (c) 1999 - 2005 NetGroup,Politecnico di Torino (Italy)

 * Copyright (c) 2005 - 2006 CACE Technologies,Davis (California)

 * All rights reserved.

 *

 * Redistribution and use in source and binaryforms, with or without

 * modification, are permitted provided thatthe following conditions

 * are met:

 *

 * 1. Redistributions of source code mustretain the above copyright

 * notice, this list of conditions and thefollowing disclaimer.

 * 2. Redistributions in binary form mustreproduce the above copyright

 * notice, this list of conditions and thefollowing disclaimer in the

 * documentation and/or other materialsprovided with the distribution.

 * 3. Neither the name of the Politecnico diTorino, CACE Technologies

 * nor the names of its contributors may beused to endorse or promote

 * products derived from this software withoutspecific prior written

 * permission.

 *

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHTHOLDERS AND CONTRIBUTORS

 * "AS IS" AND ANY EXPRESS OR IMPLIEDWARRANTIES, INCLUDING, BUT NOT

 * LIMITED TO, THE IMPLIED WARRANTIES OFMERCHANTABILITY AND FITNESS FOR

 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NOEVENT SHALL THE COPYRIGHT

 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANYDIRECT, INDIRECT, INCIDENTAL,

 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT

 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODSOR 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) ARISINGIN ANY WAY OUT OF THE USE

 * OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF SUCH DAMAGE.

 *

 */

#include"pcap.h"

#ifndef WIN32

    #include <sys/socket.h>

    #include <netinet/in.h>

#else

    #include <winsock.h>

#endif

// 函式原型

voidifprint(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 tolist:\n"

            "rpcap://              ==> lists interfaces in thelocal machine\n"

            "rpcap://hostname:port ==>lists interfaces in a remote machine\n"

            "                          (rpcapd daemon mustbe up and running\n"

            "                           and it must accept'null' authentication)\n"

            "file://foldername     ==> lists all pcap files in the givefolder\n\n"

            "Enter your choice: ");

  fgets(source, PCAP_ERRBUF_SIZE, stdin);

  source[PCAP_ERRBUF_SIZE] = '\0';

  /* 獲得介面列表 */

  if (pcap_findalldevs_ex(source, NULL,&alldevs, errbuf) == -1)

  {

    fprintf(stderr,"Error inpcap_findalldevs: %s\n",errbuf);

    exit(1);

  }

  /* 掃描列表並列印每一項 */

  for(d=alldevs;d;d=d->next)

  {

    ifprint(d);

  }

  pcap_freealldevs(alldevs);

  return 1;

}

/* 列印所有可用資訊 */

voidifprint(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(((structsockaddr_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");

}

/* 將數字型別的IP地址轉換成字串型別的*/

#defineIPTOSBUFFERS    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(structsockaddr_storage);

    #endif

    if(getnameinfo(sockaddr,

        sockaddrlen,

        address,

        addrlen,

        NULL,

        0,

        NI_NUMERICHOST) != 0) address = NULL;

    return address;

}

1.3.  開啟介面卡捕獲資料包

現在,我們已經知道如何獲取要使用的介面卡,那就讓我們開始真正的工作,開啟一個介面卡,然後捕獲一些資料包。在這一課中,我們將編寫一個程式,它把每一個通過介面卡的資料包列印一些資訊。

開啟裝置的函式是pcap_open()。下面是引數snaplen,flags和to_ms 的解釋說明。

snaplen 指定需要捕獲資料包的那個部分。 在一些作業系統中 (比如 xBSD 和Win32), 驅動可以被配置成只捕獲資料包的初始化部分:這樣可以減少應用程式間複製的資料量,從而提高捕獲效率。本例中,我們將值定為65535,它比我們能遇到的最大的MTU還要大。因此,我們確信應用程式總是能夠收到完整的資料包。

flags: 最重要的flag是用來指示介面卡是否要被設定成混雜模式。一般情況下,介面卡只接收發給它自己的資料包,而那些在其他機器之間通訊的資料包將會被丟棄。相反,如果介面卡使用混雜模式,那麼不管這個資料包是不是發給網絡卡的,它都會被捕獲。也就是說,應用程式會去捕獲所有的資料包。這意味著在一個共享媒介(比如:匯流排型乙太網),WinPcap能捕獲其他主機的所有的資料包。大多數用於資料捕獲的應用程式都會將介面卡設定成混雜模式,所以,我們也會在下面的範例中使用混雜模式。

to_ms指定讀取資料的超時時間,以毫秒計(1s=1000ms)。在介面卡上進行讀取操作(比如:用 pcap_dispatch() 或 pcap_next_ex()) 都會在 to_ms 毫秒時間內響應,即使在網路上沒有可用的資料包。在統計模式下,to_ms 還可以用來定義統計的時間間隔。將to_ms設定為0意味著沒有超時,那麼如果沒有資料包到達的話,讀操作將永遠不會返回。如果設定成-1,則情況恰好相反,無論有沒有資料包到達,讀操作都會立即返回。

#include"pcap.h"

/* packethandler 函式原型 */

voidpacket_handler(u_char *param, const struct pcap_pkthdr *header, const u_char*pkt_data);

int main()

{

pcap_if_t*alldevs;

pcap_if_t *d;

int inum;

int i=0;

pcap_t*adhandle;

charerrbuf[PCAP_ERRBUF_SIZE];

    /* 獲取本機裝置列表 */

    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL, &alldevs, errbuf) == -1)

    {

        fprintf(stderr,"Error in pcap_findalldevs:%s\n", errbuf);

        exit(1);

    }

    /* 列印列表 */

    for(d=alldevs; d; d=d->next)

    {

        printf("%d. %s", ++i,d->name);

        if (d->description)

            printf(" (%s)\n",d->description);

        else

            printf(" (No descriptionavailable)\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 ofrange.\n");

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    /* 跳轉到選中的介面卡 */

    for(d=alldevs, i=0; i< inum-1;d=d->next, i++);

    /* 開啟裝置 */

    if ( (adhandle= pcap_open(d->name,          // 裝置名

            65536,            // 65535保證能捕獲到不同資料鏈路層上的每個資料包的全部內容

                            PCAP_OPENFLAG_PROMISCUOUS,    // 混雜模式

            1000,             // 讀取超時時間

            NULL,             // 遠端機器驗證

            errbuf            // 錯誤緩衝池

            ) ) == NULL)

    {

        fprintf(stderr,"\nUnable to openthe adapter. %s is not supported by WinPcap\n", d->name);

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    printf("\nlistening on %s...\n",d->description);

    /* 釋放裝置列表 */

    pcap_freealldevs(alldevs);

    /* 開始捕獲 */

    pcap_loop(adhandle, 0, packet_handler,NULL);

    return 0;

}

/* 每次捕獲到資料包時,libpcap都會自動呼叫這個回撥函式*/

voidpacket_handler(u_char *param, const struct pcap_pkthdr *header, const u_char*pkt_data)

{

    struct tm *ltime;

    char timestr[16];

    time_t local_tv_sec;

    /* 將時間戳轉換成可識別的格式 */

    local_tv_sec = header->ts.tv_sec;

    ltime=localtime(&local_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() 當超時時間到了(timeout expires)就返回 (儘管不能保證) ,但是 pcap_loop() 不會因此而返回,只有當 cnt 資料包被捕獲才能返回,所以pcap_loop()會在一小段時間內,阻塞網路的使用。pcap_loop()對於我們這個簡單的範例來說,可以滿足需求,不過pcap_dispatch() 函式一般用於比較複雜的程式中。

這兩個函式都有一個“回撥”引數,packet_handler指向一個可以接收資料包的函式。這個函式會在收到每個新的資料包並收到一個通用狀態時被libpcap所呼叫 (與函式 pcap_loop() 和 pcap_dispatch() 中的 user 引數相似),資料包的首部一般有一些諸如:時間戳,資料包長度的資訊,還有包含了協議首部的實際資料。

注意:冗餘校驗碼CRC不再支援,因為幀到達介面卡,並經過校驗確認以後,介面卡就會將CRC刪除,與此同時,大部分介面卡會直接丟棄CRC錯誤的資料包,所以WinPcap沒法捕獲到它們。

上面的程式將每一個數據包的時間戳和長度從pcap_pkthdr的首部解析出來,然後列印在螢幕上。

請注意,使用 pcap_loop() 函式可能會遇到障礙,主要因為它直接由資料包捕獲驅動所呼叫。因此,使用者程式是不能直接控制它的。另一個實現方法(也是提高可讀性的方法),是使用 pcap_next_ex() 函式。有關這個函式的使用,我們將在下一講為您展示。 (不用回撥方法捕獲資料包).

1.4.  不用回撥方法捕獲資料包

這節課的範例程式實現的功能和效果和上一課的非常相似 (開啟介面卡並且捕獲資料包)。但本講將使用 pcap_next_ex() 函式代替上一講的 pcap_loop()函式。

pcap_loop()函式是基於回撥的原理來進行資料捕獲,這是一種精妙的方法,並且在某些場合中,它是一種很好的選擇。然而,處理回撥有時候並不是很實用 -- 它會增加程式的複雜度,特別是在擁有多執行緒的C++程式中。

可以通過直接呼叫pcap_next_ex()函式來獲得一個數據包 -- 只有當程式設計人員使用了 pcap_next_ex() 函式才能收到資料包。

這個函式的引數和捕獲回撥函式的引數是一樣的-- 它包含一個網路介面卡的描述符和兩個可以初始化和返回給使用者的指標 (一個指向 pcap_pkthdr 結構體,另一個指向資料報資料的緩衝)。

在下面的程式中,我們會再次用到上一講中的有關回調方面的程式碼,只是我們將它放入了main()函式,之後呼叫 pcap_next_ex()函式。

#include"pcap.h"

int main()

{

pcap_if_t*alldevs;

pcap_if_t *d;

int inum;

int i=0;

pcap_t*adhandle;

int res;

charerrbuf[PCAP_ERRBUF_SIZE];

struct tm*ltime;

chartimestr[16];

structpcap_pkthdr *header;

const u_char*pkt_data;

time_tlocal_tv_sec;

    /* 獲取本機裝置列表 */

    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL, &alldevs, errbuf) == -1)

    {

        fprintf(stderr,"Error inpcap_findalldevs: %s\n", errbuf);

        exit(1);

    }

    /* 列印列表 */

    for(d=alldevs; d; d=d->next)

    {

        printf("%d. %s", ++i,d->name);

        if (d->description)

            printf(" (%s)\n",d->description);

        else

            printf(" (No descriptionavailable)\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 ofrange.\n");

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    /* 跳轉到已選中的介面卡 */

    for(d=alldevs, i=0; i< inum-1;d=d->next, i++);

    /* 開啟裝置 */

    if ( (adhandle= pcap_open(d->name,          // 裝置名

                              65536,            // 要捕捉的資料包的部分

                            // 65535保證能捕獲到不同資料鏈路層上的每個資料包的全部內容

                             PCAP_OPENFLAG_PROMISCUOUS,    // 混雜模式

                              1000,             // 讀取超時時間

                              NULL,             // 遠端機器驗證

                              errbuf            // 錯誤緩衝池

                              ) ) == NULL)

    {

        fprintf(stderr,"\nUnable to openthe adapter. %s is not supported by WinPcap\n", d->name);

        /* 釋放設列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    printf("\nlistening on %s...\n",d->description);

    /* 釋放裝置列表 */

    pcap_freealldevs(alldevs);

    /* 獲取資料包 */

    while((res = pcap_next_ex( adhandle,&header, &pkt_data)) >= 0){

        if(res == 0)

            /* 超時時間到 */

            continue;

        /* 將時間戳轉換成可識別的格式 */

        local_tv_sec = header->ts.tv_sec;

        ltime=localtime(&local_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),因此,如果資料包是從檔案讀取來的,那麼它就不那麼有用了。

值得注意的是,pcap_next_ex()在成功、超時、出錯或EOF的情況下,會返回不同的值。

1.5.  過濾資料包

WinPcap和Libpcap的最強大的特性之一是擁有過濾資料包的引擎。它提供一種非常高效的方法接收網路中的某些資料包,這通常整合在WinPcap提供的捕獲機制。過濾資料包的函式是pcap_compile() 和 pcap_setfilter() 。

pcap_compile()函式把一個高階的布林過濾表示式編譯生成一個能夠被過濾引擎所解釋的低層的位元組碼。有關布林過濾表示式的語法可以參見“過濾表示式語法”這一節的內容。

pcap_setfilter()將一個過濾器與核心驅動中的捕獲會話相關聯。當pcap_setfilter()被呼叫時,這個過濾器將應用到來自網路的所有資料包,並且所有的符合要求的資料包 (即那些經過過濾器以後,布林表示式為真的資料包) 將會立即複製給應用程式。

以下程式碼顯示瞭如何編譯和設定過濾器。 請注意,我們必須從pcap_if結構體中獲得掩碼,因為一些使用 pcap_compile() 建立的過濾器需要它。

在這段程式碼片斷中,傳遞給pcap_compile()的過濾器是"ipand tcp",它的含義是“只希望保留IPv4和TCP的資料包,並且把他們傳送給應用程式”。

    if (d->addresses != NULL)

        /* 獲取介面第一個地址的掩碼 */

        netmask=((struct sockaddr_in*)(d->addresses->netmask))->sin_addr.S_un.S_addr;

    else

        /* 如果這個介面沒有地址,那麼我們假設這個介面在C類網路中 */

        netmask=0xffffff;

編譯過濾器

    if (pcap_compile(adhandle, &fcode,"ip and tcp", 1, netmask) < 0)

    {

        fprintf(stderr,"\nUnable tocompile the packet filter. Check the syntax.\n");

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

設定過濾器

    if (pcap_setfilter(adhandle, &fcode)< 0)

    {

        fprintf(stderr,"\nError settingthe filter.\n");

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

如果你想閱讀本課中顯示的過濾函式的一些程式碼,請閱讀下一課中的“分析資料包”例子。

1.6.  分析資料包

現在,我們可以捕捉並且過濾網路流量了,那就讓我們學以致用,來完成一個簡單使用的程式吧!

在本講中,我們將會利用上一講的一些程式碼,來建立一個更實用的程式。 本程式的主要目標是顯示如何解析所捕獲的資料包的協議首部。這個程式可以稱為UDPdump,列印一些網路上傳輸的UDP資料的資訊。

我們選擇解析和顯示UDP協議,而不是TCP等其它協議,是因為它比其它的協議更簡單,作為一個入門程式範例,這是很不錯的選擇。讓我們看看程式碼:

/*

 * Copyright (c) 1999 - 2005 NetGroup,Politecnico di Torino (Italy)

 * Copyright (c) 2005 - 2006 CACE Technologies,Davis (California)

 * All rights reserved.

 *

 * Redistribution and use in source and binaryforms, with or without

 * modification, are permitted provided thatthe following conditions

 * are met:

 *

 * 1. Redistributions of source code mustretain the above copyright

 * notice, this list of conditions and thefollowing disclaimer.

 * 2. Redistributions in binary form mustreproduce the above copyright

 * notice, this list of conditions and thefollowing disclaimer in the

 * documentation and/or other materialsprovided with the distribution.

 * 3. Neither the name of the Politecnico diTorino, CACE Technologies

 * nor the names of its contributors may beused to endorse or promote

 * products derived from this software withoutspecific prior written

 * permission.

 *

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHTHOLDERS AND CONTRIBUTORS

 * "AS IS" AND ANY EXPRESS OR IMPLIEDWARRANTIES, INCLUDING, BUT NOT

 * LIMITED TO, THE IMPLIED WARRANTIES OFMERCHANTABILITY AND FITNESS FOR

 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NOEVENT SHALL THE COPYRIGHT

 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANYDIRECT, INDIRECT, INCIDENTAL,

 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT

 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODSOR 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) ARISINGIN ANY WAY OUT OF THE USE

 * OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF SUCH DAMAGE.

 *

 */

#include"pcap.h"

/* 4位元組的IP地址 */

typedef structip_address{

    u_char byte1;

    u_char byte2;

    u_char byte3;

    u_char byte4;

}ip_address;

/* IPv4 首部 */

typedef structip_header{

    u_char ver_ihl;        // 版本 (4 bits) +首部長度 (4 bits)

    u_char tos;            // 服務型別(Type ofservice)

    u_short tlen;           // 總長(Total length)

    u_short identification; // 標識(Identification)

    u_short flags_fo;       // 標誌位(Flags) (3 bits) +段偏移量(Fragment offset) (13 bits)

    u_char ttl;            // 存活時間(Time tolive)

    u_char proto;          // 協議(Protocol)

    u_short crc;            // 首部校驗和(Headerchecksum)

    ip_address saddr;      // 源地址(Sourceaddress)

    ip_address daddr;      // 目的地址(Destinationaddress)

    u_int  op_pad;         // 選項與填充(Option+ Padding)

}ip_header;

/* UDP 首部*/

typedef structudp_header{

    u_short sport;          // 源埠(Source port)

    u_short dport;          // 目的埠(Destinationport)

    u_short len;            // UDP資料包長度(Datagramlength)

    u_short crc;            // 校驗和(Checksum)

}udp_header;

/* 回撥函式原型 */

voidpacket_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;

charerrbuf[PCAP_ERRBUF_SIZE];

u_int netmask;

charpacket_filter[] = "ip and udp";

structbpf_program fcode;

    /* 獲得裝置列表 */

    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL, &alldevs, errbuf) == -1)

    {

        fprintf(stderr,"Error inpcap_findalldevs: %s\n", errbuf);

        exit(1);

    }

    /* 列印列表 */

    for(d=alldevs; d; d=d->next)

    {

        printf("%d. %s", ++i,d->name);

        if (d->description)

            printf(" (%s)\n",d->description);

        else

            printf(" (No descriptionavailable)\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 ofrange.\n");

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    /* 跳轉到已選裝置 */

    for(d=alldevs, i=0; i< inum-1;d=d->next, i++);

    /* 開啟介面卡 */

    if ( (adhandle= pcap_open(d->name,  // 裝置名

                             65536,     // 要捕捉的資料包的部分

                            // 65535保證能捕獲到不同資料鏈路層上的每個資料包的全部內容

                            PCAP_OPENFLAG_PROMISCUOUS,        // 混雜模式

                             1000,      // 讀取超時時間

                             NULL,      // 遠端機器驗證

                             errbuf     // 錯誤緩衝池

                             ) ) == NULL)

    {

        fprintf(stderr,"\nUnable to openthe adapter. %s is not supported by WinPcap\n");

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    /* 檢查資料鏈路層,為了簡單,我們只考慮乙太網*/

    if(pcap_datalink(adhandle) != DLT_EN10MB)

    {

        fprintf(stderr,"\nThis programworks only on Ethernet networks.\n");

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    if(d->addresses != NULL)

        /* 獲得介面第一個地址的掩碼 */

        netmask=((struct sockaddr_in*)(d->addresses->netmask))->sin_addr.S_un.S_addr;

    else

        /* 如果介面沒有地址,那麼我們假設一個C類的掩碼 */

        netmask=0xffffff;

    //編譯過濾器

    if (pcap_compile(adhandle, &fcode,packet_filter, 1, netmask) <0 )

    {

        fprintf(stderr,"\nUnable tocompile the packet filter. Check the syntax.\n");

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    //設定過濾器

    if (pcap_setfilter(adhandle,&fcode)<0)

    {

        fprintf(stderr,"\nError settingthe filter.\n");

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    printf("\nlistening on %s...\n",d->description);

    /* 釋放裝置列表 */

    pcap_freealldevs(alldevs);

    /* 開始捕捉 */

    pcap_loop(adhandle, 0, packet_handler,NULL);

    return 0;

}

/* 回撥函式,當收到每一個數據包時會被libpcap所呼叫 */

voidpacket_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;

    time_t local_tv_sec;

    /* 將時間戳轉換成可識別的格式 */

    local_tv_sec = header->ts.tv_sec;

    ltime=localtime(&local_tv_sec);

    strftime( timestr, sizeof timestr,"%H:%M:%S", ltime);

    /* 列印資料包的時間戳和長度 */

    printf("%s.%.6d len:%d ",timestr, header->ts.tv_usec, header->len);

    /* 獲得IP資料包頭部的位置 */

    ih = (ip_header *) (pkt_data +

        14); //乙太網頭部長度

    /* 獲得UDP首部的位置 */

    ip_len = (ih->ver_ihl & 0xf) * 4;

    uh = (udp_header *) ((u_char*)ih + ip_len);

    /* 將網路位元組序列轉換成主機位元組序列 */

    sport = ntohs( uh->sport );

    dport = ntohs( uh->dport );

    /* 列印IP地址和UDP埠 */

    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 andudp”。在這種方式下,我們確信packet_handler()只會收到基於IPv4的UDP資料包;這將簡化解析過程,提高程式的效率。

我們還分別建立了用於描述IP首部和UDP首部的結構體。packet_handler()所使用的這些結構體會合理地定位到各個頭部欄位上。

packet_handler(),雖然只限於單個協議的解析(比如基於IPv4的UDP),不過它顯示一個複雜的捕捉器(sniffers),就像TcpDump或WinDump一樣,對網路資料流進行解碼那樣。因為我們對MAC首部不感興趣,所以我們忽略了它。為了簡化,在開始捕捉之前,使用了pcap_datalink() 對MAC層進行了檢測,以確保我們是在處理一個乙太網絡。這樣,我們就能確保MAC層的首部為14位元組。

IP資料包的首部位於MAC首部的後面。我們將從IP資料包的首部解析到源IP地址和目的IP地址。

處理UDP的首部有一些複雜,因為IP資料包的首部的長度並不是固定不變的。然而,我們可以通過IP資料包的length欄位來得到它的長度。一旦我們知道了UDP首部的位置,我們就能提取源埠和目的埠。

提取出來的值會列印在螢幕上,結果如下所示:

1.\Device\Packet_{A7FD048A-5D4B-478E-B3C1-34401AC3B72F} (Xircom t 10/100 Adapter)

 Enter the interface number (1-2):1

listening onXircom 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

最後3行中的每一行分別代表了一個數據包。

1.7.  處理離線輸出檔案

在本講中,我們將學習如何處理捕獲到檔案中的資料包。 WinPcap提供了很多函式來將網路資料流儲存到檔案並讀取它們。本講將教你如何使用所有這些函式。我們還將看到如何使用WinPcap核心輸出特性來獲取一個高效能的輸出(請注意:此時,由於一些有關新核心緩衝的問題,可能無法使用這些特性) 。

輸出檔案的格式是libpcap的一種。這種格式包含捕捉到的資料包的二進位制資料,並且這種格式是許多網路工具所使用的一種標準,這些工具包括WinDump,Etheral和Snort。

1.7.1.  儲存資料包到輸出檔案

首先,讓我們看一下如何以libpcap的格式把一個數據包寫入到檔案。

接下來的例子描述從一個選定的介面捕獲資料包,然後將它們儲存到使用者指定的檔案中。

#include"pcap.h"

/* 回撥函式原型 */

voidpacket_handler(u_char *param, const struct pcap_pkthdr *header, const u_char*pkt_data);

int main(intargc, 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;

    /* 檢查程式輸入引數 */

    if(argc != 2)

    {

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

        return -1;

    }

    /* 獲取本機裝置列表 */

    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL, &alldevs, errbuf) == -1)

    {

        fprintf(stderr,"Error inpcap_findalldevs: %s\n", errbuf);

        exit(1);

    }

    /* 列印列表 */

    for(d=alldevs; d; d=d->next)

    {

        printf("%d. %s", ++i,d->name);

        if (d->description)

            printf(" (%s)\n",d->description);

        else

            printf(" (No descriptionavailable)\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 ofrange.\n");

        /* 釋放列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    /* 跳轉到選中的介面卡 */

    for(d=alldevs, i=0; i< inum-1;d=d->next, i++);

    /* 開啟介面卡 */

    if ( (adhandle= pcap_open(d->name,          // 裝置名

                              65536,            // 要捕捉的資料包的部分

                                       // 65535保證能捕獲到不同資料鏈路層上的每個資料包的全部內容

                             PCAP_OPENFLAG_PROMISCUOUS,    // 混雜模式

                              1000,             // 讀取超時時間

                              NULL,             // 遠端機器驗證

                              errbuf            // 錯誤緩衝池

                              ) ) == NULL)

    {

        fprintf(stderr,"\nUnable to openthe adapter. %s is not supported by WinPcap\n", d->name);

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        return -1;

    }

    /* 開啟輸出檔案 */

    dumpfile = pcap_dump_open(adhandle,argv[1]);

    if(dumpfile==NULL)

    {

        fprintf(stderr,"\nError openingoutput file\n");

        return -1;

    }

    printf("\nlistening on %s... PressCtrl+C to stop...\n", d->description);

    /* 釋放裝置列表 */

    pcap_freealldevs(alldevs);

    /* 開始捕獲 */

    pcap_loop(adhandle, 0, packet_handler,(unsigned char *)dumpfile);

    return 0;

}

/* 回撥函式,用來處理資料包*/

voidpacket_handler(u_char *dumpfile, const struct pcap_pkthdr *header, const u_char*pkt_data)

{

    /* 儲存資料包到輸出檔案 */

    pcap_dump(dumpfile, header, pkt_data);

}

你可以看到,這個程式的結構和前面幾講的程式非常相似,它們的區別有:

1)  只有當介面開啟時,呼叫pcap_dump_open()才是有效的。 這個呼叫將開啟一個輸出檔案,並將它關聯到特定的介面上。

2)  資料包將會通過pcap_dump() 函式寫入輸出檔案中,這個函式是packet_handler()的回撥函式。 pcap_dump() 的引數和 pcap_handler() 函式中的引數是一一對應的。

1.7.2.  從輸出檔案中讀取資料包

既然我們有了一個可用的輸出檔案,那我們就能讀取它的內容。 以下程式碼將開啟一個WinPcap/libpcap的輸出檔案,並顯示檔案中每一個包的資訊。輸出檔案通過 pcap_open_offline() 開啟,然後我們通常使用 pcap_loop() 來有序地獲取資料包。你可以看到,從離線檔案中讀取資料包和從物理介面中接收它們是很相似的。

這個例子還會介紹另一個函式:pcap_createsrcsrc()。這個函式用於建立一個源字串,這個源字串以一個標誌開頭,這個標誌用於告訴WinPcap這個源的型別。比如,使用“rpcap://”標誌來開啟一個介面卡,使用“file://”來開啟一個檔案。如果pcap_findalldevs_ex() 已經被使用,那麼這部是不需要的,因為其返回值已經包含了這些字串。然而,在這個例子中我們需要它。因為檔案的名字來自於使用者的輸入。

#include<stdio.h>

#include<pcap.h>

#define LINE_LEN16

voiddispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *);

int main(intargc, 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;

    }

    /* 根據新WinPcap語法建立一個源字串 */

    if ( pcap_createsrcstr( source,         // 源字串

                            PCAP_SRC_FILE,  // 我們要開啟的檔案

                            NULL,           // 遠端主機

                            NULL,           // 遠端主機埠

                            argv[1],       // 我們要開啟的檔名

                            errbuf          // 錯誤緩衝區

                            ) != 0)

    {

        fprintf(stderr,"\nError creating asource string\n");

        return -1;

    }

    /* 開啟捕獲檔案 */

    if ( (fp= pcap_open(source,         // 裝置名

                        65536,          // 要捕捉的資料包的部分

                       //65535保證能捕獲到不同資料鏈路層上的每個資料包的全部內容

                        PCAP_OPENFLAG_PROMISCUOUS,     // 混雜模式

                         1000,              // 讀取超時時間

                         NULL,              // 遠端機器驗證

                         errbuf         // 錯誤緩衝池

                         ) ) == NULL)

    {

        fprintf(stderr,"\nUnable to openthe file %s.\n", source);

        return -1;

    }

    // 讀取並解析資料包,直到EOF為真

    pcap_loop(fp, 0, dispatcher_handler, NULL);

    return 0;

}

voiddispatcher_handler(u_char *temp1, const struct pcap_pkthdr *header, constu_char *pkt_data)

{

    u_int i=0;

    /* 列印pkt時間戳和pkt長度 */

    printf("%ld:%ld (%ld)\n",header->ts.tv_sec, header->ts.tv_usec, header->len);         

    /* 列印資料包 */

    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_LEN16

int main(intargc, char **argv)

{

pcap_t *fp;

char errbuf[PCAP_ERRBUF_SIZE];

char source[PCAP_BUF_SIZE];

struct pcap_pkthdr *header;

const u_char *pkt_data;

u_int i=0;

int res;

    if(argc != 2)

    {

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

        return -1;

    }

    /* 根據新WinPcap語法建立一個源字串 */

    if ( pcap_createsrcstr( source,         // 源字串

                            PCAP_SRC_FILE,  // 我們要開啟的檔案

                            NULL,           // 遠端主機

                            NULL,           // 遠端主機埠

                            argv[1],        // 我們要開啟的檔名

                            errbuf          // 錯誤緩衝區

                            ) != 0)

    {

        fprintf(stderr,"\nError creating asource string\n");

        return -1;

    }

    /* 開啟捕獲檔案 */

    if ( (fp= pcap_open(source,         // 裝置名

                        65536,          // 要捕捉的資料包的部分

                   // 65535保證能捕獲到不同資料鏈路層上的每個資料包的全部內容

                        PCAP_OPENFLAG_PROMISCUOUS,     // 混雜模式

                         1000,              // 讀取超時時間

                         NULL,              // 遠端機器驗證

                         errbuf         // 錯誤緩衝池

                         ) ) == NULL)

    {

        fprintf(stderr,"\nUnable to openthe file %s.\n", source);

        return -1;

    }

    /* 從檔案獲取資料包 */

    while((res = pcap_next_ex( fp, &header,&pkt_data)) >= 0)

    {

        /* 列印pkt時間戳和pkt長度 */

        printf("%ld:%ld (%ld)\n",header->ts.tv_sec, header->ts.tv_usec, header->len);         

        /* 列印資料包 */

        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;

}

1.7.3.  使用pcap_live_dump將包寫入堆檔案

注意: 此時,由於新核心緩衝的一些問題,這個特性可能不可用。

WinPcap的最近幾個版本提供了一個更好的途徑來把資料流儲存到磁碟,那就是pcap_live_dump() 函式。 pcap_live_dump()函式有3個引數:檔名、檔案最大的大小(位元組為單位)和檔案可以允許儲存的資料包的最大數量,0表示沒有限制。注意,在呼叫 pcap_live_dump()將資料流儲存下來之前,程式可以設定過濾器(使用 pcap_setfilter(),詳情請參見教程的“過濾資料包”這部分) ,這樣我們就可以定義要儲存的那部分資料流了。

pcap_live_dump()不會被阻塞, 因此它開始輸出處理後會立即返回。 輸出處理以非同步的方式進行,直到檔案達到最大的大小或者儲存的資料包達到最大數量。

應用程式可以使用pcap_live_dump_ended()來檢查資料是否儲存完畢。特別注意:sync引數必須是非零的,如果它們是0,那麼程式將永遠被阻塞。

/*

 * Copyright (c) 1999 - 2005 NetGroup,Politecnico di Torino (Italy)

 * Copyright (c) 2005 - 2006 CACE Technologies,Davis (California)

 * All rights reserved.

 *

 * Redistribution and use in source and binaryforms, with or without

 * modification, are permitted provided thatthe following conditions

 * are met:

 *

 * 1. Redistributions of source code mustretain the above copyright

 * notice, this list of conditions and thefollowing disclaimer.

 * 2. Redistributions in binary form mustreproduce the above copyright

 * notice, this list of conditions and thefollowing disclaimer in the

 * documentation and/or other materialsprovided with the distribution.

 * 3. Neither the name of the Politecnico diTorino, CACE Technologies

 * nor the names of its contributors may beused to endorse or promote

 * products derived from this software withoutspecific prior written

 * permission.

 *

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHTHOLDERS AND CONTRIBUTORS

 * "AS IS" AND ANY EXPRESS OR IMPLIEDWARRANTIES, INCLUDING, BUT NOT

 * LIMITED TO, THE IMPLIED WARRANTIES OFMERCHANTABILITY AND FITNESS FOR

 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NOEVENT SHALL THE COPYRIGHT

 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANYDIRECT, INDIRECT, INCIDENTAL,

 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT

 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODSOR 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) ARISINGIN ANY WAY OUT OF THE USE

 * OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF SUCH DAMAGE.

 *

 */

#include<stdlib.h>

#include<stdio.h>

#include<pcap.h>

#error At themoment the kernel dump feature is not supported in the driver

int main(intargc, char **argv) {

    pcap_if_t *alldevs, *d;

    pcap_t *fp;

    u_int inum, i=0;

    char errbuf[PCAP_ERRBUF_SIZE];

    printf("kdump: saves the networktraffic to file using WinPcap kernel-level dump faeature.\n");

    printf("\t Usage: %s [adapter] |dump_file_name max_size max_packs\n", argv[0]);

    printf("\t Where: max_size is themaximum size that the dump file will reach (0 means no limit)\n");

    printf("\t Where: max_packs is themaximum number of packets that will be saved (0 means no limit)\n\n");

    if(argc < 5){

        /* 使用者沒有提供資料來源。獲取裝置列表 */

        if (pcap_findalldevs(&alldevs,errbuf) == -1)

        {

            fprintf(stderr,"Error inpcap_findalldevs: %s\n", errbuf);

            exit(1);

        }

        /* 列印列表 */

        for(d=alldevs; d; d=d->next)

        {

            printf("%d. %s", ++i,d->name);

            if (d->description)

                printf(" (%s)\n",d->description);

            else

                printf(" (No descriptionavailable)\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 outof range.\n");

            /* 釋放列表 */

            return -1;

        }

        /* 跳轉到所選的裝置 */

        for(d=alldevs, i=0; i< inum-1;d=d->next, i++);

        /* 開啟裝置 */

        if ( (fp = pcap_open_live(d->name,100, 1, 20, errbuf) ) == NULL)

        {

            fprintf(stderr,"\nErroropening adapter\n");

            return -1;

        }

        /* 釋放裝置列表 */

        pcap_freealldevs(alldevs);

        /* 開始堆過程 */

        if(pcap_live_dump(fp, argv[1],atoi(argv[2]), atoi(argv[3]))==-1){

            printf("Unable to start thedump, %s\n", pcap_geterr(fp));

            return -1;

        }

    }

    else{

        /* 開啟裝置 */

        if ( (fp= pcap_open_live(argv[1], 100,1, 20, errbuf) ) == NULL)

        {

            fprintf(stderr,"\nErroropening adapter\n");

            return -1;

        }

        /* 開始堆過程 */

        if(pcap_live_dump(fp, argv[0],atoi(argv[1]), atoi(argv[2]))==-1){

            printf("Unable to start thedump, %s\n", pcap_geterr(fp));

            return -1;

        }

    }

    /* 等待,知道堆過程完成,也就是當資料到達max_size或max_packs的時候 */

    pcap_live_dump_ended(fp, TRUE);

    /* 關閉介面卡,這樣,就可以將資料立刻輸出到檔案了 */

    pcap_close(fp);

    return 0;

}

pcap_live_dump()和 pcap_dump()的區別,除了可以設定限制之外,就是執行結果。pcap_live_dump() 使用了WinPcap NPF驅動自帶的功能(詳情請參見 NPF驅動核心手冊) ,在核心級來寫輸出檔案,並將上下文交換的數量和記憶體拷貝的數量最小化。

顯然,這個特性目前並不能應用於其它作業系統,因為 pcap_live_dump() 是WinPcap的特性之一,並且只運行於Win32平臺下。

1.8.  傳送資料包

儘管從 WinPcap 的名字上看這個庫的目標應該是資料捕捉(Packet Capture),然而它也提供了很多其它有用的特性。其中,使用者可以找到一組很完整的用於傳送資料包的函式。

請注意:原始的libpcap庫是不支援傳送資料包的,因此這裡展示的函式都屬於是WinPcap的擴充套件,並且它們不能運行於Unix平臺下。

1.8.1.  使用 pcap_sendpacket() 傳送單個數據包

下面的程式碼展示了傳送一個數據包的最簡單的方式。開啟介面卡以後,呼叫 pcap_sendpacket() 來發送手工製作的資料包。pcap_sendpacket() 的引數中有一個要包含傳送資料的緩衝區,緩衝的長度,以及用來發送資料的介面卡。注意,緩衝資料將直接傳送到網路,而不會進行任何加工和處理。這就意味著應用程式需要建立一個正確的協議首部,為了使這個資料包更有意義。

#include<stdlib.h>

#include<stdio.h>

#include<pcap.h>

void main(intargc, char **argv)

{

pcap_t *fp;

char errbuf[PCAP_ERRBUF_SIZE];

u_char packet[100];

int i;

    /* 檢查命令列引數的合法性 */

    if (argc != 2)

    {

        printf("usage: %s interface (e.g.'rpcap://eth0')", argv[0]);

        return;

    }

    /* 開啟輸出裝置 */

    if ( (fp= pcap_open(argv[1],            // 裝置名

                        100,                // 要捕獲的部分 (只捕獲前100個位元組)

                       PCAP_OPENFLAG_PROMISCUOUS,  // 混雜模式

                        1000,               // 讀超時時間

                        NULL,               // 遠端機器驗證

                        errbuf              // 錯誤緩衝

                        ) ) == NULL)

    {

        fprintf(stderr,"\nUnable to openthe adapter. %s is not supported by WinPcap\n", argv[1]);

        return;

    }

    /* 假設在乙太網上,設定MAC的目的地址為1:1:1:1:1:1 */

    packet[0]=1;

    packet[1]=1;

    packet[2]=1;

    packet[3]=1;

    packet[4]=1;

    packet[5]=1;

    /* 設定MAC源地址為2:2:2:2:2:2 */

    packet[6]=2;

    packet[7]=2;

    packet[8]=2;

    packet[9]=2;

    packet[10]=2;

    packet[11]=2;

    /* 填充剩下的內容 */

    for(i=12;i<100;i++)

    {

        packet[i]=i%256;

    }

    /* 傳送資料包 */

    if (pcap_sendpacket(fp, packet, 100 /* size*/) != 0)

    {

        fprintf(stderr,"\nError sendingthe packet: \n", pcap_geterr(fp));

        return;

    }

    return;

}

1.8.2.  傳送佇列

pcap_sendpacket()提供了一種簡單而直接的方法來發送單個數據包,而“傳送佇列”則提供了一種高階的、強大的、結構更優的方法來發送一組資料包。傳送佇列是一個容器,它能容納不同數量的資料包,這些資料包將被髮送到網路上。佇列有大小,它代表了它能儲存的資料包的最大數量。

傳送佇列通過呼叫pcap_sendqueue_alloc() 函式建立,並且需要指定佇列的大小。

一旦傳送佇列被建立,pcap_sendqueue_queue() 就可以將資料包新增到傳送佇列中。這個函式的引數包含一個pcap_pkthdr 的結構體,它包含時間戳和長度,同時,引數還包含一個指向資料包資料的緩衝。這些引數和那些被 pcap_next_ex() 和 pcap_handler()接收到的資料相同,因此,為那些剛剛捕獲到的或是從檔案讀取出來的資料包排隊,就相當於把三個引數傳遞給pcap_sendqueue_queue()。

WinPcap提供了pcap_sendqueue_transmit()函式來發送一個佇列。請注意第三個引數:如果非零,那麼傳送過程將是同步進行,也就是說,只有時間戳相符的資料包才會被處理。這個操作需要消耗大量的CPU資源,因為同步操作由核心驅動中的“忙等 (busy wait)”迴圈來實現的。儘管這個操作對CPU的要求很高,但它對包傳送的處理結果,通常是很精確的。(通常在數微秒左右,或更小)

請注意,使用pcap_sendqueue_transmit() 要比pcap_sendpacket() 來發送一系列資料更加有效率,因為傳送佇列儲存在核心級的緩衝區,因此,減少了上下文交換的次數。

當佇列不再需要時,我們可以使用pcap_sendqueue_destroy() 來釋放它所佔用的記憶體。

下一個程式將演示如何使用傳送佇列。先用pcap_open_offline() 開啟一個捕獲檔案,然後,將檔案中的資料包移到已分配的傳送佇列。這時就可以傳送隊列了,如果使用者指定了同步,那麼它將同步傳送佇列。

注意,輸出檔案的鏈路層將會那些傳送資料包的介面中的一個進行比