#!/usr/bin/perl -w # # $Id: bind9-query-logger.txt,v 1.4 2003/01/16 18:35:55 nate Exp $ # # by Nate Campi # # This script is for BIND9 *only*. BIND8 keeps stats for you, all you have to do is dump and # parse the dump file. See http://www.campin.net/DNS/graph.html for more info. # # start BIND9 in the foreground so that you can direct query logs to STDERR in named.conf # but pipe it with the shell into the stats tallying script something # like this (not tested, this is just an example off the top of my head): # # # nohup named -f -u bind -c /etc/named.conf -t /var/bind.root/r 2>&1 | bind9-query-logger # # and in named.conf: #--------------------------- #logging { # channel "query_log" { # stderr; # }; # # channel default_syslog { # syslog daemon; // usually the default # print-category yes; # }; # # category "queries" { "query_log"; }; # category "default" { "default_syslog"; "default_debug"; }; #}; #--------------------------- # # Personally, I don't start it from an init script or the command line, I run it # under daemontools and capture its output in my "log/run" script. # # In /service/bind/run: # # #!/sbin/sh # exec 2>&1 # exec /usr/local/sbin/named-9.3.0s20020722 -f -u bind -c /etc/named.conf -t /var/bind.root/r # # ... and in /service/bind/log/run # # #!/bin/sh # exec setuidgid dnslog bind9-query-logger --log -- multilog t ./main # # You don't have to capture the output with multilog, to eliminate logging # the individual queries to disk, and only update the stats file do this: # # #!/bin/sh # exec setuidgid dnslog bind9-query-logger # ###################################################################### # # To query these stats over SNMP use these lines in net-snmp snmpd.conf # (adjust paths as needed). # #exec VALUES /bin/echo A PTR ANY MX NS CNAME SOA SRV AAAA TOTAL #exec bindstats /bin/cat /home/zoneaxfr/stats/stats_file # # see http://www.campin.net/DNS/graph.html for the rest of what you need to # graph the stats # ################################################################## use Getopt::Long; use Fcntl qw(:DEFAULT :flock); use strict; my $stats_file = "/home/zoneaxfr/stats/stats_file"; my $stats_file_temp = "/home/zoneaxfr/stats/stats_file.temp"; my $stats_flush_interval = 60; # between 60 and 300 seconds is probably best my $time = time(); my $stats_flush_time = ( $time + $stats_flush_interval ); $SIG{CHLD} = sub {wait ()}; my ( $total, $srv, $any, $a, $ns, $cname, $soa, $aaaa, $mx, $ptr, $other, @line, ) = 0; my ( $oldtotal, $oldsrv, $oldany, $olda, $oldns, $oldcname, $oldsoa, $oldaaaa, $oldmx, $oldptr, $total_a, $total_any, $total_srv, $total_total, $total_ns, $total_soa, $total_cname, $total_aaaa, $total_mx, $total_ptr, $DEBUG, $query_types, %opts, $pid, $i, @stats, ); %opts = ('log' => 0, 'nostats' => 0, 'debug' => $DEBUG, ); GetOptions (\%opts, 'log!', 'nostats!', 'debug!', ) or exit 2; $DEBUG = $opts{debug}; if ( $opts{log} || $opts{logpretty} ) { # pipe to multilog $| = 1; my $command = join " ", @ARGV; open (MULTI, "|$command") or die "Could not open $command: $!"; my $oldfh = select MULTI; $| = 1; select $oldfh; } while () { # go on only if it identifies itself as a query log entry next unless /query:/ ; $total++; $time = time(); print "INPUT is $_\n" if $DEBUG; print MULTI "$_" if $opts{log}; # output for multilog's pleasure @line = split(/\s+/); # split it for easy parsing SWITCH: { if ( $line[5] eq "SOA" ) { $soa++; last SWITCH; } if ( $line[5] eq "PTR" ) { $ptr++; last SWITCH; } if ( $line[5] eq "MX" ) { $mx++; last SWITCH; } if ( $line[5] eq "A" ) { $a++; last SWITCH; } if ( $line[5] eq "SRV" ) { $srv++; last SWITCH; } if ( $line[5] eq "NS" ) { $ns++; last SWITCH; } if ( $line[5] eq "CNAME" ) { $cname++; last SWITCH; } if ( $line[5] eq "ANY" ) { $any++; last SWITCH; } if ( $line[5] eq "AAAA" ) { $aaaa++; last SWITCH; } $other++; } if ( !($opts{nostats}) && ($time >= $stats_flush_time) ) { #flush the stats with a child proc $stats_flush_time += $stats_flush_interval; # set the time to flush stats again $pid = fork(); die "Cannot fork: $!" unless defined($pid); if ($pid == 0) { # Child process updateStats(); exit(0); # Child process exits when it is done. } # clear out the stats now that we've flushed them to disk ( $total, $srv, $any, $a, $ns, $cname, $soa, $aaaa, $mx, $ptr, $other, @line, ) = 0; } # else 'tis the parent process, which goes back to processing logs } ### subs sub updateStats { sysopen(STATS_FILE,"$stats_file", O_RDWR|O_CREAT) || die "Sorry, I couldn't open $stats_file for writing: $!\n"; flock(STATS_FILE, LOCK_EX) or die "Can't write-lock $stats_file: $!\n"; sysopen(STATS_FILE_TEMP,"$stats_file_temp", O_RDWR|O_CREAT) || die "Sorry, I couldn't open $stats_file_temp for writing: $!\n"; flock(STATS_FILE_TEMP, LOCK_EX) or die "Can't write-lock $stats_file_temp: $!\n"; while () { chomp; @stats = split(/\s+/); # split it for easy parsing $olda = $stats[0]; $oldptr = $stats[1]; $oldany = $stats[2]; $oldmx = $stats[3]; $oldns = $stats[4]; $oldcname = $stats[5]; $oldsoa = $stats[6]; $oldsrv = $stats[7]; $oldaaaa = $stats[8]; $oldtotal = $stats[9]; } print "oldA oldPTR oldANY oldMX oldNS oldCNAME oldSOA oldSRV oldAAAA oldTOTAL\n" if $DEBUG; print "$olda $oldptr $oldany $oldmx $oldns $oldcname $oldsoa $oldsrv $oldaaaa $oldtotal\n" if $DEBUG; print "A PTR ANY MX NS CNAME SOA SRV AAAA TOTAL\n" if $DEBUG; print "$a $ptr $any $mx $ns $cname $soa $srv $aaaa $total\n" if $DEBUG; $total_a = ( $olda + $a ); $total_ptr = ( $oldptr + $ptr ); $total_any = ( $oldany + $any ); $total_any = ( $oldany + $any ); $total_mx = ( $oldmx + $mx ); $total_ns = ( $oldns + $ns ); $total_cname = ( $oldcname + $cname ); $total_soa = ( $oldsoa + $soa ); $total_srv = ( $oldsrv + $srv ); $total_aaaa = ( $oldaaaa + $aaaa ); $total_total = ( $oldtotal + $total ); # be careful and truncate it seek(STATS_FILE_TEMP, 0, 0) or die "can't rewind $stats_file: $!"; truncate(STATS_FILE_TEMP, 0) or die "can't truncate $stats_file: $!"; print STATS_FILE_TEMP "$total_a $total_ptr $total_any $total_mx $total_ns $total_cname $total_soa $total_srv $total_aaaa $total_total\n"; rename("$stats_file_temp","$stats_file") || die "Can't rename $stats_file_temp to $stats_file: $!"; close(STATS_FILE); close(STATS_FILE_TEMP); print "$total_a $total_ptr $total_any $total_mx $total_ns $total_cname $total_soa $total_srv $total_aaaa $total_total\n" if $DEBUG; } __END__