libpcap使用整理
libpcap
簡介
Libpcap是Packet Capture Libray的英文縮寫,即數據包捕獲函數庫。該庫提供的C函數接口用于捕捉經過指定網絡接口的數據包,該接口應該是被設為混雜模式。這個在原始套接字中有提到。
功能
Libpcap主要有如下功能:
(1)數據包捕獲
捕獲流經本網卡的所有原始數據包,甚至對交換設備中的數據包也能夠進行捕獲,本功能是嗅探器的基礎。
(2)自定義數據包發送
構造任意格式的原始數據包,并發送到目標網絡,本功能是新協議驗證、甚至攻擊驗證的基礎。
(3)流量采集與統計
對所采集到的網絡中的流量信息進行按照新規則分類,按指標進行統計,并輸出到指定終端。利用這項功能可以分析目標網絡的流量特性。
(4)規則過濾
Libpcap自帶規則過濾功能,并提供腳本編程接口,能夠按照用戶編程的方式對已經采集到的數據包進行過濾,以便提高分析的性能。
原理
一個包捕獲機制包含三個主要部分,分別是面向底層的包捕獲引擎,面向中間層的數據包過濾器,面向應用層的用戶接口。
Linux操作系統對于數據包的處理流程是從底到上的方式,依次經歷網絡接口卡、網卡驅動層、數據鏈路層、IP層、傳輸層,最后到達應用程序。
Libpcap也是基于這種原理,Libpcap的捕獲機制并不影響Linux操作系統中網絡協議棧對數據包的處理。
對應用程序而言,Libpcap包捕獲機制只是提供了一個統一的API接口,用戶只需要按照相關的編程流程,簡單地調用若干函數就可以捕獲到感興趣的數據包。
具體來說,Libpcap庫主要由三個部分組成,網絡分接頭、數據包過濾器和用戶API。
(1)網絡分接頭
網絡分接頭(Network Tap)是一種鏈路層旁路機制,負責采集網卡數據包。
(2) 數據包過濾器
數據包過濾器(Packet Filter)是針對數據包的一種過濾機制,在Libpcap中采用BPF(BSD Packet Filter)算法對數據包執行過濾操作,這種算法的基本思想就是基于規則匹配,對伊符合條件的額數據包進行放行。
(3) 用戶API
用戶API是Libpcap面向上層應用程序提供的編程接口,用戶通過調用相關的函數實現數據包的捕獲或者發送。
具體來說,Libpcap的工作原理可以描述為,當一個數據包到達網卡時,Libpcap利用創建的套接字從鏈路層驅動程序中獲得該數據包的拷貝,即旁路機制,同時通過Tap函數將數據包發給BPF過濾器。
BPF過濾器根據用戶已經定義好的過濾過則對數據包進行逐一匹配,若匹配成功則放入內核緩沖區,并傳遞給用戶緩沖區,匹配失敗則直接丟棄。如果沒有設置過濾規則,所有的數據包都將放入內核緩沖區,并傳遞給用戶緩沖區。
pcap基本工作流程
(1)確定將要嗅探的接口,在linux下是類似eth0的東西。在BSD下是類似xll的東西。可以在一個字符串中聲明設備,也可以讓pcap提供備選接口(我們想要嗅探的接口)的名字。
(2)初始化pcap,此時才真正告訴pcap我們要嗅探的具體接口,只要我們愿意,我們可以嗅探多個接口。但是如何區分多個接口呢,使用文件句柄。就像讀寫文件時使用文件句柄一樣。我們必須給嗅探任務命名,以至于區分不同的嗅探任務。
(3)指定過濾規則,當我們只想嗅探特殊的流量時(例如,僅僅嗅探TCP/IP包、僅僅嗅探經過端口80的包,等等)我們必須設定一個規則集,“編譯”并應用它。這是一個三相的并且緊密聯系的過程,規則集存儲與字符串中,在“編譯”之后會轉換成pcap可以讀取的格式。“編譯過程”實際上是調用自定義的函數完成的,不涉及外部的函數。然后我們可以告訴pcap在我們想要過濾的任何任務上實施。
(4)抓包,最后,告訴pcap進入主要的執行循環中,在此階段,在接收到任何我們想要的包之前pcap將一直循環等待。在每次抓取到一個新的數據包時,它將調用另一個自定義的函數,我們可以在這個函數中肆意妄為,例如,解析數據包并顯示數據內容、保存到文件或者什么都不做等等。
當嗅探完美任務完成時,記得關掉任務。
下面是pcap工作流程圖(摘自官網)
用戶級API
1)獲取數據包捕獲描述字
函數名稱: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時用于傳遞錯誤消息。
2)打開保存捕獲數據包文件
函數名稱:pcap_t *pcap_open_offline(char *fname, char *ebuf)
函數功能:打開以前保存捕獲數據包的文件,用于讀取。
參數說明:fname參數指定打開的文件名。該文件中的數據格式與tcpdump和tcpslice兼容。”-“為標準輸入。ebuf參數則僅在pcap_open_offline()函數出錯返回NULL時用于傳遞錯誤消息。
**3)轉儲數據包
函數名稱: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()函數獲取錯誤消息。
4)查找網絡設備
函數名稱:char *pcap_lookupdev(char *errbuf)
函數功能:用于返回可被pcap_open_live()或pcap_lookupnet()函數調用的網絡設備名指針。
返回值:如果函數出錯,則返回NULL,同時errbuf中存放相關的錯誤消息。
5)獲取網絡號和掩碼
函數名稱:int pcap_lookupnet(char *device, bpf_u_int32 *netp,bpf_u_int32 *maskp, char *errbuf)
函數功能:獲得指定網絡設備的網絡號和掩碼。
參數說明:netp參數和maskp參數都是bpf_u_int32指針。
**返回值:**如果函數出錯,則返回-1,同時errbuf中存放相關的錯誤消息。
6)捕獲并處理數據包
** 函數名稱**: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()函數獲取錯誤消息。
7)捕獲和處理數據包
函數名稱: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()函數將始終循環運行,除非出現錯誤。
8)輸出數據包
函數名稱:void pcap_dump(u_char *user, struct pcap_pkthdr *h,u_char *sp)
函數功能:向調用pcap_dump_open()函數打開的文件輸出一個數據包。該函數可作為pcap_dispatch()函數的回調函數。
參數說明: 參數1: 所建立的文件pcap_dump_open()的返回值,要進行強制轉換.;參數2: 數據包特有的內容.;參數 3: 數據包內容指針
9)編譯字串至過濾程序
函數名稱:int pcap_compile(pcap_t *p, struct bpf_program *fp,char *str, int optimize, bpf_u_int32 netmask)
函數功能:將str參數指定的字符串編譯到過濾程序中。
參數說明:
- p pcap_t類型句柄
- fp是一個bpf_program結構的指針,在pcap_compile()函數中被賦值。
- str是過濾表達式
- optimize 表示是否需要優化過濾表達式。
- netmask參數指定本地網絡的網絡掩碼。
10)指定過濾程序
函數名稱:int pcap_setfilter(pcap_t p, struct bpf_program fp)
函數功能:指定一個過濾程序。
**參數說明:**fp參數是bpf_program結構指針,通常取自pcap_compile()函數調用。
** 返回值:出錯時返回-1;成功時返回0
11)獲取下一個數據包
函數名稱:u_char pcap_next(pcap_t p, struct pcap_pkthdr *h)
** 函數功能:返回指向下一個數據包的u_char指針
12)獲取數據鏈路層類型
**函數名稱:*int pcap_datalink(pcap_t p)
** 函數功能:返回數據鏈路層類型,例如DLT_EN10MB
13)獲取快照參數值
函數名稱:int pcap_snapshot(pcap_t p)
** 函數功能*:返回pcap_open_live被調用后的snapshot參數值
14)檢測字節順序
函數名稱:int pcap_is_swapped(pcap_t *p)
**函數功能:**返回當前系統主機字節與被打開文件的字節順序是否不同
15)獲取主版本號
** 函數名稱**:int pcap_major_version(pcap_t *p)
函數功能:返回寫入被打開文件所使用的pcap函數的主版本號
16)獲取輔版本號
函數名稱:int pcap_minor_version(pcap_t p)
** 函數功能*:返回寫入被打開文件所使用的pcap函數的輔版本號
17)結構賦值
函數名稱:int pcap_stats(pcap_t *p, struct pcap_stat *ps)
函數功能:向pcap_stat結構賦值。成功時返回0。這些數值包括了從開始捕獲數據以來至今共捕獲到的數據包統計。如果出錯或不支持數據包統計,則返回-1,且可調用pcap_perror()或pcap_geterr()函數來獲取錯誤消息。
18)獲取打開文件名
函數名稱:FILE *pcap_file(pcap_t *p)
函數功能:返回被打開文件的文件名。
19)獲取描述字號碼
函數名稱:int pcap_fileno(pcap_t *p)
函數功能:返回被打開文件的文件描述字號碼
20)顯示錯誤消息
函數名稱:void pcap_perror(pcap_t *p, char *prefix)
函數功能:在標準輸出設備上顯示最后一個pcap庫錯誤消息。以prefix參數指定的字符串為消息頭。示最后一個pcap庫錯誤消息。以prefix參數指定的字符串為消息頭。
21)用來關閉pcap_dump_open打開的文件
函數名稱:void pcap_dump_close(pcap_dumper_t *p);
函數功能:用來關閉pcap_dump_open打開的文件,入參是pcap_dump_open返回的指針;
21)刷新緩沖區
函數名稱:int pcap_dump_flush(pcap_dumper_t *p)
函數功能:刷新緩沖區,把捕獲的數據包從緩沖區真正拷貝到文件;
example
main.c
#include <stdio.h> #include "pcaptest.h"/* 我盡量讓每一個例子都完全獨立,在一個到兩個函數之間完成 而不是去封裝重復的內容,這樣做在實際開發中肯定是沒有好處的, 唯一的好處是 在觀看例子的時候 我不需要去理解函數的調用關系,因為基本上就一到兩個函數直接調用。 所以哪怕重復 我也不做修改,反而可能會刻意重復。 參考鏈接 https://www.devdungeon.com/content/using-libpcap-c 這個鏈接教程非常棒 */int main() {//getHostDeviceInfo();//openNetDev(NULL);//testCapNext();//testCaploop();//testCapAndSaveFile();//testOpenCapFile();//testSetFilter();//testParseEtherData();//testParseIPData();testParseTCPData();return 0; }pcaptest.h
#ifndef PCAPTEST_H #define PCAPTEST_H//獲取本地網卡設備信息 void getHostDeviceInfo(void);//打開網卡并獲取屬性 void testOpenDev(char*network);//測試捕獲數據 next方式 void testCapNext(void);//測試捕獲方式 loop方式 void testCaploop(void);//設置過濾 void testSetFilter(void);//測試捕獲數據保存為文件 void testCapAndSaveFile(void);//測試打開捕獲文件并解析 void testOpenCapFile(void);//解析以太網鏈路層 void testParseEtherData(void); //解析ip層 void testParseIPData(void); //解析tcp void testParseTCPData(void);#endif // PCAPTEST_Hpcaptest.c
#include "pcaptest.h" #include <pcap.h> #include <arpa/inet.h> #include <stdlib.h> #include <netinet/in.h> #include <netinet/if_ether.h>//ip地址轉換示例 void TestIpv6() {char ipv6_addr[64];//內嵌 IPv4 地址的 IPv6 地址//該函數將字符串 src 轉換為 af 地址族中的網絡地址結構,然后將該網絡地址結構復制到 dst。af 參數必須是 AF_INET 或 AF_INET6。//inet_pton() 成功時返回 1(網絡地址已成功轉換)。如果 src 不包含表示指定地址族中有效網絡地址的字符串,則返回 0。//如果 af 不包含有效的地址族,則返回 -1 并將 errno 設置為 EAFNOSUPPORT。inet_pton(AF_INET6, "0:0:0:0:0:0:192.168.200.65", ipv6_addr);printf("%s\n", ipv6_addr);char ipv6_str[64] = {'\0'};//該函數將af地址族中的網絡地址結構src轉換為字符串。結果字符串被復制到 dst 指向的緩沖區,該緩沖區必須是一個非 NULL 指針。調用者在參數大小中指定此緩沖區中可用的字節數。//成功時,inet_ntop() 返回一個指向 dst 的非 NULL 指針。如果發生錯誤,則返回 NULL,并設置 errno 以指示錯誤。inet_ntop(AF_INET6, ipv6_addr, ipv6_str, 64);printf("%s\n", ipv6_str); }void TestIpv4() {int ipv4_addr;inet_pton(AF_INET, "192.168.200.65", &ipv4_addr);printf("%d\n", ipv4_addr);char ipv4_str[64] = {'\0'};inet_ntop(AF_INET, &ipv4_addr, ipv4_str, 64);printf("%s\n", ipv4_str); } /*******************************************內部調用**************************************************/ // 解析鏈路層 ip TCP void printPKInfo(const struct pcap_pkthdr *header, const u_char *packet){printf("數據包捕獲時間: %s", ctime(&header->ts.tv_sec));printf("數據包捕獲長度: %d\n", header->caplen);printf("數據包長度 %d\n", header->len);for (int i = 0; i < header->caplen; ++i) {if (i % 8 == 0 && i > 7)printf("\n");printf("%x\t", packet[i]);}printf("\n");struct ether_header *eth_header = (struct ether_header *) packet;if (ntohs(eth_header->ether_type) != ETHERTYPE_IP) {return;}/* Pointers to start point of various headers */const u_char *ip_header;const u_char *tcp_header;const u_char *payload;/* Header lengths in bytes */const int ethernet_header_length = 14; /* Doesn't change *//* Find start of IP header */ip_header = packet + ethernet_header_length;//ip層前8個位分別四位版本 四位長度int ip_header_length = ((*ip_header) & 0x0F);//首部長度的記錄都是按照4個字節的單位進行增減的,所以我們算出4bit的首部長度的值之后,乘以4個字節,就可以知道首部的長度了,一個字節代表了8bitip_header_length = ip_header_length * 4;printf("IP 頭長度: %d\n", ip_header_length);//獲取協議層u_char protocol = *(ip_header + 9);if (protocol != IPPROTO_TCP) {printf("Not a TCP packet. Skipping...\n\n");return;}//獲取ip地址int src, dest;memcpy(&src, ip_header + 12, 4);memcpy(&dest, ip_header + 16, 4);char ipv4_str[64] = {'\0'};inet_ntop(AF_INET, &src, ipv4_str, 64);printf("源IP:%s\n", ipv4_str);char ipv4_dest[64] = {'\0'};inet_ntop(AF_INET, &dest, ipv4_dest, 64);printf("目標IP:%s\n", ipv4_dest);//tcp頭開始位置tcp_header = packet + ethernet_header_length + ip_header_length;uint16_t srcport1, srcport2,destport1,destport2;memcpy(&srcport1, tcp_header , 2);memcpy(&destport1, tcp_header+ 2, 2);srcport2 = ntohs(srcport1);destport2 = ntohs(destport1);printf("srcPort:%d\n",srcport2);printf("destport:%d\n",destport2);//tcp長度 tcp首部偏移12字節后 前四位為長度int tcp_header_length = ((*(tcp_header + 12)) & 0xF0) >> 4;tcp_header_length = tcp_header_length * 4;printf("TCP 頭長度: %d\n", tcp_header_length);/* 協議頭總大小 */int total_headers_size = ethernet_header_length + ip_header_length + tcp_header_length;printf("所有協議頭總長度: %d bytes\n", total_headers_size);//數據長度int payload_length = header->caplen - (ethernet_header_length + ip_header_length + tcp_header_length);printf("有效數據長度: %d bytes\n", payload_length);//數據頭payload = packet + total_headers_size;printf("有效數據內存地址: %p\n", payload);printf("有效數據:[\t");if (payload_length > 0) {const u_char *temp_pointer = payload;int byte_count = 0;while (byte_count++ < payload_length) {printf("%c", *temp_pointer);temp_pointer++;}}printf("]\n\n"); }//回調函數 loop回調用 void my_loop(u_char *args,const struct pcap_pkthdr *header,const u_char *packet){printPKInfo(header,packet);return }//回調函數 保存文件回調 void processPacket(u_char *arg, const struct pcap_pkthdr *pkthdr, const u_char *packet) {/*void pcap_dump(u_char *user, struct pcap_pkthdr *h,u_char *sp)向調用pcap_dump_open()函數打開的文件輸出一個數據包。該函數可作為pcap_dispatch()函數的回調函數。*/pcap_dump(arg, pkthdr, packet);printf("Received Packet Size: %d\n", pkthdr->len);return; }//解析數據 void testParseData(pcap_handler func) {char error_buffer[PCAP_ERRBUF_SIZE];char *device = pcap_lookupdev(error_buffer); //獲取第一個可以捕獲的設備if (device == NULL) {printf("錯誤 未找到設備: %s\n", error_buffer);return ;}/* 打開網絡接口 */pcap_t *handle = pcap_open_live(device,//設備名稱BUFSIZ,//抓取個數 最大不超過655351, //混合模式10000, //超時時間error_buffer);struct bpf_program filter;char filter_exp[] = "port 3306";if (pcap_compile(handle, &filter, filter_exp, 0, 0) == -1) {printf("Bad filter - %s\n", pcap_geterr(handle));return ;}if (pcap_setfilter(handle, &filter) == -1) {printf("Error setting filter - %s\n", pcap_geterr(handle));return ;}pcap_loop(handle, 20, func, NULL);pcap_close(handle);return ; }//回調函數 解析鏈路層 void my_parseEth(u_char *args,const struct pcap_pkthdr *header,const u_char *packet ) {// 數據包肯定是大于ether_ header結構,// 但我們只想看看數據包標頭的第一部分。我們強制編譯器將指向數據包的指針視為ether_ header指針結構。// 數據包的數據有效載荷到達在標題之后。不同的數據包類型具有不同的報頭長度,但以太網報頭始終相同(14字節)IP層則為20字節(但存在可變長度,不過一般不會有)// struct ether_header// {// uint8_t ether_dhost[ETH_ALEN]; /* destination eth addr */// uint8_t ether_shost[ETH_ALEN]; /* source ether addr */// uint16_t ether_type; /* packet type ID field */// } __attribute__((__packed__));struct ether_header *eth_header = (struct ether_header *) packet;if (ntohs(eth_header->ether_type) != ETHERTYPE_IP) {printf("Not an IP packet. Skipping...\n\n");return;}printf("Total packet available: %d bytes\n", header->caplen);printf("Expected packet size: %d bytes\n", header->len);if (ntohs(eth_header->ether_type) == ETHERTYPE_IP) {printf("IP\n");} else if (ntohs(eth_header->ether_type) == ETHERTYPE_ARP) {printf("ARP\n");} else if (ntohs(eth_header->ether_type) == ETHERTYPE_REVARP) {printf("Reverse ARP\n");} }//回調函數 解析IP void my_parseIP(u_char *args,const struct pcap_pkthdr *header,const u_char *packet ) {/* 鏈路層長度 */int ethernet_header_length = 14; /* 不要修改 鏈路層固定為14字節 */struct ether_header *eth_header = (struct ether_header *) packet;if (ntohs(eth_header->ether_type) != ETHERTYPE_IP) {printf("Not an IP packet. Skipping...\n\n");return;}/* ip層指針 */const u_char *ip_header;/*ip頭開頭 包頭+14(鏈路層長度)*/ip_header = packet + ethernet_header_length;//ip層長度 可變 一般為20字節 ip層前8個位分別四位版本 四位長度int ip_header_length = ((*ip_header) & 0x0F);//首部長度的記錄都是按照4個字節的單位進行增減的,所以我們算出4bit的首部長度的值之后,乘以4個字節,就可以知道首部的長度了,一個字節代表了8bitip_header_length = ip_header_length * 4;printf("IP header length (IHL) in bytes: %d\n", ip_header_length);//獲取協議層u_char protocol = *(ip_header + 9);if (protocol != IPPROTO_TCP) {printf("Not a TCP packet. Skipping...\n\n");return;}//獲取ip地址int src, dest;memcpy(&src, ip_header + 12, 4);memcpy(&dest, ip_header + 16, 4);char ipv4_str[64] = {'\0'};inet_ntop(AF_INET, &src, ipv4_str, 64);printf("%s\n", ipv4_str);char ipv4_dest[64] = {'\0'};inet_ntop(AF_INET, &dest, ipv4_dest, 64);printf("%s\n", ipv4_dest); }/*******************************************外部調用**************************************************/ //獲取本地網卡設備信息 void getHostDeviceInfo() {//interface結構體pcap_if_t *devlist, *curr;//網絡地址類pcap_addr_t *addr;char errbuf[PCAP_ERRBUF_SIZE];//查找所有設備if (pcap_findalldevs(&devlist, errbuf)) {printf("pcap: %s\n", errbuf);return ;}//遍歷設備for (curr = devlist; curr; curr = curr->next) {for (addr = curr->addresses; addr; addr = addr->next) {struct sockaddr *realaddr;if (addr->addr)realaddr = addr->addr;else if (addr->dstaddr)realaddr = addr->dstaddr;elsecontinue;//ipv4或ipv6if (realaddr->sa_family == AF_INET || realaddr->sa_family == AF_INET6) {//打印屬性struct sockaddr_in *sin = (struct sockaddr_in *) realaddr;if (sin->sin_addr.s_addr) {printf("dev_name: %s\t desc:%s\t flags:%d\t ip:%s\n", curr->name,curr->description, curr->flags, inet_ntoa(sin->sin_addr));}}}}//釋放資源pcap_freealldevs(devlist); }void testOpenDev(char *network) {char *networkIF = NULL;char errbuf[PCAP_ERRBUF_SIZE];//獲取第一個合適的網絡接口的字符串指針if (network != NULL) {networkIF = network;} else {networkIF = pcap_lookupdev(errbuf);}if (networkIF == NULL) {printf("err:%s\n", errbuf);return ;}printf("interName : %s\n", networkIF);//打開網絡接口/*pcap_t * pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char * errbuf)device : 網絡接口字符串,可以直接使用硬編碼。snaplen: 對于每個數據包,從開頭要抓多少個字節,我們可以設置這個值來只抓每個數據包的頭部,而不關心具體的內容。典型的以太網幀長度是1518字節,但其他的某些協議的數據包會更長一點,但任何一個協議的一個數據包長度都必然小于65535個字節。promisc: 指定是否打開混雜模式(Promiscuous Mode),0表示非混雜模式,任何其他值表示混合模式。如果要打開混雜模式,那么網卡必須也要打開混雜模式,可以使用如下的命令打開eth0混雜模式:ifconfig eth0 promiscto_ms: 指定需要等待的毫秒數,超過這個數值后就會立即返回。0表示一直等待直到有數據包到來。errbuf: 存放出錯信息的數組。*/pcap_t *pcap = pcap_open_live(networkIF, 65535, 0, 0, errbuf);if (pcap == NULL) {printf("err:%s\n", errbuf);return ;}/*int pcap_lookupnet(char *device, bpf_u_int32 * netp, bpf_u_int32 * maskp, char *errbuf);功能:獲得指定網絡設備的網絡號和掩碼函數:device: 網絡設備名netp: 存放網絡號的指針maskp: 存放掩碼的指針errbuf: 存放出錯信息返回值:成功:0,失敗: -1*/bpf_u_int32 buf, mask;if (pcap_lookupnet(networkIF, &buf, &mask, errbuf) != 0) {printf("err:%s\n", errbuf);return ;}char ipaddr[64] = {'\0'};//獲取本地ip地址方式未找到 暫時先使用如下方式{//interface結構體pcap_if_t *devlist, *curr;//網絡地址類pcap_addr_t *addr;char errbuf[PCAP_ERRBUF_SIZE];//查找所有設備if (pcap_findalldevs(&devlist, errbuf)) {printf("pcap: %s\n", errbuf);return ;}//遍歷設備for (curr = devlist; curr; curr = curr->next) {for (addr = curr->addresses; addr; addr = addr->next) {struct sockaddr *realaddr;if (addr->addr)realaddr = addr->addr;else if (addr->dstaddr)realaddr = addr->dstaddr;elsecontinue;//ipv4或ipv6if (realaddr->sa_family == AF_INET || realaddr->sa_family == AF_INET6) {//拷貝struct sockaddr_in *sin = (struct sockaddr_in *) realaddr;if (sin->sin_addr.s_addr && !strcmp(curr->name, networkIF)) {strcpy(ipaddr, inet_ntoa(sin->sin_addr));break;}}}}//釋放資源pcap_freealldevs(devlist);}printf("ipAddr:%s\n", ipaddr);char ipv4_str[64] = {'\0'};inet_ntop(AF_INET, &buf, ipv4_str, 64);printf("網絡號: %s\n", ipv4_str);inet_ntop(AF_INET, &mask, ipv4_str, 64);printf("mask掩碼: %s\n", ipv4_str);/*struct pcap_pkthdr{struct timeval ts; // 抓到包的時間bpf_u_int32 caplen; // 表示抓到的數據長度bpf_u_int32 len; // 表示數據包的實際長度}成功返回捕獲數據包的地址,失敗返回 NULL*/struct pcap_pkthdr pkthdr;pcap_next(pcap, &pkthdr);printf("數據包捕獲時間: %s", ctime(&pkthdr.ts.tv_sec));printf("數據包捕獲長度: %d\n", pkthdr.caplen);printf("數據包長度 %d\n", pkthdr.len);/*struct pcap_stat {u_int ps_recv; // number of packets receivedu_int ps_drop; // number of packets droppedu_int ps_ifdrop; // drops by interface -- only supported on some platforms};*/struct pcap_stat stat;pcap_stats(pcap, &stat);printf("recv: %d\t drop: %d\t ifdrop: %d\n", stat.ps_recv, stat.ps_drop, stat.ps_ifdrop);//關閉網絡接口/* 在操作為網絡接口后,我們應該要釋放它:void pcap_close(pcap_t * p)該函數用于關閉pcap_open_live()獲取的pcap_t的網絡接口對象并釋放相關資源。*/pcap_close(pcap); }void testCapNext() {char error_buffer[PCAP_ERRBUF_SIZE];char *device = pcap_lookupdev(error_buffer); //獲取第一個可以捕獲的設備if (device == NULL) {printf("錯誤 未找到設備: %s\n", error_buffer);return ;}/* 打開網絡接口 */pcap_t *handle = pcap_open_live(device,//設備名稱BUFSIZ,//抓取個數 最大不超過655351, //混合模式10000, //超時時間error_buffer);/*獲取下一個數據包u_char _pcap_next(pcap_t _p, struct pcap_pkthdr *h)返回指向下一個數據包的u_char指針*/struct pcap_pkthdr packet_header;const u_char *packet = pcap_next(handle, &packet_header);if (packet == NULL) {printf("沒有抓取到包\n");return ;}printPKInfo(&packet_header,packet);pcap_close(handle); }void testCaploop() {char error_buffer[PCAP_ERRBUF_SIZE];char *device = pcap_lookupdev(error_buffer); //獲取第一個可以捕獲的設備if (device == NULL) {printf("錯誤 未找到設備: %s\n", error_buffer);return ;}/* 打開網絡接口 */pcap_t *handle = pcap_open_live(device,//設備名稱BUFSIZ,//抓取個數 最大不超過655351, //混合模式10000, //超時時間error_buffer);/*int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)p:需要捕獲的設備cnt:捕獲的包個數 0 代表一直捕獲callback:回調函數 原型如下user: 回調函數的參數typedef void (*pcap_handler)(u_char *args, const struct pcap_pkthdr *header,const u_char *packet);args: 回調函數傳遞的參數 userheader: 捕獲包信息packet: 數據包*/pcap_loop(handle, 0, my_loop, NULL);pcap_close(handle);return ; }void testSetFilter() {char error_buffer[PCAP_ERRBUF_SIZE];char *device = pcap_lookupdev(error_buffer); //獲取第一個可以捕獲的設備if (device == NULL) {printf("錯誤 未找到設備: %s\n", error_buffer);return ;}/* 打開網絡接口 */pcap_t *handle = pcap_open_live(device,//設備名稱BUFSIZ,//抓取個數 最大不超過655351, //混合模式10000, //超時時間error_buffer);//設置過濾struct bpf_program filter;char filter_exp[] = "port 80";//將過濾字符串放入bpf_program結構體if (pcap_compile(handle, &filter, filter_exp, 0, 0) == -1) {printf("Bad filter - %s\n", pcap_geterr(handle));return ;}//設置過濾if (pcap_setfilter(handle, &filter) == -1) {printf("Error setting filter - %s\n", pcap_geterr(handle));return ;}/*捕獲數據包*/pcap_loop(handle, 0, my_loop, NULL);pcap_close(handle); }void testCapAndSaveFile() {char error_buffer[PCAP_ERRBUF_SIZE];char *device = pcap_lookupdev(error_buffer); //獲取第一個可以捕獲的設備if (device == NULL) {printf("錯誤 未找到設備: %s\n", error_buffer);return ;}/* 打開網絡接口 */pcap_t *handle = pcap_open_live(device,//設備名稱BUFSIZ,//抓取個數 最大不超過655351, //混合模式10000, //超時時間error_buffer);/*打開需要寫入的文件*/pcap_dumper_t *out_pcap = pcap_dump_open(handle, "mysql_test.pcapng");/*捕獲數據包并且寫入文件*/pcap_loop(handle, 20, processPacket, (u_char *)out_pcap);/*刷新*/pcap_dump_flush(out_pcap);//關閉文件pcap_dump_close(out_pcap);pcap_close(handle); }void testOpenCapFile() {char error_buffer[PCAP_ERRBUF_SIZE];pcap_t *handle = pcap_open_offline("mysql_test.pcapng", error_buffer);struct bpf_program filter;char filter_exp[] = "tcp dst port 3306";if (pcap_compile(handle, &filter, filter_exp, 0, 0) == -1) {printf("Bad filter - %s\n", pcap_geterr(handle));return ;}if (pcap_setfilter(handle, &filter) == -1) {printf("Error setting filter - %s\n", pcap_geterr(handle));return ;}pcap_loop(handle, 0, my_loop, NULL); }void testParseEtherData() {testParseData(my_parseEth); }void testParseIPData() {testParseData(my_parseIP); }void testParseTCPData() {testParseData(my_loop); }參考鏈接
【數據包捕獲技術】libpcap原理
Linux下網絡數據包捕獲-Libpcap
libpcap api接口及詳細教程
Linux網絡編程——原始套接字編程
Linux下使用libpcap進行網絡抓包并保存到文件
c語言 libpcap示例
tcpdump官網
總結
以上是生活随笔為你收集整理的libpcap使用整理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 04-03/遭遇 rookit/ynqc
- 下一篇: kali linux 安装搜狗输入法(解