diff --git a/README b/README index afa0ab9..0c4b80c 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -DNS FLood Detector 1.12 +DNS FLood Detector 1.2 Dennis Opacki dopacki@adotout.com @@ -21,6 +21,16 @@ By default, it will count dns queries directed to any address in the same network as the primary IP address on the interface being watched; the -A, -M, and -Q options can be used to modify this behaviour. +As of version 1.2, DNS Flood Detector can now send source IP request +data to a network-based collector as JSON. This lets you gather near +real-time information about who is using your DNS servers, and from +where. I've included a sample application called dns_flood_collector.pl, +which you can use to receive and report these data. The output of this +program can be easily fed into a graphing tool, such as Caida's +plot-latlong: + +http://www.caida.org/tools/visualization/plot-latlong/ + How do I build it? Execute ./configure.pl to select the appropriate make target. Then simply @@ -41,7 +51,7 @@ What platforms does it work on? Linux, BSDI, FreeBSD, Mac OSX, Solaris -Will it run under Windows {95,98,NT,2000,XP}? +Will it run under Windows {95,98,NT,2000,XP,2003,2008 or Win7}? Maybe. I haven't tried. If it doesn't, feel free to submit a fix. @@ -62,6 +72,9 @@ Usage: ./dns_flood_detector [OPTION] -d run in background in daemon mode -D dump dns packets (implies -b) -v verbose output - use again for more verbosity +-s send source IP stats to collector as JSON +-z N.N.N.N address to send stats to (default 226.1.1.2) +-p N UDP port to send stats to (default 2000) -h display this usage information Sample Output: diff --git a/dns_flood_collector.pl b/dns_flood_collector.pl new file mode 100755 index 0000000..ff61a2d --- /dev/null +++ b/dns_flood_collector.pl @@ -0,0 +1,157 @@ +#!/usr/bin/perl + +use strict; +use threads; +use threads::shared; +use Sys::Syslog; +use Data::Dumper; +use Getopt::Long; +use POSIX; +use IO::Socket::Multicast; +use JSON; + +# Native Maxmind library - http://www.maxmind.com/download/geoip/api/perl/ +# requires: http://www.maxmind.com/app/c +use Geo::IP; + +# set these to the same port and multicast (or unicast) address as the detector +use constant GROUP => '226.1.1.2'; +use constant PORT => '2000'; + +my %ipc_source :shared; +my %ipc_customer :shared; +my $time_to_die :shared = 0; +my $debug; +my $foreground=0; + +# determines how often you want to aggregage and write-out stats dumps +my $interval = 60; + +# you can get the binary format GeoLiteCity.dat from Maxmind +# http://www.maxmind.com/app/geolitecity +my $gi = Geo::IP->open("/usr/local/GeoLiteCity.dat",GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE); + +# adjust this to the path where you want to keep the +sub PATH {'/tmp/'} + +$|=1; + +GetOptions( + "debug" => \$debug, + "foreground" => \$foreground, + "interval=s" => \$interval, +); + + +main(); +exit(); + +sub main() { + + # daemonize unless running in foreground + unless ($foreground){ + daemonize(); + } + + # prepare data acquisition thread + threads->new(\&get_data); + + while (! $time_to_die ) { + + # record time started to help evenly space runs + my $start_run = time(); + my $next_run = $start_run + $interval; + + # de-serialize latest copy of source address structure + # execute this in a isolated scope so that lock goes out of scope + { + my $source_distance; + + # lock data structure to prevent other thread from updating it + lock(%ipc_source); + + # open coordinates file for graph generation + open(CRDS, ">".PATH."/coords.txt.tmp"); + + # calculate great circle distance between each source IP and local POP + foreach my $key (keys %ipc_source) { + + eval { + my $r = $gi->record_by_addr($key); + + # write raw entry to coordinates file + print CRDS $key.",".$ipc_source{$key}.",".$r->latitude.",".$r->longitude."\n"; + }; + if ($@) { + print CRDS $key.",".$ipc_source{$key}.",0,0\n"; + } + } + + # close coordinate file + close CRDS; + system("mv ".PATH."/coords.txt.tmp ".PATH."/coords.txt"); + + # clean out structure for next sample period + %ipc_source = (); + } + + # sleep to make the interval + while((my $time_left = ($next_run - time())) > 0) { + sleep($time_left); + } + } + threads->join(); + return; +} + +# fetch data from UDP multicast +sub get_data() { + + # set up our multicast listener + # note: this will receive unicast fine too + my $sock = IO::Socket::Multicast->new(LocalPort=>PORT,ReuseAddr=>1); + $sock->mcast_add(GROUP) || die "Couldn't set group: $!\n"; + + + while ( ! $time_to_die ) { + my $data; + next unless $sock->recv($data,1500); + + # decode JSON + eval { + my $obj = decode_json $data; + print Dumper $obj; + foreach my $ip (keys %{$obj->{data}}) { + my $count = $obj->{data}->{$ip}; + lock(%ipc_source); + $ipc_source{$ip}+=$count; + } + }; + + } + + # done! + threads->exit(); +} + +# daemonize application +sub daemonize { + + chdir '/' or die "Can't chdir to /: $!"; + open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; + open STDOUT, '>/dev/null'; + + # fork and exit parent + my $pid = fork(); + exit if $pid; + die "Couldn't fork: $!" unless defined ($pid); + POSIX::setsid() || die ("$0 can't start a new session: $!"); + open STDERR, '>&STDOUT' or die "Can't dup stdout: $!"; + + # signal handlers + $SIG{KILL} = \&handler; +} + +sub handler { + $time_to_die = 1; +} diff --git a/dns_flood_detector.c b/dns_flood_detector.c index b474ad2..ffcb31e 100644 --- a/dns_flood_detector.c +++ b/dns_flood_detector.c @@ -30,6 +30,10 @@ -d run in background in "daemon" mode -D dump dns packets (implies -b) -v detailed information (use twice for more detail) + -s send source IP stats to collector as JSON + -z N.N.N.N address to send stats to (default 226.1.1.2) + -p N UDP port to send stats to (default 2000) + -h usage info Copyright (C) 2003 Dennis Opacki @@ -93,6 +97,9 @@ store addresses raw, instead of as text (speedup/reduce memory usage) - fix crash on long syslog messages - + --- new in v1.2 --- + 05/10/2012 - added sending of source-IP telemetry to a network collector - + ********************************************************************************/ #include @@ -124,14 +131,17 @@ #include #include #include "dns_flood_detector.h" +#include +#include +#include // global variables and their defaults pthread_mutex_t stats_lock; struct bucket **bb; int option_t = 60; int option_a = 90; -int option_w = 10; -int option_x = 50; +int option_w = 20; +int option_x = 10000; int option_m = 0; int option_b = 0; int option_d = 0; @@ -141,730 +151,881 @@ int option_h = 0; int option_Q = 0; int option_A = 0; int option_M = 0; +int option_s = 0; int totals = 0; -char VERSION[] = "1.12"; + +static char *target_ip = NULL; +int target_port = DEFAULT_PORT; +int mcast_ttl = 10; +char hostname[HOST_NAME_MAX]; + +char VERSION[] = "1.2"; // 255.255.255.255 is invalid as a src IP address; we'll use it to mark empty buckets #define BCAST 0xffFFffFF // this is our statistics thread void *run_stats () { - while (1) { + // prepare multicast socket + struct sockaddr_in addr; + int sock; + sock = socket(AF_INET, SOCK_DGRAM,0); - // check statistical stuff - pthread_mutex_lock(&stats_lock); - calculate_averages(); - pthread_mutex_unlock(&stats_lock); + if (sock<0) { + perror("can't set up socket"); + exit(1); + } - sleep (option_w); - } + // is it harmful to set this on non-multicast sockets? + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&mcast_ttl, sizeof(mcast_ttl)); + + bzero((char*)&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(target_ip); + addr.sin_port = htons(target_port); + + // get our hostname + gethostname(hostname, HOST_NAME_MAX-1); + + while (1) { + + // check statistical stuff + pthread_mutex_lock(&stats_lock); + calculate_averages(); + saddr_stats(sock,addr,hostname); + pthread_mutex_unlock(&stats_lock); + sleep (option_w); + } } +// report saddr stats +int saddr_stats(int sock, struct sockaddr_in addr, char *hostname) { + u_int i; + int addrlen; + char buff[MAXMESSAGE]; + int buffhead = 0; + char st_time[10]; + time_t now = time(0); + struct tm *raw_time = localtime(&now); + addrlen = sizeof(addr); + snprintf(st_time, 9, "%02d:%02d:%02d",raw_time->tm_hour,raw_time->tm_min,raw_time->tm_sec); + + // prepare jason structure for multicast datagrams + char head[MAXHEAD]; + char *tail = "}}"; + snprintf(head,MAXHEAD,"{\"hostname\":\"%s\",\"type\":\"source\",\"data\":{",hostname); + int netsize = MAXMESSAGE - strlen(head) - strlen(tail); + if (netsize<=0) exit(EXIT_FAILURE); // this should never ever happen + + int avail = netsize; + int dlen = 0; + char datalet[MAXDATALET]; + + // copy the initial json header into the buffer + bzero(buff,sizeof(buff)); + memcpy(buff,head,strlen(head)); + buffhead = buffhead + strlen(head); + + // report all source address stats, cleaning up afterward + for (i=0; ( (i < option_x) && ( bb[i]->ip_addr.s_addr != 0 ) ); i++) { + + if ( bb[i]->ip_addr.s_addr != BCAST ) { + + // prepare a datalet + snprintf(datalet,MAXDATALET,"\"%s\":%d,",inet_ntoa(bb[i]->ip_addr),bb[i]->udp_count+bb[i]->tcp_count); + dlen = strlen(datalet); + + // see if the current datagram has room for the datalet + if ( avail > dlen ) { + + // append this datalet to the current datagram (minus null terminator) + avail = avail - dlen; + memcpy(buff+buffhead,datalet,dlen); + buffhead = buffhead + dlen; + } + // no room in current dgram + else { + + // remove trailing comma from the buffer so we can close it out + buffhead = buffhead - 1; + + // add the tail + strncpy(buff+buffhead,tail, strlen(tail)); + + // send the transmission if option_s is set + if (option_s > 0 ) { + sendto(sock,buff,strlen(buff)+1,0,(struct sockaddr *) &addr, addrlen); + microsleep(10); + } + + // init next datagram + bzero(buff,sizeof(buff)); + memcpy(buff,head,strlen(head)); + buffhead = strlen(head); + avail = netsize; + + // append this datalet to the current datagram (minus null terminatin) + avail = avail - dlen; + memcpy(buff+buffhead,datalet,dlen); + buffhead = buffhead + dlen; + } + } + + scour_bucket(i); + } + + // transmit final buffer contents if needed + if ( ( option_b == 0) && (buffhead>strlen(head)) ) { + + // remove trailing comma + buffhead = buffhead - 1; + + // add the tail + strncpy(buff+buffhead,tail,strlen(tail)); + + // send the multicast transmission + sendto(sock,buff,strlen(buff)+1,0,(struct sockaddr *) &addr, addrlen); + microsleep(10); + + bzero(buff,sizeof(buff)); + } + return 1; +} + + // calculate the running average within each bucket int calculate_averages() { - u_int i,j,delta,cursize,qps; - int newsize; - float qpsf; - char st_time[10]; - time_t now = time(0); - u_int types[] = {1,2,5,6,12,15,28,38,252,255,0}; - char *target; - char *names[] = {"A","NS","CNAME","SOA","PTR","MX","AAAA","A6","AXFR","ANY",""}; - struct tm *raw_time = localtime(&now); - snprintf(st_time, 9, "%02d:%02d:%02d",raw_time->tm_hour,raw_time->tm_min,raw_time->tm_sec); + u_int i,j,delta,cursize,qps; + int newsize; + float qpsf; + char st_time[10]; + time_t now = time(0); + u_int types[] = {1,2,5,6,12,15,28,38,252,255,0}; + char *target; + char *names[] = {"A","NS","CNAME","SOA","PTR","MX","AAAA","A6","AXFR","ANY",""}; + struct tm *raw_time = localtime(&now); + snprintf(st_time, 9, "%02d:%02d:%02d",raw_time->tm_hour,raw_time->tm_min,raw_time->tm_sec); - for (i=0; iip_addr.s_addr != BCAST) { - delta = now - bb[i]->first_packet; + // only process valid buckets + if ( bb[i]->ip_addr.s_addr != BCAST) { + delta = now - bb[i]->first_packet; - // let's try to avoid a divide-by-zero, shall we? - if (delta > 1 ) { + // let's try to avoid a divide-by-zero, shall we? + if (delta > 1 ) { + + // round our average and save it in the bucket + bb[i]->qps = (u_int)ceil( (bb[i]->tcp_count + bb[i]->udp_count) / (float)delta); + + // handle threshold crossing + if ( bb[i]->qps > option_t ) { + + // display detail to either syslog or stdout + if ( option_b ) { + if ( ! option_v ) { + printf("[%s] source [%s] - %u qps\n",st_time,inet_ntoa(bb[i]->ip_addr),bb[i]->qps); + fflush(stdout); + } + else { + printf("[%s] source [%s] - %u qps tcp : %u qps udp ",st_time,inet_ntoa(bb[i]->ip_addr), + (u_int)ceil( ((float)bb[i]->tcp_count/delta)), + (u_int)ceil( ((float)bb[i]->udp_count/delta))); + + if ( option_v >1 ) { + for (j=0;types[j];j++) { + qps = (u_int)ceil((float)bb[i]->qstats[types[j]]/delta); + if (qps){ + printf("[%u qps %s] ",qps,names[j]); + } + } + } + printf("\n"); + fflush(stdout); + } + } + else { + // if running in background, use alarm reset timer + if ((now-bb[i]->alarm_set)>option_a) { + + // display appropriate level of detail via syslog + if ( ! option_v ) { + syslog(LOG_NOTICE,"source [%s] - %u qps\n",inet_ntoa(bb[i]->ip_addr),bb[i]->qps); + } + else if (option_v > 1) { + target = (char *)malloc(sizeof(char)*MAXSYSLOG); + newsize = MAXSYSLOG; + cursize = snprintf(target,newsize,"source [%s] - %u tcp qps : %u udp qps ",inet_ntoa(bb[i]->ip_addr), + (u_int)ceil( ((float)bb[i]->tcp_count/delta)), + (u_int)ceil( ((float)bb[i]->udp_count/delta))); + newsize-=cursize; - // round our average and save it in the bucket - bb[i]->qps = (u_int)ceil( (bb[i]->tcp_count + bb[i]->udp_count) / (float)delta); + for (j=0;types[j];j++ ) { + qps = (u_int)ceil(((float)bb[i]->qstats[types[j]]/delta)); + if ( ( qps > 0) && ( newsize > 1 ) ) { + cursize = snprintf(target+(MAXSYSLOG-newsize),newsize,"[%u qps %s] ",qps,names[j]); + newsize-=cursize; + } + } + if (newsize <= 0 ) { + target[MAXSYSLOG-1]='\0'; + } + syslog(LOG_NOTICE,"%s",target); + free(target); + } + else { + syslog(LOG_NOTICE,"source [%s] - %u tcp qps - %u udp qps\n",inet_ntoa(bb[i]->ip_addr), + (u_int)ceil( ((float)bb[i]->tcp_count/delta)), + (u_int)ceil( ((float)bb[i]->udp_count/delta))); + } - // handle threshold crossing - if ( bb[i]->qps > option_t ) { + // reset alarm + bb[i]->alarm_set = now; + } + } + } + } + } + } + + // 'mark stats' if required and it is time + delta = (u_int)(now - bb[totals]->first_packet); + if ( (option_m > 0)&&(delta > 1)&&(delta >= option_m) ) { + // handle bindsnap mode + if (option_b) { + printf("[%s] totals - %3.2f qps tcp : %3.2f qps udp ",st_time, ((float)bb[totals]->tcp_count/delta),((float)bb[totals]->udp_count/delta)); + if (option_v) { + for (j=0;types[j];j++) { + qpsf = ((float)bb[totals]->qstats[types[j]]/delta); + if (qpsf > 0){ + printf("[%3.2f qps %s] ",qpsf,names[j]); + } + } + } + printf("\n"); + fflush(stdout); + } + else { + // agonizing high verbosity code + if (option_v) { + target = (char *)malloc(sizeof(char)*MAXSYSLOG); + newsize = MAXSYSLOG; + cursize = snprintf(target,newsize,"[totals] - %3.2f tcp qps : %3.2f udp qps ", + ((float)bb[totals]->tcp_count/delta), + ((float)bb[totals]->udp_count/delta)); + newsize-=cursize; - // display detail to either syslog or stdout - if ( option_b ) { - if ( ! option_v ) { - printf("[%s] source [%s] - %u qps\n",st_time,inet_ntoa(bb[i]->ip_addr),bb[i]->qps); - fflush(stdout); - } - else { - printf("[%s] source [%s] - %u qps tcp : %u qps udp ",st_time,inet_ntoa(bb[i]->ip_addr), - (u_int)ceil( ((float)bb[i]->tcp_count/delta)), - (u_int)ceil( ((float)bb[i]->udp_count/delta)) - ); - if ( option_v >1 ) { - for (j=0;types[j];j++) { - qps = (u_int)ceil((float)bb[i]->qstats[types[j]]/delta); - if (qps){ - printf("[%u qps %s] ",qps,names[j]); - } - } - } - printf("\n"); - fflush(stdout); - } - } - else { - // if running in background, use alarm reset timer - if ((now-bb[i]->alarm_set)>option_a) { - - // display appropriate level of detail via syslog - if ( ! option_v ) { - syslog(LOG_NOTICE,"source [%s] - %u qps\n",inet_ntoa(bb[i]->ip_addr),bb[i]->qps); - } - else if (option_v > 1) { - target = (char *)malloc(sizeof(char)*MAXSYSLOG); - newsize = MAXSYSLOG; - cursize = snprintf(target,newsize,"source [%s] - %u tcp qps : %u udp qps ",inet_ntoa(bb[i]->ip_addr), - (u_int)ceil( ((float)bb[i]->tcp_count/delta)), - (u_int)ceil( ((float)bb[i]->udp_count/delta)) - ); - newsize-=cursize; - - for (j=0;types[j];j++ ) { - qps = (u_int)ceil(((float)bb[i]->qstats[types[j]]/delta)); - if ( ( qps > 0) && ( newsize > 1 ) ) { - cursize = snprintf(target+(MAXSYSLOG-newsize),newsize,"[%u qps %s] ",qps,names[j]); - newsize-=cursize; - } - } - if (newsize <= 0 ) { - target[MAXSYSLOG-1]='\0'; - } - syslog(LOG_NOTICE,"%s",target); - free(target); - } - else { - syslog(LOG_NOTICE,"source [%s] - %u tcp qps - %u udp qps\n",inet_ntoa(bb[i]->ip_addr), - (u_int)ceil( ((float)bb[i]->tcp_count/delta)), - (u_int)ceil( ((float)bb[i]->udp_count/delta)) - ); - } - - // reset alarm - bb[i]->alarm_set = now; - } - } - } - } - } - } - - // 'mark stats' if required and it is time - delta = (u_int)(now - bb[totals]->first_packet); - if ( (option_m > 0)&&(delta > 1)&&(delta >= option_m) ) { - - // handle bindsnap mode - if (option_b) { - printf("[%s] totals - %3.2f qps tcp : %3.2f qps udp ",st_time, ((float)bb[totals]->tcp_count/delta),((float)bb[totals]->udp_count/delta)); - if (option_v) { - for (j=0;types[j];j++) { - qpsf = ((float)bb[totals]->qstats[types[j]]/delta); - if (qpsf > 0){ - printf("[%3.2f qps %s] ",qpsf,names[j]); - } - } - } - printf("\n"); - fflush(stdout); - } - else { - // agonizing high verbosity code - if (option_v) { - target = (char *)malloc(sizeof(char)*MAXSYSLOG); - newsize = MAXSYSLOG; - cursize = snprintf(target,newsize,"[totals] - %3.2f tcp qps : %3.2f udp qps ", - ((float)bb[totals]->tcp_count/delta), - ((float)bb[totals]->udp_count/delta) - ); - newsize-=cursize; - - for (j=0;types[j];j++ ) { - qpsf = ((float)bb[totals]->qstats[types[j]]/delta); - if ( ( qpsf > 0) && ( newsize > 1 ) ) { - cursize = snprintf(target+(MAXSYSLOG-newsize),newsize,"[%3.2f qps %s] ",qpsf,names[j]); - newsize-=cursize; - } - } - if (newsize <= 0 ) { - target[MAXSYSLOG-1]='\0'; - } - syslog(LOG_NOTICE,"%s",target); - free(target); - } - else { - syslog(LOG_NOTICE,"[totals] - %3.2f tcp qps : %3.2f udp qps\n", - ((float)bb[totals]->tcp_count/delta), - ((float)bb[totals]->udp_count/delta) - ); - } - } - scour_bucket(totals); - } - - return 1; + for (j=0;types[j];j++ ) { + qpsf = ((float)bb[totals]->qstats[types[j]]/delta); + if ( ( qpsf > 0) && ( newsize > 1 ) ) { + cursize = snprintf(target+(MAXSYSLOG-newsize),newsize,"[%3.2f qps %s] ",qpsf,names[j]); + newsize-=cursize; + } + } + if (newsize <= 0 ) { + target[MAXSYSLOG-1]='\0'; + } + syslog(LOG_NOTICE,"%s",target); + free(target); + } + else { + syslog(LOG_NOTICE,"[totals] - %3.2f tcp qps : %3.2f udp qps\n", + ((float)bb[totals]->tcp_count/delta), + ((float)bb[totals]->udp_count/delta)); + } + } + scour_bucket(totals); + } + return 1; } int valid_dns_char(char c) { - if((c >= '0' && c <= '9') - || (c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c == '-') - || (c == '_')) // is valid for SRV records. - return 1; + if((c >= '0' && c <= '9') + || (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c == '-') + || (c == '_')) // is valid for SRV records. + return 1; - return 0; + return 0; } + // purge and initialize all buckets void init_buckets() { u_int i; - // create bucket brigade (final bucket is for totals) - pthread_mutex_lock(&stats_lock); - if ( ( bb = malloc( sizeof(struct bucket *) * (option_x+1)) ) == NULL ) malloc_fail("bb", sizeof(struct bucket *) * (option_x+1)); - for (i=0; i <=option_x; i++ ) { - if ( ( bb[i] = (struct bucket *)malloc( sizeof(struct bucket) ) ) == NULL) malloc_fail("bb[i]", sizeof(struct bucket) ); - scour_bucket(i); - } - pthread_mutex_unlock(&stats_lock); + // create bucket brigade (final bucket is for totals) + pthread_mutex_lock(&stats_lock); + if ( ( bb = malloc( sizeof(struct bucket *) * (option_x+1)) ) == NULL ) malloc_fail("bb", sizeof(struct bucket *) * (option_x+1)); + for (i=0; i <=option_x; i++ ) { + if ( ( bb[i] = (struct bucket *)malloc( sizeof(struct bucket) ) ) == NULL) malloc_fail("bb[i]", sizeof(struct bucket) ); + scour_bucket(i); + } + pthread_mutex_unlock(&stats_lock); } // clean out a bucket while avoiding obvious memory leak int scour_bucket( int i ) { - int j; + int j; - bb[i]->ip_addr.s_addr=BCAST; - bb[i]->tcp_count=0; - bb[i]->udp_count=0; - bb[i]->qps=0; - bb[i]->first_packet=time(0); - bb[i]->last_packet=(time_t)0; - bb[i]->alarm_set=(time_t)0; + bb[i]->ip_addr.s_addr=BCAST; + bb[i]->tcp_count=0; + bb[i]->udp_count=0; + bb[i]->qps=0; + bb[i]->first_packet=time(0); + bb[i]->last_packet=(time_t)0; + bb[i]->alarm_set=(time_t)0; - for (j=0;j<256;j++) { - bb[i]->qstats[j]=0; - } - return 1; + for (j=0;j<256;j++) { + bb[i]->qstats[j]=0; + } + + return 1; } // add a packet to a bucket int add_to_bucket ( struct in_addr *ip_src, int ip_proto, int num_queries, u_int8_t qtype) { - int bucket = 0; + int bucket = 0; - // get the bucket to put packet in - pthread_mutex_lock(&stats_lock); - bucket = find_bucket(ip_src); + // get the bucket to put packet in + pthread_mutex_lock(&stats_lock); + bucket = find_bucket(ip_src); - // set bucket fields - bb[bucket]->last_packet = time(0); - if (ip_proto == 6 ) { - bb[bucket]->tcp_count+=num_queries; - bb[totals]->tcp_count+=num_queries; - } - else { - bb[bucket]->udp_count+=num_queries; - bb[totals]->udp_count+=num_queries; - } + // set bucket fields + bb[bucket]->last_packet = time(0); + if (ip_proto == 6 ) { + bb[bucket]->tcp_count+=num_queries; + bb[totals]->tcp_count+=num_queries; + } + else { + bb[bucket]->udp_count+=num_queries; + bb[totals]->udp_count+=num_queries; + } - bb[bucket]->qstats[qtype]+=num_queries; - bb[totals]->qstats[qtype]+=num_queries; - pthread_mutex_unlock(&stats_lock); + bb[bucket]->qstats[qtype]+=num_queries; + bb[totals]->qstats[qtype]+=num_queries; + pthread_mutex_unlock(&stats_lock); - return 1; + return 1; } // figure out where to put this packet int find_bucket(struct in_addr *ip_src) { - int i, bucket=0; - time_t oldest=0; + int i, bucket=0; + time_t oldest=0; - // look for an existing bucket for this IP - for (i=0; i< option_x; i++ ){ - // ip field of bucket seems to match the ip we are checking - if (bb[i]->ip_addr.s_addr == ip_src->s_addr) { - return i; - } - } + // look for an existing bucket for this IP + for (i=0; i< option_x; i++ ){ + // ip field of bucket seems to match the ip we are checking + if (bb[i]->ip_addr.s_addr == ip_src->s_addr) { + return i; + } + } - // look for unused buckets - for (i=0; i< option_x; i++ ) { + // look for unused buckets + for (i=0; i< option_x; i++ ) { - // found an unused one - clean it, init it, and return it - if ( bb[i]->ip_addr.s_addr == BCAST ) { - scour_bucket(i); - bb[i]->ip_addr.s_addr = ip_src->s_addr; - return i; - } + // found an unused one - clean it, init it, and return it + if ( bb[i]->ip_addr.s_addr == BCAST ) { + scour_bucket(i); + bb[i]->ip_addr.s_addr = ip_src->s_addr; + return i; + } - // find the most stagnant bucket in case we need it - // avoids another loop through the buckets - // TODO - should we autoflush buckets after some idle time, - // or after alarming? fixes the case where - // alarms are unlikely to reappear even if a client - // resumes flooding if there isn't bucket contention - // churning them out and resetting the timer for the rate - // calculation... - if ( ( bb[i]->last_packet != 0 ) && ((oldest==0)||( bb[i]->last_packet < oldest))) { - oldest = bb[i]->last_packet; - bucket = i; - } - } + // find the most stagnant bucket in case we need it + // avoids another loop through the buckets + // TODO - should we autoflush buckets after some idle time, + // or after alarming? fixes the case where + // alarms are unlikely to reappear even if a client + // resumes flooding if there isn't bucket contention + // churning them out and resetting the timer for the rate + // calculation... - // use the most stagnant bucket since all are in use - // clean it, init it, and return it - scour_bucket(bucket); - bb[i]->ip_addr.s_addr = ip_src->s_addr; + if ( ( bb[i]->last_packet != 0 ) && ((oldest==0)||( bb[i]->last_packet < oldest))) { + oldest = bb[i]->last_packet; + bucket = i; + } + } - return bucket; + // use the most stagnant bucket since all are in use + // clean it, init it, and return it + scour_bucket(bucket); + bb[i]->ip_addr.s_addr = ip_src->s_addr; + + return bucket; } // handle all packets we throw at it void handle_IP(u_char *args, const struct pcap_pkthdr* pkthdr,const u_char* packet){ - const struct ip* ip; - const struct my_dns *dns; - const struct tcphdr *tcp; - const struct udphdr *udp; - u_int length = pkthdr->len; - u_int caplen = pkthdr->caplen; - u_int hlen,off,version; - unsigned char dname[NS_MAXDNAME]=""; - struct in_addr ip_src; - unsigned char *data; - u_int len,dpos; - u_int8_t qtype,tlen; + const struct ip* ip; + const struct my_dns *dns; + const struct tcphdr *tcp; + const struct udphdr *udp; + u_int length = pkthdr->len; + u_int caplen = pkthdr->caplen; + u_int hlen,off,version; + unsigned char dname[NS_MAXDNAME]=""; + struct in_addr ip_src; + unsigned char *data; + u_int len,dpos; + u_int8_t qtype,tlen; - // skip the ethernet header - length -= sizeof(struct ether_header); + // skip the ethernet header + length -= sizeof(struct ether_header); - // make sure packet is a valid length - if (length < sizeof(struct ip)) { - return; - } + // make sure packet is a valid length + if (length < sizeof(struct ip)) { + return; + } - // snap off the ip portion - ip = (struct ip*)(packet + sizeof(struct ether_header)); + // snap off the ip portion + ip = (struct ip*)(packet + sizeof(struct ether_header)); - // get utility params for sanity checking - len = ntohs(ip->ip_len); - hlen = ip->ip_hl; - version = ip->ip_v; + // get utility params for sanity checking + len = ntohs(ip->ip_len); + hlen = ip->ip_hl; + version = ip->ip_v; - // let's not do ipv6 just yet - if(version != 4) { - return; - } + // let's not do ipv6 just yet + if(version != 4) { + return; + } - // make sure we have a sane header length - if(hlen < 5 ) { - return; - } + // make sure we have a sane header length + if(hlen < 5 ) { + return; + } - // do we have the everything we are supposed to? - if(length < len) { - return; - } + // do we have the everything we are supposed to? + if(length < len) { + return; + } - // make sure we are only processing the first fragment - off = ntohs(ip->ip_off); - if((off & 0x1fff) == 0 ) { + // make sure we are only processing the first fragment + off = ntohs(ip->ip_off); + if((off & 0x1fff) == 0 ) { - // get the source ip - ip_src.s_addr = ip->ip_src.s_addr; + // get the source ip + ip_src.s_addr = ip->ip_src.s_addr; - // process udp packets - if ( ip->ip_p == 17 ) { - udp = (struct udphdr *) ( (char *) packet + sizeof(struct ether_header)+ sizeof (struct ip) ); + // process udp packets + if ( ip->ip_p == 17 ) { + udp = (struct udphdr *) ( (char *) packet + sizeof(struct ether_header)+ sizeof (struct ip) ); - // try to make sure it is safe to cast packet into dns structure - if ( (sizeof(struct my_dns)+sizeof(struct ether_header)+sizeof(struct ip)+sizeof(struct udphdr)) >= caplen ) { - return; - } - else { - // populate dns header - dns = (struct my_dns *) ( (char *) packet + sizeof(struct ether_header) + sizeof (struct ip) + sizeof (struct udphdr) ); - data = (char *) packet +sizeof(struct ether_header) + sizeof (struct ip) + sizeof (struct udphdr) + sizeof(struct my_dns); - } - } + // try to make sure it is safe to cast packet into dns structure + if ( (sizeof(struct my_dns)+sizeof(struct ether_header)+sizeof(struct ip)+sizeof(struct udphdr)) >= caplen ) { + return; + } + else { + // populate dns header + dns = (struct my_dns *) ( (char *) packet + sizeof(struct ether_header) + sizeof (struct ip) + sizeof (struct udphdr) ); + data = (unsigned char *) packet +sizeof(struct ether_header) + sizeof (struct ip) + sizeof (struct udphdr) + sizeof(struct my_dns); + } + } - // process tcp packets - else if ( ip->ip_p == 6 ) { - tcp = (struct tcphdr *) ( (char *) packet + sizeof(struct ether_header)+ sizeof (struct ip) ); + // process tcp packets + else if ( ip->ip_p == 6 ) { + tcp = (struct tcphdr *) ( (char *) packet + sizeof(struct ether_header)+ sizeof (struct ip) ); - // ignore packets without push flag set - if (! tcp->th_flags & TH_PUSH) return; + // ignore packets without push flag set + if (! (tcp->th_flags & TH_PUSH)) return; - // try to make sure it is safe to cast packet into dns structure - if ( (sizeof(struct my_dns)+sizeof(struct ether_header)+sizeof(struct ip)+(tcp->th_off * sizeof(u_int32_t)) + sizeof(u_int16_t)) >= caplen ) { - return; - } - else { - // populate dns header - // tcp dns lookups also include a 16bit length field = dns header + data. - dns = (struct my_dns *) ( (char *) packet + sizeof(struct ether_header)+ sizeof (struct ip) + (tcp->th_off * sizeof(u_int32_t) + sizeof(u_int16_t))); - data = (char *) packet + sizeof(struct ether_header) + sizeof (struct ip) + (tcp->th_off * sizeof(u_int32_t)) + sizeof(struct my_dns) + sizeof(u_int16_t); - } - } - - // hmm.. not tcp, not udp.. move on. - else { - return; - } + // try to make sure it is safe to cast packet into dns structure + if ( (sizeof(struct my_dns)+sizeof(struct ether_header)+sizeof(struct ip)+(tcp->th_off * sizeof(u_int32_t)) + sizeof(u_int16_t)) >= caplen ) { + return; + } + else { + // populate dns header + // tcp dns lookups also include a 16bit length field = dns header + data. + dns = (struct my_dns *) ( (char *) packet + sizeof(struct ether_header)+ sizeof (struct ip) + (tcp->th_off * sizeof(u_int32_t) + sizeof(u_int16_t))); + data = (unsigned char *) packet + sizeof(struct ether_header) + sizeof (struct ip) + (tcp->th_off * sizeof(u_int32_t)) + sizeof(struct my_dns) + sizeof(u_int16_t); + } + } - // we only want queries, not responses - if ( dns->dns_flags1 & 0x80 ) { - return; - } + // hmm.. not tcp, not udp.. move on. + else { + return; + } - // ignore packets with no questions - if (ntohs(dns->dns_qdcount) == 0) { - return; - } + // we only want queries, not responses + if ( dns->dns_flags1 & 0x80 ) { + return; + } + + // ignore packets with no questions + if (ntohs(dns->dns_qdcount) == 0) { + return; + } - // get the domain name and query type - tlen=dpos=0; - for (;(*data)&&((void *)data<((void *)packet+caplen-1)); data++) { - if (!tlen) tlen=*data; - for (;(tlen&&((void *)data<((void *)packet+caplen-1)));tlen--){ - data++; - // bail on an invalid dns char - if(!valid_dns_char(*data)) { - return; - } - if (dposip_p == 17 ? "udp" : "tcp"), qtype, dname); - } + if( option_D ) { + printf("src: %-15s proto: %s qtype: 0x%02x domain: %s\n", (inet_ntoa(ip_src)), + (ip->ip_p == 17 ? "udp" : "tcp"), qtype, dname); + } - // add packet to bucket array - if (ntohs(dns->dns_qdcount)&&qtype) { - add_to_bucket( &ip_src, ip->ip_p, 1, qtype ); - } - } - return; + // add packet to bucket array + if (ntohs(dns->dns_qdcount)&&qtype) { + add_to_bucket( &ip_src, ip->ip_p, 1, qtype ); + } + } + return; } // main logic // some pcap code borrowed from http://www.cet.nau.edu/~mc8/Socket/Tutorials/section1.html -int main(int argc,char **argv){ - char *dev = NULL; - pthread_t thread; - char errbuf[PCAP_ERRBUF_SIZE]; - pcap_t* descr; - struct bpf_program fp; /* hold compiled program */ - bpf_u_int32 maskp=0; /* subnet mask */ - bpf_u_int32 netp=0; /* ip */ - char *filter = NULL; - char *dst_addr = NULL; - char *dst_mask = NULL; - struct sigaction sa; - struct in_addr addr,tmpaddr; - u_int f_size; - char *args = NULL; - char *name = NULL; - u_int c = 0; + int main(int argc,char **argv){ + char *dev = NULL; + pthread_t thread; + char errbuf[PCAP_ERRBUF_SIZE]; + pcap_t* descr; + struct bpf_program fp; /* hold compiled program */ + bpf_u_int32 maskp=0; /* subnet mask */ + bpf_u_int32 netp=0; /* ip */ + char *filter = NULL; + char *dst_addr = NULL; + char *dst_mask = NULL; + struct sigaction sa; + struct in_addr addr,tmpaddr; + u_int f_size; + char *name = NULL; + u_int c = 0; - if ( ( name = (char *)strdup(argv[0]) ) == NULL) malloc_fail("name", strlen(argv[0]) ); - // loop through command line options and get options - while(1) { - c = getopt(argc, argv,"i:t:a:w:x:m:A:M:QbdDvh"); + if ( ( name = (char *)strdup(argv[0]) ) == NULL) malloc_fail("name", strlen(argv[0]) ); + + // loop through command line options and get options + while(1) { + c = getopt(argc, argv,"i:t:a:w:x:m:A:M:QbdDvsh"); - if (c==-1) break; - switch(c) { - case 0: - break; - case 'i': - if (optarg) { - if ( ( dev = (char *)strdup(optarg) ) == NULL) malloc_fail("dev", strlen(optarg) ); - } - break; - case 't': - if (optarg) { - if ( abs (atoi(optarg)) > 0) { - option_t = abs( atoi(optarg)); - } - } - break; - case 'a': - if (optarg) { - if ( abs (atoi(optarg)) > 10) { - option_a = abs( atoi(optarg)); - } - } - break; - case 'w': - if (optarg) { - if ( abs (atoi(optarg)) > 1) { - option_w = abs( atoi(optarg)); - } - } - break; - case 'x': - if (optarg) { - if ( abs (atoi(optarg)) > 10) { - option_x = abs( atoi(optarg)); - } - } - break; - case 'm': - if (optarg) { - if ( abs (atoi(optarg)) > 0) { - option_m = abs( atoi(optarg)); - } - } - break; - case 'M': - if (optarg && (dst_mask == NULL) ) { - if ( inet_aton(optarg, &tmpaddr) ) { - if ( ( dst_mask = (char *)strdup(optarg) ) == NULL) malloc_fail("filter mask", strlen(optarg) ); - option_M=1; - } else { - fprintf(stderr,"Invalid filter mask \"%s\"\n",optarg); - option_h = 1; - } - } - break; - case 'A': - if (optarg && (dst_addr == NULL) ) { - if ( inet_aton(optarg, &tmpaddr) ) { - if ( ( dst_addr = (char *)strdup(optarg) ) == NULL) malloc_fail("dest filter", strlen(optarg) ); - option_A=1; - } else { - fprintf(stderr,"Invalid filter address \"%s\"\n",optarg); - option_h = 1; - } - } - break; - case 'Q': - option_Q = 1; - break; - case 'b': - option_b = 1; - break; - case 'd': - option_d = 1; - break; - case 'D': - option_D = 1; - break; - case 'v': - option_v++; - break; - case 'h': - option_h = 1; - default: - break; - } - } + if (c==-1) break; + switch(c) { + case 0: + break; + case 'i': + if (optarg) { + if ( ( dev = (char *)strdup(optarg) ) == NULL) malloc_fail("dev", strlen(optarg) ); + } + break; + case 't': + if (optarg) { + if ( abs (atoi(optarg)) > 0) { + option_t = abs( atoi(optarg)); + } + } + break; + case 'a': + if (optarg) { + if ( abs (atoi(optarg)) > 10) { + option_a = abs( atoi(optarg)); + } + } + break; + case 'w': + if (optarg) { + if ( abs (atoi(optarg)) > 1) { + option_w = abs( atoi(optarg)); + } + } + break; + case 'x': + if (optarg) { + if ( abs (atoi(optarg)) > 10) { + option_x = abs( atoi(optarg)); + } + } + break; + case 'm': + if (optarg) { + if ( abs (atoi(optarg)) > 0) { + option_m = abs( atoi(optarg)); + } + } + break; + case 'M': + if (optarg && (dst_mask == NULL) ) { + if ( inet_aton(optarg, &tmpaddr) ) { + if ( ( dst_mask = (char *)strdup(optarg) ) == NULL) malloc_fail("filter mask", strlen(optarg) ); + option_M=1; + } else { + fprintf(stderr,"Invalid filter mask \"%s\"\n",optarg); + option_h = 1; + } + } + break; + case 'A': + if (optarg && (dst_addr == NULL) ) { + if ( inet_aton(optarg, &tmpaddr) ) { + if ( ( dst_addr = (char *)strdup(optarg) ) == NULL) malloc_fail("dest filter", strlen(optarg) ); + option_A=1; + } else { + fprintf(stderr,"Invalid filter address \"%s\"\n",optarg); + option_h = 1; + } + } + break; + case 'Q': + option_Q = 1; + break; + case 'b': + option_b = 1; + break; + case 'd': + option_d = 1; + break; + case 'D': + option_D = 1; + break; + case 'v': + option_v++; + break; + case 's': + option_s = 1; + break; + case 'h': + option_h = 1; + break; + case 'z': + target_ip = optarg; + break; + case 'p': + target_port = atoi(optarg); + break; + default: + break; + } + } - // display usage info if needed - if (optindN queries per second\n"); - fprintf(stderr,"-a N reset alarm after N seconds\n"); - fprintf(stderr,"-w N calculate stats every N seconds\n"); - fprintf(stderr,"-x N create N buckets\n"); - fprintf(stderr,"-m N report overall stats every N seconds\n"); - fprintf(stderr,"-A addr filter for specific address\n"); - fprintf(stderr,"-M mask netmask for filter (in conjunction with -A)\n"); - fprintf(stderr,"-Q don't filter by local interface address\n"); - fprintf(stderr,"-b run in foreground in bindsnap mode\n"); - fprintf(stderr,"-d run in background in daemon mode\n"); - fprintf(stderr,"-D dump dns packets (implies -b)\n"); - fprintf(stderr,"-v verbose output - use again for more verbosity\n"); - fprintf(stderr,"-h display this usage information\n"); - exit(1); - } + // display usage info if needed + if (optindN queries per second\n"); + fprintf(stderr,"-a N reset alarm after N seconds\n"); + fprintf(stderr,"-w N calculate stats every N seconds\n"); + fprintf(stderr,"-x N create N buckets\n"); + fprintf(stderr,"-m N report overall stats every N seconds\n"); + fprintf(stderr,"-A addr filter for specific address\n"); + fprintf(stderr,"-M mask netmask for filter (in conjunction with -A)\n"); + fprintf(stderr,"-Q don't filter by local interface address\n"); + fprintf(stderr,"-b run in foreground in bindsnap mode\n"); + fprintf(stderr,"-d run in background in daemon mode\n"); + fprintf(stderr,"-D dump dns packets (implies -b)\n"); + fprintf(stderr,"-v verbose output - use again for more verbosity\n"); + fprintf(stderr,"-s send source-IP data to network collector as JSON\n"); + fprintf(stderr,"-z addr address to send stats to (default 226.1.1.2)\n"); + fprintf(stderr,"-p N UDP port to send stats to (default 2000)\n"); + fprintf(stderr,"-h display this usage information\n"); + exit(1); + } + + if ( target_ip == NULL ) { + target_ip = DEFAULT_IP; + } + + // if dumping packets, force option_b and disable option_d + if( option_D ) { + if( ! option_b ) + option_b = 1; - if( option_d ) - option_d = 0; + if( option_d ) + option_d = 0; + } - } + if ( ( option_Q ) && ( option_A ) ) { + fprintf(stderr,"%s couldn't start\n",name); + fprintf(stderr,"You can't specify both -A (address filter) and -Q (no filter)\n"); + exit(1); + } - if ( ( option_Q ) && ( option_A ) ) { - fprintf(stderr,"%s couldn't start\n",name); - fprintf(stderr,"You can't specify both -A (address filter) and -Q (no filter)\n"); - exit(1); - } - if ( ( ! option_d ) && ( ! option_b ) ) { - fprintf(stderr,"%s couldn't start\n",name); - fprintf(stderr,"You must specify either -d (daemon) or -b (bindsnap)\n"); - exit(1); - } - free(name); - // set up for daemonized operation unless running in bindsnap mode - if ( ! option_b ) { - openlog("dns_flood_detector",LOG_PID|LOG_CONS,LOG_DAEMON); - syslog(LOG_NOTICE,"dns_flood_detector starting"); + if ( ( ! option_d ) && ( ! option_b ) ) { + fprintf(stderr,"%s couldn't start\n",name); + fprintf(stderr,"You must specify either -d (daemon) or -b (bindsnap)\n"); + exit(1); + } + free(name); - // daemonize unless running in bindsnap mode - daemonize(); + // set up for daemonized operation unless running in bindsnap mode + if ( ! option_b ) { + openlog("dns_flood_detector",LOG_PID|LOG_CONS,LOG_DAEMON); + syslog(LOG_NOTICE,"dns_flood_detector starting"); - // set up signal handlers - sa.sa_handler=exit; - sa.sa_flags=0; - if(sigaction(SIGTERM,&sa,NULL)) { - syslog(LOG_ERR,"Unable to set signal handler: %s. Exiting.", - strerror(errno)); - } - } + // daemonize unless running in bindsnap mode + daemonize(); - // find a valid device to open - if(dev == NULL && ( (dev=pcap_lookupdev(errbuf)) == NULL ) ){ - fprintf(stderr,"unable to bind to valid device\n"); - exit(1); - } + // set up signal handlers + sa.sa_handler=exit; + sa.sa_flags=0; + if(sigaction(SIGTERM,&sa,NULL)) { + syslog(LOG_ERR,"Unable to set signal handler: %s. Exiting.",strerror(errno)); + } + } - /* restrict to queries to primary local address? */ - if (option_Q) { - f_size = strlen("port 53 "); - if ( ( filter = (char *) malloc ( f_size+1) ) == NULL ) malloc_fail( "filter", f_size+1 ); - snprintf( filter, f_size, "port 53"); - } else { - if (! option_A) { - // get network address and netmask for device - pcap_lookupnet(dev,&netp,&maskp,errbuf); + // find a valid device to open + if(dev == NULL && ( (dev=pcap_lookupdev(errbuf)) == NULL ) ){ + fprintf(stderr,"unable to bind to valid device\n"); + exit(1); + } + + /* restrict to queries to primary local address? */ + if (option_Q) { + f_size = strlen("port 53 "); + if ( ( filter = (char *) malloc ( f_size+1) ) == NULL ) malloc_fail( "filter", f_size+1 ); + snprintf( filter, f_size, "port 53"); + } else { + if (! option_A) { + // get network address and netmask for device + pcap_lookupnet(dev,&netp,&maskp,errbuf); - // set up filter with local network - addr.s_addr = (unsigned long int)netp; - if ( ( dst_addr = (char *)malloc( strlen((char *)inet_ntoa(addr))+1) ) == NULL ) malloc_fail("dest_addr", strlen((char *)inet_ntoa(addr))+1 ); - strncpy(dst_addr,(char*)inet_ntoa(addr),strlen((char *)inet_ntoa(addr))); - dst_addr[strlen((char *)inet_ntoa(addr))]='\0'; + // set up filter with local network + addr.s_addr = (unsigned long int)netp; + if ( ( dst_addr = (char *)malloc( strlen((char *)inet_ntoa(addr))+1) ) == NULL ) malloc_fail("dest_addr", strlen((char *)inet_ntoa(addr))+1 ); + strncpy(dst_addr,(char*)inet_ntoa(addr),strlen((char *)inet_ntoa(addr))); + dst_addr[strlen((char *)inet_ntoa(addr))]='\0'; - addr.s_addr = (unsigned long int)maskp; - if (!option_M) { - if ( ( dst_mask = (char *)malloc( strlen((char *)inet_ntoa(addr))+1) ) == NULL ) malloc_fail("dest_mask", strlen((char *)inet_ntoa(addr))+1 ); - strncpy(dst_mask,(char*)inet_ntoa(addr),strlen((char *)inet_ntoa(addr))); - dst_mask[strlen((char *)inet_ntoa(addr))]='\0'; - } - } else { - // we're using an address from -A - if (!option_M) { - // if no mask was specified, then use just a host mask - if ( ( dst_mask = (char *)malloc(16) ) == NULL ) malloc_fail("dest_mask", 16); - strncpy(dst_mask,"255.255.255.255",15); - } - } + addr.s_addr = (unsigned long int)maskp; + + if (!option_M) { + if ( ( dst_mask = (char *)malloc( strlen((char *)inet_ntoa(addr))+1) ) == NULL ) malloc_fail("dest_mask", strlen((char *)inet_ntoa(addr))+1 ); + strncpy(dst_mask,(char*)inet_ntoa(addr),strlen((char *)inet_ntoa(addr))); + dst_mask[strlen((char *)inet_ntoa(addr))]='\0'; + } + } else { + // we're using an address from -A + if (!option_M) { + // if no mask was specified, then use just a host mask + if ( ( dst_mask = (char *)malloc(16) ) == NULL ) malloc_fail("dest_mask", 16); + strncpy(dst_mask,"255.255.255.255",15); + } + } - f_size = strlen("port 53 and dst net mask ")+ strlen(dst_mask)+ strlen(dst_addr); - if ( ( filter = (char *) malloc ( f_size+1) ) == NULL ) malloc_fail( "filter", f_size+1 ); - snprintf( filter, f_size, "port 53 and dst net %s mask %s", dst_addr, dst_mask); + f_size = strlen("port 53 and dst net mask ")+ strlen(dst_mask)+ strlen(dst_addr); + if ( ( filter = (char *) malloc ( f_size+1) ) == NULL ) malloc_fail( "filter", f_size+1 ); + snprintf( filter, f_size, "port 53 and dst net %s mask %s", dst_addr, dst_mask); - free (dst_mask); - free (dst_addr); - } + free (dst_mask); + free (dst_addr); + } - if ( option_b && option_v ) { - printf("using filter \"%s\" on dev %s\n", filter, dev); - } - // open device for reading only local traffic - descr = pcap_open_live(dev,1500,0,1,errbuf); - if(descr == NULL) { - fprintf(stderr,"unable to open device %s\n",dev); - exit(1); - } + if ( option_b && option_v ) { + printf("using filter \"%s\" on dev %s\n", filter, dev); + } - // compile filter - if(pcap_compile(descr,&fp,filter,0,netp) == -1) { - fprintf(stderr,"error compiling filter: %s\n",pcap_geterr(descr)); - exit(1); - } + // open device for reading only local traffic + descr = pcap_open_live(dev,1500,0,1,errbuf); + if(descr == NULL) { + fprintf(stderr,"unable to open device %s\n",dev); + exit(1); + } - // set filter - if(pcap_setfilter(descr,&fp) == -1){ - fprintf(stderr,"error setting filter: %s\n",pcap_geterr(descr)); - exit(1); - } + // compile filter + if(pcap_compile(descr,&fp,filter,0,netp) == -1) { + fprintf(stderr,"error compiling filter: %s\n",pcap_geterr(descr)); + exit(1); + } - // initialize buckets and mark overall stats bucket - init_buckets(); - totals = option_x; + // set filter + if(pcap_setfilter(descr,&fp) == -1){ + fprintf(stderr,"error setting filter: %s\n",pcap_geterr(descr)); + exit(1); + } - // create mutex lock - if (pthread_mutex_init(&stats_lock, NULL) < 0) { - exit(1); - } + // initialize buckets and mark overall stats bucket + init_buckets(); + totals = option_x; - // launch watcher thread - if (pthread_create (&thread, NULL, run_stats, (void *)0)) { - exit(1); - } + // create mutex lock + if (pthread_mutex_init(&stats_lock, NULL) < 0) { + exit(1); + } - // main pcap loop - pcap_loop(descr,-1,handle_IP,args); + // launch watcher thread + if (pthread_create (&thread, NULL, run_stats, (void *)0)) { + exit(1); + } - // done - closelog(); - return 0; + // main pcap loop + pcap_loop(descr,-1,&handle_IP,NULL); + + // done + closelog(); + return 0; } // daemonize the process int daemonize(void) { - pid_t pid; - int fd; - - fd=open("/dev/null",O_RDWR); - if(fd<0) { - syslog(LOG_ERR,"Failed to open /dev/null: %s. Exiting.",strerror(errno)); - exit(1); - } - - dup2(fd,0); - dup2(fd,1); - dup2(fd,2); + pid_t pid; + int fd; + int a; - if((pid=fork())<0) { - syslog(LOG_ERR,"Fork failed: %s. Exiting.",strerror(errno)); - exit(1); - } - else if (pid!=0) { - exit(0); - } + fd=open("/dev/null",O_RDWR); + if(fd<0) { + syslog(LOG_ERR,"Failed to open /dev/null: %s. Exiting.",strerror(errno)); + exit(1); + } + + dup2(fd,0); + dup2(fd,1); + dup2(fd,2); + + if((pid=fork())<0) { + syslog(LOG_ERR,"Fork failed: %s. Exiting.",strerror(errno)); + exit(1); + } + else if (pid!=0) { + exit(0); + } - setsid(); - chdir("/"); - umask(0); - return 0; + setsid(); + a = chdir("/"); + umask(0); + return 0; } int malloc_fail( char * var, int size ) { - // print error to stderr if running in bindsnap mode - if (option_b) { - fprintf(stderr, "our OS wouldn't let me malloc %d bytes for a new %s. giving up", size, var); - } - else { - syslog(LOG_ERR, "our OS wouldn't let me malloc %d bytes for a new %s. giving up", size, var); - } - exit(1); + // print error to stderr if running in bindsnap mode + if (option_b) { + fprintf(stderr, "our OS wouldn't let me malloc %d bytes for a new %s. giving up", size, var); + } + else { + syslog(LOG_ERR, "our OS wouldn't let me malloc %d bytes for a new %s. giving up", size, var); + } + exit(1); } + +int microsleep(unsigned int usec) { + struct timeval timeout; + timeout.tv_sec = usec / 1000000; + timeout.tv_usec = usec % 1000000; + + while ((select(0, (fd_set *) 0, (fd_set *) 0,(fd_set *) 0, &timeout) < 0) && (errno == EINTR)); + return(0); +} + diff --git a/dns_flood_detector.h b/dns_flood_detector.h index 13b7745..617cba8 100644 --- a/dns_flood_detector.h +++ b/dns_flood_detector.h @@ -30,6 +30,13 @@ #endif #define NS_MAXDNAME 1025 #define MAXSYSLOG 192 +#define MAXMESSAGE 1200 +#define MAXDATALET 64 +#define MAXHEAD 300 +#define MAX_TIME_LEN 20 +#define DEFAULT_PORT 2000 +#define DEFAULT_IP "226.1.1.2" +#define HOST_NAME_MAX 254 // evil Solaris hack #ifdef __sun__ @@ -41,10 +48,12 @@ typedef uint32_t u_int32_t; // prototypes void handle_IP(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char* packet); int calculate_averages(); +int saddr_stats(int sock, struct sockaddr_in addr, char *hostname); int scour_bucket(int i); int find_bucket(struct in_addr *ip_src); int daemonize(void); int malloc_fail(char * var, int size); +int microsleep(unsigned int usec); // data structures struct my_dns { diff --git a/makefiles/Makefile-OSX b/makefiles/Makefile-OSX index b72c947..ae29c01 100644 --- a/makefiles/Makefile-OSX +++ b/makefiles/Makefile-OSX @@ -1,5 +1,5 @@ CFLAGS+=-Wall -O -g -I/usr/local/include -I/usr/include -LDLIBS=-L/usr/local/lib -lpcap -lpthread -lm +LDLIBS=-lpcap -lpthread -lm all: dns_flood_detector strip dns_flood_detector