#!/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@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@safehostnet.com>
#
# Some inspiration is taked from others mon monitors and from
# routerinfo.pl by Ben Buxton (bb@zipworld.net), also under GPL, see http://www.zipworld.com.au/~bb/linux/
# and from routerint.monitor by P. Strauss (philou@philou.ch) and me self (marc@safehostnet.com).
#

# This script need the SNMP Session module from Simon Leinen <simon@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] [--timeout usecs] [--version N] [--retries nn] router [...]\n";
GetOptions(\%opt, "exclude=s", "community=s", "timeout=i", "version=i", "retries=i", "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} . ')';
$opt{exclude}= "NOT_USED" if $opt{exclude} eq "^()";


## --
my $community = $opt{'community'} || $ENV{'COMMUNITY'} || "public";
my $timeout= $opt{'timeout'} || 5000000;
my $retries= $opt{'retries'} || 3;
my $version= $opt{'version'} || 1;

## --

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",
	"ifDescr"			=>	"1.3.6.1.2.1.2.2.1.2",
	"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",
	);


use vars qw($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.$instance: $session->{ErrorStr}";
		return 0;
	}
	return $result;
}
foreach $router (@ARGV) {
	# Get some infos about this router
	my $sess = new SNMP::Session (
		DestHost => $router,
		Community => $community,
		Version=> $version,
		Timeout=> $timeout,
		Retries=> $retries,
	);
	if (!defined($sess))
	{
		push @failures, $router;
		push @details, "$router: cannot create SNMP session";
		next;
	}
	
	my $ospfRouterID = snmpget1($sess, "ospfRouterId", "0") || next;
	
	push @details, "$router (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");
		my $ifinfo= $int; 
		if ($int =~ /0\.0\.0\.0\.(\d+)$/) {
			my $ifindex= $1;
			$ifinfo= snmpget1($sess, "ifDescr", $ifindex) . " (.$ifindex)";
		}
		push @details, sprintf("$router:  Interface %-23s  %-15s",$ifinfo, $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}\b/ 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;
};

