#!/usr/bin/perl
#
# check_v46
# Nagios plugin wrapper for running the actual plugin for both / either of
# IPv6 and/or IPv4. The worst result of the actual plugin runs will be
# the wrapper return value, that is, result will be OK only if all checks
# returned OK. Compatible with any plugin with standard command line options
# -6/-4.
#
# 2012-02, 2012-03 Ville.Mattila@csc.fi
# Copyright (C) 2012-2013 CSC - IT Center for Science Ltd.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, see .
use strict;
use warnings FATAL => qw(all);
use File::Basename;
use Getopt::Long;
use Socket; # AF_INET, AF_INET6
use Socket6; # getaddrinfo(), getnameinfo()
my %ERRORS = (
'OK' => 0,
'WARNING' => 1,
'CRITICAL' => 2,
'UNKNOWN' => 3,
);
my %ERRSTR = reverse %ERRORS;
$SIG{__DIE__} = sub { die( basename($0) . ": Error: " . $_[0] ); };
$SIG{__WARN__} = sub { print STDERR basename($0) . ": Warning: " . $_[0]; };
Getopt::Long::Configure('bundling');
Getopt::Long::Configure('pass_through');
my %opts;
GetOptions( \%opts,
"debug-wrapper",
"help|h",
"hostname|H=s",
"IP-address|I=s", # support 'check_http -I'
"use-ipv4|4",
"use-ipv6|6",
"ipv4-address|a=s",
"ipv6-address|A=s",
) or die "Problems parsing command line: $!\n";
if ( $opts{help} ) {
usage();
exit 0;
}
# Sanity checks for command line options
if (!scalar @ARGV) {
die("Nothing to do. Try '".basename($0)." -h'.\n");
}
if (!defined($opts{hostname}) && !defined($opts{'IP-address'}) &&
!(defined($opts{'ipv4-address'}) ||
defined($opts{'ipv6-address'}))) {
die("No host/address specified. Try '".basename($0)." -h'.\n");
}
if ( $opts{'use-ipv4'} && $opts{'use-ipv6'} ) {
die("Please specify either or none of --use-ipv4 or --use-ipv6, not both.\n");
}
my $plugin_prog = shift(@ARGV);
# Do both IPv4 and IPv6 checks by default, skipping
# IPv6 in case -4 was specified skipping IPv4 in case
# of -6.
my @protocols = ();
if ( $opts{'use-ipv6'} ) {
push @protocols, "IPv6";
}
elsif ( $opts{'use-ipv4'} ) {
push @protocols, "IPv4";
}
else {
# No address family selected.
push @protocols, qw(IPv6 IPv4);
}
my $out;
my $rv;
my @checked;
foreach my $p (@protocols) {
# Use the address specified as --ipv6-address or --ipv4-address argument
# or, in case addresses were not specified, resolve --IP-address or
# --hostname instead.
# Resolve.
my @addresses = ();
my $addr_optname = '--hostname';
my $optname;
my $af;
if ( $p eq 'IPv6' ) {
$optname = 'ipv6-address';
$af = AF_INET6;
}
elsif ( $p eq 'IPv4' ) {
$optname = 'ipv4-address';
$af = AF_INET;
}
else {
die "Protocol '$p' is not supported.\n";
}
if ( defined($opts{$optname}) ) {
# --ipvX-address was specified on command line.
push @addresses, split( ',', $opts{$optname} );
}
elsif ( defined($opts{'IP-address'}) && resolve_address( $opts{'IP-address'} , $af ) ) {
# $opts{'IP-address'} has an $af address. Pass it to plugin.
@addresses = ( $opts{'IP-address'} );
$addr_optname = '--IP-address';
}
elsif ( resolve_address( $opts{hostname}, $af ) ) {
# $opts{hostname} has an $af address. Pass it to plugin.
@addresses = ( $opts{hostname} );
}
else {
# Neither $opts{'IP-address'} nor $opts{hostname} resolve for $af.
next;
}
my @plugin_args = @ARGV;
# Insert -6 or -4 as first arg for plugin command.
unshift(@plugin_args, ( $p eq "IPv6" ) ? "-6" : "-4");
# Pass through --hostname option as-is if --IP-address was also specified.
if (defined($opts{'IP-address'}) && defined($opts{hostname})) {
push(@plugin_args, '--hostname', $opts{hostname});
}
my $an = 1;
foreach my $a (@addresses) {
my @addr_args = ( $addr_optname, $a );
debug(
"Running '$plugin_prog " . join( " ", @plugin_args, @addr_args ) );
open( PLUGIN, "-|", $plugin_prog, @plugin_args, @addr_args )
or die "Problems executing plugin.\n";
push(@checked, $a);
my $first = ;
chomp($first);
debug( "Plugin output 1st line: '" . $first . "'" );
# Strip off performance data from the $first line of plugin
# output.
$first =~ s/^([^\|]+)\|(.*)/$1/;
$out->{$p}->{$a}->{first} = $first;
# Add lc($p) as prefix to performance data labels, e.g.
# 'loss' becomes 'ipv6_loss' etc.
my $perfdata = $2;
if ( defined($perfdata) ) {
my $label_prefix = lc($p);
$label_prefix .= "_a" . $an if ( @addresses );
$perfdata =~ s/(^|\s+)([^=]+)/$1${label_prefix}_$2/g;
debug( "Reformatted performance data: '" . $perfdata . "'" );
$out->{$p}->{$a}->{perfdata} = $perfdata;
}
# Store the rest of plugin output lines.
while () {
chomp;
push(@{ $out->{$p}->{$a}->{multiline} }, $_);
debug( "Plugin multiline output: '" . $_ . "'" );
}
close(PLUGIN);
# Store plugin command return value.
my $plugin_rv = ( $? >> 8 );
$rv->{$p}->{$a} = $plugin_rv;
debug("Plugin result: ".$plugin_rv." (".$ERRSTR{$plugin_rv}.")");
$an++;
}
}
if ( !@checked ) {
print "UNKNOWN: No " . join( ", ", @protocols ) . " address to check.\n";
exit $ERRORS{UNKNOWN};
}
my @status_summary = ();
my @status_details = ();
my @perfdata = ();
my $worst_rv = undef;
foreach my $p ( sort { $b cmp $a } keys %$rv ) {
foreach my $a ( sort keys %{ $rv->{$p} } ) {
if ( !defined $worst_rv || $worst_rv < $rv->{$p}->{$a} ) {
$worst_rv = $rv->{$p}->{$a};
}
push(@status_summary, "$p/$a " . $ERRSTR{ $rv->{$p}->{$a} });
push(@perfdata, $out->{$p}->{$a}->{perfdata})
if ( defined $out->{$p}->{$a}->{perfdata} );
push(@status_details, "$p/$a:");
push(@status_details, $out->{$p}->{$a}->{first});
push(@status_details, @{ $out->{$p}->{$a}->{multiline} })
if ( defined $out->{$p}->{$a}->{multiline} );
}
}
print $ERRSTR{$worst_rv} . ": " . join( ", ", @status_summary );
print " | " . join( " ", @perfdata ) if ( @perfdata );
print "\n";
print "Status details:\n";
print $_. "\n" foreach (@status_details);
exit $worst_rv;
sub resolve_address {
my ( $hostname, @address_families ) = @_;
if (!defined($hostname)) {
return ();
}
$hostname = lc($hostname);
@address_families = ( AF_INET6, AF_INET )
unless ( scalar @address_families );
my @addrs = ();
foreach my $af (@address_families) {
my @res = getaddrinfo( $hostname, 'daytime', $af, SOCK_STREAM );
while ( scalar(@res) >= 5 ) {
my ( $family, $socktype, $proto, $saddr, $canonname ) =
@res[ 0 .. 4 ];
@res = @res[ 5 .. ($#res) ];
my ( $address, $port ) =
getnameinfo( $saddr, NI_NUMERICHOST | NI_NUMERICSERV );
push @addrs, $address;
}
}
return @addrs; # NB: unsorted!
}
sub usage {
my $ME = basename($0);
print <<"AMEN";
Usage: $ME -H [--use-ipv4|--use-ipv6] [] \
[]
Options:
-H, --hostname
Hostname or IPv6/IPv4 address to connect to.
-I, --IP-address
IPv6 or IPv4 address or hostname, preferred over --hostname.
Use this with the check_http plugin if you need to specify
both --IP-address and --hostname for it.
-4, --use-ipv4
Check IPv4 only.
-6, --use-ipv6
Check IPv6 only.
-a, --ipv4-address=a.b.c.d,e.f.g.h
Check IPv4 using addresses a.b.c.d and e.f.g.h;
plugin will be run with the address as --hostname
option argument (replacing the original argument of
--hostname).
-A, --ipv6-address=A::B,C::D
Check IPv6 using addresses A::B and C::D; see -a above
for notes on plugin --hostname option handling.
Path to the actual plugin program to run.
Any command line arguments besides -H, -4, -6, -a and -A
will be passed as-is to the .
The order of options is not relevant, e.g.
'$ME -H ' is effectively the same as
'$ME -H '.
Examples:
$ME check_ssh -H host.example.org
- "Automatic dual stack test": Run check_ssh for IPv6 only if
the system resolver returns an IPv6 address and likewise for
IPv4.
$ME check_ssh -H host.example.org -4
- Run IPv4 check only.
$ME check_ssh -H host.example.org -6
- Run IPv6 check only.
$ME check_ssh -H host.example.org -a a.b.c.d
- Pass "--hostname a.b.c.d" to check_ssh when checking IPv4
and have IPv6 checks run automatically as in the first example.
$ME check_ssh -H host.example.org -6 -A A::B
- IPv6 checking only, skip resolving host.example.org and
just pass "--hostname A::B" to check_ssh.
$ME check_http -I vhost.example.org -H lb-01.example.org
- Check the HTTP virtual host vhost.example at load balancer node
lb-01.
AMEN
}
sub debug {
my ($msg) = @_;
return unless ( $opts{'debug-wrapper'} );
chomp $msg;
print STDERR "debug: " . $msg . "\n";
}