#!/usr/bin/perl -w
# -*- perl -*-
# Plugin to monitor usage of bind 9 servers using rndc stats
#
# Contributed by Laurent Facq 15/06/2004
# Based on Nicolai Langfeldt bind9 plugin
# Reworked by Dagfinn Ilmari Mannsåker
#
# To intall, put
#
#      statistics-file "/var/named/data/named.stats";
# 
# in the options part of your named.conf or set the querystats variable
# (see below) to where your named puts the statistics file by default.
#
# You must also make sure the rndc.key file is readable by the plugin.
#
# Environment variables:
#       rndc            location of the rndc command.
#                       set to empty if the stats are updated otherwise
#       querystats      location of the statistics file
#
# $Log$
# Revision 1.5  2004/12/15 18:05:40  ilmari
# - Don't use a state file (the statistics file is enough).
# - Make rndc and statistics file location configurable.
# - Linearize control flow.
# - General style cleanup.
#
# 2008/06/12 - Added support for bind 9.6
#
#%# family=contrib

use strict;

my $rndc = defined($ENV{rndc}) ? $ENV{rndc} : '/usr/sbin/rndc';
my $querystats = $ENV{querystats} || '/var/named/data/named_stats.txt';
my %IN;
my $version96 = 0;

# check to see if we're running bind 9.6
if ( open VERSION, "$rndc 2>&1 |" ) {
    while ( my $line = <VERSION> ) {
        if ( $line =~ m/^Version:\s+9\.(\d+)\./o ) {
            $version96 = 1 if $1 >= 6;
        }
    }
}

my %labels = (
    requests => 'Requests received',
    responses => 'Responses sent',
    success => 'Successful answers',
    auth_answer => 'Authoritative answers',
    nonauth_answer => 'Non-authoritative answers',
    duplicates => 'Duplicate Queries' );

# execute rndc stats
system("$rndc stats") if $rndc;

# open the statistics file
open STATS, '<', $querystats or die "$querystats: $!\n";

if ( $version96 ) {
    # bind 9.6 stats are very big, so give some more padding
    seek STATS , -10000, 2;

    my $found_stats = 0;
    while ( my $line = <STATS> ) {
        chomp $line;

        if ( $found_stats ) {    
            if ( $line =~ m/^\+\+/o ) {
                $found_stats = 0;
                next;
            }

            if ( $line =~ m/^\s+(\d+) (.*)$/o ) {
                my $n = $1;
                my $msg = $2;

                if ( $msg =~ m/requests received$/io ) {
                    $IN{requests} = 0 unless $IN{requests};
                    $IN{requests} += $n;

                } elsif ( $msg =~ m/responses sent$/io ) {
                    $IN{responses} = 0 unless $IN{requests};
                    $IN{responses} += $n;

                } elsif ( lc $msg eq 'queries resulted in successful answer' ) {
                    $IN{success} = $n;

                } elsif ( lc $msg eq 'queries resulted in authoritative answer' ) {
                    $IN{auth_answer} = $n;

                } elsif ( lc $msg eq 'queries resulted in non authoritative answer' ) {
                    $IN{nonauth_answer} = $n;

                } elsif ( lc $msg eq 'queries resulted in nxrrset' ) {
                    $IN{nxrrset} = $n;

                } elsif ( lc $msg eq 'queries resulted in servfail' ) {
                    $IN{failure} = $n;

                } elsif ( lc $msg eq 'queries resulted in nxdomain' ) {
                    $IN{nxdomain} = $n;

                } elsif ( lc $msg eq 'queries caused recursion' ) {
                    $IN{recursion} = $n;

                } elsif ( lc $msg eq 'duplicate queries received' ) {
                    $IN{duplicates} = $n;
                }
            }

        } elsif ( $line =~ m/^\+\+ Name Server Statistics \+\+$/o ) {
            $found_stats = 1;
            %IN = ( requests => 0, responses => 0, success => 0, auth_answer => 0, nonauth_answer => 0,
                    nxrrset => 0, failure => 0, nxdomain => 0, recursion => 0, duplicates => 0 );
        }
    }

} else {
    # go nearly to the end of the file to avoid reading it all
    seek STATS , -400, 2;

    ## pre-9.6 method
    while (my $line = <STATS>) {
        chomp $line;

        # We want the last block like this in the file
        #+++ Statistics Dump +++ (1087277501)
        #success 106183673
        #referral 2103636
        #nxrrset 43534220
        #nxdomain 47050478
        #recursion 37303997
        #failure 17522313
        #--- Statistics Dump --- (1087277501)

        if ($line =~ m/\+\+\+ Statistics Dump \+\+\+/) {
            # reset
            undef %IN;
        } else {
            my ($what, $nb)= split('\s+', $line);
            if ($what && ($what ne '---')) {
                $IN{$what} = $nb;
            }
        }
    }
}
close(STATS);

if (defined($ARGV[0]) and ($ARGV[0] eq 'config')) {
    print "graph_title DNS Queries by status\n";
    print "graph_vlabel queries / \${graph_period}\n";

    for my $key (keys %IN) {
        print "query_$key.label ", $labels{$key} || $key, "\n";
        print "query_$key.type DERIVE\n";
        print "query_$key.min 0\n";
    }
} else {
    print "query_$_.value $IN{$_}\n" for keys %IN;
}
