From ff970c8dc6d8c9211b00ff7e61b8557d02cf0f39 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Thu, 30 Jun 2011 11:28:28 +0200 Subject: [PATCH] Added initial version of check_junos_bgp.pl. The plugin connects to a Juniper Router and queries BGP routing information using the 'show bgp neighbor' command. It then executes any of the following checks: * peers_count: Total number of peers. * prefix_count: Number of active prefixes of single peers. Each check may consider a subset of peers only. This is done by specifying a "target" which may either be an IPv4/IPv6 address, a regex or a string. In the latter two cases, the regex/string is compared with the peer description. Warning and critical ranges may be specified for each single check. The format for specifying checks is: checkname[,target[,warning[,critical]]] --- check_junos_bgp.pl | 518 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100755 check_junos_bgp.pl diff --git a/check_junos_bgp.pl b/check_junos_bgp.pl new file mode 100755 index 0000000..41dc337 --- /dev/null +++ b/check_junos_bgp.pl @@ -0,0 +1,518 @@ +#!/usr/bin/perl + +############################################################################# +# (c) 2001, 2003 Juniper Networks, Inc. # +# (c) 2011 Sebastian "tokkee" Harl # +# and team(ix) GmbH, Nuernberg, Germany # +# # +# This file is part of "team(ix) Monitoring Plugins" # +# URL: http://oss.teamix.org/projects/monitoringplugins/ # +# # +# All rights reserved. # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions # +# are met: # +# 1. Redistributions of source code must retain the above copyright # +# notice, this list of conditions and the following disclaimer. # +# 2. Redistributions in binary form must reproduce the above copyright # +# notice, this list of conditions and the following disclaimer in the # +# documentation and/or other materials provided with the distribution. # +# 3. The name of the copyright owner may not be used to endorse or # +# promote products derived from this software without specific prior # +# written permission. # +# # +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # +# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, # +# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING # +# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # +# POSSIBILITY OF SUCH DAMAGE. # +############################################################################# + +use strict; +use warnings; + +use utf8; + +use POSIX qw( :termios_h ); +use Nagios::Plugin; + +use Regexp::Common; +use Regexp::IPv6 qw( $IPv6_re ); + +use JUNOS::Device; + +my $valid_checks = "peers_count|prefix_count"; + +my $plugin = Nagios::Plugin->new( + plugin => 'check_junos_bgp', + shortname => 'check_junos_bgp', + version => '0.1', + url => 'http://oss.teamix.org/projects/monitoringplugins', + blurb => 'Monitor Juniperâ„¢ Router\'s BGP tables.', + usage => +"Usage: %s [-v|--verbose] [-H ] [-p ] [-t ] [-P +"This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. +It may be used, redistributed and/or modified under the terms of the 3-Clause +BSD License (see http://opensource.org/licenses/BSD-3-Clause).", + extra => " +This plugin connects to a Juniperâ„¢ Router device and requests BGP table +information using the 'show bgp neighbor' command. It then checks the +specified thresholds depending on the specified checks. + +A check-tuple consists of the name of the check and, optionally, a \"target\" +(e.g., peer address), and warning and critical thresholds: +checkname[,target[,warning[,critical]]] + +The following checks are available: + * peers_count: Total number of peers. If a target is specified, only peers + matching that target are taken into account. + + * prefix_count: Number of active prefixes for a single peer. If multiple + peers match the specified target, each of those is checked against the + specified thresholds. + +Targets are either specified as IPv4/IPv6 addresses or regular expressions / +strings. In the former case, the target is compared against the peer's +address, else against the peer's description. When specifying regular +expressions, they have to be enclosed in '/'. Else, the pattern is treated as +verbatim string that has to be matched. + +Warning and critical thresholds may be specified in the format documented at +http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT.", +); + +# Predefined arguments (by Nagios::Plugin) +my @predefined_args = qw( + usage + help + version + extra-opts + timeout + verbose +); + +my @args = ( + { + spec => 'host|H=s', + usage => '-H, --host=HOSTNAME', + desc => 'Hostname/IP of Juniper box to connect to', + default => 'localhost', + }, + { + spec => 'port|p=i', + usage => '-p, --port=PORT', + desc => 'Port to connect to', + default => 22, + }, + { + spec => 'user|U=s', + usage => '-U, --user=USERNAME', + desc => 'Username to log into box as', + default => 'root', + }, + { + spec => 'password|P=s', + usage => '-P, --password=PASSWORD', + desc => 'Password for login username', + default => '', + }, +); + +my %conf = (); +my $junos = undef; + +my $neigh_info = undef; +my @peers = (); + +foreach my $arg (@args) { + add_arg($plugin, $arg); +} + +$plugin->getopts; +# Initialize this first, so it may be used right away. +$conf{'verbose'} = $plugin->opts->verbose; + +foreach my $arg (@args) { + my @c = get_conf($plugin, $arg); + $conf{$c[0]} = $c[1]; +} + +foreach my $arg (@predefined_args) { + $conf{$arg} = $plugin->opts->$arg; +} + +add_checks(\%conf, @ARGV); + +if (! $plugin->opts->password) { + my $term = POSIX::Termios->new(); + my $lflag; + + print "Password: "; + + $term->getattr(fileno(STDIN)); + $lflag = $term->getlflag; + $term->setlflag($lflag & ~POSIX::ECHO); + $term->setattr(fileno(STDIN), TCSANOW); + + $conf{'password'} = ; + chomp($conf{'password'}); + + $term->setlflag($lflag | POSIX::ECHO); + print "\n"; +} + +verbose(1, "Connecting to host $conf{'host'} as user $conf{'user'}."); +$junos = JUNOS::Device->new( + hostname => $conf{'host'}, + login => $conf{'user'}, + password => $conf{'password'}, + access => 'ssh', + 'ssh-compress' => 0); + +if (! ref $junos) { + $plugin->die("ERROR: failed to connect to " . $conf{'host'} . "!"); +} + +verbose(1, "Querying BGP neighbor information."); +$neigh_info = get_neighbor_information($junos); +if (! ref $neigh_info) { + $plugin->die($neigh_info); +} + +@peers = $neigh_info->getElementsByTagName('bgp-peer'); +if ($conf{'verbose'} >= 3) { + my @p = map { get_peer_address($_) . " => " . get_peer_description($_) } @peers; + verbose(3, "Peers: " . join(", ", @p)); +} + +foreach my $check (@{$conf{'checks'}}) { + my $code; + my $value; + + my @relevant_peers = get_relevant_peers($check, @peers); + if ($conf{'verbose'} >= 2) { + my @p = map { get_peer_address($_) . " => " . get_peer_description($_) } @relevant_peers; + verbose(2, "Relevant peers: " . join(", ", @p)); + } + + $plugin->set_thresholds( + warning => $check->{'warning'}, + critical => $check->{'critical'}, + ); + + if ($check->{'name'} eq 'peers_count') { + $value = scalar(@relevant_peers); + $code = $plugin->check_threshold($value); + + $plugin->add_message($code, "$value peer" . (($value == 1) ? "" : "s")); + $plugin->add_perfdata( + label => 'peers_count', + value => $value, + min => 0, + max => undef, + uom => '', + threshold => $plugin->threshold(), + ); + } + elsif ($check->{'name'} eq 'prefix_count') { + foreach my $peer (@relevant_peers) { + my $peer_addr = get_peer_address($peer); + + $value = get_peer_element($peer, 'peer-state'); + + verbose(2, "Peer $peer_addr: peer-state = $value."); + + if ($value eq 'Established') { + $value = $peer->getElementsByTagName('bgp-rib'); + $value = get_peer_element($value->[0], 'active-prefix-count'); + $code = $plugin->check_threshold($value); + $plugin->add_message($code, "peer $peer_addr: $value prefix" + . (($value == 1) ? "" : "es")); + + verbose(2, "Peer $peer_addr: active-prefix-count = $value."); + } + else { + $value = ""; + $code = CRITICAL; + $plugin->add_message($code, + "peer $peer_addr: no established connection"); + } + + $plugin->add_perfdata( + label => '\'prefix_count[' . $peer_addr . ']\'', + value => $value, + min => 0, + max => undef, + uom => '', + threshold => $plugin->threshold(), + ); + } + } +} + +my ($code, $msg) = $plugin->check_messages(join => ', '); + +$junos->disconnect(); + +$plugin->nagios_exit($code, $msg); + +sub send_query +{ + my $device = shift; + my $query = shift; + my $queryargs = shift; + + my $res; + my $err; + + verbose(3, "Sending query '$query' to router."); + + if (ref $queryargs) { + $res = $device->$query(%$queryargs); + } else { + $res = $device->$query(); + } + + if (! ref $res) { + return "ERROR: Failed to execute query '$query'"; + } + + $err = $res->getFirstError(); + if ($err) { + return "ERROR: " . $err->{message}; + } + return $res; +} + +sub get_neighbor_information +{ + my $device = shift; + my @table; + + my $query = "get_bgp_summary_information"; + my $res = send_query($device, $query); + my $err; + + if (! ref $res) { + return $res; + } + + $err = $res->getFirstError(); + if ($err) { + return "ERROR: " . $err->{message}; + } + return $res; +} + +sub add_arg +{ + my $plugin = shift; + my $arg = shift; + + my $spec = $arg->{'spec'}; + my $help = $arg->{'usage'}; + + if (defined $arg->{'desc'}) { + my @desc; + + if (ref($arg->{'desc'})) { + @desc = @{$arg->{'desc'}}; + } + else { + @desc = ( $arg->{'desc'} ); + } + + foreach my $d (@desc) { + $help .= "\n $d"; + } + + if (defined $arg->{'default'}) { + $help .= " (default: $arg->{'default'})"; + } + } + elsif (defined $arg->{'default'}) { + $help .= "\n (default: $arg->{'default'})"; + } + + $plugin->add_arg( + spec => $spec, + help => $help, + ); +} + +sub get_conf +{ + my $plugin = shift; + my $arg = shift; + + my ($name, undef) = split(m/\|/, $arg->{'spec'}); + my $value = $plugin->opts->$name || $arg->{'default'}; + + if ($name eq 'password') { + verbose(3, "conf: password => " + . (($value eq '') ? '' : '')); + } + else { + verbose(3, "conf: $name => $value"); + } + return ($name => $value); +} + +sub add_single_check +{ + my $conf = shift; + my @check = split(m/,/, shift); + + my %c = (); + + if ($check[0] !~ m/\b(?:$valid_checks)\b/) { + return "ERROR: invalid check '$check[0]'"; + } + + $c{'name'} = $check[0]; + + if ((! defined($check[1])) || ($check[1] eq "")) { + $c{'target'} = qr//, + $c{'ttype'} = 'address', + } + elsif ($check[1] =~ m/^(?:$RE{'net'}{'IPv4'}|$IPv6_re)$/) { + $c{'target'} = $check[1]; + $c{'ttype'} = 'address'; + } + elsif ($check[1] =~ m/^\/(.*)\/$/) { + $c{'target'} = qr/$1/; + $c{'ttype'} = 'description'; + } + else { + $c{'target'} = $check[1]; + $c{'ttype'} = 'description'; + } + + $c{'warning'} = $check[2]; + $c{'critical'} = $check[3]; + + # check for valid thresholds + # set_threshold() will die if any threshold is not valid + $plugin->set_thresholds( + warning => $c{'warning'}, + critical => $c{'critical'}, + ) || $plugin->die("ERROR: Invalid thresholds: " + . "warning => $c{'warning'}, critical => $c{'critical'}"); + + push @{$conf->{'checks'}}, \%c; +} + +sub add_checks +{ + my $conf = shift; + my @checks = @_; + + my $err_str = "ERROR:"; + + if (scalar(@checks) == 0) { + $conf->{'checks'}[0] = { + name => 'peers_count', + target => qr//, + ttype => 'address', + warning => undef, + critical => undef, + }; + return 1; + } + + $conf->{'checks'} = []; + + foreach my $check (@checks) { + my $e; + + $e = add_single_check($conf, $check); + if ($e =~ m/^ERROR: (.*)$/) { + $err_str .= " $1,"; + } + } + + if ($err_str ne "ERROR:") { + $err_str =~ s/,$//; + $plugin->die($err_str); + } +} + +sub get_relevant_peers +{ + my $check = shift; + my @peers = @_; + + my @rpeers = (); + + my $cmp = sub { + my ($a, $b, undef) = @_; + if (ref $b) { + my $r = $a =~ $b; + verbose(3, "Checking peer '$a' against regex '$b' -> " + . ($r ? "true" : "false") . "."); + return $r; + } + else { + my $r = $a eq $b; + verbose(3, "Comparing peer '$a' with string '$b' -> " + . ($r ? "true" : "false") . "."); + return $r; + } + }; + + my $get_peer_elem; + + if ($check->{'ttype'} eq 'description') { + $get_peer_elem = \&get_peer_description; + } + else { + $get_peer_elem = \&get_peer_address; + } + + @rpeers = grep { $cmp->($get_peer_elem->($_), $check->{'target'}) } @peers; + return @rpeers; +} + +sub get_peer_element +{ + my $peer = shift; + my $elem = shift; + + $elem = $peer->getElementsByTagName($elem); + return $elem->item(0)->getFirstChild->getNodeValue; +} + +sub get_peer_description +{ + my $peer = shift; + return get_peer_element($peer, 'description'); +} + +sub get_peer_address +{ + my $peer = shift; + return get_peer_element($peer, 'peer-address'); +} + +sub verbose +{ + my $level = shift; + my @msgs = @_; + + if ($level > $conf{'verbose'}) { + return; + } + + foreach my $msg (@msgs) { + print "V$level: $msg\n"; + } +} +