AMPR-Rip44

 view release on metacpan or  search on metacpan

bin/rip44d  view on Meta::CPAN

# see Changes

# Things to do in the future:
#
# - proper logging to syslog
# - support for better authentication, if one would be supported
# - support for multiple RIP masters, to fix the single point of failure
#

use strict;
use warnings;

use IO::Socket::Multicast;
use Getopt::Std;

use constant {
	RIP_HDR_LEN => 4,
	RIP_ENTRY_LEN => 2+2+4*4,
	RIP_CMD_REQUEST => 1,
	RIP_CMD_RESPONSE => 2,
	RIP_AUTH_PASSWD => 2,
	AF_INET => 2,
};

my $rip_passwd;
my $tunnel_if = 'tunl0';
my $routebin = '/sbin/ip';
my $ifconfig = '/sbin/ifconfig';
my $verbose = 0;
# Local gateway addresses (whose routes are skipped)
my %my_addresses;
# Allowed route destination networks
my $net_44_regexp = '^44\.';
# We do not accept routes less specific than /15
my $minimum_prefix_len = 15;
# tcp window to set
my $tcp_window = 840;
# time (in seconds) to use routes which are no longer advertised
# - this is set to a large value, so that if the rip advertisements
# from mirrorshades stop, the network won't go down right away.
my $route_ttl = 7*24*60*60;

my %current_routes;

my $me = 'rip44d';
my $VERSION = '1.1';

# help and version texts
$Getopt::Std::STANDARD_HELP_VERSION = 1;

sub HELP_MESSAGE()
{
	my($fh) = @_;
	
	print $fh "Usage:\n"
		. "  $me [-v] [-d] [-i <tunnelif>] [-a <localaddrs>] [-p <password>]\n"
		. "Options:\n"
		. "  -v   increase verbosity slightly to print error messages on stderr\n"
		. "  -d   increase verbosity greatly (debug mode)\n"
		. "  -i <tunnelinterface>\n"
		. "       use the specified tunnel interface, defaults to tunl0\n"
		. "  -a <comma-separated-ip-list>\n"
		. "       ignore routes pointing to these (local) gateways\n"
		. "       (list contains system's local IP addresses by default)\n"
		. "  -p <password>\n"
		. "       use RIPv2 password 'authentication', defaults to none\n"
		;
}

sub VERSION_MESSAGE()
{
	my($fh) = @_;
	
	print $fh "$me version $VERSION\n";
}

# Figure out local interface IP addresses so that routes to them can be ignored

sub fill_local_ifs()
{
	my $s = `$ifconfig -a`;
	
	while ($s =~ s/inet addr:(\d+\.\d+\.\d+\.\d+)//) {
		warn "found local address: $1\n" if ($verbose);
		$my_addresses{$1} = 1;
	}
}

# Convert a netmask (in integer form) to the corresponding prefix length,
# and validate it too. This is a bit ugly, optimizations are welcome.

sub mask2prefix($)
{
	my($mask) = @_; # integer
	
	# convert to a string of 1's and 0's, like this (/25):
	# 11111111111111111111111110000000
	my($bits) = unpack("B32", pack('N', $mask));
	
	# There should be a continuous row of 1's in the
	# beginning, and a continuous row of 0's in the end.
	# Regexp is our hammer, again.
	return -1 if ($bits !~ /^(1*)(0*)$/);
	
	# The amount of 1's in the beginning is the prefix length.
	return length($1);
}

# delete a route from the kernel's table

sub route_delete($)
{
	my($rkey) = @_;
	
	# This is ugly and slow - we fork /sbin/ip twice for every route change.
	# Should talk to the netlink device instead, but this is easier to
	# do right now, and good enough for this little routing table.
	my($out, $cmd);
	$cmd = "LANG=C $routebin route del $rkey";
	$out = `$cmd 2>&1`;
	if ($?) {
		if ($verbose > 1 || $out !~ /No such process/) {
			warn "route del failed: '$cmd': $out\n";
		}
	}
}

bin/rip44d  view on Meta::CPAN

		return -1;
	}
	if ($rip_version != 2) {
		warn "$me: ignored RIP version $rip_version packet from $addr_s (only accept v2)\n";
		return -1;
	}
	if ($zero1 != 0 || $zero2 != 0) {
		warn "$me: ignored RIP packet from $addr_s: zero bytes are not zero in header\n";
		return -1;
	}
	
	my $init_msg = 0;
	
	# if password auth is required, require it!
	if (defined $rip_passwd) {
		return -1 if (!process_rip_auth_entry(substr($entries, 0, RIP_ENTRY_LEN)));
		$init_msg += RIP_ENTRY_LEN;
	}
	
	# Ok, process the actual route entries
	my $routes = 0;
	for (my $i = $init_msg; $i < length($entries); $i += RIP_ENTRY_LEN) {
		my $entry = substr($entries, $i, RIP_ENTRY_LEN);
		my $n = process_rip_route_entry($entry);
		return -1 if ($n < 0);
		$routes += $n;
	}
	
	return $routes;
}

#
####### main #############################################
#

# command line parsing
my %opts;
getopts('i:p:a:vd', \%opts);

if (defined $opts{'i'}) {
	$tunnel_if = $opts{'i'};
}
if (defined $opts{'p'}) {
	$rip_passwd = $opts{'p'};
}
if ($opts{'v'} && !$verbose) {
	$verbose = 1;
}
if ($opts{'d'}) {
	$verbose = 2;		
}
if ($opts{'a'}) {
	foreach my $a (split(',', $opts{'a'})) {
		$my_addresses{$a} = 1;
	}
}

fill_local_ifs();

# Enable multicast on the tunnel interface, the flag is
# not set by default
system($ifconfig, $tunnel_if, 'multicast') == 0 or die "ifconfig $tunnel_if multicast failed: $?\n";

# Create the UDP multicast socket to receive RIP broadcasts
warn "opening UDP socket...\n" if ($verbose);
my $socket = IO::Socket::Multicast->new(
	LocalPort => 520,
	ReuseAddr => 1,
) or die $!;

$socket->mcast_add('224.0.0.9', $tunnel_if) or die $!;

my $expire_interval = 60*60;
my $next_expire = time() + $expire_interval;

# Main loop: receive broadcasts, check that they're from the correct
# address and port, and pass them on to processing
warn "entering main loop, waiting for RIPv2 datagrams\n" if ($verbose);
while (1) {
	my $msg;
	my $remote_address = recv($socket, $msg, 1500, 0);
	
	if (!defined $remote_address) {
		next;
	}
	
	my ($peer_port, $peer_addr) = unpack_sockaddr_in($remote_address);
	my $addr_s = inet_ntoa($peer_addr);
	
	if ($addr_s ne '44.0.0.1' || $peer_port ne 520) {
		warn "$me: ignored packet from $addr_s: $peer_port: " . length($msg) . "\n";
		next;
	}
	
	warn "received from $addr_s: $peer_port: " . length($msg) . " bytes\n" if ($verbose);
	
	my $routes = process_msg($addr_s, $peer_port, $msg);
	warn "processed $routes route entries\n" if ($verbose && $routes >= 0);
	
	# Consider expiring old routes. This is actually never run if we do not receive
	# any RIP broadcasts at all (the recv() is blocking)
	# The (desired) side effect is that if the RIP announcer
	# dies, the entries do not time out.
	if (time() > $next_expire || $next_expire > time() + $expire_interval) {
		$next_expire = time() + $expire_interval;
		expire_routes();
	}
}



( run in 0.483 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )