Winpcap提供(libpcap也提供)的一个强大特性是过滤引擎(filtering engine)。它提供了一个非常有效的接收网络流量的方法,并且它通常与Winpcap提供的抓包机制集成在一起。用于过滤数据包的函数是pcap_complie()和pcap_setfilter()。 |
pcap_complie()使用一个包含高级布尔表达式的字符串并且产生一个能被过滤引擎集成到数据包驱动中的低级字节码。 pcap_setfilter()把一个过滤器与核心驱动抓包会话关联起来。一旦pcap_setfilter()被调用,相关的过滤器将被应用到所有的来自网络的数据包上,并且所有的一致的数据包将被复制给应用程序。 抓包和过滤 经过前面几天的知识准备,现在我们将把前面的知识综合后应用于一个简单的实际应用程序。下面试验的目的是如何解析和解释被捕获的数据包的协议头。应用程序运行的结果是打印出一组我们的网络上的UDP通信数据。我们之所以选择解析和显示UDP协议是因为它比起其他协议(例如:TCP)更易于理解,对于初学者更适合。 试验代码: #include <pcap.h> #include <remote-ext.h> /* 4 bytes IP address */ typedef struct ip_address { u_char byte1; u_char byte2; u_char byte3; u_char byte4; }ip_address; /* IPv4 header */ typedef struct ip_header { u_char ver_ihl; /* Version (4 bits) + Internet header length (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 header */ typedef struct udp_header { u_short sport; /* Source port */ u_short dport; /* Destination port */ u_short len; /* Datagram length */ u_short crc; /* Checksum */ }udp_header; /* Prototype of the pachet handler */ 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; /* Retrieve the device list */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) { fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf); exit(1); } /* Print the list*/ 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"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } /* Jump to the selected adapter */ for (d = alldevs; d; d = d->next); /* Open the adapter */ if ((adhandle = pcap_open(d->name, /*name of the device */ 65536, /* portion of the packet to capture */ /* 65536 grants that the whole packet will be captured on all the MACs */ PCAP_OPENFLAG_PROMISCUOUS, /* promiscuous mode */ 1000, /* read timeout */ NULL, /* remote authentication */ errbuf /* error buffer */ )) == NULL) { fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Winpcap\n"); /* Free the devices list */ pcap_freealldevs(alldevs); return -1; } /* Check the link layer. We support only Ethernet for simplicity */ if (pcap_datalink(adhandle) != DLT_EN10MB) { fprintf(stderr, "\nThis program works only on Ethernet networks.\n"); /* Free the devices list */ pcap_freealldevs(alldevs); return -1; } if (d->addresses != NULL) { /* Retrieve the mask of the first address of the interface */ netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; } else { /* If the interface is without addresses we suppose to be in a C class network */ netmask = 0xffffffff; } /* complie the filter */ if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) < 0) { fprintf(stderr, "\nUnable to compile the packet filter. Check the syntax.\n"); /* Free the devices list */ pcap_freealldevs(alldevs); return -1; } /* set the filter */ if (pcap_setfilter(adhandle, &fcode) < 0) { fprintf(stderr, "\nError setting the filter.\n"); /* Free the devices list */ pcap_freealldevs(alldevs); return -1; } printf("\nlistening on %s ...\n", d->description); /* At this point,we don't need any more the device list. Free it */ pcap_freealldevs(alldevs); /* Start the capture */ pcap_loop(adhandle, 0, packet_handler, NULL); return 1; } /* Callback function invoked by libpcap for every incoming packet */ 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; /* convert the timestamp to readable format */ ltime = localtime(&header->ts.tv_sec); strftime(timestr, sizeof(timestr), "%H:%M:%S", ltime); /* print timestamp and length of the packet */ printf("%s.%.6d len: %d ", timestr, header->ts.tv_usec, header->len); /* retrieve the position of the ip header */ ih = (ip_header*)(pkt_data + 14); /* length of ethernet header */ /* retrieve the position of the udp header */ ip_len = (ih->ver_ihl & 0xf) * 4; uh = (udp_header*)((u_char*)ih + ip_len); /* convert from network byte order to host byte order */ /*sport = ntohs(uh->sport); dport = ntohs(uh->dport);*/ /* print ip addresses and udp ports */ printf("%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*/); } 函数1: int pcap_datalink(pcap_t* p) 返回链路层上的一个适配器。返回链路层的类型,链路层的类型包括: DLT_NULL: BSD回路封装;链路层协议头是一个4字节的域,以主机字节顺序(host byte order),包含一个从socket.h来的PF_value。主机字节顺序(host byte order)是捕获数据包的机器的字节顺序,而PF_value是捕获数据包的机器的OS。如果一个读取一个文件,字节顺序和PF_value不一定是抓取文件的那些机器。 DLT_EN10MB: 以太网(10Mb, 100Mb, 1000Mb, 或者更高)。 DLT_IEEE802: IEEE802.5令牌环网。 DLT_ARCNET:ARCNET。 DLT_SLIP:SLIP。 DLT_PPP:PPP;如果{dy}个字节是0xff或0x03,它是类HDLC帧上的PPP。 DLT_FDDI:FDDI DLT_ATM_RFC1483:RFC1483LLC/SNAP ATM;数据包以IEEE802.2 LLC头开始。 DLT_RAW:原始IP(raw IP);数据包以IP头开始。 DLT_PPP_SERIAL:按照RFC1662,基于类HDLC帧的PPP,或者按照RFC1547的4.3.1,基于HDLC帧的Cisco PPP;前者的{dy}个字节是0xFF,后者的{dy}个字节是0x0F或0x8F。 DLT_PPP_ETHER:按照RFC2516,PPPoE;数据包以PPPoE头开始。 DLT_C_HDLC:按照RFC1547的4.3.1,基于HDLC帧的Cisco PPP。 DLT_IEEE802_11:IEEE 802.11无线局域网。 DLT_FRELAY:帧中继(Frame Relay)。 DLT_LOOP:OpenBSD回路封装。 DLT_LINUX_SLL:Linux抓包封装。 DLT_LTALK:苹果的LocalTalk,数据包以AppleTalk LLAP头开始。 DLT_PFLOG:OpenBSD pflog。 DLT_PRISM_HEADER:后接802.11头的棱镜监视器模式(Prism monitor mode)信息。 DLT_IP_OVER_FC:RFC2625 IP-over-Fiber 频道,以RFC2625中定义的Network_Header开始。 DLT_SUNATM:SunATM设备。 DLT_IEEE802_11_RADIO:后接802.11头的链路层信息。 DLT_ARCNET_LINUX:没有异常帧的ARCNET。 DLT_LINUX_IRDA:Linux-IrDA数据包,DLT_LINUX_SLL头后接IrLAP头。 函数2: int pcap_compile(pcap_t* p, struct bpf_program* fp, char* str, int optimize, bpf_u_int32 netmask) 编译一个数据包过滤器,将一个能被核心态(kernel-level)过滤器引擎解释的程序中的高层过滤表达式(filtering expression)进行转化。pcap_compile()被用来将字符串str编译进过滤器程序(fp),程序(fp)是一个指向bpf_program结构体并被pcap_compile()赋值的指针。optimize控制是否对目标代码(resulting code)的性能进行优化。Netmask表明IPv4掩码,它仅在检查过滤器程序中的IPv4广播地址的时候被使用。如果网络掩码对于程序是不可知的或者数据包是在Linux的”任何(any)”伪接口上被捕获的,则赋值0;IPv4广播地址的测试将不被正确进行,但过滤器程序中的其他所有的测试都不会有问题。返回-1表示发生了错误,此时,pcap_geterr()将被用来显示错误信息。 函数3: int pcap_setfilter(pcap_t* p, struct bpf_program* fp) 把一个过滤器同一次抓包关联起来。pcap_setfilter被用来指定一个过滤器程序。fp是一个指向bpf_program结构体的指针,通常是pcap_compile()执行的结果。当失败时返回-1,此时,pcap_geterr()被用来显示错误信息;返回0表示成功。 首先,首先我们设置过滤器为“ip and udp”。用这个方法我们可以确保packet_handler()仅接收IPv4上的UDP数据包:这就简化了解析过程并且提高了程序的效率。 我们还创建了IP和UDP两个结构体,这些结构体被packet_handler()用来定位不同的头域。 packet_handler()展现了像tcpdump/WinDump这些复杂的嗅探器(sniffer)如何解析网络数据包。因为我们不关心MAC头,所以我们跳过它。为了简便起见,抓包前我们用pcap_datalink()检查MAC层,以确保我们处理的是以太网。该方法可以确保MAC头是14字节。 IP头的定位在MAC头定位之后。我们从IP包头中取出源IP地址和目标IP地址。 展开UDP包头有点复杂,因为IP包头不是定长的。因此,我们使用IP包头的长度域来获得它的大小。一旦我们知道了UDP头的位置,我们可以取出源端口和目的端口。 |