#!/usr/bin/perl

# THIS IS A HACK!

# Copyright (c) 2005 Nick Johnson
# All rights reserved.
#
# The Spatula Public License:
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted for non-commercial purposes, 
# provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# For (3) and (4) below, "incorporated" means used in whole or part
# in a derived work or used as a dependency of a separate work.
#
# 3. Neither source code or binary form may be incorporated into any
#    work falling under a license which requires the work or any derived
#    work to use a particular license.
# 4. Neither source code or binary form may be incorporated into any
#    work which requires for its operation the installation of other works 
#    which may fall under the restrictions given in (3) above.
# 5. If this work is included or integrated, modified or unmodified,
#    with a derived or separate work with a more restrictive license than 
#    this one, this work must be included as a separate whole in either 
#    source or binary form, according to the provisions in (1) and (2) 
#    above.
#
# 6. This license is subject to periodic updates.  For the latest version
#    of this license, visit http://spatula.net/license.txt
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

for ($bits = 24; $bits >= 8; $bits--) {
    $size = int(2 ** (32 - $bits));
    $sizes{$size} = $bits;
    $bitsize{$bits} = $size;
}


if ($> != 0) {
    die "This script must be run as root";
}

# fetch the latest RIPE NCC data
$allocations = `fetch -o - ftp://ftp.ripe.net/ripe/stats/delegated-ripencc-latest 2>/dev/null`;

# for testing, comment out the above line and uncomment the next line, which should be something
# you fetched as above
#$allocations = `cat vodka`;

@blocks = split(/[\r\n]+/, $allocations);
@interestingBlocks = grep(/\|(UA|PL|RU|RO)\|/, @blocks);

# Greedy algorithm for merging ajacent blocks.
# It does not guarantee the optimal merge, but it does a reasonably good job in O(n) time
# except for the following sort, which is n-log-n or something like that.

@interestingBlocks = sort ipsort @interestingBlocks;

# we are guaranteed by the NIC that blocks won't overlap
# so this range-finding is OK kinds

foreach (@interestingBlocks) {
    ($nic, $cc, $type, $base, $addrs, $date, $status) = split(/\|/, $_);
    next if $status ne 'allocated';
    next if $type ne 'ipv4';
    $mask = $sizes{$addrs};
    $rangeStart = ipToInt($base);
    $rangeEnd = ipToInt($base) + $addrs;
    push (@ranges, [ $rangeStart, $rangeEnd]);
}

# try to merge the current block with the next block, and 
# keep merging in blocks as long as they're adjacent.  Keep
# track of the last block to have a valid size and only merge
# as many adjacent blocks as can be to meet that valid size.
# Whatever's left over will have to stand on its own.

for ($i = 0; $i <= $#ranges; $i++ ) {
    $lastValid = -1;
    $lastStart = $ranges[$i][0];
    $lastEnd = $ranges[$i][1];

    if ($i <= $#ranges - 1) {
        for ($j = $i+1; $j <= $#ranges; $j++) {
            $thisStart = $ranges[$j][0];
            if ($thisStart == $lastEnd) {
                $lastEnd = $ranges[$j][1];
                $size = $lastEnd - $lastStart;
                $lastValid = $j if ($sizes{$size}) > 0;
            } else {
                last;
            }
        }
    }

    if ($lastValid > 0) {
        $lastEnd = $ranges[$lastValid][1];
        $i = $lastValid;  # i will be incremented next time thru the loop
    } else {
        $lastEnd = $ranges[$i][1];
    }
    push(@merged, [ $lastStart, $lastEnd]);
}

# apply the firewall rules 

print scalar(@ranges) . " ranges in, " . scalar(@merged) . " ranges out\n";
#exit;

`/sbin/ipfw del 11`;

foreach (@merged)  {
    $base = $_->[0];
    $size = $_->[1] - $_->[0];
    $bits = $sizes{$size};
    `/sbin/ipfw add 11 deny ip from $base/$bits to me`
}

sub ipsort {
    $a =~ /\|(\d+\.\d+\.\d+\.\d+)\|/;
    my $aip = $1;
    $b =~ /\|(\d+\.\d+\.\d+\.\d+)\|/;
    my $bip = $1;
    return ipToInt($aip) <=> ipToInt($bip);
}

sub ipToInt { 
    my ($ip) = @_;
    @parts = split(/\./, $ip);
    $sum = $parts[0] * 16777216 + $parts[1] * 65536 + $parts[2] * 256 + $parts[3];
    return $sum;
}

sub intToIp {
    my ($int) = @_;
    $part1 = int($int / 16777216);
    $int -= $part1 * 16777216;
    $part2 = int($int / 65536);
    $int -= $part2 * 65536;
    $part3 = int($int / 256);
    $int -= $part3 * 256;
    return "$part1.$part2.$part3.$int";
}
