Tuesday, September 9, 2008

Firehol get-iana scripts

Wow. I just found one of the best scripts out there for cutting down the amount of Iptables commands that Firehol generates using two little Perl scripts.

First off, let me preface this by saying I am not running Debian and getting aggregate-flim setup and running was a little too difficult. But no matter, perl can be used to pull off the same functionality by aggregating the CIDR records. A big thanks to zwitterion.org.

The first script, list-iana-reserved-ranges grabs all of the IANA reserved address ranges and outputs them.

#!/usr/bin/perl -Tw
# [MJS 22 Oct 2001] List IANA Reserved ranges (for firewall purposes)
# [MJS 3 Mar 2008] IANA reformated document to use /8s and not ranges

use strict;
use LWP;

#
# Download Official IANA document
#
my $ua = new LWP::UserAgent;
my $res = $ua->get('http://www.iana.org/assignments/ipv4-address-space');
$res->is_success or die "HTTP request failed: " . $res->message . "\n";

#
# Print all the /8s.
#
print map { "$_\n" }
$res->content =~ m{ ( \d{3} \/ 8 ) .+? (?: UNALLOCATED | RESERVED ) }gx;

# $Id: list-iana-reserved-ranges,v 1.2 2008/05/17 07:00:42 suter Exp $



The second script, aggregate-cidr-addresses, aggregates all the addresses into larger subnets, cutting down on the amount of lines fed into iptables.

#!/usr/bin/perl -Tw
# [MJS 22 Oct 2001] Aggregate CIDR addresses
# [MJS 9 Oct 2007] Overlap idea from Anthony Ledesma at theplanet dot com.

use strict;
use Net::IP;

## Read in all the IP addresses from <>
my @addrs = map { new Net::IP $_ or die "Not an IP: \"$_\"."; }
map { /^\s*(.*?)\s*$/ and $1; } <>;

## Sort the IP addresses
@addrs = sort {
$a->bincomp( 'lt', $b ) ? -1 : ( $a->bincomp( 'gt', $b ) ? 1 : 0 );
} @addrs;

## Handle overlaps
my $count = 0;
my $current = $addrs[0];
foreach my $next ( @addrs[ 1 .. $#addrs ] ) {
my $r = $current->overlaps($next);
if ( $r == $IP_NO_OVERLAP ) {
$current = $next;
$count++;
}
elsif ( $r == $IP_A_IN_B_OVERLAP ) {
$current = $next;
splice( @addrs, $count, 1 );
}
elsif ( $r == $IP_B_IN_A_OVERLAP or $r == $IP_IDENTICAL ) {
splice( @addrs, $count + 1, 1 );
}
else {
die "$0: internal error - overlaps() returned an unexpected value!\n";
}
}

## Keep aggregating until we don't change anything
my $change = 1;
while ($change) {
$change = 0;
my @new_addrs = ();
my $current = $addrs[0];
foreach my $next ( @addrs[ 1 .. $#addrs ] ) {
if ( my $total = $current->aggregate($next) ) {
$current = $total;
$change = 1;
}
else {
push @new_addrs, $current;
$current = $next;
}
}
push @new_addrs, $current;
@addrs = @new_addrs;
}

## Print out the IP addresses
foreach (@addrs) {
print $_->prefix(), "\n";
}

# $Id: aggregate-cidr-addresses,v 1.3 2008/05/17 07:00:42 suter Exp $


To put everything together, you just need to replace your get-iana.sh script with something that uses the perl scripts, like the following:

#!/bin/bash

tempfile="/tmp/iana.$$.$RANDOM"

perl -Tw "/etc/firehol/list-iana-reserved-ranges" | perl -Tw "/etc/firehol/aggregate-cidr-addresses" >"${tempfile}"

echo >&2
echo >&2
echo >&2 "FOUND THE FOLLOWING RESERVED IP RANGES:"
printf "RESERVED_IPS=\""
i=0
for x in `cat ${tempfile}`
do
i=$[i + 1]
printf "${x} "
done
printf "\"\n"

if [ $i -eq 0 ]
then
echo >&2
echo >&2
echo >&2 "Failed to find reserved IPs."
echo >&2 "Possibly the file format has been changed, or I cannot fetch the URL."
echo >&2

rm -f ${tempfile}
exit 1
fi
echo >&2
echo >&2
echo >&2 "Differences between the fetched list and the list installed in"
echo >&2 "/etc/firehol/RESERVED_IPS:"

echo >&2 "# diff /etc/firehol/RESERVED_IPS ${tempfile}"
diff /etc/firehol/RESERVED_IPS ${tempfile}

if [ $? -eq 0 ]
then
echo >&2
echo >&2 "No differences found."
echo >&2

rm -f ${tempfile}
exit 0
fi

echo >&2
echo >&2
echo >&2 "Would you like to save this list to /etc/firehol/RESERVED_IPS"
echo >&2 "so that FireHOL will automatically use it from now on?"
echo >&2
while [ 1 = 1 ]
do
printf >&2 "yes or no > "
read x
case "${x}" in
yes) cp -f /etc/firehol/RESERVED_IPS /etc/firehol/RESERVED_IPS.old 2>/dev/null
cat "${tempfile}" >/etc/firehol/RESERVED_IPS || exit 1
echo >&2 "New RESERVED_IPS written to '/etc/firehol/RESERVED_IPS'."
break
;;

no)
echo >&2 "Saved nothing."
break
;;

*) echo >&2 "Cannot understand '${x}'."
;;
esac
done

rm -f ${tempfile}



There you have it, updated and compact RESERVED_IPS.