WinPcap和Libpcap的最強大的特性之一,是擁有過濾數據包的引擎。 它提供了有效的方法去獲取網絡中的某些數據包,這也是WinPcap捕獲機制中的一個組成部分。 用來過濾數據包的函數是 pcap_compile() 和pcap_setfilter() 。
pcap_compile() 它將一個高層的布爾過濾表達式編譯成一個能夠被過濾引擎所解釋的低層的字節碼。有關布爾過濾表達式的語法可以參見 Filtering expression syntax 這一節的內容。
pcap_setfilter() 將一個過濾器與內核捕獲會話向關聯。當 pcap_setfilter() 被調用時,這個過濾器將被應用到來自網絡的所有數據包,並且,所有的符合要求的數據包 (即那些經過過濾器以後,布爾表達式爲真的包) ,將會立即複製給應用程序。
現在,我們可以捕捉並過濾網絡流量了,那就讓我們學以致用,來做一個簡單使用的程序吧。
在本講中,我們將會利用上一講的一些代碼,來建立一個更實用的程序。 本程序的主要目標是展示如何解析所捕獲的數據包的協議首部。這個程序可以稱爲UDPdump,打印一些網絡上傳輸的UDP數據的信息。
我們選擇分析和現實UDP協議而不是TCP等其它協議,是因爲它比其它的協議更簡單,作爲一個入門程序範例,是很不錯的選擇。讓我們看看代碼:
- #include "pcap.h"
- /* 4字節的IP地址 */
- typedef struct ip_address{
- u_char byte1;
- u_char byte2;
- u_char byte3;
- u_char byte4;
- }ip_address;
- /* IPv4 首部 */
- typedef struct ip_header{
- u_char ver_ihl; // 版本 (4 bits) + 首部長度 (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 首部*/
- typedef struct udp_header{
- u_short sport; // 源端口(Source port)
- u_short dport; // 目的端口(Destination port)
- u_short len; // UDP數據包長度(Datagram length)
- u_short crc; // 校驗和(Checksum)
- }udp_header;
- /* 回調函數原型 */
- void packet_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;
- char errbuf[PCAP_ERRBUF_SIZE];
- u_int netmask;
- char packet_filter[] = "ip and udp";
- struct bpf_program fcode;
- /* 獲得設備列表 */
- 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 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");
- /* 釋放設備列表 */
- 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 open the adapter. %s is not supported by WinPcap/n");
- /* 釋放設備列表 */
- pcap_freealldevs(alldevs);
- return -1;
- }
- /* 檢查數據鏈路層,爲了簡單,我們只考慮以太網 */
- if(pcap_datalink(adhandle) != DLT_EN10MB)
- {
- fprintf(stderr,"/nThis program works 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 to compile the packet filter. Check the syntax./n");
- /* 釋放設備列表 */
- pcap_freealldevs(alldevs);
- return -1;
- }
- //設置過濾器
- if (pcap_setfilter(adhandle, &fcode)<0)
- {
- fprintf(stderr,"/nError setting the 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所調用 */
- 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;
- 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);
- }