监听所有的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
#include
#include //For standard things
#include //malloc
#include //strlen
#include
#include //Provides declarations for icmp header
#include //Provides declarations for udp header
#include //Provides declarations for tcp header
#include //Provides declarations for ip header
#include //For ETH_P_ALL
#include //For ether_header
#include
#include
#include
#include
#include
#include
#include //struct sock_fprog
#include //struct packet_mreq
#include //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=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