#!/usr/local/bin/perl # # Check running and snapshot juniper firmware versions. # nagios: -epn # Above line tells nagios not to use ePN. # Must be in first 10 lines of file. # # $Header: /home/doke/work/nagios/RCS/check_juniper_firmware,v 1.27 2017/04/03 19:14:29 doke Exp $ # # use strict; use warnings; no warnings 'uninitialized'; no warnings 'redefine'; use Net::SSH2; use Getopt::Long; #use Data::Dumper; use vars qw( $hostname $allowed_version_pattern $login $passwdfile $verbose $help @crits @warns @unknowns @oks @ignores $rc $sep $max_retries $ssh2 $timeout ); $hostname = ''; $allowed_version_pattern = '12.3R6.6'; $login = 'nagios'; $passwdfile = '/usr/local/nagios/etc/nagios.pw'; $max_retries = 3; $timeout = 30; ################# # print the usage of this script sub usage { print "Usage: $0 [options] -H -a pattern matching allowed junos versions -l A login name accepted by the target router. -p file containing the password for the login name. -v increase verbosity -h help "; exit -1; } # check arguments Getopt::Long::Configure ("bundling"); GetOptions( 'H=s' => \$hostname, 'a=s' => \$allowed_version_pattern, 'l=s' => \$login, 'p=s' => \$passwdfile, 'v+' => \$verbose, 'h' => \$help, ); &usage( 0 ) if ( $help ); $hostname || usage(); $login || usage(); $passwdfile || usage(); check_juniper_firmware(); $rc = 0; $sep = ''; if ( $#crits >= 0 ) { $rc = 2; print "CRITICAL ", join( ", ", @crits ); $sep = '; '; } if ( $#warns >= 0 ) { $rc = 1 if ( $rc == 0 ); print $sep, "Warning ", join( ", ", @warns ); $sep = '; '; } if ( $#unknowns >= 0 ) { $rc = 3 if ( $rc == 0 ); print $sep, "Unknown ", join( ", ", @unknowns ); $sep = '; '; } if ( $rc == 0 || $verbose ) { print $sep, "Ok ", join( ", ", @oks ); $sep = '; '; } if ( $#ignores >= 0 ) { print $sep, "Ignoring ", join( ", ", @ignores ); } print "\n"; exit $rc; ########################### sub check_juniper_firmware { my( $rport, $passwd, $stdout, $stderr, $rc, $cmd, $fpc, $model, $version, $version2, $disk, $retries ); ssh2_setup(); # get the firmware versions $cmd = "show version"; $stdout = ssh2_cmd( $cmd ); if ( ! $stdout ) { push @unknowns, "unable to ssh to $hostname: $@ $stderr"; return; } $fpc = 'chassis'; $model = ''; $version = ''; $version2 = ''; foreach $_ ( split m/\n/, $stdout ) { $verbose > 1 && print "<$_\n"; if ( m/^(fpc\d+):/ ) { $fpc = $1; } elsif ( m/Model:\s*(.*)/ ) { $model = $1; } elsif ( m/JUNOS (?:Base OS boot|Software Release|EX.* Software Suite) \[(.*)\]/ ) { $version2 = $1; $version = $1 unless $version; # save the first version we see $verbose && print "$fpc $model $version2\n"; if ( $version2 ne $version ) { push @warns, "$fpc is running $version2 != fpc0 $version"; } check_version( "$fpc $model", $version2 ); } } if ( ! $version ) { push @warns, "couldn't find running JunOS version"; } check_alternate_firmware( $model, $version ); $cmd = 'exit'; $stdout = ssh2_cmd( $cmd ); # give the switch session time to exit gracefully # not sure this is neccessary sleep 2; $ssh2->disconnect(); } sub check_alternate_firmware { my( $model, $version ) = @_; my( $cmd, $expected_copies, $stdout, $stderr, $rc, $in_snapshot, $fpc, $disk, $version2, $need_fix_mounts, %fpc_disk_counts ); # get the alternate slice firmware versions if ( $model =~ m/mx\d\d/i ) { $cmd = 'show system snapshot'; $expected_copies = 1; } elsif ( $model =~ m/ex[68]2\d\d|srx/i ) { $cmd = 'show system snapshot media internal'; $expected_copies = 2; } elsif ( $model =~ m/ex\d\d\d\d/i ) { $cmd = 'show system snapshot media internal all-members'; $expected_copies = 2; } elsif ( $model =~ m/qfx\d\d\d\d/i ) { # qfx uses a VM system and doesn't have alternate roots return; } else { $cmd = 'show system snapshot'; $expected_copies = 2; } $stdout = ssh2_cmd( $cmd ); $in_snapshot = 0; $fpc = 'chassis'; $disk = ''; $version2 = ''; $need_fix_mounts = 0; foreach $_ ( split m/\n/, $stdout ) { $verbose > 1 && print "<$_\n"; if ( $in_snapshot ) { if ( m/(?:jbase|junos)\s*:\s*(\S.*)/i ) { ( $version2 = $1 ) =~ s/^(?:ex|ppc)-//; $version2 =~ s/-domestic$//; # SRX $verbose && print "$fpc $disk $version2\n"; check_version( "$fpc $disk", $version2 ); if ( $version2 ne $version ) { push @warns, "$fpc $disk $version2 does not match running $version"; } $in_snapshot = 0; } } elsif ( m/^(fpc\d+):/ ) { $fpc = $1; $fpc_disk_counts{ $fpc } ||= 0; } elsif ( m/Information for snapshot on \s*(\S.*)/i ) { $disk = $1; $in_snapshot = 1; $fpc_disk_counts{ $fpc } ++; } elsif ( m!error:.*(/dev\S.*)!i ) { push @warns, "$fpc $_"; $disk = $1; $in_snapshot = 0; if ( m!error: cannot mount /dev/da\ds\d\w! ) { $need_fix_mounts = 1; } } elsif ( m!error|missing!i ) { push @warns, "$fpc $_"; $in_snapshot = 0; } } foreach $fpc ( sort keys %fpc_disk_counts ) { if ( $fpc_disk_counts{ $fpc } < $expected_copies ) { push @warns, "$fpc does not have a viable alternate root" } } if ( $need_fix_mounts ) { $cmd = "/usr/local/bin/timeout -t 300 -- " . " /usr/local/openssh/bin/ssh -4aqxn -tt " . " -o 'BatchMode yes'" . " -o 'ConnectionAttempts 3'" . " -o 'ConnectTimeout 30'" . " -o 'ServerAliveInterval 15'" . " -o 'StrictHostKeyChecking no'" . " nagios\@metal1.nss.udel.edu" . " '/usr/local/sbin/fix_junos_snapmounts -vv -n -H $hostname &'"; $verbose && print "+ $cmd\n"; system( $cmd ); } # give the switch time to umount everything # not sure this is neccessary, but might be the cause of hung snap-tmp mounts sleep 3; } sub check_version { my( $where, $version ) = @_; if ( $version =~ m/$allowed_version_pattern/i ) { push @oks, "$where $version"; } else { push @warns, "$where $version bad"; } } sub ssh2_setup { my( $rport, $retries, $error_code, $error_name, $error_string, $passwd, $msg ); # Get ssh tcp port number if it exists $rport = ( getservbyname( 'ssh', 'tcp' ) )[2]; $rport ||= 22; $retries = 0; while ( $retries < $max_retries ) { # create the ssh session # Can't connect messages will occur here, and the library will exit $verbose && print "\ncreating Net::SSH2 object\n"; eval { $ssh2 = Net::SSH2->new( timeout => $timeout * 1000, ); }; if ( ! $ssh2 ) { ( $error_code, $error_name, $error_string ) = $ssh2->error(); $msg = "can't create ssh session to $hostname: $error_string"; $verbose && print $msg, "\n"; push @unknowns, $msg; next; } if ( $verbose >= 2 ) { $ssh2->trace( -1 ); $ssh2->debug( 1 ); } #$ssh2->keepalive_config( 1, 15 ); $ssh2->blocking( 1 ); $verbose && print "\nconnecting to $hostname $rport\n"; eval { $rc = $ssh2->connect( $hostname, $rport ); }; if ( ! $rc ) { ( $error_code, $error_name, $error_string ) = $ssh2->error(); $msg = "can't ssh connect to $hostname: $error_string"; $verbose && print $msg, "\n"; push @unknowns, $msg; next; } # log in $passwd = get_passwd( $passwdfile ); $verbose && print "tring ssh login as $login\n"; #$verbose >= 2 && print " password '$passwd'\n"; eval { $rc = $ssh2->auth_password( $login, $passwd ); }; if ( ! $rc ) { ( $error_code, $error_name, $error_string ) = $ssh2->error(); $msg = "can't login to $hostname: $error_string"; $verbose && print $msg, "\n"; push @unknowns, $msg; next; } last; } } sub ssh2_cmd { my( $cmd ) = @_; my( $chan, $stdout, $error_code, $error_name, $error_string, $msg ); $verbose && print "ssh2_cmd( $cmd )\n"; $stdout = ''; eval { $chan = $ssh2->channel(); if ( ! $chan ) { ( $error_code, $error_name, $error_string ) = $ssh2->error(); $verbose && print "LIBSSH2_SESSION_BLOCK_INBOUND 1 \n"; $verbose && print "LIBSSH2_SESSION_BLOCK_OUTBOUND 2\n"; $verbose && print "block_directions: ", $ssh2->block_directions, "\n"; $verbose && print "error_code $error_code, $error_name, $error_string\n"; $msg = "can't ssh $hostname chan $cmd: $error_name $error_string"; $verbose && print $msg, "\n"; push @unknowns, $msg; return ''; } $chan->blocking( 1 ); if ( ! $chan->exec( $cmd ) ) { ( $error_code, $error_name, $error_string ) = $ssh2->error(); $msg = "can't ssh $hostname exec $cmd: $error_string"; $verbose && print $msg, "\n"; push @unknowns, $msg; return ''; } if ( ! $chan->send_eof() ) { ( $error_code, $error_name, $error_string ) = $ssh2->error(); $msg = "can't ssh $hostname eof $cmd: $error_string"; $verbose && print $msg, "\n"; push @unknowns, $msg; return ''; } while ( <$chan> ) { $stdout .= $_; } }; $verbose && print "stdout: $stdout\n"; if ( ! defined $stdout ) { push @unknowns, "no data from ssh $hostname $cmd: " . ($ssh2->error())[2]; return; } return $stdout; } sub get_passwd { my( $file ) = @_; open( fH, $file ) || die "can't open $file: $!\n"; my $passwd = ; close fH; chomp $passwd; return $passwd; }