#!/usr/local/bin/perl
# -*- perl -*-

# Copyright (c) 1999 by Jeff Weisberg
# Author: Jeff Weisberg <jaw @ tcp4me.com>

# Announce routes via RIPv2
# see RFC{1058,1388}

use Socket;
use Sys::Hostname;
use Getopt::Std;

srand( $$ ^ $^T );
getopts('b:c:m:np:Tv');
# -b broadcastaddr
# -c configfile
# -m metric
# -n	no forking
# -p port
# -T	Test Mode
# -v	verbose

# ToDo:
#  aggregate


# get addresses, from config file or set of test addrs
# addresses in file may optionally be followed by /len and/or :metric
# eg. www.example.com/32:1

# NB: config file metric is ADDed to -m metric (if any)

if( ! $opt_T ){
    $config = $opt_c || "/etc/hosts.rip";
    
    open(C, $config ) || die "Cannot open $config: $!\n";
    @addr = <C>;
    chop @addr;
    close C;
}else{
    @addr = map { "10.1.2.$_:$_" } (1 .. 5);

}

# resolve addresses
foreach $a ( @addr ){

    print STDERR "$a " if $opt_v;
    $ip = cannon($a);

    die "cannot resolve '$a'\n" if length($ip->[0]) != 4;
    $a = $ip;
}
print STDERR "\n" if $opt_v;


$proto = getprotobyname('udp');
socket(S, PF_INET, SOCK_DGRAM, $proto) || die "socket: $!";
setsockopt(S, SOL_SOCKET, SO_BROADCAST, 1); 			# needed for linux


if( $opt_b ){
    $mip = resolve($opt_b);
}else{
    $mip = resolve(hostname());
    substr($mip, 3, 1) = "\xFF";       	# XXX - magic handwaving...
}
print STDERR "B: ", join(".", unpack("CCCC", $mip)), "\n" if $opt_v;

$sin = sockaddr_in($opt_p || 520, $mip);
connect(S, $sin) || die "connect: $!\n";

$riphdr = pack("CCn", 2, 2, 0);
$metric = $opt_m || 1;


if( !$opt_n ){
    exit if fork;			# to the background, Robin!
}

while( 1 ){
    if( $opt_n || !fork ){
 	while( 1 ){
	    $nip = 0;
	    $rip = '';
	    
	    foreach $ip ( @addr ){
		$rip .= pack( "nnA4NNN",
			     # af, rt, addr,     mask,     nexthop, metric
			       2,  0,  $ip->[0], $ip->[1], 0,       $ip->[2] + $metric );

		if( $nip++ == 25 ){
		    print "sending RIP ($nip)\n" if $opt_v;
		    send(S, $riphdr . $rip, 0) || die "send: $!\n";
		    $nip = 0;
		    $rip = '';
		}
	    }

	    if( $nip ){
		print "sending RIP ($nip)\n" if $opt_v;
		send(S, $riphdr . $rip, 0) || die "send: $!\n";
	    }
	    
	    sleep( 25 + rand(10) );
	}
    }else{
	# process died - restart
	wait;
	print STDERR "RIP respawning\n";
    }
}

sub cannon {
    my( $a ) = @_;
    my( $addr, $len, $metric, $ip, $mask );

    ($addr, $len, $metric) = $a =~ /([^:\/]*)(?:\/(\d+))?(?::(\d+))?/;
    print STDERR "A:$addr L:$len M:$metric\n" if $opt_v;

    $ip = resolve($addr);
    $mask = l2m($len || 32);
    
    [ $ip, $mask, $metric || 0 ];
}

sub l2m {
    my( $l ) = @_;
    my( $i, $r );

    $r = 2**32 - 2**(32-$l);
    $r;
}

sub resolve {
    my( $a ) = @_;
    my( $ip, @o );
    
    if( $a =~ /^\d+\.\d+\.\d+\.\d+$/ ){
	@o = split( /\./, $a );
	$ip = pack ("CCCC", @o);
    }else{
	$ip = gethostbyname($a);
	if( length($ip) != 4){
	    sleep(1);
	    $ip = gethostbyname($a);
	}
    }
    $ip;
}

