Commit 63b4b0fa authored by waja's avatar waja
Browse files

Imported Upstream version 1.20

parent 912e105e
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:
......
#!/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;
}
......@@ -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) - <erikm@buh.org>
fix crash on long syslog messages - <jwestfall@surrealistic.net>
--- new in v1.2 ---
05/10/2012 - added sending of source-IP telemetry to a network collector - <dopacki@adotout.com>
********************************************************************************/
#include <pcap.h>
......@@ -124,14 +131,17 @@
#include <string.h>
#include <sys/stat.h>
#include "dns_flood_detector.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
// 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) {
// check statistical stuff
pthread_mutex_lock(&stats_lock);
calculate_averages();
pthread_mutex_unlock(&stats_lock);
// prepare multicast socket
struct sockaddr_in addr;
int sock;
sock = socket(AF_INET, SOCK_DGRAM,0);
if (sock<0) {
perror("can't set up socket");
exit(1);
}
// 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);
}
}
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);
for (i=0; i<option_x; i++) {
// 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 ) {
// 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 ) {
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; i<option_x; i++) {
// 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 ) {
// 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;
// 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;
}
}
}
}
}
}
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;
// '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++) {