AMPR-Rip44

 view release on metacpan or  search on metacpan

bin/rip44d  view on Meta::CPAN

# (public domain, no warranty of any sort).
# http://evvk.com/evvktvh.html#english
#
#
# Version history:
#
# 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";
		}
	}
}

# expire old routes

sub expire_routes()
{
	warn "expiring old routes\n" if ($verbose);
	
	my $exp_t = time() - $route_ttl;

bin/rip44d  view on Meta::CPAN

	if ($?) {
		warn "route add failed: '$cmd': $out\n";
	}
}

# process a RIPv2 password authentication entry

sub process_rip_auth_entry($)
{
	my($entry) = @_;
	
	my $e_af = unpack('n', substr($entry, 0, 2));
	if ($e_af != 0xFFFF) {
		warn "RIPv2 first message does not contain auth password: ignoring\n" if ($verbose);
		return 0;
	}
	
	my $e_type = unpack('n', substr($entry, 2, 2));
	if ($e_type != RIP_AUTH_PASSWD) {
		warn "ignoring unsupported rip auth type $e_type\n" if ($verbose);
		return 0;
	}
	
	my $e_passwd = substr($entry, 4, 16);
	$e_passwd =~ s/\0*$//; # it's null-padded in the end
	
	if (!defined $rip_passwd) {
		warn "RIPv2 packet contains password $e_passwd but we require none\n" if ($verbose);
		return 0;
	}
	
	if ($e_passwd ne $rip_passwd) {
		warn "RIPv2 invalid password $e_passwd\n" if ($verbose);
		return 0;
	}
	
	return 1;
}

# validate a route entry, make sure we can rather safely
# insert it in the routing table

sub validate_route($$$$$)
{
	my($e_net_i, $e_net_s, $e_netmask, $e_netmask_s, $e_nexthop_s) = @_;
	
	# netmask is correct and not too wide
	my $prefix_len = mask2prefix($e_netmask);
	if ($prefix_len < 0) {
		warn "invalid netmask: $e_netmask_s\n" if ($verbose);
		return (0, 'invalid netmask');
	}
	
	if ($prefix_len < $minimum_prefix_len) {
		warn "$e_net_s/$e_netmask_s => $e_nexthop_s blocked, prefix too short\n";
		return (0, 'prefix length too short');
	}
	
	# the network-netmask pair makes sense: network & netmask == network
	if (($e_net_i & $e_netmask) != $e_net_i) {
		#print "e_net '$e_net_i' e_netmask '$e_netmask' ANDs to " . ($e_net_i & $e_netmask) . "\n";
		warn "$e_net_s/$e_netmask_s => $e_nexthop_s blocked, subnet-netmask pair does not make sense\n" if ($verbose);
		return (0, 'invalid subnet-netmask pair');
	}
	
	# network is in 44/8
	if ($e_net_s !~ /$net_44_regexp/) {
		warn "$e_net_s/$e_netmask_s => $e_nexthop_s blocked, non-amprnet address\n" if ($verbose);
		return (0, 'net not in 44/8');
	}
	
	# nexthop address is not in 44/8
	if ($e_nexthop_s =~ /$net_44_regexp/) {
		warn "$e_net_s/$e_netmask_s => $e_nexthop_s blocked, nexthop is within amprnet\n" if ($verbose);
		return (0, 'nexthop is in 44/8');
	}
	
	# nexthop address does not point to self
	if (defined $my_addresses{$e_nexthop_s}) {
		warn "$e_net_s/$e_netmask_s => $e_nexthop_s blocked, local gw\n" if ($verbose);
		return (0, 'local gw');
	}
	
	return (1, 'ok');
}

# process a RIPv2 route entry

sub process_rip_route_entry($)
{
	my($entry) = @_;
	
	my $e_af = unpack('n', substr($entry, 0, 2));
	my $e_rtag = unpack('n', substr($entry, 2, 2));

	if ($e_af == 0xFFFF) {
		process_rip_auth_entry($entry);
		return -1;
	}
	
	if ($e_af != AF_INET) {
		warn "$me: RIPv2 entry has unsupported AF $e_af\n";
		return 0;
	}
	
	my $e_net = substr($entry, 4, 4);
	my $e_net_i = unpack('N', $e_net);
	my $e_netmask = substr($entry, 8, 4);
	my $e_netmask_i = unpack('N', $e_netmask);
	my $e_nexthop = substr($entry, 12, 4);
	my $e_metric = unpack('N', substr($entry, 16, 4));
	my $e_net_s = inet_ntoa($e_net);
	my $e_netmask_s = inet_ntoa($e_netmask);
	my $e_nexthop_s = inet_ntoa($e_nexthop);
	
	# Validate the route
	my($result, $reason) = validate_route($e_net_i, $e_net_s, $e_netmask_i, $e_netmask_s, $e_nexthop_s);
	if (!$result) {
		warn "entry ignored ($reason): af $e_af rtag $e_rtag $e_net_s/$e_netmask_s via $e_nexthop_s metric $e_metric\n" if ($verbose);
		return 0;
	}



( run in 0.544 second using v1.01-cache-2.11-cpan-df04353d9ac )