more on caching of resolver objects
authorheiko
Tue, 06 Jan 2015 14:21:38 +0100
changeset 10 4243e22505f9
parent 9 b2a26d05b063
child 11 cd4343d59850
more on caching of resolver objects
Build.PL
MANIFEST
plugins/check_dns-delegation
plugins/check_dns-serial
t/10-minimal.t
--- a/Build.PL	Tue Jan 06 13:35:59 2015 +0100
+++ b/Build.PL	Tue Jan 06 14:21:38 2015 +0100
@@ -4,11 +4,11 @@
 use Module::Build;
 
 my $builder = Module::Build->new(
-    dist_name         => 'nagios-plugin-dns-serial',
-    dist_version_from => 'plugins/check_dns-serial',
+    dist_name         => 'nagios-plugin-dns-delegation',
+    dist_version_from => 'plugins/check_dns-delegation',
     dist_abstract     => 'nagios check for dns serial numbers',
     checks_files      => {
-        'plugins/check_dns-serial' => 'nagios/plugins/ius/check_dns-serial',
+        'plugins/check_dns-delegation' => 'nagios/plugins/ius/check_dns-delegation',
     },
     license  => 'perl',
     requires => {
--- a/MANIFEST	Tue Jan 06 13:35:59 2015 +0100
+++ b/MANIFEST	Tue Jan 06 14:21:38 2015 +0100
@@ -1,5 +1,5 @@
 .hgignore
 Build.PL
 MANIFEST			This list of files
-plugins/check_dns-serial
+plugins/check_dns-delegation
 t/10-minimal.t
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/check_dns-delegation	Tue Jan 06 14:21:38 2015 +0100
@@ -0,0 +1,269 @@
+#! /usr/bin/perl
+# source: https://ssl.schlittermann.de/hg/ius/nagios/nagios-plugin-dns-serial
+# © 2014 Heiko Schlittermann <hs@schlittermann.de>
+
+=head1 NAME
+
+ check_dns-serial - check the dns serial number from multiple sources
+
+=head1 SYNOPSIS
+
+ check_dns-serial [options] DOMAINS
+
+=head1 DESCRIPTION
+
+B<check_dns-serial> is designed as a Icinga/Nagios plugin to verify that
+all responsible NS have the same serial number for their zones.
+
+Domains we are not responsible for are marked as B<critical>.
+
+The list of domains may consist of the following items:
+
+=over
+
+=item I<domain>
+
+A plain domain name.
+
+=item B<file://>I<file>
+
+A file name containing the domains, line by line.
+
+=item B<local:>
+
+This item uses the output of C<named-checkconf -p> to get the list of
+master/slave zones. The 127.in-addr.arpa, 168.192.in-addr.arpa, and
+0.in-addr.arpa, and 127.in-addr.arpa zones are suppressed.
+
+=back
+
+=cut
+
+use 5.014;
+use strict;
+use warnings;
+use Getopt::Long qw(GetOptionsFromArray);
+use Net::DNS;
+use Pod::Usage;
+use if $ENV{DEBUG} => 'Smart::Comments';
+
+sub uniq { my %h; @h{@_} = (); return keys %h; }
+my @extns = qw(8.8.8.8 8.8.4.4);
+
+package Net::DNS::Resolver {
+    use Storable qw(freeze);
+    sub new {
+        my $class = shift;
+        state %cache;
+        return $cache{freeze \@_} //= $class->SUPER::new(@_);
+    }
+}
+
+# return a list of the zones known to the local
+# bind
+sub get_local_zones {
+    my @conf;
+    open(my $z, '-|', 'named-checkconf -p');
+    while (<$z>) {
+        state $line;
+        s/^\s*(.*?)\s*$/$1 /;
+        chomp($line .= $_);    # continuation line
+        if (/\A\}/) {          # config item done
+            $line =~ s/\s$//;
+            push @conf, $line;
+            $line = '';
+        }
+    }
+    return grep { 
+	# FIXME: 172.0 .. 172.31 is missing
+	not /\b(?:0|127|10|168\.192|255)\.in-addr\.arpa$/ and
+	not /^localhost$/;
+    } map { /zone\s"(\S+)"\s/ } grep { /type (?:master|slave)/ } @conf;
+}
+
+sub get_domains {
+    my @sources = @_;
+    my @domains = ();
+
+    foreach my $src (@sources) {
+
+        if ($src =~ m{^(?:(/.*)|file://(/.*))}) {
+            open(my $f, '<', $1) or die "$0: Can't open $1 for reading: $!\n";
+            push @domains, map { /^\s*(\S+)\s*/ } <$f>;
+            next;
+        }
+
+        if ($src =~ m{^local:}) {
+            push @domains, get_local_zones;
+            next;
+        }
+
+        push @domains, $src;
+    }
+
+    return @domains;
+}
+
+# return a list of "official" nameservers
+sub ns {
+    my $domain = shift;
+    ### assert: @_ % 2 == 0
+    my %resflags = (nameservers => \@extns, @_);
+    my $aa = delete $resflags{aa};
+    my $nameservers = join ',' => @{$resflags{nameservers}};
+    my @ns;
+
+    my $r = Net::DNS::Resolver->new(%resflags);
+    my $q;
+
+    for (my $i = 3; $i; --$i) {
+        $q = $r->query($domain, 'NS') and last;
+    }
+    die $r->errorstring . "\@$nameservers\n" if not $q;
+
+    die "no aa \@$nameservers\n" if $aa and not $q->header->aa;
+    push @ns, map { $_->nsdname } grep { $_->type eq 'NS' } $q->answer;
+
+    return sort @ns;
+}
+
+sub serial {
+    my $domain = shift;
+    my %resflags = (nameservers => \@extns, @_);
+    my $nameservers = join ',' => @{$resflags{nameservers}};
+
+    my $r = Net::DNS::Resolver->new(%resflags);
+    my $q;
+
+    for (my $i = 3; $i; --$i) {
+        $q  = $r->query($domain, 'SOA') and last;
+    }
+    die $r->errorstring, "\@$nameservers\n" if not $q;
+
+    return (map { $_->serial } grep { $_->type eq 'SOA' } $q->answer)[0];
+}
+
+# - the nameservers known from the ns records
+# - from the primary master if this is not one of the
+#   NS for the zone
+# - from a list of additional (hidden) servers
+#
+# OK - if the serial numbers are in sync
+# WARNING - if there is some difference
+# CRITICAL - if the serial cannot be found at one of the sources
+
+sub ns_ok {
+    my ($domain, $reference) = @_;
+
+    my @errs;
+    my @our = eval { sort +ns($domain, nameservers => [$reference], aa => 1) };
+    push @errs, $@ if $@;
+    my @their = eval { sort +ns($domain) };
+    push @errs, $@ if $@;
+
+    if (@errs) {
+        chomp @errs;
+        die join(', ' => @errs) . "\n";
+    }
+    
+    if ("@our" ne "@their") {
+        local $" = ', ';
+        die "NS differ (our @our) vs (their @their)\n";
+    }
+
+    return uniq sort @our, @their;
+}
+
+sub serial_ok {
+    my ($domain, @ns) = @_;
+    my @serials = map { my $s = serial $domain, nameservers => [$_]; "$s\@$_" } @ns;
+
+    if (uniq(map { /(\d+)/ } @serials) != 1) {
+        die "serials do not match: @serials\n";
+    }
+    
+    $serials[0] =~ /(\d+)/;
+    return $1;
+}
+
+sub main {
+    my @argv          = @_;
+    my $opt_reference = '127.0.0.1';
+    my $opt_progress  = -t;
+
+    GetOptionsFromArray(
+        \@argv,
+        'reference=s' => \$opt_reference,
+        'progress!'   => \$opt_progress,
+        'h|help'      => sub { pod2usage(-verbose => 1, -exit => 0) },
+        'm|man'       => sub {
+            pod2usage(
+                -verbose   => 2,
+                -exit      => 0,
+                -noperldoc => system('perldoc -V 2>/dev/null 1>&2')
+            );
+        }
+      )
+      and @argv
+      or pod2usage;
+    my @domains = get_domains(@argv);
+
+    my (@OK, %CRITICAL);
+    foreach my $domain (@domains) {
+        print STDERR "$domain " if $opt_progress;
+
+        my @ns = eval { ns_ok($domain, $opt_reference) };
+	if ($@) { 
+            $CRITICAL{$domain} = $@;
+            say STDERR 'ns not ok' if $opt_progress;
+            next;
+        }
+        print STDERR 'ok(ns) ' if $opt_progress;
+
+        my @serial = eval { serial_ok($domain, @ns) };
+        if ($@) {
+            $CRITICAL{$domain} = $@;
+            say STDERR 'serial not ok' if $opt_progress;
+            next;
+        }
+        say STDERR 'ok(serial)' if $opt_progress;
+        push @OK, $domain;
+
+    }
+
+    #    use DDP;
+    #    p @OK;
+    #    p %CRITICAL;
+
+    if (my $n = keys %CRITICAL) {
+        print "CRITICAL: $n of " . @domains . " domains\n",
+          map { "$_: $CRITICAL{$_}" } sort keys %CRITICAL;
+        return 2;
+    }
+
+    say 'OK: ' . @OK . ' domains checked';
+    return 0;
+
+}
+
+exit main @ARGV unless caller;
+
+__END__
+
+=head1 OPTIONS
+
+=over
+
+=item B<--reference>=I<address>
+
+The address of the reference server for our own domains (default: 127.0.0.1)
+
+=item B<--progress>
+
+Tell about the progress. (default: on if input is connected to a terminal)
+
+=back
+
+=cut
+
+# vim:sts=4 ts=8 sw=4 et:
--- a/plugins/check_dns-serial	Tue Jan 06 13:35:59 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,269 +0,0 @@
-#! /usr/bin/perl
-# source: https://ssl.schlittermann.de/hg/ius/nagios/nagios-plugin-dns-serial
-# © 2014 Heiko Schlittermann <hs@schlittermann.de>
-
-=head1 NAME
-
- check_dns-serial - check the dns serial number from multiple sources
-
-=head1 SYNOPSIS
-
- check_dns-serial [options] DOMAINS
-
-=head1 DESCRIPTION
-
-B<check_dns-serial> is designed as a Icinga/Nagios plugin to verify that
-all responsible NS have the same serial number for their zones.
-
-Domains we are not responsible for are marked as B<critical>.
-
-The list of domains may consist of the following items:
-
-=over
-
-=item I<domain>
-
-A plain domain name.
-
-=item B<file://>I<file>
-
-A file name containing the domains, line by line.
-
-=item B<local:>
-
-This item uses the output of C<named-checkconf -p> to get the list of
-master/slave zones. The 127.in-addr.arpa, 168.192.in-addr.arpa, and
-0.in-addr.arpa, and 127.in-addr.arpa zones are suppressed.
-
-=back
-
-=cut
-
-use 5.014;
-use strict;
-use warnings;
-use Getopt::Long qw(GetOptionsFromArray);
-use Net::DNS;
-use Pod::Usage;
-use if $ENV{DEBUG} => 'Smart::Comments';
-
-sub uniq { my %h; @h{@_} = (); return keys %h; }
-my @extns = qw(8.8.8.8 8.8.4.4);
-
-package Net::DNS::Resolver {
-    use Storable qw(freeze);
-    sub new {
-        my $class = shift;
-        state %cache;
-        return $cache{freeze \@_} //= $class->SUPER::new(@_);
-    }
-}
-
-# return a list of the zones known to the local
-# bind
-sub get_local_zones {
-    my @conf;
-    open(my $z, '-|', 'named-checkconf -p');
-    while (<$z>) {
-        state $line;
-        s/^\s*(.*?)\s*$/$1 /;
-        chomp($line .= $_);    # continuation line
-        if (/\A\}/) {          # config item done
-            $line =~ s/\s$//;
-            push @conf, $line;
-            $line = '';
-        }
-    }
-    return grep { 
-	# FIXME: 172.0 .. 172.31 is missing
-	not /\b(?:0|127|10|168\.192|255)\.in-addr\.arpa$/ and
-	not /^localhost$/;
-    } map { /zone\s"(\S+)"\s/ } grep { /type (?:master|slave)/ } @conf;
-}
-
-sub get_domains {
-    my @sources = @_;
-    my @domains = ();
-
-    foreach my $src (@sources) {
-
-        if ($src =~ m{^(?:(/.*)|file://(/.*))}) {
-            open(my $f, '<', $1) or die "$0: Can't open $1 for reading: $!\n";
-            push @domains, map { /^\s*(\S+)\s*/ } <$f>;
-            next;
-        }
-
-        if ($src =~ m{^local:}) {
-            push @domains, get_local_zones;
-            next;
-        }
-
-        push @domains, $src;
-    }
-
-    return @domains;
-}
-
-# return a list of "official" nameservers
-sub ns {
-    my $domain = shift;
-    ### assert: @_ % 2 == 0
-    my %resflags = (nameservers => \@extns, @_);
-    my $aa = delete $resflags{aa};
-    my $nameservers = join ',' => @{$resflags{nameservers}};
-    my @ns;
-
-    my $r = Net::DNS::Resolver->new(%resflags);
-    my $q;
-
-    for (my $i = 3; $i; --$i) {
-        $q = $r->query($domain, 'NS') and last;
-    }
-    die $r->errorstring . "\@$nameservers\n" if not $q;
-
-    die "no aa \@$nameservers\n" if $aa and not $q->header->aa;
-    push @ns, map { $_->nsdname } grep { $_->type eq 'NS' } $q->answer;
-
-    return sort @ns;
-}
-
-sub serial {
-    my $domain = shift;
-    my %resflags = (nameservers => \@extns, @_);
-    my $nameservers = join ',' => @{$resflags{nameservers}};
-
-    my $r = Net::DNS::Resolver->new(%resflags);
-    my $q;
-
-    for (my $i = 3; $i; --$i) {
-        $q  = $r->query($domain, 'SOA') and last;
-    }
-    die $r->errorstring, "\@$nameservers\n" if not $q;
-
-    return (map { $_->serial } grep { $_->type eq 'SOA' } $q->answer)[0];
-}
-
-# - the nameservers known from the ns records
-# - from the primary master if this is not one of the
-#   NS for the zone
-# - from a list of additional (hidden) servers
-#
-# OK - if the serial numbers are in sync
-# WARNING - if there is some difference
-# CRITICAL - if the serial cannot be found at one of the sources
-
-sub ns_ok {
-    my ($domain, $reference) = @_;
-
-    my @errs;
-    my @our = eval { sort +ns($domain, nameservers => [$reference], aa => 1) };
-    push @errs, $@ if $@;
-    my @their = eval { sort +ns($domain) };
-    push @errs, $@ if $@;
-
-    if (@errs) {
-        chomp @errs;
-        die join(', ' => @errs) . "\n";
-    }
-    
-    if ("@our" ne "@their") {
-        local $" = ', ';
-        die "NS differ (our @our) vs (their @their)\n";
-    }
-
-    return uniq sort @our, @their;
-}
-
-sub serial_ok {
-    my ($domain, @ns) = @_;
-    my @serials = map { my $s = serial $domain, nameservers => [$_]; "$s\@$_" } @ns;
-
-    if (uniq(map { /(\d+)/ } @serials) != 1) {
-        die "serials do not match: @serials\n";
-    }
-    
-    $serials[0] =~ /(\d+)/;
-    return $1;
-}
-
-sub main {
-    my @argv          = @_;
-    my $opt_reference = '127.0.0.1';
-    my $opt_progress  = -t;
-
-    GetOptionsFromArray(
-        \@argv,
-        'reference=s' => \$opt_reference,
-        'progress!'   => \$opt_progress,
-        'h|help'      => sub { pod2usage(-verbose => 1, -exit => 0) },
-        'm|man'       => sub {
-            pod2usage(
-                -verbose   => 2,
-                -exit      => 0,
-                -noperldoc => system('perldoc -V 2>/dev/null 1>&2')
-            );
-        }
-      )
-      and @argv
-      or pod2usage;
-    my @domains = get_domains(@argv);
-
-    my (@OK, %CRITICAL);
-    foreach my $domain (@domains) {
-        print STDERR "$domain " if $opt_progress;
-
-        my @ns = eval { ns_ok($domain, $opt_reference) };
-	if ($@) { 
-            $CRITICAL{$domain} = $@;
-            say STDERR 'ns not ok' if $opt_progress;
-            next;
-        }
-        print STDERR 'ok(ns) ';
-
-        my @serial = eval { serial_ok($domain, @ns) };
-        if ($@) {
-            $CRITICAL{$domain} = $@;
-            say STDERR 'serial not ok' if $opt_progress;
-            next;
-        }
-        say STDERR 'ok(serial)' if $opt_progress;
-        push @OK, $domain;
-
-    }
-
-    #    use DDP;
-    #    p @OK;
-    #    p %CRITICAL;
-
-    if (my $n = keys %CRITICAL) {
-        print "CRITICAL: $n of " . @domains . " domains\n",
-          map { "$_: $CRITICAL{$_}" } sort keys %CRITICAL;
-        return 2;
-    }
-
-    say 'OK: ' . @OK . ' domains checked';
-    return 0;
-
-}
-
-exit main @ARGV unless caller;
-
-__END__
-
-=head1 OPTIONS
-
-=over
-
-=item B<--reference>=I<address>
-
-The address of the reference server for our own domains (default: 127.0.0.1)
-
-=item B<--progress>
-
-Tell about the progress. (default: on if input is connected to a terminal)
-
-=back
-
-=cut
-
-# vim:sts=4 ts=8 sw=4 et:
--- a/t/10-minimal.t	Tue Jan 06 13:35:59 2015 +0100
+++ b/t/10-minimal.t	Tue Jan 06 14:21:38 2015 +0100
@@ -5,6 +5,7 @@
 use Test::More;
 use File::Temp;
 use Test::Exception;
+use Storable qw(freeze);
 
 my $tmp = File::Temp->new;
 $tmp->print(<<__);
@@ -21,7 +22,7 @@
 }
 
 # we require it, it's not a normal module
-require_ok 'blib/nagios/plugins/ius/check_dns-serial'
+require_ok 'blib/nagios/plugins/ius/check_dns-delegation'
   or BAIL_OUT q{can't require the module};
 
 
@@ -41,13 +42,17 @@
     is $r2a, $r2b => 'same level3';
     isnt $r1a, $r2a => 'not same google/level3';
 
-    my @a;
-    @a = qw[8.8.8.8];
+    my (@a, @b);
+    @a = qw[8.8.8.1];
     my $r3a = Net::DNS::Resolver->new(nameservers => \@a);
-    @a = qw[8.8.4.4];
+    @a = qw[8.8.4.2];
     my $r3b = Net::DNS::Resolver->new(nameservers => \@a);
+    isnt $r3a, $r3b => 'same ref, but not same object';
 
-    isnt $r3a, $r3b => 'same ref, but not same object';
+    @b = @a;
+    is freeze(\@a), freeze(\@b) => 'frozen lists';
+    my $r3c = Net::DNS::Resolver->new(nameservers => \@b);
+    is $r3b, $r3c => 'same servers, but not same ref';
 
 };