1. 程式人生 > >libpcap講解與API介面函式講解

libpcap講解與API介面函式講解

轉載有道:http://blog.chinaunix.net/uid-21556133-id-120228.html
libpcap(Packet Capture Library),即資料包捕獲函式庫,是Unix/Linux平臺下的網路資料包捕獲函式庫。它是一個獨立於系統的使用者層包捕獲的API介面,為底層網路監測提供了一個可移植的框架。 一、libpcap工作原理 libpcap主要由兩部份組成:網路分接頭(Network Tap)和資料過濾器(Packet Filter)。網路分接頭從網路裝置驅動程式中收集資料拷貝,過濾器決定是否接收該資料包。Libpcap利用BSD Packet Filter(BPF)演算法對網絡卡接收到的鏈路層資料包進行過濾。BPF演算法的基本思想是在有BPF監聽的網路中,網絡卡驅動將接收到的資料包複製一份交給BPF過濾器,過濾器根據使用者定義的規則決定是否接收此資料包以及需要拷貝該資料包的那些內容,然後將過濾後的資料給與過濾器相關聯的上層應用程式。 libpcap的包捕獲機制就是在資料鏈路層加一個旁路處理。當一個數據包到達網路介面時,libpcap首先利用已經建立的Socket從鏈路層驅動程式中獲得該資料包的拷貝,再通過Tap函式將資料包發給BPF過濾器。BPF過濾器根據使用者已經定義好的過濾規則對資料包進行逐一匹配,匹配成功則放入核心緩衝區,並傳遞給使用者緩衝區,匹配失敗則直接丟棄。如果沒有設定過濾規則,所有資料包都將放入核心緩衝區,並傳遞給使用者層緩衝區。 二、libpcap的抓包框架 pcap_lookupdev()函式用於查詢網路裝置,返回可被pcap_open_live()函式呼叫的網路裝置名指標。 pcap_open_live()函式用於開啟網路裝置,並且返回用於捕獲網路資料包的資料包捕獲描述字。對於此網路裝置的操作都要基於此網路裝置描述字。 pcap_lookupnet()函式獲得指定網路裝置的網路號和掩碼。 pcap_compile()函式用於將使用者制定的過濾策略編譯到過濾程式中。 pcap_setfilter()函式用於設定過濾器。 pcap_loop()函式pcap_dispatch()函式用於捕獲資料包,捕獲後還可以進行處理,此外pcap_next()和pcap_next_ex()兩個函式也可以用來捕獲資料包。 pcap_close()函式用於關閉網路裝置,釋放資源。 其實pcap的應用程式格式很簡單,總的來說可以可以分為以下5部分,相信看了以下的5部分,大概能對pcap的總體佈局有個大概的瞭解了吧: 1.我們從決定用哪一個介面進行嗅探開始。在Linux中,這可能是eth0,而在BSD系統中則可能是xl1等等。我們也可以用一個字串來定義這個裝置,或者採用pcap提供的介面名來工作。 2.初始化pcap。在這裡我們要告訴pcap對什麼裝置進行嗅探。假如願意的話,我們還可以嗅探多個裝置。怎樣區分它們呢?使用 檔案控制代碼。就像開啟一個檔案進行讀寫一樣,必須命名我們的嗅探“會話”,以此使它們各自區別開來。 3.假如我們只想嗅探特定的傳輸(如TCP/IP包,發往埠23的包等等),我們必須建立一個規則集合,編譯並且使用它。這個過程分為三個相互緊密關聯的階段。規則集合被置於一個字串內,並且被轉換成能被pcap讀的格式(因此編譯它)。編譯實際上就是在我們的程式裡呼叫一個不被外部程式使用的函式。接下來我們要告訴 pcap使用它來過濾出我們想要的那一個會話。 4.最後,我們告訴pcap進入它的主體執行迴圈。在這個階段內pcap一直工作到它接收了所有我們想要的包為止。每當它收到一個包就呼叫另一個已經定義好的函式,這個函式可以做我們想要的任何工作,它可以剖析所部獲的包並給使用者打印出結果,它可以將結果儲存為一個檔案,或者什麼也不作。 5.在嗅探到所需的資料後,我們要關閉會話並結束。 三、實現libpcap的每一個步驟 (1)設定裝置 這是很簡單的。有兩種方法設定想要嗅探的裝置。 第一種,我們可以簡單的讓使用者告訴我們。考察下面的程式:    #include    #include    int main(int argc, char *argv[])    {    char *dev = argv[1];    printf("Device: %s", dev);    return(0);    } 使用者通過傳遞給程式的第一個引數來指定裝置。字串“dev”以pcap能“理解”的格式儲存了我們要嗅探的介面的名字(當然,使用者必須給了我們一個真正存在的介面)。 另一種也是同樣的簡單。來看這段程式:    #include    #include    int main()    {    char *dev, errbuf[PCAP_ERRBUF_SIZE];    dev = pcap_lookupdev(errbuf);    printf("Device: %s", dev);    return(0);    } (2)開啟裝置進行嗅探 建立一個嗅探會話的任務真的非常簡單。為此,我們使用pcap_open_live()函式。此函式的原型(根據pcap的手冊頁)如下:    pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf) 其第一個引數是我們在上一節中指定的裝置,snaplen是整形的,它定義了將被pcap捕捉的最大位元組數。當promisc設為true時將置指定介面為混雜模式(然而,當它置為false時介面仍處於混雜模式的非凡情況也是有可能的)。to_ms是讀取時的超時值,單位是毫秒(假如為0則一直嗅探直到錯誤發生,為-1則不確定)。最後,ebuf是一個我們可以存入任何錯誤資訊的字串(就像上面的errbuf)。此函式返回其會話控制代碼。 混雜模式與非混雜模式的區別:這兩種方式區別很大。一般來說,非混雜模式的嗅探器中,主機僅嗅探那些跟它直接有關的通訊,如發向它的,從它發出的,或經它路由的等都會被嗅探器捕捉。而在混雜模式中則嗅探傳輸線路上的所有通訊。在非交換式網路中,這將是整個網路的通訊。這樣做最明顯的優點就是使更多的包被嗅探到,它們因你嗅探網路的原因或者對你有幫助,或者沒有。但是,混雜模式是可被探測到的。一個主機可以通過高強度的測試判定另一臺主機是否正在進行混雜模式的嗅探。其次,它僅在非交換式的網路環境中有效工作(如集線器,或者交換中的ARP層面)。再次,在高負荷的網路中,主機的系統資源將消耗的非常嚴重。 (3)過濾通訊 實現這一過程由pcap_compile()與pcap_setfilter()這兩個函式完成。 在使用我們自己的過濾器前必須編譯它。過濾表示式被儲存在一個字串中(字元陣列)。其句法在tcpdump的手冊頁中被證實非常好。我建議你親自閱讀它。但是我們將使用簡單的測試表達式,這樣你可能很輕易理解我的例子。 我們呼叫pcap_compile()來編譯它,其原型是這樣定義的:    int pcap_compile(pcap_t *p, strUCt bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask) 第一個引數是會話控制代碼。接下來的是我們儲存被編譯的過濾器版本的地址的引用。再接下來的則是表示式本身,儲存在規定的字串格式裡。再下邊是一個定義表示式是否被優化的整形量(0為false,1為true,標準規定)。最後,我們必須指定應用此過濾器的網路掩碼。函式返回-1為失敗,其他的任何值都表明是成功的。 表示式被編譯之後就可以使用了。現在進入pcap_setfilter()。仿照我們介紹pcap的格式,先來看一看pcap_setfilter()的原型:    int pcap_setfilter(pcap_t *p, struct bpf_program *fp) 這非常直觀,第一個引數是會話控制代碼,第二個引數是被編譯表示式版本的引用(可推測出它與pcap_compile()的第二個引數相同)。 下面的程式碼示例可能能使你更好的理解:    #include    pcap_t *handle; /* 會話的控制代碼 */    char dev[] = "eth0"; /* 執行嗅探的裝置 */    char errbuf[PCAP_ERRBUF_SIZE]; /* 儲存錯誤 資訊的字串 */    struct bpf_program filter; /*已經編譯好的過濾表示式*/    char filter_app[] = "port 23"; /* 過濾表示式*/    bpf_u_int32 mask; /* 執行嗅探的裝置的網路掩碼 */    bpf_u_int32 net; /* 執行嗅探的裝置的IP地址 */    pcap_lookupnet(dev, &net, &mask, errbuf);    handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);    pcap_compile(handle, &filter, filter_app, 0, net);    pcap_setfilter(handle, &filter); 這個程式使嗅探器嗅探經由埠23的所有通訊,使用混雜模式,裝置是eth0。 (4)實際的嗅探 有兩種手段捕捉包。我們可以一次只捕捉一個包,也可以進入一個迴圈,等捕捉到多個包再進行處理。我們將先看看怎樣去捕捉單個包,然後再看看使用迴圈的方法。為此,我們使用函式pcap_next()。 pcap_next()的原型及其簡單:    u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h) 第一個引數是會話控制代碼,第二個引數是指向一個包括了當前資料包總體資訊(被捕捉時的時間,包的長度,其被指定的部分長度)的結構體的指標(在這裡只有一個片斷,只作為一個示例)。pcap_next()返回一個u_char指標給被這個結構體描述的包。我們將稍後討論這種實際讀取包本身的手段。    這裡有一個演示怎樣使用pcap_next()來嗅探一個包的例子:    #include    #include    int main()    {    pcap_t *handle; /* 會話控制代碼 */    char *dev; /* 執行嗅探的裝置 */    char errbuf[PCAP_ERRBUF_SIZE]; /* 儲存錯誤資訊的字串 */    struct bpf_program filter; /* 已經編譯好的過濾器 */    char filter_app[] = "port 23"; /* 過濾表示式 */    bpf_u_int32 mask; /* 所在網路的掩碼 */    bpf_u_int32 net; /* 主機的IP地址 */    struct pcap_pkthdr header; /* 由pcap.h定義 */    const u_char *packet; /* 實際的包 */    /* Define the device */    dev = pcap_lookupdev(errbuf);    /* 探查裝置屬性 */    pcap_lookupnet(dev, &net, &mask, errbuf);    /* 以混雜模式開啟會話 */    handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);    /* 編譯並應用過濾器 */    pcap_compile(handle, &filter, filter_app, 0, net);    pcap_setfilter(handle, &filter);    /* 截獲一個包 */    packet = pcap_next(handle, &header);    /* 列印它的長度 */    printf("Jacked a packet with length of [%d]    ", header.len);    /* 關閉會話 */    pcap_close(handle);    return(0);    } 這個程式嗅探被pcap_lookupdev()返回的裝置並將它置為混雜模式。它發現第一個包經過埠23(telnet)並且告訴使用者此包的大小(以字 節為單位)。這個程式又包含了一個新的呼叫pcap_close(),我們將在後面討論(儘管它的名字就足夠證實它自己的作用)。 實際上很少有嗅探程式會真正的使用pcap_next()。通常,它們使用pcap_loop()或者 pcap_dispatch()(它就是用了pcap_loop())。 pcap_loop()的原型如下:    int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user) 第一個引數是會話控制代碼,接下來是一個整型,它告訴pcap_loop()在返回前應捕捉多少個數據包(若為負值則表示應該一直工作直至錯誤發生)。第三個引數是回撥函式的名稱(正像其識別符號所指,無括號)。最後一個引數在有些應用裡有用,但更多時候則置為NULL。假設我們有我們自己的想送往回調函式的引數,另外還有pcap_loop()傳送的引數,這就需要用到它。很明顯,必須是一個u_char型別的指標以確保結果正確;正像我們稍後見到的,pcap使用了很有意思的方法以u_char指標的形勢傳遞資訊。pcap_dispatch()的用法幾乎相同。唯一不同的是它們如何處理超時(還記得在呼叫pcap_open_live()時怎樣設定超時嗎?這就是它起作用的地方)。Pcap_loop()忽略超時而pcap_dispatch()則不。關於它們之間區別的更深入的討論請參見pcap的手冊頁。 回撥函式的原型:    void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet); 讓我們更細緻的考察它。首先,你會注重到該函式返回void型別,這是符合邏輯的,因為pcap_loop()不知道如何去處理一個回撥返回值。第一個引數相應於pcap_loop()的最後一個引數。每當回撥函式被老婆 呼叫時,無論最後一個引數傳給pcap_loop()什麼值,這個值都會傳給我們回撥函式的第一個引數。第二個引數是pcap標頭檔案定義的,它包括資料包被嗅探的時間、大小等資訊。結構體pcap_pkhdr在pcap.h中定義如下:    struct pcap_pkthdr {    struct timeval ts; /* 時間戳 */    bpf_u_int32 caplen; /* 已捕捉部分的長度 */    bpf_u_int32 len; /* 該包的離線長度 */    }; 這些量都相當明瞭。最後一個引數在它們中是最有意思的,也最讓pcap程式新手感到迷惑。這又是一個u_char指標,它包含了被pcap_loop()嗅探到的所有包。 但是你怎樣使用這個我們在原型裡稱為packet的變數呢?一個數據包包含許多屬性,因此你可以想象它不只是一個字串,而實質上是一個結構體的集合(比如,一個TCP/IP包會有一個乙太網的頭部,一個IP頭部,一個TCP頭部,還有此包的有效載荷)。這個u_char就是這些結構體的串聯版本。為了使用它,我們必須作一些有趣的匹配工作。 下面這些是一些資料包的結構體:    /* 乙太網幀頭部 */    struct sniff_ethernet {    u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主機的地址 */    u_char ether_shost[ETHER_ADDR_LEN]; /* 源主機的地址 */    u_short ether_type; /* IP? ARP? RARP? etc */    };    /* IP資料包的頭部 */    struct sniff_ip {    #if BYTE_ORDER == LITTLE_ENDIAN    u_int ip_hl:4, /* 頭部長度 */    ip_v:4; /* 版本號 */    #if BYTE_ORDER == BIG_ENDIAN    u_int ip_v:4, /* 版本號 */    ip_hl:4; /* 頭部長度 */    #endif    #endif /* not _IP_VHL */    u_char ip_tos; /* 服務的型別 */    u_short ip_len; /* 總長度 */    u_short ip_id; /*包標誌號 */    u_short ip_off; /* 碎片偏移 */    #define IP_RF 0x8000 /* 保留的碎片標誌 */    #define IP_DF 0x4000 /* dont fragment flag */    #define IP_MF 0x2000 /* 多碎片標誌*/    #define IP_OFFMASK 0x1fff /*分段位 */    u_char ip_ttl; /* 資料包的生存時間 */    u_char ip_p; /* 所使用的協議 */    u_short ip_sum; /* 校驗和 */    struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/    };    /* TCP 資料包的頭部 */    struct sniff_tcp {    u_short th_sport; /* 源埠 */    u_short th_dport; /* 目的埠 */    tcp_seq th_seq; /* 包序號 */    tcp_seq th_ack; /* 確認序號 */    #if BYTE_ORDER == LITTLE_ENDIAN    u_int th_x2:4, /* 還沒有用到 */    th_off:4; /* 資料偏移 */    #endif    #if BYTE_ORDER == BIG_ENDIAN    u_int th_off:4, /* 資料偏移*/    th_x2:4; /*還沒有用到 */    #endif    u_char th_flags;    #define TH_FIN 0x01    #define TH_SYN 0x02    #define TH_RST 0x04    #define TH_PUSH 0x08    #define TH_ACK 0x10    #define TH_URG 0x20    #define TH_ECE 0x40    #define TH_CWR 0x80    #define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)    u_short th_win; /* TCP滑動視窗 */    u_short th_sum; /* 頭部校驗和 */    u_short th_urp; /* 緊急服務位 */    }; pcap嗅探資料包時正是使用的這些結構。接下來,它簡單的建立一個u_char字串並且將這些結構體填入。那麼我們怎樣才能區分它們呢?預備好見證指標最實用的好處之一吧。 我們再一次假定要對乙太網上的TCP/IP包進行處理。同樣的手段可以應用於任何資料包,唯一的區別是你實際所使用的結構體的型別。讓我們從宣告分解u_char包的變數開始:    const struct sniff_ethernet *ethernet; /* 乙太網幀頭部*/    const struct sniff_ip *ip; /* IP包頭部 */    const struct sniff_tcp *tcp; /* TCP包頭部 */    const char *payload; /* 資料包的有效載荷*/    /*為了讓它的可讀性好,我們計算每個結構體中的變數大小*/    int size_ethernet = sizeof(struct sniff_ethernet);    int size_ip = sizeof(struct sniff_ip);    int size_tcp = sizeof(struct sniff_tcp);    現在我們開始讓人感到有些神祕的匹配:    ethernet = (struct sniff_ethernet*)(packet);    ip = (struct sniff_ip*)(packet + size_ethernet);    tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);    payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp); 此處如何工作?考慮u_char在記憶體中的層次。基本的,當pcap將這些結構體填入u_char的時候是將這些資料存入一個字串中,那個字串將被送入我們的回撥函式中。反向轉換是這樣的,不考慮這些結構體制中的值,它們的大小將是一致的。例如在我的平臺上,一個sniff_ethernet結構體的大小是14位元組。一個sniff_ip結構體是20位元組,一個sniff_tcp結構體也是20位元組。 u_char指標正是包含了記憶體地址的一個變數,這也是指標的實質,它指向記憶體的一個區域。簡單而言,我們說指標指向的地址為x,假如三個結構體恰好線性排列,第一個(sniff_ethernet)被裝載到記憶體地址的x處則我們很輕易的發現其他結構體的地址,讓我們以表格顯示之:    Variable Location (in bytes)    sniff_ethernet X    sniff_ip X + 14    sniff_tcp X + 14 + 20    payload X + 14 + 20 + 20 結構體sniff_ethernet正好在x處,緊接著它的sniff_ip則位於x加上它本身佔用的空間(此例為14位元組),依此類推可得全部地址。 注重:你沒有假定你的變數也是同樣大小是很重要的。你應該總是使用sizeof()來確保尺寸的正確。這是因為這些結構體中的每個成員在不同平臺下可以有不同的尺寸。 下面是主要函式介面: pcap_t *pcap_open_live(char *device, int snaplen,    int promisc, int to_ms, char *ebuf)    獲得用於捕獲網路資料包的資料包捕獲描述字。device引數為指定開啟    的網路裝置名。snaplen引數定義捕獲資料的最大位元組數。promisc指定    是否將網路介面置於混雜模式。to_ms引數指定超時時間(毫秒)。    ebuf引數則僅在pcap_open_live()函數出錯返回NULL時用於傳遞錯誤消    息。 pcap_t *pcap_open_offline(char *fname, char *ebuf)    開啟以前儲存捕獲資料包的檔案,用於讀取。fname引數指定開啟的文    件名。該檔案中的資料格式與tcpdump和tcpslice相容。"-"為標準輸    入。ebuf引數則僅在pcap_open_offline()函數出錯返回NULL時用於傳    遞錯誤訊息。 pcap_dumper_t *pcap_dump_open(pcap_t *p, char *fname)    開啟用於儲存捕獲資料包的檔案,用於寫入。fname引數為"-"時表示    標準輸出。出錯時返回NULL。p引數為呼叫pcap_open_offline()或    pcap_open_live()函式後返回的pcap結構指標。fname引數指定開啟    的檔名。如果返回NULL,則可呼叫pcap_geterr()函式獲取錯誤消    息。 char *pcap_lookupdev(char *errbuf)    用於返回可被pcap_open_live()或pcap_lookupnet()函式呼叫的網路    裝置名指標。如果函數出錯,則返回NULL,同時errbuf中存放相關的    錯誤訊息。 int pcap_lookupnet(char *device, bpf_u_int32 *netp,    bpf_u_int32 *maskp, char *errbuf)    獲得指定網路裝置的網路號和掩碼。netp引數和maskp引數都是    bpf_u_int32指標。如果函數出錯,則返回-1,同時errbuf中存放相    關的錯誤訊息。 int pcap_dispatch(pcap_t *p, int cnt,    pcap_handler callback, u_char *user)    捕獲並處理資料包。cnt引數指定函式返回前所處理資料包的最大值。    cnt=-1表示在一個緩衝區中處理所有的資料包。cnt=0表示處理所有    資料包,直到產生以下錯誤之一:讀取到EOF;超時讀取。callback    引數指定一個帶有三個引數的回撥函式,這三個引數為:一個從    pcap_dispatch()函式傳遞過來的u_char指標,一個pcap_pkthdr結構    的指標,和一個數據包大小的u_char指標。如果成功則返回讀取到的    位元組數。讀取到EOF時則返回零值。出錯時則返回-1,此時可呼叫    pcap_perror()或pcap_geterr()函式獲取錯誤訊息。 int pcap_loop(pcap_t *p, int cnt,    pcap_handler callback, u_char *user)    功能基本與pcap_dispatch()函式相同,只不過此函式在cnt個數據包    被處理或出現錯誤時才返回,但讀取超時不會返回。而如果為    pcap_open_live()函式指定了一個非零值的超時設定,然後呼叫    pcap_dispatch()函式,則當超時發生時pcap_dispatch()函式會返回。    cnt引數為負值時pcap_loop()函式將始終迴圈執行,除非出現錯誤。 void pcap_dump(u_char *user, struct pcap_pkthdr *h,    u_char *sp)    向呼叫pcap_dump_open()函式開啟的檔案輸出一個數據包。該函式可    作為pcap_dispatch()函式的回撥函式。 int pcap_compile(pcap_t *p, struct bpf_program *fp,    char *str, int optimize, bpf_u_int32 netmask)    將str引數指定的字串編譯到過濾程式中。fp是一個bpf_program結    構的指標,在pcap_compile()函式中被賦值。optimize引數控制結果    程式碼的優化。netmask引數指定本地網路的網路掩碼。 int pcap_setfilter(pcap_t *p, struct bpf_program *fp)    指定一個過濾程式。fp引數是bpf_program結構指標,通常取自    pcap_compile()函式呼叫。出錯時返回-1;成功時返回0。 u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)    返回指向下一個資料包的u_char指標。 int pcap_datalink(pcap_t *p)    返回資料鏈路層型別,例如DLT_EN10MB。 int pcap_snapshot(pcap_t *p)    返回pcap_open_live被呼叫後的snapshot引數值。 int pcap_is_swapped(pcap_t *p)    返回當前系統主機位元組與被開啟檔案的位元組順序是否不同。 int pcap_major_version(pcap_t *p)    返回寫入被開啟檔案所使用的pcap函式的主版本號。 int pcap_minor_version(pcap_t *p)    返回寫入被開啟檔案所使用的pcap函式的輔版本號。 int pcap_stats(pcap_t *p, struct pcap_stat *ps)    向pcap_stat結構賦值。成功時返回0。這些數值包括了從開始    捕獲資料以來至今共捕獲到的資料包統計。如果出錯或不支援    資料包統計,則返回-1,且可呼叫pcap_perror()或    pcap_geterr()函式來獲取錯誤訊息。 FILE *pcap_file(pcap_t *p)    返回被開啟檔案的檔名。 int pcap_fileno(pcap_t *p)    返回被開啟檔案的檔案描述字號碼。 void pcap_perror(pcap_t *p, char *prefix)    在標準輸出裝置上顯示最後一個pcap庫錯誤訊息。以prefix參    數指定的字串為訊息頭。 char *pcap_geterr(pcap_t *p)    返回最後一個pcap庫錯誤訊息。 char *pcap_strerror(int error)    如果strerror()函式不可用,則可呼叫pcap_strerror函式替代。 void pcap_close(pcap_t *p)    關閉p引數相應的檔案,並釋放資源。 libpcap(Packet Capture Library),即資料包捕獲函式庫,是Unix/Linux平臺下的網路資料包捕獲函式庫。它是一個獨立於系統的使用者層包捕獲的API介面,為底層網路監測提供了一個可移植的框架。 一、libpcap工作原理 libpcap主要由兩部份組成:網路分接頭(Network Tap)和資料過濾器(Packet Filter)。網路分接頭從網路裝置驅動程式中收集資料拷貝,過濾器決定是否接收該資料包。Libpcap利用BSD Packet Filter(BPF)演算法對網絡卡接收到的鏈路層資料包進行過濾。BPF演算法的基本思想是在有BPF監聽的網路中,網絡卡驅動將接收到的資料包複製一份交給BPF過濾器,過濾器根據使用者定義的規則決定是否接收此資料包以及需要拷貝該資料包的那些內容,然後將過濾後的資料給與過濾器相關聯的上層應用程式。 libpcap的包捕獲機制就是在資料鏈路層加一個旁路處理。當一個數據包到達網路介面時,libpcap首先利用已經建立的Socket從鏈路層驅動程式中獲得該資料包的拷貝,再通過Tap函式將資料包發給BPF過濾器。BPF過濾器根據使用者已經定義好的過濾規則對資料包進行逐一匹配,匹配成功則放入核心緩衝區,並傳遞給使用者緩衝區,匹配失敗則直接丟棄。如果沒有設定過濾規則,所有資料包都將放入核心緩衝區,並傳遞給使用者層緩衝區。 二、libpcap的抓包框架 pcap_lookupdev()函式用於查詢網路裝置,返回可被pcap_open_live()函式呼叫的網路裝置名指標。 pcap_open_live()函式用於開啟網路裝置,並且返回用於捕獲網路資料包的資料包捕獲描述字。對於此網路裝置的操作都要基於此網路裝置描述字。 pcap_lookupnet()函式獲得指定網路裝置的網路號和掩碼。 pcap_compile()函式用於將使用者制定的過濾策略編譯到過濾程式中。 pcap_setfilter()函式用於設定過濾器。 pcap_loop()函式pcap_dispatch()函式用於捕獲資料包,捕獲後還可以進行處理,此外pcap_next()和pcap_next_ex()兩個函式也可以用來捕獲資料包。 pcap_close()函式用於關閉網路裝置,釋放資源。 其實pcap的應用程式格式很簡單,總的來說可以可以分為以下5部分,相信看了以下的5部分,大概能對pcap的總體佈局有個大概的瞭解了吧: 1.我們從決定用哪一個介面進行嗅探開始。在Linux中,這可能是eth0,而在BSD系統中則可能是xl1等等。我們也可以用一個字串來定義這個裝置,或者採用pcap提供的介面名來工作。 2.初始化pcap。在這裡我們要告訴pcap對什麼裝置進行嗅探。假如願意的話,我們還可以嗅探多個裝置。怎樣區分它們呢?使用 檔案控制代碼。就像開啟一個檔案進行讀寫一樣,必須命名我們的嗅探“會話”,以此使它們各自區別開來。 3.假如我們只想嗅探特定的傳輸(如TCP/IP包,發往埠23的包等等),我們必須建立一個規則集合,編譯並且使用它。這個過程分為三個相互緊密關聯的階段。規則集合被置於一個字串內,並且被轉換成能被pcap讀的格式(因此編譯它)。編譯實際上就是在我們的程式裡呼叫一個不被外部程式使用的函式。接下來我們要告訴 pcap使用它來過濾出我們想要的那一個會話。 4.最後,我們告訴pcap進入它的主體執行迴圈。在這個階段內pcap一直工作到它接收了所有我們想要的包為止。每當它收到一個包就呼叫另一個已經定義好的函式,這個函式可以做我們想要的任何工作,它可以剖析所部獲的包並給使用者打印出結果,它可以將結果儲存為一個檔案,或者什麼也不作。 5.在嗅探到所需的資料後,我們要關閉會話並結束。 三、實現libpcap的每一個步驟 (1)設定裝置 這是很簡單的。有兩種方法設定想要嗅探的裝置。 第一種,我們可以簡單的讓使用者告訴我們。考察下面的程式:    #include    #include    int main(int argc, char *argv[])    {    char *dev = argv[1];    printf("Device: %s", dev);    return(0);    } 使用者通過傳遞給程式的第一個引數來指定裝置。字串“dev”以pcap能“理解”的格式儲存了我們要嗅探的介面的名字(當然,使用者必須給了我們一個真正存在的介面)。 另一種也是同樣的簡單。來看這段程式:    #include    #include    int main()    {    char *dev, errbuf[PCAP_ERRBUF_SIZE];    dev = pcap_lookupdev(errbuf);    printf("Device: %s", dev);    return(0);    } (2)開啟裝置進行嗅探 建立一個嗅探會話的任務真的非常簡單。為此,我們使用pcap_open_live()函式。此函式的原型(根據pcap的手冊頁)如下:    pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf) 其第一個引數是我們在上一節中指定的裝置,snaplen是整形的,它定義了將被pcap捕捉的最大位元組數。當promisc設為true時將置指定介面為混雜模式(然而,當它置為false時介面仍處於混雜模式的非凡情況也是有可能的)。to_ms是讀取時的超時值,單位是毫秒(假如為0則一直嗅探直到錯誤發生,為-1則不確定)。最後,ebuf是一個我們可以存入任何錯誤資訊的字串(就像上面的errbuf)。此函式返回其會話控制代碼。 混雜模式與非混雜模式的區別:這兩種方式區別很大。一般來說,非混雜模式的嗅探器中,主機僅嗅探那些跟它直接有關的通訊,如發向它的,從它發出的,或經它路由的等都會被嗅探器捕捉。而在混雜模式中則嗅探傳輸線路上的所有通訊。在非交換式網路中,這將是整個網路的通訊。這樣做最明顯的優點就是使更多的包被嗅探到,它們因你嗅探網路的原因或者對你有幫助,或者沒有。但是,混雜模式是可被探測到的。一個主機可以通過高強度的測試判定另一臺主機是否正在進行混雜模式的嗅探。其次,它僅在非交換式的網路環境中有效工作(如集線器,或者交換中的ARP層面)。再次,在高負荷的網路中,主機的系統資源將消耗的非常嚴重。