监听所有的udp包,需要在数据链路层做,如果在网络层,不是发往本地的IP包都上不来; 另外,网络监听一个问题是上来的数据包太多, 处理不过来,会导致丢包,需要过滤。如果在应用层做,会有内核空间拷贝到用户空间的开销,最好内核里面做, linux已经原生提供了Linux Packet Filter。表示方式采用BPF。
BPF 原始格式看起来像汇编语言, tcpdump -dd可以把如下的编译成BPF:
src host 192.168.2.1 and tcp port 8000-9000 #端口范围
'(((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' #ip包长度-ip头长度-tcp头长度
'tcp[20:2]=0x4745 or tcp[20:2]=0x4854' #tcp内容开头为"GE"或"HT", http get/http ok
然后SO_ATTACH_FILTER到socket就开始过滤了。
rtp包的特征是udp包地9个字节是0x80, rtcp是0x81, 我们根据这个来作为过滤条件。测试发现dns也可能是0x81开头,
81 93 01 00 //dns query, Transaction ID:0x8193, dst.port = 53
81 93 81 80 //dns response, srt.port = 53
因此还要加上端口号的判断[3]。
下面的代码参考[1], 删掉了tcp,icmp相关部分:
#include<errno.h> #include<netdb.h> #include<stdio.h> //For standard things #include<stdlib.h> //malloc #include<string.h> //strlen #include<netinet/in.h> #include<netinet/ip_icmp.h> //Provides declarations for icmp header #include<netinet/udp.h> //Provides declarations for udp header #include<netinet/tcp.h> //Provides declarations for tcp header #include<netinet/ip.h> //Provides declarations for ip header #include<netinet/if_ether.h> //For ETH_P_ALL #include<net/ethernet.h> //For ether_header #include<sys/socket.h> #include<arpa/inet.h> #include<sys/ioctl.h> #include<sys/time.h> #include<sys/types.h> #include<unistd.h> #include <linux/filter.h> //struct sock_fprog #include<netpacket/packet.h> //struct packet_mreq #include<net/if.h> //struct ifreq static FILE *logfile; void PrintData (unsigned char* data , int Size) { int i , j; for(i=0 ; i < Size ; i++) { if( i!=0 && i%16==0) //if one line of hex printing is complete... { fprintf(logfile , " "); for(j=i-16 ; j<i ; j++) { if(data[j]>=32 && data[j]<=128) fprintf(logfile , "%c",(unsigned char)data[j]); //if its a number or alphabet else fprintf(logfile , "."); //otherwise print a dot } fprintf(logfile , "\n"); } if(i%16==0) fprintf(logfile , " "); fprintf(logfile , " %02X",(unsigned int)data[i]); if( i==Size-1) //print the last spaces { for(j=0;j<15-i%16;j++) { fprintf(logfile , " "); //extra spaces } fprintf(logfile , " "); for(j=i-i%16 ; j<=i ; j++) { if(data[j]>=32 && data[j]<=128) { fprintf(logfile , "%c",(unsigned char)data[j]); } else { fprintf(logfile , "."); } } fprintf(logfile , "\n" ); } } } void print_udp_packet(unsigned char *Buffer , int Size) { unsigned short iphdrlen; struct iphdr *iph = (struct iphdr *)(Buffer + sizeof(struct ethhdr)); iphdrlen = iph->ihl*4; struct udphdr *udph = (struct udphdr*)(Buffer + iphdrlen + sizeof(struct ethhdr)); int header_size = sizeof(struct ethhdr) + iphdrlen + sizeof(*udph); char source[16], dest[16]; inet_ntop(AF_INET, &iph->saddr, source, sizeof(source)); inet_ntop(AF_INET, &iph->daddr, dest, sizeof(dest)); fprintf(logfile, "UDP %s:%u --> %s:%u len %u:%u\n", source,ntohs(udph->source), dest,ntohs(udph->dest), ntohs(udph->len), Size - header_size); //PrintData(Buffer + header_size , (Size - header_size) ); } void ProcessPacket(unsigned char* buffer, int size) { static int tcp=0,udp=0,icmp=0,others=0,igmp=0,total=0; struct iphdr *iph = (struct iphdr*)(buffer + sizeof(struct ethhdr)); ++total; switch (iph->protocol){ case 1: ++icmp; break; case 2: ++igmp; break; case 6: ++tcp; break; case 17: ++udp; print_udp_packet(buffer , size); break; default: //Some Other Protocol like ARP etc. ++others; break; } //printf("TCP : %d UDP : %d ICMP : %d IGMP : %d Others : %d Total : %d\r", tcp , udp , icmp , igmp , others , total); } int get_iface_index(int fd, const char* iface_name) { struct ifreq ifr; if (iface_name == NULL){ return -1; } memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, iface_name); if(ioctl(fd, SIOCGIFINDEX, &ifr) == -1){ printf("get index error\n"); return -1; } return ifr.ifr_ifindex; } int set_iface_promisc(int fd, int dev_id) { struct packet_mreq mr; memset(&mr,0,sizeof(mr)); mr.mr_ifindex = dev_id; mr.mr_type = PACKET_MR_PROMISC; if(setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) < 0){ fprintf(stderr,"set promisc failed"); return -1; } return 0; } int main(int ac, char **av) { int saddr_size , data_size; struct sockaddr saddr; unsigned char *buffer = (unsigned char *)malloc(65536); char *iface_name = ac > 1 ? av[1] : "eth0"; if(ac > 2){ logfile = fopen(av[2],"w"); }else{ logfile = stdout; } if(!logfile){ printf("Unable to create log.txt file."); return -1; } /*ETH_P_IP |ETH_P_ARP -- cant receive multicast*/ int sock_raw = socket( AF_PACKET , SOCK_RAW , htons(ETH_P_ALL)); if(sock_raw < 0){ perror("Socket Error"); return 1; } int iface_idx = get_iface_index(sock_raw, iface_name); setsockopt(sock_raw , SOL_SOCKET , SO_BINDTODEVICE , iface_name , strlen(iface_name)+1); set_iface_promisc(sock_raw, iface_idx); struct sock_filter BPF_code[] = { //sudo tcpdump -s 65535 -dd 'udp && udp[0:2] > 1024 && udp[2:2] > 1024 && (udp[8] == 0x80 || udp[8] == 0x81)' { 0x28, 0, 0, 0x0000000c }, { 0x15, 14, 0, 0x000086dd }, { 0x15, 0, 13, 0x00000800 }, { 0x30, 0, 0, 0x00000017 }, { 0x15, 0, 11, 0x00000011 }, { 0x28, 0, 0, 0x00000014 }, { 0x45, 9, 0, 0x00001fff }, { 0xb1, 0, 0, 0x0000000e }, { 0x48, 0, 0, 0x0000000e }, { 0x25, 0, 6, 0x00000400 }, { 0x48, 0, 0, 0x00000010 }, { 0x25, 0, 4, 0x00000400 }, { 0x50, 0, 0, 0x00000016 }, { 0x15, 1, 0, 0x00000080 }, { 0x15, 0, 1, 0x00000081 }, { 0x6, 0, 0, 0x0000ffff }, { 0x6, 0, 0, 0x00000000 }, }; struct sock_fprog sfilter = { .len = sizeof(BPF_code)/sizeof(BPF_code[0]), .filter = BPF_code, }; int ret = setsockopt(sock_raw, SOL_SOCKET, SO_ATTACH_FILTER, &sfilter, sizeof(sfilter)); if(ret < 0){ perror("attach filter"); return -1; } while(1){ saddr_size = sizeof saddr; data_size = recvfrom(sock_raw , buffer , 65536 , 0 , &saddr, (socklen_t*)&saddr_size); if(data_size < 0){ printf("failed to get packets\n"); return 1; } ProcessPacket(buffer, data_size); } close(sock_raw); return 0; }
windows下只能从IP层开始过滤[2], 如果非要统一linux和windows, 目前可选择libpcap/winpcap。网上说linux libpcap 默认的mem map方式可能会丢包, windows版的有一些限制,暂时不打算统一搞。
[1] http://www.binarytides.com/packet-sniffer-code-in-c-using-linux-sockets-bsd-part-2/
[2] http://blog.csdn.net/cyberhero/article/details/5785158
[3] https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports