[c-nsp] Here's a tool to monitor an OSPF network

Ed Ravin eravin at panix.com
Mon Feb 6 01:46:45 EST 2006


On Wed, Jan 18, 2006 at 05:28:07PM +0100, Code Monkey wrote:
> I run an OSPF network that has gone from 30 to 300 routers during the past
> year
[...]
> Does anyone know of any script that analyzes a (Cisco) OSPF database?
> Ideally tying it in with Nagios or maybe BigBrother, but essentially just
> transforming it so that it's easy to see whether a given link is up? I'd
> like to do a show ip ospf database and put the output into the script.

Attached is my first try at an OSPF monitoring script meant for use in the
Mon monitoring system ( http://linux.kernel.org/software/mon/ ).  I suspect
it wouldn't be hard for a savvy programmer to adapt it to another
monitoring system.

The script walks through your OSPF interface table, and lists all enabled
OSPF interfaces and whether you have adjacencies on them.  It exits with
a non-zero exit status (and an extra line of output at the top of its
listing) if it finds an interface that OSPF has open but has no adjacency.

Though it's not quite what you're asking for, if you point it at every
router (yup, all 300 routers in your case) you will find any failed
adjacencies.  Usage is as follows:

  ospf.monitor [--exclude regexp] [--community blah] router1 router2 ...

Where "router1", "router2", etc. are the routers you are polling, "blah"
is the SNMP community, and "regexp" is either a single IP address of
an interface to ignore, or a regexp in the form "ip1|ip2|ip3" if you
want to ignore multiple interfaces.

I was rather surprised when I ran this script on my routers - a few of
my routers were trying to start adjacencies on dead interfaces or in
other places that they shouldn't have been.  Here's a sample output:

$ ./ospf.monitor router1
router1

router1 (ID 10.99.99.99)
router1:   Interface 10.99.37.8.0     state : down             [NO ADJACENCY]
router1:   Interface 10.99.37.9.0     state : pointToPoint   
router1:   Interface 10.99.37.10.0    state : pointToPoint   
router1:   Interface 10.99.37.11.0    state : pointToPoint   
router1:   Interface 10.99.37.12.0    state : down             [NO ADJACENCY]
router1:   Interface 10.99.38.88.0    state : designatedRouter
router1:   Interface 10.99.38.89.0    state : loopback         [NO ADJACENCY]
router1:   Interface 10.99.38.90.0    state : designatedRouter
router1:   Interface 10.99.38.91.0    state : designatedRouter

For this router, the two "down" links are lines that have been disconnected
and I forgot to tell OSPF not to listen on them anymore, and the "loopback"
line is a loopback interface that I've incorrectly told OSFP to listen on.

The script will also tell you about addressless interfaces that have
OSPF enabled - I need to put a bit more code in the script to identify
them (currently you just see an ifIndex without explanation).

This script is fresh out of the oven, so consider it beta quality or
worse - but I'd be pleased if folks with the right environment (Perl and
the SNMP_Session module from http://www.switch.ch/misc/leinen/snmp/perl)
could give it a spin and let me know what they think of it.  It sticks
to the RFC1253 OSPF MIB, so it should work on any router, not just
Cisco.

	-- Ed
-------------- next part --------------
#!/usr/bin/perl
#
# Router ospf (Open Shortest Path First) monitor
# Look at each router and get the status of all OSPF neighbors.
# Issue alarm if any interfaces configured for neighbors do not
#   have a full adjacencies
# Detail log shows status of all enabled OSPF interfaces.

# Usage:
#     ospf.monitor [--exclude pattern] [--community str] router1 [...]
#
# --exclude - don't alarm for IP addresses that match <pattern>.  Periods
# in the IP address will be escaped so that they only match periods.  Use
# [0-9] or the like if you need character class matching.  Use 'ip|ip|ip'
# to exclude multiple peers.
#
# --community - SNMPv1 community name to use.  But it's more secure
# to pass the community in via the environment variable COMMUNITY.


#
# Edit history below
# Version 0.1
# 
# By Ed Ravin <eravin at panix.com>  This code is made available courtesy of
# PANIX http://www.panix.com.
# Copyright 2005, by Ed Ravin
#
# License: GNU GPL v2, see http://www.gnu.org/copyleft/gpl.html
#
# Loosely based on bgp.monitor which is:
#   Copyright 2002, by Marc Hauswirth, Safe Host SA <marc at safehostnet.com>
#
# Some inspiration is taked from others mon monitors and from
# routerinfo.pl by Ben Buxton (bb at zipworld.net), also under GPL, see http://www.zipworld.com.au/~bb/linux/
# and from routerint.monitor by P. Strauss (philou at philou.ch) and me self (marc at safehostnet.com).
#

# This script need the SNMP Session module from Simon Leinen <simon at switch.ch>
#   Wich you could found under http://www.switch.ch/misc/leinen/snmp/perl/
#   It is also part of MRTG (http://people.ee.ethz.ch/~oetiker/webtools/mrtg/)

use SNMP;
use SNMP_Session;
use Getopt::Long;
use strict;

my %opt;

$opt{'community'}= undef;
$opt{'exclude'}= "";
$opt{'debug'}= undef;
my $usage="Usage: [COMMUNITY=str] ospf.monitor [--exclude regexp] [--community str] router [...]\n";
GetOptions(\%opt, "exclude=s", "community=s", "debug") or die $usage;

# It's highly unlikely someone wants dots in an IP address to be treated
# as a regexp pattern, so we'll escape them to make behavior more predictable.
# If you really want to use pattern matching, use a character class like
# [0-9] instead.
$opt{'exclude'} =~ s/\./\\./g;
$opt{'exclude'}= '^' . $opt{exclude} . '$';


## --
my $community = $opt{'community'} || $ENV{'COMMUNITY'} || "public";

## --

my @failures;
my @details;

$ENV{'MIBS'}= ""; # all OIDs needed are specified in script

# OID's to the SNMP elements that I want to show...
# From Cisco's MIB and RFC's
# http://sunsite.cnlab-switch.ch/ftp/doc/standard/rfc/16xx/1657
# http://www.telecomm.uh.edu/stats/rfc/BGP4-MIB.html

my %oids = ( 
	"SysUptime"			=>	"1.3.6.1.2.1.1.3.0",
	"ospfRouterId"			=>	"1.3.6.1.2.1.14.1.1" ,
	"ospfIfIpAddress"		=>	"1.3.6.1.2.1.14.7.1.1" ,
	"ospfAddressLessIf"		=>	"1.3.6.1.2.1.14.7.1.2" ,
	"ospfIfAdminStat"		=>	"1.3.6.1.2.1.14.7.1.5" ,
	"ospfIfState"			=>	"1.3.6.1.2.1.14.7.1.12" ,
	);


my %ospfIfStates = (
	1 => "down",
	2 => "loopback",
	3 => "waiting",
	4 => "pointToPoint",
	5 => "designatedRouter",
	6 => "backupDesignatedRouter",
	7 => "otherDesignatedRouter",
	);

my %ospfAdminStatus = (
	1 => "enabled",
	2 => "disabled",
	);


my %state;
my $router;

sub snmpget1 # session, oid-hashstr, instance
{
	my $session= shift;
	my $oidstr= shift;
	my $instance = shift;
	my $result= $session->get(".$oids{$oidstr}.$instance");

	if ($session->{ErrorNum})
	{
		push @failures, $router;
		push @details, "$router: error on SNMP get of $oidstr: $session->{ErrorStr}";
		return 0;
	}
	return $result;
}
foreach $router (@ARGV) {
	# Get some infos about this router
	my $sess = new SNMP::Session ( DestHost => $router, Community => $community );
	if (!defined($sess))
	{
		push @failures, $router;
		push @details, "$router: cannot create SNMP session";
		next;
	}
	
	my $ospfRouterID = snmpget1($sess, "ospfRouterId", "0") || next;
	
	push @details, "$router (ID $ospfRouterID)";

	# Find the indexes of the interfaces with OSPF enabled
	my @ospfinterfaces;

	my $vars  = new SNMP::VarList([$oids{ospfIfAdminStat}]);
	for (my @vals = $sess->getnext($vars);
			$vars->[0]->tag =~ /1\.3\.6\.1\.2\.1\.14\.7\.1\.5/       # still in table (Did you have a cleaner solutions ?)
			and 
			not $sess->{ErrorStr};          # and not end of mib or other error
			@vals = $sess->getnext($vars))
		{
			
			my $textIfAdminStatus = $ospfAdminStatus{$vals[0]};
			push  @ospfinterfaces, $vars->[0]->tag
				if $textIfAdminStatus eq "enabled";
		}
	# trim down OID to keep just the interface part, which we will use
	# shortly as an instance ID
	map {s/^\.$oids{ospfIfAdminStat}\.//} @ospfinterfaces;

	foreach my $int (@ospfinterfaces)
	{
	my $ifstate = snmpget1($sess, "ospfIfState", "$int");
		push @details, sprintf("$router:   Interface %-16s  state : %-15s",$int, $ospfIfStates{$ifstate}); 

		# if ospfIfState not in [4..7] (OSPF full adjacency states)
		if ($ifstate < 4 or $ifstate > 7) {
			push @failures, $router unless $int =~ $opt{exclude} or grep(/$router/, @failures);
			$details[$#details] .= "  [NO ADJACENCY]";
		}
	}
}

if (@failures) {
	print join(' ', @failures), "\n";
};
if (@details) {
	print "\n";
	print join("\n", @details), "\n";
}

if (@failures) {
	# Error state exit
	exit 1;
} else {
	# Correct exit
	exit 0;
};



More information about the cisco-nsp mailing list