#!/usr/bin/perl

use strict;
use Net::Telnet ();

#(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/bgpmon.pl >/dev/null 2>&1
#This script makes use of a zebra that receives a full bgp-table, in my
#case this zebra is in a full bgp-mesh with my network to receive unfiltered
#bgp from all routers, it checks the community we use to give transit to all
#our bgp-speaking and non bgp-speaking customers, and if a prefix is added or
#removed in that community a mail is sent that contains the prefix and the
#registered data for that prefix (ripedb).

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

#f.ex my $community='1653:0';
my $community='1653:0';
my $password='apassword';
my $mailadress="my\@mailadress.se";


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

my @out;
my $out;
my @diffs;
my $added;
my $removed;

#this retreives data from telneting to the bgp-daemon
#or telnet to whois-server
sub getdata {

	my ($cmd, $file, $port, $prompt, $host) = @_;

	#print "will fetch \"$cmd\" from \"$host\" port \"$port\" to \"$file\"\n";
	#print "with the prompt=$prompt\n";

	my $t = new Net::Telnet (Timeout 	=> 40,
				Prompt 		=> $prompt,
				Port		=> $port);

	$t->open($host);

	if ($host eq 'localhost') {
		#print "getting bgp-data\n";
		$t->print($password);
		$t->waitfor($prompt);
		$t->cmd("ter len 0");
		@out = $t->cmd("$cmd");
	} else {
		#print "getting whois-data\n";
		$t->cmd($cmd);
		@out = $t->getlines(Telnetmode => 0, Timeout => 10,);
	}

	open DATA, "> $file" or die "can't open $file $!";
	        print DATA @out;
	$t->close($host);
	close DATA;

	return @out;
}

#convert ip-adress to hostname
sub gethostname {
	my ($ip) = @_;
	my @octets = split ('\.', $ip) ;
	my $ip_number = pack ("CCCC", @octets[0..3]);
	(my $name, my $aliases, my $type, my $len, my $addr) = gethostbyaddr ($ip_number, 2) ;
	return $name;
}




#first, move the old data
rename("/tmp/bgpparsed.new", "/tmp/bgpparsed.old") or warn "can't rename /tmp/bgpparsed.new to /tmp/bgpparsed.old $!";

#saving bgpoutput to file
my $cmd="sh ip bgp community $community";
my $prefix;
my $file='/tmp/bgpfile.new';
my $port='2605';
my $prompt='/.*[>#]\s$/';
my $host='localhost';

getdata ($cmd, $file, $port, $prompt, $host);

#open the fetched data
open BGP, "< /tmp/bgpfile.new" or die "can't open /tmp/bgpfile.new $!";

my $datat;
my $done=0;

#checking for the start of the interesting output, remove stuff in the beginning
while (!$done) {
	$datat = <BGP>;
	$done = 1 if ($datat =~ /Network/);
}
		

#open file to save my parsed data
open BGPPARSED, "> /tmp/bgpparsed.new" or die "can't open /tmp/bgpparsed.new $!";

#cleaning the fetched data, removing all exept prefix, nexthop and as-path
while (defined ($datat=<BGP>)) {

	#remove blank lines
	next if ($datat =~ /^$/) ;

	#remove unwanted output at the bottom
	last if ($datat =~ /Total/) ;

	#check for "pretty-printed" lines, and merge them
	if ($datat =~ /(^\*..)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?)$/) {
		chomp($datat);
		$datat .= " " . <BGP>;
	}
	chomp($datat);

	#remove redundant blanks
	$datat =~ s/\s\s+/ /g;

	#print "datat=$datat\n";

	#find the prefix, to fix the lines without prefixes
	if ($datat =~ /(^\*..)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?)/) {
		$prefix=$2;
	}

	#add the prefix to the lines without prefixes
	if ($datat =~ /(^\*..)(\s.*$)/) {
		$datat = $1 . $prefix . $2;
	}

	#getting the prefix, nexthop and as-path
	if ($datat =~ /(^\*..)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?\s\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(.*)/) {
		#print "4=$4\n";

		#this is prefix and nexhop
		$datat = $2 . " ";

		#splitting the rest to get the as-path
		my @list=split /\s+/, $4 ;
		my $n=@list;
		my $rev="";
		while ($n) {
			$n --;
			#print "list[$n]=$list[$n]\n";

			#if we get to a "0", this will be the weight, and the actual
			#beginning of the as-path
			last if ($list[$n] eq 0);

			#if we see a i,e or ? this is the end of the
			#data with the origin-code, not interesting.
			next if ($list[$n] =~ /(i|e|\?)/);

			$rev .= " " . $list[$n];
			#print "list[$n]=$list[$n]\n";
		}
		#print "rev=$rev\n";
		
		#our match is in reverse, fixing.
		$n = join(" ", reverse split(" ", $rev));

		#print "n=$n\n";
		if ($n) {
			$datat .= join(" ", reverse split(" ", $rev)) . "\n";
		} else {
			$datat .= 1653 . "\n";
		}
		#print $datat;
	}

	#save the parsed data to file
	print BGPPARSED $datat;
}

close BGP;
close BGPPARSED;

#check for diffs in the bgp.
@diffs = `diff -u  /tmp/bgpparsed.old /tmp/bgpparsed.new`;

#print "checking for added/removed prefixes\n";

#are there any diffs between the saved bgp-data?
my $diff;
foreach $diff (@diffs) {

	#checking for "-", removed prefixes
	if ($diff =~ /(^-)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.*)/) {
		$removed = $removed . $2 . "\n";
		#print "removed=$removed\n";
	}

	#checking for "+", added prefixes
	if ($diff =~ /(^\+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.*)/) {
		$added = $added . $2 . "\n";
		#print "added=$added\n";
	}
}
my @added=split /\n/, $added;
my @removed=split /\n/, $removed;

my $descadded;
my $descremoved;
my $descas;
my $descnexthop;
my $prefix;
my $nexthop;
my @aspath;
my $aspath;
my $line;
my $ases;

$file='/dev/null';
$port=43;
$prompt='/.*copyright\.html/';
$host='whois.ripe.net';

#this gets the name and description of a ASn via whois.ripe.net
sub checkases {
	my ($origortransit,$as, $n, @result) = @_;
	$cmd="-T inetnum -r $prefix";
	my $line;
	my $ases="  " . $origortransit . " AS" . $as;
	foreach $line (@result) {
		$line =~ /((^as-name:\s+)|(^descr:\s+))(.*)/;
		if ($4) {
			$ases .= " " . $4;
		}
	}
	return $ases;
}

#save the output to a file to send a mail later
open TOMAIL, "> /tmp/mailas" or die "can't open /tmp/mailas $!";

foreach $added (@added) {
	$descadded="";
	print "\n--------------------------------------------------------------------------------\n";
	print TOMAIL "\n--------------------------------------------------------------------------------\n";

	#split the data, mainly to parse the as-path
	($prefix, $nexthop, @aspath)=split /\s/, $added;

	#get the data for the ip-adresses
	$cmd="-T inetnum -r $prefix";
	my @result = getdata ($cmd, $file, $port, $prompt, $host);

	#get the name and description of the ip-adresses
	foreach my $line (@result) {
		$line =~ /((^netname:\s+)|(^descr:\s+))(.*)/;
		$descadded .= " " . $4;
		chomp($descadded);
	}
	$descadded =~ s/\s+/ /;
	chomp($descadded);

	#get the hostname of the nexthop
	my $name=gethostname ($nexthop);

	#parse the as-path and get data for each AS
	my $n=@aspath;
	my $b=$n - 1;
	while ($n) {
		$n --;
		$cmd="-T aut-num -r AS$aspath[$n]\n";
		#print "AS\$aspath[$n]=AS$aspath[$n]\n";

		#the first AS (or last in the path) is always the origin-as
		if ($n == $b) {
			my @result = getdata ($cmd, $file, $port, $prompt, $host);
			my $as = $aspath[$n];
			$ases = checkases ('origin ', $as, $n, @result);
			print "prefix $prefix ADDED\n\n  Description: $descadded\n  nexthop $nexthop $name\n";
			print TOMAIL "prefix $prefix ADDED\n\n  Description: $descadded\n  nexthop $nexthop $name\n";
			print $ases . "\n";
			print TOMAIL $ases . "\n";

		#this is the transit-ases
		} else {
			my @result = getdata ($cmd, $file, $port, $prompt, $host);
			my $as = $aspath[$n];
			$ases = checkases ('transit', $as, $n, @result);
			#print "prefix $prefix ADDED, transit AS $aspath[$n], nexthop $nexthop $name\n";
			#print TOMAIL "prefix $prefix ADDED, transit AS $aspath[$n], nexthop $nexthop $name\n";
			print $ases . "\n";
			print TOMAIL $ases . "\n";
		}
	}


}
foreach $removed (@removed) {
	$descremoved="";
	print "\n--------------------------------------------------------------------------------\n";
	print TOMAIL "\n--------------------------------------------------------------------------------\n";
	($prefix, $nexthop, @aspath)=split /\s/, $removed;
	$cmd="-T inetnum -r $prefix";
	my @result = getdata ($cmd, $file, $port, $prompt, $host);
	foreach my $line (@result) {
		$line =~ /((^netname:\s+)|(^descr:\s+))(.*)/;
		$descremoved .= " " . $4;
		chomp($descremoved);
	}
	$descremoved =~ s/\s+/ /;
	chomp($descremoved);
	my $name=gethostname ($nexthop);
	my $n=@aspath;
	my $b=$n - 1;
	while ($n) {
		$n --;
		$cmd="-T aut-num -r AS$aspath[$n]\n";
		if ($n == $b) {
			my @result = getdata ($cmd, $file, $port, $prompt, $host);
			my $as = $aspath[$n];
			$ases = checkases ('origin ', $as, $n, @result);
			print "prefix $prefix REMOVED\n\n  Description: $descremoved\n  nexthop $nexthop $name\n";
			print TOMAIL "prefix $prefix REMOVED\n\n  Description: $descremoved\n  nexthop $nexthop $name\n";
			print $ases . "\n";
			print TOMAIL $ases . "\n";
		} else {
			my @result = getdata ($cmd, $file, $port, $prompt, $host);
			my $as = $aspath[$n];
			$ases = checkases ('transit', $as, $n, @result);
			print $ases . "\n";
			print TOMAIL $ases . "\n";
		}
	}
}

close TOMAIL;


#if data in the file send a mail.
if (-s '/tmp/mailas') {
	my $padded=0;
	my $premoved=0;
	print "I will send a mail\n";
	open DATA, "< /tmp/mailas" or die "Can't open /tmp/mailas $!";
	while (<DATA>) {
		if (/(^prefix.*ADDED)/) {
			print "1=$1\n";
			$padded ++;
		}
		if (/(^prefix.*REMOVED)/) {
			print "1=$1\n";
			$premoved ++;
		}
	}
	close DATA;
	open DATA, "< /tmp/mailas" or die "Can't open /tmp/mailas $!";
	open TOMAIL, "| /usr/bin/mail -s \"bgp-prefix $premoved removed, $padded added\" $mailadress or die "can't run /usr/bin/mail $!";
	while (<DATA>) {
		print TOMAIL;
	}
	close TOMAIL;
	close DATA;
}

