libpcap是數據包捕獲函數庫。該庫提供的C函數接口可用於需要捕獲經過網絡接口數據包的系統開發上。libpcap提供的接口函數主要實現和封裝了與數據包截獲有關的過程。這個庫為不同的平台提供了一致的編程接口,在安裝了libpcap的平台上,以libpcap為接口寫的程序,能夠自由的跨平台使用。
linux下libpcap的安裝:sudo apt-get install libpcap-dev
linux下gcc編譯程序:gcc my_pcap.c -lpcap
執行程序的時候如果報錯:no suitable device found,以管理員權限運行程序即可,sudo ./my_pcap
libpcap的抓包框架:
頭文件: #include <pcap.h> 在/usr/local/include/pcap目錄下
1.查找網絡設備
char *pcap_lookupdev(char *errbuf)
該函數用於返回可被pcap_open_live()或pcap_lookupnet()函數調用的網絡設備名(一個字符串指針)。如果函數出錯,則返回NULL,同時errbuf中存放相關的錯誤消息。
2.獲得指定網絡設備的網絡號和掩碼
int pcap_lookupnet(char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf)
netp參數和maskp參數都是bpf_u_int32指針。如果函數出錯,則返回-1,同時errbuf中存放相關的錯誤消息。
Bpf_u_int32:32位無符號數
Struct in_addr
{
unsigned long s_addr;
}
inet_ntoa();以a.b.c.d的形式顯示地址。
3.打開網絡設備
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
獲得用於捕獲網絡數據包的數據包捕獲描述字。device參數為指定打開的網絡設備名。snaplen參數定義捕獲數據的最大字節數,65535是最大值。promisc指定 是否將網絡接口置於混雜模式,設置為1表示混雜模式。to_ms參數指定超時時間(毫秒),設置為0表示超時時間無限大。ebuf參數則僅在pcap_open_live()函數出錯返回NULL時用於傳遞 錯誤消息。
typedef struct pcap pcap_t;
pcap結構在libpcap源碼的pcap-int.h定義,使用時一般都是使用其指針類型)。
4.打開已有的網絡數據包 //如果是抓取數據包,這個過程不需要
pcap_t *pcap_open_offline(char *fname, char *errbuf)
fname參數指定打開的文件名。該文件中的數據格式與tcpdump兼容。errbuf參數則僅在pcap_open_offline()函數出錯返回NULL時用於傳遞錯誤消息。
pcap_t *pcap_fopen_offline(FILE *fp, char *errbuf)打開文件指針。
5.編譯和設置過濾條件
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
設置過濾條件,舉一些例子:
- src host 192.168.1.1:只接收源ip地址是192.168.1.1的數據包
- dst port 80:只接收tcp、udp的目的端口是80的數據包
- not tcp:只接收不使用tcp協議的數據包
- tcp[13] == 0x02 and (dst port 22 or dst port 23) :只接收 SYN 標志位置位且目標端口是 22 或 23 的數據包( tcp 首部開始的第 13 個字節)
- icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo:只接收 icmp 的 ping 請求和 ping 響應的數據包
- ehter dst 00:e0:09:c1:0e:82:只接收以太網 mac 地址是 00:e0:09:c1:0e:82 的數據包
- ip[8] == 5:只接收 ip 的 ttl=5 的數據包(ip首部開始的第8個字節)
將str參數指定的字符串編譯到過濾程序中。fp是一個bpf_program結構的指針,在pcap_compile()函數中被賦值。optimize參數控制結果代碼的優化。netmask參數指定本地網絡的網絡掩碼,當不知道的時候可以設為0。出錯時返回-1.
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
指定一個過濾程序。fp參數是bpf_program結構指針,通常取自pcap_compile()函數調用。出錯時返回-1。
6.抓取和讀取數據包
int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
捕獲並處理數據包。cnt參數指定函數返回前所處理數據包的最大值。cnt=-1表示在一個緩沖區中處理所有的數據包。callback參數指定一個帶有三個參數的回調函數,這三個參數為:一個從 pcap_dispatch()函數傳遞過來的u_char指針,一個pcap_pkthdr結構的指針,和指向caplen大小的數據包的u_char指針。
struct pcap_pkthdr {
struct tim ts; // ts是一個結構struct timeval,它有兩個部分,第一部分是1900開始以來的秒數,第二部分是當前秒之后的毫秒數
bpf_u_int32 caplen; //表示抓到的數據長度
bpf_u_int32 len; //表示數據包的實際長度
};
user參數是留給用戶使用的,當callback被調用的時候這個值會傳遞給callback的第一個參數(也叫user)。
成功 則返回讀到的數據包數。返回0沒有抓到數據包。出錯時則返回-1,此時可調用pcap_perror()或pcap_geterr()函數獲取錯誤消息。返回-2表示調用了pcap_breakloop().
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
功能基本與pcap_dispatch()函數類似,只不過此函數在cnt個數據包被處理或出現錯誤時才返回,但讀取超時不會返回。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
讀取下一個數據包,類似於pcap_dispatch()中cnt參數設為1,返回指向讀到的數據包的指針,但是不返回這個包的pcap_pkthdr結構的參數。
7.關閉文件釋放資源
void pcap_close(pcap_t *p)
關閉P指針。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pcap.h> 4 5 #define PCAP_DATABUF_MAX 65535 6 7 #define ETHERTYPE_IPV4 0x0800 8 #define ETHERTYPE_IPV6 0x86DD 9 10 typedef unsigned char u_int8; 11 typedef unsigned short u_int16; 12 typedef unsigned int u_int32; 13 typedef unsigned long u_int64; 14 15 /*MAC頭,總長度14字節 */ 16 typedef struct _eth_hdr{ 17 u_int8 dst_mac[6]; 18 u_int8 src_mac[6]; 19 u_int16 eth_type; 20 }eth_hdr; 21 eth_hdr *ethernet; 22 23 /*IP頭*/ 24 typedef struct _ip_hdr{ 25 u_int8 ver_hl; //版本和頭長 26 u_int8 serv_type; //服務類型 27 u_int16 pkt_len; //包總長 28 u_int16 re_mark; //重組標志 29 u_int16 flag_seg; //標志位和段偏移量 30 u_int8 surv_tm; //生存時間 31 u_int8 protocol; //協議碼(判斷傳輸層是哪一個協議) 32 u_int16 h_check; //頭檢驗和 33 u_int32 src_ip; //源ip 34 u_int32 dst_ip; //目的ip 35 u_int32 option; //可選選項 36 }ip_hdr; 37 ip_hdr *ip; 38 39 /*TCP頭,總長度20字節,不包括可選選項*/ 40 typedef struct _tcp_hdr{ 41 u_int16 sport; //源端口 42 u_int16 dport; //目的端口 43 u_int32 seq; //序列號 44 u_int32 ack; //確認序號 45 u_int8 head_len; //頭長度 46 u_int8 flags; //保留和標記位 47 u_int16 wind_size; //窗口大小 48 u_int16 check_sum; //校驗和 49 u_int16 urgent_p; //緊急指針 50 }tcp_hdr; 51 tcp_hdr *tcp; 52 53 /*UDP頭,總長度8個字節*/ 54 typedef struct _udp_hdr{ 55 u_int16 sport; //源端口 56 u_int16 dport; //目的端口 57 u_int16 pktlen; //UDP頭和數據的總長度 58 u_int16 check_sum; //校驗和 59 }udp_hdr; 60 udp_hdr *udp; 61 62 //ip整型轉換點分十進制 63 char *InttoIpv4str(u_int32 num){ 64 char* ipstr = (char*)calloc(128, sizeof(char*)); 65 66 if (ipstr) 67 sprintf(ipstr, "%d.%d.%d.%d", num >> 24 & 255, num >> 16 & 255, num >> 8 & 255, num & 255); 68 else 69 printf("failed to Allocate memory..."); 70 71 return ipstr; 72 } 73 74 void pcap_callback(u_char *useless,const struct pcap_pkthdr *pkthdr, const u_char *packet) 75 { 76 printf("data len:%u\n", pkthdr->caplen); //抓到時的數據長度 77 printf("packet size:%u\n", pkthdr->len); //數據包實際的長度 78 79 /*解析數據鏈路層 以太網頭*/ 80 ethernet = (struct _eth_hdr*)packet; 81 u_int64 src_mac = ntohs( ethernet->src_mac ); 82 u_int64 dst_mac = ntohs( ethernet->dst_mac ); 83 84 printf("src_mac:%lu\n",src_mac); 85 printf("dst_mac:%lu\n",dst_mac); 86 printf("eth_type:%u\n",ethernet->eth_type); 87 88 u_int32 eth_len = sizeof(struct _eth_hdr); //以太網頭的長度 89 u_int32 ip_len; //ip頭的長度 90 u_int32 tcp_len = sizeof(struct _tcp_hdr); //tcp頭的長度 91 u_int32 udp_len = sizeof(struct _udp_hdr); //udp頭的長度 92 93 /*解析網絡層 IP頭*/ 94 if(ntohs(ethernet->eth_type) == ETHERTYPE_IPV4){ //IPV4 95 printf("It's IPv4!\n"); 96 97 ip = (struct _ip_hdr*)(packet + eth_len); 98 ip_len = (ip->ver_hl & 0x0f)*4; //ip頭的長度 99 u_int32 saddr = (u_int32)ntohl(ip->src_ip); //網絡字節序轉換成主機字節序 100 u_int32 daddr = (u_int32)ntohl(ip->dst_ip); 101 102 printf("eth_len:%u ip_len:%u tcp_len:%u udp_len:%u\n", eth_len, ip_len, tcp_len, udp_len); 103 104 printf("src_ip:%s\n", InttoIpv4str(saddr)); //源IP地址 105 printf("dst_ip:%s\n", InttoIpv4str(daddr)); //目的IP地址 106 107 printf("ip->proto:%u\n", ip->protocol); //傳輸層用的哪一個協議 108 109 /*解析傳輸層 TCP、UDP、ICMP*/ 110 if(ip->protocol == 6){ //TCP 111 tcp = (struct _tcp_hdr*)(packet + eth_len + ip_len); 112 printf("tcp_sport = %u\n", tcp->sport); 113 printf("tcp_dport = %u\n", tcp->dport); 114 115 /**********(pcaket + eth_len + ip_len + tcp_len)就是TCP協議傳輸的正文數據了***********/ 116 117 }else if(ip->protocol == 17){ //UDP 118 udp = (struct _udp_hdr*)(packet + eth_len + ip_len); 119 printf("udp_sport = %u\n", udp->sport); 120 printf("udp_dport = %u\n", udp->dport); 121 122 /**********(pcaket + eth_len + ip_len + udp_len)就是UDP協議傳輸的正文數據了***********/ 123 124 }else if(ip->protocol == 1){ //ICMP 125 126 } 127 128 }else if(ntohs(ethernet->eth_type) == ETHERTYPE_IPV6){ //IPV6 129 printf("It's IPv6!\n"); 130 } 131 132 printf("============================================\n"); 133 } 134 135 int main() 136 { 137 char *dev; //設備名 138 char errbuf[PCAP_ERRBUF_SIZE] = {}; //PCAP_ERRBUF_SIZE在pcap.h中已經定義 139 bpf_u_int32 netp, maskp; //網絡號和掩碼 140 pcap_t *handler; //數據包捕獲描述字 141 struct bpf_program *fp; 142 char *filter_str = "port 9000"; //過濾條件 143 144 /*Find network devices*/ 145 if((dev = pcap_lookupdev(errbuf)) == NULL){ 146 printf("lookupdev failed:%s\n", errbuf); 147 exit(1); 148 }else{ 149 printf("Device:%s\n", dev); 150 } 151 152 /*Get the network number and mask of the network device*/ 153 if(pcap_lookupnet(dev, &netp, &maskp, errbuf) == -1){ 154 printf("%s\n", errbuf); 155 exit(1); 156 } 157 158 /*Open network device*/ 159 if((handler = pcap_open_live(dev, PCAP_DATABUF_MAX, 1, 0, errbuf)) == NULL){ 160 printf("%s\n", errbuf); 161 exit(1); 162 } 163 164 /*Compiling and setting filtering conditions*/ 165 if(pcap_compile(handler, fp, filter_str, 0, maskp) == -1){ 166 printf("pcap_compile error...\n"); 167 exit(1); 168 } 169 if(pcap_setfilter(handler, fp) == -1){ 170 printf("pcap_setfilter error...\n"); 171 exit(1); 172 } 173 174 /*Capturing and processing data packets*/ 175 if(pcap_loop(handler, -1, pcap_callback, NULL) == -1){ 176 printf("pcap_loop error...\n"); 177 pcap_close(handler); 178 } 179 180 return 0; 181 }