#!FOOPERL

#
# Copyright (c) Xerox Corporation 1997, 1998. All rights reserved.
#
# License is granted to copy, to use, and to make and to use derivative
# works, provided that Xerox is acknowledged in all documentation
# pertaining to any such copy or derivative work. Xerox grants no other
# licenses expressed or implied. The Xerox trade name should not be used
# in any advertising without its written permission.
#
# XEROX CORPORATION MAKES NO REPRESENTATIONS CONCERNING EITHER THE
# MERCHANTABILITY OF THIS SOFTWARE OR THE SUITABILITY OF THIS SOFTWARE
# FOR ANY PARTICULAR PURPOSE.  The software is provided "as is" without
# express or implied warranty of any kind.
#
# These notices must be retained in any copies of any part of this software.
#



#----- default directory names and executable paths ----------
# users can override these values using the command line options listed
# the output directory for this program, command line opt -d
$outputdir =  "FOORTEMONDIR";
# the executable for the route watch program, command line opt -e
$routewatch = "FOORWEXEC";
# no modifications should occur below this point

#-----------------------------------------------
# command line options
# option: -x
#   action:  always dump the route table on exit
#   default: dump the route table only when sigusr1 or sigusr2 received
# option: -r rtr
#   action: only accept route updates from rtr
#   default: accept updates from all neighboring routers
# option: -f file
#   action: read updates from file rather than taking input from routewatch
#   default: read input from the $routewatch executable
# option: -d directory
#   action: write all output files into directory 
#   default: write all output files into $outputdir
# option: -e executable
#   action: run the routewatch program named executable
#   default: run the routewatch executable $routewatch

#------ perl standard modules and libraries -----------
use Getopt::Std;

#----- main program --------------------
&init_local_data;
#routewatch provides a continuous stream of route updates
#process updates until we are interrupted by a signal
#or the routewatch command terminates
while($line = <ROUTEWATCH>) {
    if (split(" ", $line) == 7) {
	&process_update;
    } else {
	&log_error(input);
    }
}
# if -x option was specified, dump table before exiting
if ($opt_x == 1) {
    &dump_table;
}  
&log_error(end);
exit;

#------------ initialization operations -----------------------
sub init_local_data {
    # check the command line options are correct
    if (!getopts('xr:f:d:e:') || (@ARGV > 0)) {
	print 
    "Usage: routemonitor [-x] [-r r.r.r.r] [-f file] [-d dir] [-e exec] \n";
	exit;
    }
    if (defined($opt_r) && defined($opt_f)) {
	print 
	    "Usage: -r option can not be used in conjunction with -f option\n";
	exit;
    }    
    # if user selected a output directory with -d option, use it
    if (defined($opt_d)) { $outputdir = $opt_d;}
    $datafile = "$outputdir/route_table";
    $errorfile = "$outputdir/route_monitor.err";
    $pidfile = "$outputdir/route_monitor.pid";
    $conffile = "$outputdir/route_monitor.conf";
    # record start time in error log
    &log_error(start);
    # write my process id into the pid file
    open(PIDFILE, ">$pidfile") || &log_quit(pidfile);
    print PIDFILE "$$\n";
    close(PIDFILE);
    # if conf exists, learn the metrics for adjacent routers
    local($mycost,$myrtr);
    if (open(CONFFILE, "$conffile")) {
	while($line = <CONFFILE>) {
	    if ($line !~ /^#/) {
	    # if not a comment line
		if (split(" ",$line) != 2) {
		    &log_error(configline);
		} else {
		    ($myrtr, $mycost) = split(" ",$line);
		    $cost{$myrtr} = $mycost;
		}
	    }
	}
    }
    # set up signal handling to output route tables
    $SIG{USR1} = 'dump_clean_table';
    $SIG{USR2} = 'dump_table';
    #initialize the date at which statistics were last reset
    $last_reset = time;
    #initialize the time last learned from routewatch
    $rtr_time = 0;
    # the dvmrp route timeout period (in seconds)
    $rt_timeout = 180;
    #start the routewatch program
    if (defined($opt_f)) {
	open(ROUTEWATCH, "cat $opt_f |") ||
	    die "Route Monitor unable to open input file $opt_f\n";
    } else {
	if (defined($opt_e)) {
	    $routewatch = $opt_e;
	} 
	if (defined($opt_r)) {
	    $rw_opts = "-t -r $opt_r";
	} else {
	    $rw_opts = "-t";
	}
	open(ROUTEWATCH, "$routewatch $rw_opts |") || &log_quit(rwatch);
    }
}


#------------------ process updates from route watch --------
sub process_update {
    # parse a new route update received from routewatch
    ($time,$rtr,$pktnum,$rtnum,$src,$pmetric,$metric)=split(" ",$line);
    # update the current time learned from routewatch
    $rtr_time = $time;
    # adjust the metric to reflect the cost of the first hop interface
    if (!defined($cost{$rtr})) { &lookup_iff_cost($rtr);}
    if ($metric < 32) {
    # note we only add the interface cost to non-poisson reverse updates
	$metric =  $metric + $cost{$rtr};
	if ($metric > 32) {$metric = 32;}
    }
    if (!defined($first{$src})) {  
    # the update is for a new source
	$first{$src} = $rtr;
	$time{$src} = $time;
	$updates{$src} = 1;
	$changes{$src} = 1;
	$newtree{$src} = 1;
	$metric{$src} = $metric;
	return(1);
    }
    if ($rtr eq $first{$src}) { 
    # the update is from the first hop to the source
	$time{$src} = $time;
      	$updates{$src}++;
	if ($metric{$src} != $metric) { $changes{$src}++;}
	$metric{$src} = $metric;
	return(2);
    }
    if ((($time - $time{$src}) > $rt_timeout) || ($metric < $metric{$src})) {
    # change table if either the route has timed out or the metric is smaller
	$first{$src} = $rtr;
	$time{$src} = $time;
	$updates{$src}++;
	$changes{$src}++;
	$newtree{$src}++;
	$metric{$src} = $metric;
	return(3);
    }
    local($cur_rtr) = unpack("N", pack("C4", split(/\./, $frist{$src})));
    local($new_rtr) = unpack("N", pack("C4", split(/\./, $rtr)));
    if (($metric == $metric{$src}) && ($new_rtr < $cur_rtr)) {
    # metrics are equal, but new update wins the address tiebreaker
	$first{$src} = $rtr;
	$time{$src} = $time;
	$updates{$src}++;
	$changes{$src}++;
	$newtree{$src}++;
	$metric{$src} = $metric;
	return(4);
    }	
}

sub lookup_iff_cost {
    # takes a route update sender as input and returns the interface metric
    # using the route_monitor.conf file, the default interface cost is 1
    local($best_cost) = 1;
    local($best_mask) = 0;
    local($new_rtr,$mynet,$mymask,$mynum,$mytemp,$shift,$tmp_num);
    local($iff_rtr) = $_[0];
    local($iff_num) = unpack("N",pack("C4",split(/\./o,$iff_rtr)));
    foreach $new_rtr (keys %cost) {
	if (split("/",$new_rtr) == 2) {
	    # this entry is a cidr address
	    ($mynet,$mymask) = split("/",$new_rtr);
	    if ($mymask >= $best_mask) {
		# this might be a more specific address
		$shift = 32 - $mymask;
		$tmp_num = $iff_num >> $shift;
		$mynum = unpack("N",pack("C4",split(/\./o,$new_rtr)));
		$mytemp = $mynum >> $shift;
		if ($tmp_num == $mytemp) {
		    # we have new match
		    $best_mask = $mymask;
		    $best_cost = $cost{$new_rtr};
		}
	    }
	} else {
	    # this is entry is a specific ip address
	    if ($iff_rtr eq $new_rtr) {
		# keep the exact match
		$cost{$iff_rtr} = $cost{$new_rtr};
		return($cost{$new_rtr});
	    }
	}
    }
    $cost{$iff_rtr} = $best_cost;
    return($cost{$iff_rtr});
}


#--------------- generate route table output -----------------------
sub open_dump_file {
    # open and write the header lines of a route table dump file
    open(DATAFILE, ">$datafile") || &log_error(dump);
    local($dtime) = time;
    local($sc,$mn,$hr,$dy,$mo,$yr,$wd,$yd,$isd) = gmtime($dtime);
    $mo = $mo +1;
    # okay, let's get ready for year 2000...
    if ($yr < 100) { 
        $yr = "19$yr"; 
    } else {
    # this assumes gmtime really returns 108 for 2008 like it
    # should, but if different times implement different gmtime 
    # the we have trouble
        $yr = $yr -100;
        $yr = "20$yr";
    }
    if ($hr < 10) { $hr = "0$hr"; }
    if ($mn < 10) { $mn = "0$mn"; }
    if ($sc < 10) { $sc = "0$sc"; }
    local($dump_date) = "$mo/$dy/$yr,$hr:$mn:$sc-GMT";
    ($sc,$mn,$hr,$dy,$mo,$yr,$wd,$yd,$isd) = gmtime($last_reset);
       # okay, let's get ready for year 2000...
    if ($yr < 100) { 
        $yr = "19$yr"; 
    } else {
    # this assumes gmtime really returns 108 for 2008 like it
    # should, but if different times implement different gmtime 
    # the we have trouble
        $yr = $yr -100;
        $yr = "20$yr";
    }
    $mo = $mo +1;
    if ($hr < 10) { $hr = "0$hr"; }
    if ($mn < 10) { $mn = "0$mn"; }
    if ($sc < 10) { $sc = "0$sc"; }
    local($reset_date) = "$mo/$dy/$yr,$hr:$mn:$sc-GMT";
    local($routes) = int( keys(%first));
    local($rpt_int) = int((time - $last_reset) /60);
    print DATAFILE 
    "Route Table Dumped at $dtime ($dump_date), $routes routes in table \n";
    print DATAFILE
    "Report interval is $rpt_int minutes, table last reset at $last_reset ($reset_date)\n";
}

sub dump_table {
    # dump the current routing table into a file
    &open_dump_file;
    local($src);
    foreach $src (keys %first) {
	print DATAFILE 
"$src $first{$src} $metric{$src} $changes{$src} $updates{$src} $newtree{$src}\n";
    }
    close(DATAFILE);
} 

sub dump_clean_table {
    # dump the current routing table into a file
    # reset the updates and changes statistics and remove any expired routes
    &open_dump_file;
    local($src);
    foreach $src (keys %first) {
	print DATAFILE
"$src $first{$src} $metric{$src} $changes{$src} $updates{$src} $newtree{$src}\n";
	if (($rtr_time - $time{$src}) > $rt_timeout) {
        # delete an expired route from the table
	    delete($first{$src});
	    delete($time{$src});
	    delete($metric{$src});
	    delete($updates{$src});
	    delete($changes{$src});
	    delete($newtree{$src});
	} else {
	# reset the stats for a current route
	    $updates{$src} = 0;
	    $changes{$src} = 0;
	    $newtree{$src} = 0;
	}
    }
    close(DATAFILE);
    $last_reset = time;
}

#---------------- error handling -----------------------
sub log_error {
    # write any error messages into the errorfile
    local($errname) = $_[0];
    local($sc,$mn,$hr,$dy,$mo,$yr,$wd,$yd,$isd) = gmtime();
    $mo = $mo +1;
    # okay, let's get ready for year 2000...
    if ($yr < 100) { 
        $yr = "19$yr"; 
    } else {
    # this assumes gmtime really returns 108 for 2008 like it
    # should, but if different times implement different gmtime 
    # the we have trouble
        $yr = $yr -100;
        $yr = "20$yr";
    }
    if ($hr < 10) { $hr = "0$hr"; }
    if ($mn < 10) { $mn = "0$mn"; }
    if ($sc < 10) { $sc = "0$sc"; }
    local($errdate) = "$mo/$dy/$yr $hr:$mn:$sc GMT";
    open(ERRORFILE, ">>$errorfile") ||
	die "Route Monitor unable to open error log $errorfile\n";
    if ($errname eq "input") {
	print ERRORFILE "$errdate ERROR: wrong size line from routewatch:\n";
	print ERRORFILE "$line";
	print ERRORFILE "LINE IGNORED, RESUMING...\n";
    } elsif ($errname eq "configline") {
	print ERRORFILE "$errdate ERROR: invalid config file line:\n";
	print ERRORFILE "$line";
	print ERRORFILE "LINE IGNORED, RESUMING...\n";
    } elsif ($errname eq "dump") {
	print ERRORFILE "$errdate ERROR: unable to open dumpfile $datafile:\n";
	print ERRORFILE "ROUTE TABLE DUMP FAILED, RESUMING...\n";
    } elsif ($errname eq "start") {
	print ERRORFILE "$errdate START: route monitor started\n";
    } elsif ($errname eq "end") {
	print ERRORFILE "$errdate END: route monitor exited\n";    
    } else {
	print ERRORFILE "$errdate ERROR: unidentified error code $errname:\n";
	print ERRORFILE "ERROR CODE IGNORED, RESUMING...\n";
    }
    close(ERRORFILE);
}

sub log_quit {
    # write any error messages into the errorfile and exit
    local($errname) = $_[0];
    local($sc,$mn,$hr,$dy,$mo,$yr,$wd,$yd,$isd) = gmtime();
    $mo = $mo +1;
    # okay, let's get ready for year 2000...
    if ($yr < 100) { 
        $yr = "19$yr"; 
    } else {
    # this assumes gmtime really returns 108 for 2008 like it
    # should, but if different times implement different gmtime 
    # the we have trouble
        $yr = $yr -100;
        $yr = "20$yr";
    }    
    if ($hr < 10) { $hr = "0$hr"; }
    if ($mn < 10) { $mn = "0$mn"; }
    if ($sc < 10) { $sc = "0$sc"; }
    local($errdate) = "$mo/$dy/$yr $hr:$mn:$sc GMT";
    open(ERRORFILE, ">>$errorfile") ||
        die "Route Monitor unable to open error log $errorfile\n";
    if ($errname eq "pidfile") {
        print ERRORFILE "$errdate ERROR: unable to write pid file $pidfile\n";
    } elsif ($errname eq "rwatch") {
        print ERRORFILE "$errdate ERROR: unable to to launch routewatch\n";
        print ERRORFILE "route watch executable is $routewatch $rw_opts\n";
    } else {
        print ERRORFILE "$errdate ERROR: unidentified error code $errname:\n";
    }
    print ERRORFILE "FATAL ERROR, EXITING.\n";
    close(ERRORFILE);
    exit(-1);
}
