#!/usr/bin/perl
# RatEventDaemon Version 0.1
# vim: ts=4 ft=perl sw=4 ai:
# (c) Copyright Roelf Diedericks <rodent at rodent dot za dot net>
#
# This tool does a source routed ping as configured in /etc/ratroute.conf
# for each interface with the "fakeevents" flag set.
# It then monitors the interface using pings, and should the interface go down,
# or come up, it will manually generate an event for ratroute, similar
# to the /etc/network/ifup|down scripts
# 
#	ChangeLog:
#		0.1	- Initial Version
#
use Net::Ping;
use FindBin qw($Script);
use Sys::Hostname;
use strict;


#globals required everywhere
use vars qw(@interfaces $debug);

# ___________________________
# initialization
# ---------------------------
$debug=1;
my $thishost = hostname;
my $configfile = "/etc/ratroute.conf";

# __________________________________________________
# perform a ping from the source address specified
# --------------------------------------------------
sub source_ping {
	my $host=shift;
	my $from_addr=shift;
	my $linkname=shift;

	my $p = Net::Ping->new("icmp");

	eval {
		$p->bind($from_addr); # Specify source interface of pings
	};
	warning("binding to $from_addr failed. Check /etc/network/interfaces : $@") if $@; 

	my $verdict="down";
	my $numpings=5;
	for (my $i=0; $i<$numpings; $i++) {
		
		my ($ret,$duration,$ip)=$p->ping($host, 2) ;
		my $msg="Sourced Ping: $ip ";
		if ( !$ret ) {
			$msg.="timed out via [$linkname]";
		} else {
			$verdict="up";
			$msg.="is reachable via [$linkname] (count=".($i+1).")";
		}
		logstdout($msg);
	}
	logstdout(">>>> $linkname is $verdict <<<<");
	$p->close();
	return $verdict;
}

# __________________________________________________
# log a message to syslog, stdout
# --------------------------------------------------
sub logmsg {
	my $msg=join(" ",@_);
	`logger -t \"$Script\" \"$msg\"`;
	print "\e[32;1m[$Script\@$thishost]:\e[36;1m $msg\e[0m\n";
}

# __________________________________________________
# log a message to stdout only
# --------------------------------------------------
sub logstdout {
	my $msg=join(" ",@_);
	print "\e[32;1m[$Script\@$thishost]:\e[36;1m $msg\e[0m\n";
}

# _____________________________
# fatal: abort with fatal error
# -----------------------------
sub fatal{
	my $msg=join(" ",@_);
	`logger -t \"$Script\" \"fatal:$msg\"`;
	print "\e[32;1m[$Script\@$thishost]:\e[31;1m fatal:$msg\e[0m\n";
	exit;
}

# _____________________________
# fatal: abort with fatal error
# -----------------------------
sub warning{
	my $msg=join(" ",@_);
	`logger -t \"$Script\" \"fatal:$msg\"`;
	print "\e[32;1m[$Script\@$thishost]:\e[35;1m warning:$msg\e[0m\n";
}


# _________________________________________
# routine to parse /etc/network/interfaces
# -----------------------------------------
sub get_deviceconfig {
	my $device=shift;
	my %r;

	open (IFCFG, "/etc/network/interfaces") or die "Could not open: /etc/network/interfaces\n"; 
	while (<IFCFG>) {  
		chomp;  
		my $currentline = $_; 
		foreach my $s ($currentline) { 
			$s =~ s/^\s+//; 
			$s =~ s/\s+$//; 
			$s =~ s/\s+/ /g; 
		} 

		my ($entry,$value) = split (/\ /, $currentline); 

		if ( (${entry} eq 'iface') and (${value} eq $device)) {  

			while(<IFCFG>) {  
				chomp;  

				my $currentline = $_; 
				foreach my $s ($currentline) { 
					$s =~ s/^\s+//; 
					$s =~ s/\s+$//; 
					$s =~ s/\s+/ /g; 
				} 

				($entry,$value) = split (/\ /, $currentline); 
				$r{$entry}=$value;
				next unless $_;  
			} 
		}  
		next unless $_;  
	}
	close(IFCFG);
	return %r;
}

# _____________________________________________________
# catch signals for proper shutdown event generation
# -----------------------------------------------------
sub catch_sig {
	my $signame = shift;
	logmsg("Caught SIG$signame, shutting down...");
	foreach my $interface (@interfaces)
	{
		if ( $interface->{cisco} && $interface->{lastevent} ne "down" ) {
			
			my %devinfo=get_deviceconfig($interface->{physical_if});
			my %merged=(%devinfo, %{$interface});

			#generate_event(%merged,"down");
		}
	}

	fatal("Exited due to SIG$signame");
}
$SIG{INT} = \&catch_sig;  
$SIG{TERM} = \&catch_sig;  
$SIG{HUP} = \&catch_sig;  

# _____________________________________________________
# generate a up/down event for the managed interface
# -----------------------------------------------------
sub generate_event {
	my %int=@_;
	

	$ENV{IFACE}=$int{physical_if};
	$ENV{IF_LINKNAME}=$int{linkname};
	$ENV{MODE}=($int{event} eq "up") ? "start" : "stop";
	$ENV{IF_ADDRESS}=$int{address};
	$ENV{IF_BALANCED_GATEWAY}=$int{balanced_gateway};
	logmsg("event: $int{event} $int{linkname}");
	my $prog="/usr/bin/ratroute";
	system($prog) ;
	if ($? == -1) {
		warning("failed to execute $prog: $!\n");
	}
	elsif ($? & 127) {
		warning( sprintf("$prog died with signal %d, %s coredump\n",
		($? & 127),  ($? & 128) ? 'with' : 'without') );
	}
	else {
		logmsg(sprintf("$prog exited with value %d\n", $? >> 8));
	}

}

#________________________________________________________
# main
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#check if already running
my $tag="rateventd_running";
my $pr=`pidof -s $tag`;
$pr=~s/\n//;
if ($pr ne "") {
	logmsg("already running as pid $pr");
	exit;
}
$0=$tag;

do "$configfile" or fatal("unable to open $configfile -- $@");

sub main {
	while (1) {	
		foreach my $interface (@interfaces)
		{
			if ( $interface->{cisco} ) {
				
				my %devinfo=get_deviceconfig($interface->{physical_if});
				my %merged=(%devinfo, %{$interface});
				foreach my $thing (keys %merged) {
					#print "$thing = $merged{$thing}\n";
				}

				logmsg("checking interface $merged{linkname}...");
				my $event=source_ping( $merged{ping_host},$merged{address},
					$merged{linkname} );

				#only trigger events upon state change
				if ( $event ne $interface->{lastevent} )  {
					logmsg("generating \"$event\" event for managed interface $merged{linkname}");
					$interface->{lastevent}=$event;
					$merged{event}=$event;
					generate_event(%merged,$event);
				}

				undef(%merged);
				undef(%devinfo);
			}

		}
		logmsg("sleeping 5 seconds.");
		sleep(5);

	}
}

main();
