#!/bin/perl

# removes def routes not in static-routes file (e.g. from DHCP)
# if /etc/default/routing has guessdefaults in it, that over-rides
# any other def routes and we just pick the first two ips on each subnet
# and load balance among them based on flow (dest IP).
# one can also hard code default routes by putting:
# guessdefaults ip1:eth0 ip2:eth1
# in /etc/default/routing
# put newline separated pairs of subnet2omit <subnet>
# in /etc/default/routing to prevent rc.routing from
# choosing a def rt on those subnets

require '/etc/systemvars.pl';
unless (defined $ID_EXEC){
	$ID_EXEC = $ENV{'ID_EXEC'};
}

$path=$ENV{'PATH'};
$ENV{'PATH'}     = '/bin:/sbin:/usr/bin:/usr/sbin:$path';

$rtr_file = "/var/lib/route_watcher/defrtrs";
$bad_rtr_file = "/var/lib/route_watcher/badrtrs";
$int_file = "/var/lib/route_watcher/ints"; # our ints in case anybody needs them
unlink $rtr_file;
unlink $int_file;

exit 0 unless ( $ID_EXEC =~ /linux/ );

open(OLDOUT, ">&STDOUT");
open(OLDERR, ">&STDERR");

open(STDOUT,">/var/tmp/rc.routing.log") ||
    die "Unable to open STDOUT to /var/tmp/rc.routing.log: $!\n";
open(STDERR,">&STDOUT") ||
    die "Unable to dup STDOUT to STDERR: $!\n";

select(STDERR); $| = 1;
select(STDOUT); $| = 1;

undef @omitted;
if (-f "/etc/default/routing") {
	&fill_omitted;
}

# setup arptables invocations for each interface
undef @mysubnets;
&fill_mysubnets;
undef %subnet_int;
&fill_subnet_int;


my $interfaces_ref;
for my $subnet (@mysubnets) {
	my $int = $subnet_int{$subnet};
	$interfaces_ref->{$int} = {};
	get_mac_from_int($interfaces_ref, $int);
	get_ip_from_int($interfaces_ref, $int);
}


# We want to build a data structure that looks like this to feed the arptables handler
#
#$VAR1 = {
#          'eth1' => {
#                      'ip' => '10.163.17.42/27',
#                      'mac' => '00:11:25:75:0d:11'
#                    },
#          'eth0' => {
#                      'ip' => '10.163.17.10/27',
#                      'mac' => '00:11:25:75:0d:10'
#                    }
#        };

#
# Only add the arptables config if we're on more than 1 subnet.
# This should automatically include all zebra machines plus other variations of multihomed hosts
if ( scalar @mysubnets > 1 ) {
	print "More than one subnet requires arptables configuration.\n";
	make_arptables_calls($interfaces_ref);
} else {
	print "One subnet\n";
}

exit 0 unless ( (-f "/etc/sysconfig/static-routes") || (-f "/etc/default/routing") );

if ($ARGV[0] eq 'stop') {
   unlink "/var/lock/subsys/rc.routing";
   exit 0;
}

system ("touch /var/lock/subsys/rc.routing");

undef @bad_rtrs; # global array of bad rtrs from route_watcher

# remove def routes not in static-routes file (e.g. from DHCP)
if ( -f "/etc/sysconfig/static-routes" ) {

	undef @defrtrs;  # def routers from static-routes
	&fill_defrtrs;

	open (IPR, "ip route show |");
	while ( <IPR> ) {
		chomp;
		next unless /default\s+via\s+(\d+\.\d+\.\d+\.\d+)/;
		chomp ( $artr = $1 );
		my $line = $_;
		unless ( grep /^$artr$/, @defrtrs ) {
			print "$0: attempting to delete supplementary def route to $artr.\n";
			system( "ip route delete $line 2>&1\n" );
		}
	}
	close IPR;

}

# if route_watcher detected a bad rtr, we need to know and chk if it's still bad
if ( -f "$bad_rtr_file" ) {
	open (RF, "$bad_rtr_file") || print "$0 cant open badrtrs $!\v";
	while(<RF>) { next unless ( /^\d/ ); chomp;  push( @bad_rtrs, $_ ); }
	close (RF);
	&fix_bad_rtrs; # see if the bad router is still down
}

if ( -f "/etc/default/routing" ) {

	open (RTNG, "/etc/default/routing") || die "$0: cant open /etc/default/routing\n";

	while (<RTNG>) {
		if (/guessdefaults/i) {

			exit 0 if $guessed;  # ignore more than one guessdefaults
			chomp;
			my $line = $_;

			&write_ints; # write out our valid ints to the state file

			undef $artr;
			@hard_rtrs = split (/\s+/, $line);
			shift @hard_rtrs;  # list of hard coded rtrs if any

			if ( $hard_rtrs[0] !~ /\d+\.\d+\.\d+\.\d+/ ) { # no hard coded rtrs

				# first, delete any def rtrs (e.g. from DHCP)
				&del_defs;

				# try to glean number of IPs on subnets from the DHCP lease file
				# default to using two if no lease file is found
				my $num_gws = 2;

				foreach $asub (@mysubnets) {
					my $int = $subnet_int{$asub};
					my $lease_file = "/var/lib/dhcp/dhclient-$int.leases";

					if (-r $lease_file && open(DHCP, "<$lease_file")) {
						my @router_lines = map { chomp; $_ } grep(/option routers/, <DHCP>);
						my @routers = map { s/;$//; $_ } split /,/, (split / routers /, $router_lines[0])[1];

						if (scalar @routers > 0) {
							$num_gws = scalar @routers;
							close(DHCP);
							print "$0: gateways found in DHCP lease file for interface $int, " . join(',', @routers) . "\n";
							last;
						}

						close(DHCP);
					}
				}

				# next, add first N IPs on each subnet as def rtrs
				$rcmd = "ip route replace default scope global ";
				foreach $asub (@mysubnets) { # two routers per subnet
					my $int = $subnet_int{$asub};

					($b1,$b2,$b3,$b4)=split(/\./, $asub);

					for (my $i = 1; $i <= $num_gws; $i++) {
						$last_digit = $b4 + $i;
						$router = "${b1}.${b2}.${b3}.${last_digit}";

						# append to list of routers
						write_rtr_file($router);

						# check if it's online
						if (!ping($router) && !grep(/^$router$/, @bad_rtrs)) {
							add_bad_rtr($router);
						}

						if ( !grep(/^$router$/, @bad_rtrs)) {
							$rcmd .= "nexthop via $router dev $int weight 1 ";
						}
					}
				}

			} else {

				if ( $hard_rtrs[1] !~ /\d+\.\d+\.\d+\.\d+/ ) {
					die "$0: only one hard coded def rtr in /etc/default/routing, exiting\n";
				}

				# first, delete any def rtrs (e.g. from DHCP)
				&del_defs;
				# next, add the hard coded rtrs
				print "$0:  adding hard coded rtrs from /etc/default/routing\n";
				$rcmd = "ip route replace default scope global ";
				foreach $artr (@hard_rtrs) {
					# well it's really a rtr:dev tuple
					($artr, $rtr_dev) = split ( /:/, $artr );
					&write_rtr_file("$artr");
					next if ( grep(/^$artr$/, @badrtrs) );
					unless (&ping( $artr )) { print "Warning: $artr is down\n"; }
					$rcmd .= "nexthop via $artr dev $rtr_dev weight 1 "
				}

			}

			print "about to:\n $rcmd\n";
			system ("$rcmd");
			$guessed = 1;
		}
	}

	close(RTNG);
	&ping_bcasts;

}

close STDERR;
close STDOUT;
open(STDOUT, ">&OLDOUT");
open(STDERR, ">&OLDERR");
select(STDERR); $| = 1;
select(STDOUT); $| = 1;
&printlog;


# begin subs

sub printlog {

    open(LOGFILE, "/var/tmp/rc.routing.log");
    while (<LOGFILE>) { print; }
    close (LOGFILE);

}

sub fill_omitted {

	open (RTNG, "/etc/default/routing") || die "$0: cant open /etc/default/routing\n";
	while (<RTNG>) {
		chomp;
		if (/^subnet2omit/) {
			my ($foo, $sub_omit) = split(/\s+/);
			print "omitting ints on subnet $sub_omit\n";
			push (@omitted, $sub_omit);
		}
	}
	close (RTNG);

}

sub del_defs {

	open (IPR, "ip route show |");
	while ( <IPR> ) {
		chomp;
		next unless /default\s+via\s+(\d+\.\d+\.\d+\.\d+)/;
		chomp ( $artr = $1 );
		$line = $_;
		print "$0: deleting supplementary def route to $artr.\n";
		system( "ip route delete $line 2>&1\n" );
	}
	close (IPR);

}

sub write_ints {

	my @ints;
	my $asub;
	my $anint;

	# write out our ints
	unless ( open (IF, ">>$int_file") ) {
		printf("$0: cant open: %s\n",$!);
	}


	foreach $asub (@mysubnets) {
		push ( @ints, $subnet_int{$asub} );
	}

	foreach $anint (@ints) {
		open (IFG, "ifconfig $anint |");
		while(<IFG>) {
			next unless ( /inet addr:(\d+\.\d+\.\d+\.\d+)/ );
			print IF "$anint $1\n";
		}
		close IFG;
	}

	close IF;

}

sub add_bad_rtr {

    my $bad_rtr = shift;
    my $tmp_bad_rtr; undef $tmp_bad_rtr;

    print "warning cant ping $bad_rtr but it was pingable before power up\n";

    push (@bad_rtrs, $bad_rtr);

    unless ( open (BF, ">$bad_rtr_file") ) { print "cant open $bad_rtr_file\n"; }
    foreach $tmp_bad_rtr (@bad_rtrs) {
	print BF "$tmp_bad_rtr\n";
    }
    close (BF);

}

sub fix_bad_rtrs {

	my $a_bad_rtr;
	my @tmp_bad_rtrs; undef @tmp_bad_rtrs;

	foreach $a_bad_rtr (@bad_rtrs) {
		next if ( &ping( $a_bad_rtr ) );
		push ( @tmp_bad_rtrs, $a_bad_rtr );
		print "$0: Warning $a_bad_rtr is still down!\n";
	}

	@bad_rtrs = @tmp_bad_rtrs;

	if ( $bad_rtrs[0] !~ /\d+\.\d+\.\d+\.\d+/ ) { # no more bad rtrs
		print "$0: all rtrs in $bad_rtr_file are ok now.\n";
		unlink ("/var/lib/route_watcher/badrtrs");
	} else {
		unless ( open (BF, ">$bad_rtr_file") ) { print "cant open $bad_rtr_file\n"; }
		foreach my $abadrtr (@bad_rtrs) { print BF "$abadrtr\n"; }
		close BF;
	}

}

sub ping {

	my $addr = shift;
	my $rc = 0;
	    my $pingcmd = "ping -c 1 -n -w 2";

	# first ping is to prime the switches tables
	# otherwise we get false negatives
	$rc = 0xffff & system ("$pingcmd $addr > /dev/null 2>&1");
	$rc = 0xffff & system ("$pingcmd $addr > /dev/null 2>&1");

	if ( $rc == 0 ) {  return 1;  } else {  return 0;  }

}

sub fill_mysubnets {

	my $asubnet;

    open (IPR, "ip route show |");
    while ( <IPR> ) {
	chomp;
	next unless /^\d+\.\d+\.\d+\.\d+/;
	next unless /scope link/;
	next if /^127\.0\.0\.0/;
	next if /^172\.17\./;
	next if /^169\.254\./;
	$asubnet = (split (/\//))[0];
	chomp $asubnet;
	push (@mysubnets, $asubnet) unless ( grep { /^$asubnet$/ } @omitted );
    }
    close IPR;

}

sub ping_bcasts {

	my $abcast;
	my $pingcmd = "ping -c 1 -n -w 2 -b";

	open (IPA, "ip addr show |");
	while ( <IPA> ) {
	    chomp;
	    next unless /\s+brd\s+/;
		next if /\s+lo:/;
	    next unless /inet.*\s+brd\s+(\d+\.\d+\.\d+\.\d+)\s+/;
	    $abcast = $1;
		    chomp $abcast;
		    next if $abcast =~ /^127/;
		    system ("$pingcmd $abcast > /dev/null 2>&1");
	}
	close IPA;

}

sub fill_subnet_int {

	my $asubnet;
	my $int;

	open (IPR, "ip route show |");
	while ( <IPR> ) {
		chomp;
		next unless /^\d+\.\d+\.\d+\.\d+/;
		next unless /scope link/;
		next if /^127.0.0.0/;
			next if /^172\.17\./;
			next if /^169\.254\./;
		$asubnet = (split (/\//))[0];
		$int = (split (/\s+/))[2];
		chomp $asubnet;
		chomp $int;
		unless ( grep { /^$asubnet$/ } @omitted ) { $subnet_int{$asubnet} = $int; }
    }
    close IPR;

}

sub write_rtr_file {

	my $def_rtr = shift;

	unless ( open (RF, ">>$rtr_file") ) {
		printf("$0: cant open: %s\n",$!);
		return 1;
	}
    print RF "$def_rtr\n";
	close (RF);

}

sub fill_defrtrs {

	open (SR, "/etc/sysconfig/static-routes") || die "$0: cant open /etc/sysconfig/static-routes\n";
	while ( <SR> ) {
		s/\#.*//g;
		s/^\s+//g;
		s/\s+$//g;
		next if (/^$/);

#		if ( /default\s+gw\s+(\d+\.\d+\.\d+\.\d+)/ ) {
#			push (@defrtrs, $1);
#		}

		if ( /net\s+0.0.0.0\s+netmask\s+0.0.0.0\s+gw\s+(\d+\.\d+\.\d+\.\d+)/ ) {
			push (@defrtrs, $1);
		}
	}
	close SR;

}


# We want to create invocations of arptables that look like this:
#
# arptables -A OUT --source-hw 00:11:25:9e:2d:88 -j mangle --mangle-ip-s 172.31.19.52
# arptables -A OUT --source-hw 00:11:25:9e:2d:89 -j mangle --mangle-ip-s 172.31.19.76
#
sub make_arptables_calls {
  my ($interfaces) = @_;

  print "Doing arptables setup\n";
  for my $int ( keys %{ $interfaces } ) {
    my ($arptables_tmpl) = '/sbin/arptables -A OUT --source-hw %s -j mangle --mangle-ip-s %s -p Request';

    my $ip = $interfaces->{$int}->{'ip'};
    my $mac = $interfaces->{$int}->{'mac'};

    # do some sanity checking
    next if ( not defined($ip) || not defined($mac) );
    my $arptables_invoc = sprintf($arptables_tmpl, $mac, $ip);

    my $rc = `$arptables_invoc`;
    print "$arptables_invoc\n";
  }
}

sub get_mac_from_int {
  my ($interfaces, $int) = @_;

  my $cmd = "ip link show";

  open(IPLINK, "$cmd $int |") or die "Cannot execute $cmd : $! \n";

  # We expect the output of ip link show to look like the following:

  # [arijort@pa919c2n6 ~]$ ip link show eth0
  # 2: eth0: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast qlen 1000
  #    link/ether 00:11:25:75:0d:10 brd ff:ff:ff:ff:ff:ff

  my $mac;
  while (<IPLINK>) {
    next unless ( /link/ );
    $mac = (split(/\s+/))[2];
  }

  close(IPLINK);
  $interfaces->{$int}->{'mac'} = $mac;

  return $mac;
}

sub get_ip_from_int {
  my ($interfaces, $int) = @_;
  my $cmd = "ip route show";

  open(IPADDR, "$cmd |") or die "Cannot execute $cmd : $! \n";

  # We expect the output of ip route show to look like the following:
	# [root@np3c1n10 /]# ip route show
	# 172.31.29.128/25 dev eth1  proto kernel  scope link  src 172.31.29.159
	# 172.31.29.0/25 dev eth0  proto kernel  scope link  src 172.31.29.89
  # We want the 8th field

  my ($ip) = "";
  while (<IPADDR>) {
    next unless ( /scope link/ );
    next unless ( /src/ );
    next unless ( /$int/ );
    $ip = (split(/\s+/))[8];
  }

  return if ( not defined($ip) );
  $interfaces->{$int}->{'ip'} = $ip;
}
