#!/usr/local/bin/perl

use strict;
use Net::Telnet ();
use Net::IPv4Addr qw( :all );
use Socket;

#(C) Fredrik Widell (fredrik@widell.net) 2003
#use as you wish, modify as you wish
#this script will probably destroy your whole
#network in a second, dont blame me. send me
#a email if you want to. This script runs best
#via crontab, f.ex like this:
#0,5,10,15,20,25,30,35,40,45,50,55 * * * * /home/fredrik/perl/isismon.pl >/dev/null 2>&1
#the script logs in to some router and fetches the isis-database
#and compares with a saved one 5 minutes earlier to see if
#there are any missing or any new, if missing the script parses
#the corresponding saved routerconfig to show the interfacedata
#and if the route is new it logs in to that router to get interfacedata,
#the user must have login in all the routers in the network to work.




##############################################################################
# change the below values to suite your network
##############################################################################

#This is the router to fetch the isis-database from
my $host="someroutername";

#where to send notifications regarding added/removed routes
my $mailadress="my\@mailadress.se";

#wheres my mailer
my $mta='/usr/bin/mail';

#where to store temporary data
my $tmp='/tmp';

#here's all my cisco-configs
my $configs='/tftpboot/';

#my username
my $id="username";

#my password
my $pass="password";


##############################################################################


my @diffs;
my $diff;
my @added;
my @removed;
my $router;
my %nethost = ();
my %oldnethost = ();
my $curhostname = undef;
my $oldcurhostname = undef;
my $old;
my $new;
my $interfacesadded;
my $interfacesremoved;


sub askrouterfor {

	my ($cmd, $file, $brief) = @_;
	print "will fetch \"$cmd\" to \"$file\" \"$brief\"\n";
	my $t = new Net::Telnet (Timeout => 40,
	Prompt => '/.*[>#]/');
	$t->open($host);
	$t->login($id, $pass);
	$t->cmd("ter len 0");
	my @out = $t->cmd("$cmd");

	open DATA, "> $tmp/$file";
		if ($brief) {
			foreach (@out) { my @f = split /\s+/; print DATA "$f[2]\n"; }
		}
		else {
			print DATA @out;
		}

	$t->close($host);
	close DATA;
}

sub getlocalrouterdata {

	my ($subnet, $router, $newroute) = @_; 
	my ($netaddr,$netmask) = split('/', $subnet);
	my $dynga = ipv4_cidr2msk( $netmask );
	#my $errmode="return";
	$netmask=$dynga;
	my $trash;
	my @l;

	$netaddr = unpack('N', inet_aton($netaddr));
	$netmask = unpack('N', inet_aton($netmask));
	
	if (open ROUTER, "< $configs/${router}-confg") { 
		@l=<ROUTER>;
	close ROUTER;
	}
	if ($newroute) {
		eval {
			my $cmd="sh run";
			print "$subnet is a new route, logging in to $router\n";
			my $t = new Net::Telnet (Timeout => 40,
			#Prompt => '/.*[>#]/');
			Prompt => '/\w.*[>#]\s?$/');
			#Errmode    => $errmode);
			$t->open($router);
			$t->login($id, $pass);
			$t->cmd("ter len 0");
			@l = $t->cmd("$cmd");
			$t->close($host);
			#print "data for $router is ";
			#print @l;
		};
		if ($@) {
			print "error logging in to $router,$@,\n";
			$trash=$@;
			#return($@);
		}

	}
	foreach my $i (0..$#l) {
		if ($l[$i] =~ /(^|\D)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\D|$)/) {
			my $addr = unpack('N', inet_aton($2));
			if (($addr & $netmask) == $netaddr) {
				my $b = $i-3;
				$b = $i if $l[$i] !~ /ip address/;
				$b = 0 if $b < 0;
				foreach ($b..$i) {
					#print $l[$_];
					$trash .=  $l[$_];
				}
				#print "-"x72, "\n";
				#$trash .=  "-"x72, "\n";
			}
		}
	}
	if ($trash) {
		return $trash;
	} else {
		getlocalrouterdata ($subnet, $router, 1);
	}
			
}


sub getisisroutes {
	#This is the actual forwardingtable, any removed/added subnets
	#are really removed or added
	print "fetching show ip route isis\n";
	askrouterfor ("sh ip route isis | inc L2", "isisroutes.new", "brief");

}

sub getisisdatabase {

	#This is the isis-database, this can from time to time be
	#very different from the actual forwardingtable created by
	#isis, but contains data regarding which router that announces a subnet

	print "fetching isis database detail\n";
	rename("$tmp/isisdatabase.new", "$tmp/isisdatabase.old") or warn "can't rename $tmp/isisdatabase.new to $tmp/isisdatabase.old $!";
	askrouterfor ("sh isis database detail level-2", "isisdatabase.new");

	#compare the newly fetched database with the saved, if they
	#differ a lot, the new is probably not correct, the router
	#is recalculating, wait a while and fetch again.

	open OLDDATA, "$tmp/isisdatabase.old"  or die "can't open $tmp/isisdatabase.old $!";
		$old++ while <OLDDATA>;
	close OLDDATA;
	open NEWDATA, "$tmp/isisdatabase.new"  or die "can't open $tmp/isisdatabase.new $!";
		$new++ while <NEWDATA>;
	close NEWDATA;

	print "new isisdatabase contains $new, old contais $old\n";
	print $new / $old;
	while (($old / $new) > '1.1') {
		print "new isisdatabase contains $new, old contais $old, retrying in 10 seconds\n";
		sleep 10;
		askrouterfor ("sh isis database detail level-2", "isisdatabase.new");
		$new=0;
		open NEWDATA, "$tmp/isisdatabase.new"  or die "can't open $tmp/isisdatabase.new $!";
			$new++ while <NEWDATA>;
		close NEWDATA;
	}

	#create a hash of the isisdatabase with subnets as keys
	#and hostnames as values, thanks bd.
	open DATA, "< $tmp/isisdatabase.new" or die "can't open $tmp/sisdatabase.new $!";
		while (<DATA>) {
		    if (/^\s+Hostname:\s+(\S+)$/) {
			$curhostname = $1;
			#print "curhostname=$curhostname\n";
		    }
		    if (/^\s+Metric:.*IP\s+(\S+)$/) {
			$nethost{$1} = $curhostname;
		    }
		}
	close DATA;
	open OLDDATA, "< $tmp/isisdatabase.old" or die "can't open $tmp/sisdatabase.old $!";
		while (<OLDDATA>) {
		    if (/^\s+Hostname:\s+(\S+)$/) {
			$oldcurhostname = $1;
			#print "oldcurhostname=$oldcurhostname\n";
		    }
		    if (/^\s+Metric:.*IP\s+(\S+)$/) {
			$oldnethost{$1} = $oldcurhostname;
		    }
		}
	close OLDDATA;

}

########################################################################


getisisroutes;
getisisdatabase;


#check for differences in the routingtable, added or removed routes
if (-e "$tmp/isisroutes.old") {

	@diffs = `diff -u  $tmp/isisroutes.old $tmp/isisroutes.new`;
	#print @diffs;

	print "checking for added/removed subnets in the forwardingtable\n";
	foreach $diff (@diffs) {
		if ($diff =~ /(^-)(\d[1-9]\d{1,2}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{2})(.*)/) {
			@removed = (@removed , $2);
		}
		if ($diff =~ /(^\+)(\d[1-9]\d{1,2}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{2})(.*)/) {
			@added = (@added , $2);
		}
	}

} else {

	print "this is the first time we run $0 or $tmp/ have been cleaned\n";
	rename("$tmp/isisroutes.new", "$tmp/isisroutes.old") or die "can't rename $tmp/isisroutes.new to $tmp/isisroutes.old $!";

}

#check the added routes since last time
my $added=0;
open ADDED, "> $tmp/addedroutes" or die "can't open $tmp/addedroutes $!";
if (@added) {
	print "finding routers that added subnets\n";
	foreach my $subnet (@added) {
		$added ++;
		print "added subnet=$subnet\n";
		$router = $nethost{"$subnet"};
		print "router=$router, subnet=$subnet\n";
		print ADDED "\n-- subnet $subnet ADDED in $router, affected interfaces/peers etc: --\n\n";
		$interfacesadded=getlocalrouterdata ($subnet, $router, 1);
		print ADDED $interfacesadded;
		print ADDED "-"x72, "\n";	
	}
}
close ADDED;

#check the removed routes since last time
my $removed=0;
open REMOVED, "> $tmp/removedroutes" or die "can't open $tmp/removedroutes $!";
if (@removed) {
	print "finding routers that removed subnets\n";
	foreach my $subnet (@removed) {
		$removed ++;
		print "removed subnet=$subnet\n";
		$router = $oldnethost{"$subnet"};
		print "router=$router, subnet=$subnet\n";
		print REMOVED "\n-- subnet $subnet REMOVED from $router, affected interfaces/peers etc: --\n\n";
		$interfacesremoved=getlocalrouterdata ($subnet, $router, 0);
		print REMOVED $interfacesremoved;
		print REMOVED "-"x72, "\n";	
	}
}
close REMOVED;

#send a mail with added and removed routes
my $subject;
if ($added) {
	$subject="isis +$added rts";
}
if ($removed) {
	if ($subject) {
		$subject .= ", -$removed rts";
	} else {
		$subject = "isis -$removed rts";
	}
}

if ($added | $removed) {
	print "subject=$subject, mailadress=$mailadress, mta=$mta\n";

	open MAIL, "| $mta -s \"$subject \" $mailadress" or die "cant find $mta $!";
	if ($removed) {
		print "creating mail with removed routes\n";
		open REMOVED, "< $tmp/removedroutes" or die "can't open $tmp/removedroutes $!";
		print MAIL <REMOVED>;
		close REMOVED;
	}
	if ($added) {
		print "creating mail with added routes\n";
		open ADDED, "< $tmp/addedroutes" or die "can't open $tmp/addedroutes $!";
		print MAIL <ADDED>;
		close ADDED;
	}
	close MAIL;
} else {
	print "no new routes, will NOT send a mail\n";
}

#move the new database to old.
rename("$tmp/isisroutes.new", "$tmp/isisroutes.old") or die "can't rename $tmp/isisroutes.new to $tmp/isisroutes.old $!";


