Browse Source

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]]]
Sebastian Harl 13 years ago
parent
commit
ff970c8dc6
1 changed files with 518 additions and 0 deletions
  1. 518 0
      check_junos_bgp.pl

+ 518 - 0
check_junos_bgp.pl

@@ -0,0 +1,518 @@
+#!/usr/bin/perl
+
+#############################################################################
+# (c) 2001, 2003 Juniper Networks, Inc.                                     #
+# (c) 2011 Sebastian "tokkee" Harl <sh@teamix.net>                          #
+#          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 <host>] [-p <port>] [-t <timeout]
+[-U <user>] [-P <password] check-tuple [...]",
+	license   =>
+"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 => '<prompt>',
+	},
+);
+
+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'} = <STDIN>;
+	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 '<prompt>') ? '<prompt>' : '<hidden>'));
+	}
+	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";
+	}
+}
+