Browse Source

initial

master
root 1 year ago
parent
commit
9c58724128
2 changed files with 414 additions and 0 deletions
  1. 35
    0
      config.json
  2. 379
    0
      rpki-home.pl

+ 35
- 0
config.json View File

@@ -0,0 +1,35 @@
{
"ripe_export": {
"descr": "RPKI Validator (RIPE NCC)",
"_url": "http://localcert.ripe.net:8088/export.json",
"url": "https://rpki-validator.ripe.net/api/export.json",
"expires": 3600,
"cache": "ripe_export.json"
},
"ripe_ris_dump_v4": {
"descr": "RIS routes IPv4 (RIPE NCC)",
"url": "http://ris.ripe.net/dumps/riswhoisdump.IPv4.gz",
"expires": 28800,
"cache": "ris_dump_v4.txt.gz"
},
"ripe_ris_dump_v6": {
"descr": "RIS routes IPv6 (RIPE NCC)",
"url": "http://ris.ripe.net/dumps/riswhoisdump.IPv6.gz",
"expires": 28800,
"cache": "ris_dump_v6.txt.gz"
},
"blacklist": {
"cache4": "blacklist_v4.txt",
"cache6": "blacklist_v6.txt",
"debug": "debug.txt",
"filter": {
"aswrong": 0.5,
"toosmall": 0.5
}
},
"ipt": {
"chain4": "rpki",
"chain6": "rpki"
}

}

+ 379
- 0
rpki-home.pl View File

@@ -0,0 +1,379 @@
#!/usr/bin/perl


use strict;
use JSON;
use Socket qw(AF_INET AF_INET6 inet_pton inet_ntop);
use Data::Dumper;
use File::Basename;
use File::stat;
use warnings;

$|=1;

my $conf = load_conf(dirname($0) . '/config.json') || die "Failed to load configuration";
$conf->{basedir} = dirname($0);

my ($cmd) = @ARGV;

if ( !defined $cmd ) {
usage();
}
elsif ( $cmd eq 'all' ) {
foreach my $mode ( split(/ /,"rpki routes genbl apply" ) ) {
system("$0 $mode");
}
}
elsif ( $cmd eq 'rpki' ) {
getfile("ripe_export");
}
elsif ( $cmd eq 'routes' ) {
getfile("ripe_ris_dump_v4");
getfile("ripe_ris_dump_v6");
}
elsif ( $cmd eq 'genbl' ) {
print "Loading ROAs...";
my $data = loadfile("ripe_export");
my $valid = { };
foreach my $roa ( @{$data->{roas}} ) {
$roa->{asn} =~ s/^AS//;
$roa->{asn} += 0;

my ($ip,$minlen) = split(/\//, $roa->{prefix});

$valid->{$roa->{prefix}}{$roa->{asn}} = exists $roa->{maxLength} ? $roa->{maxLength} : $minlen;
}

print "\n";
#print Dumper($valid);

my $route = { };

print "Loading routing dump IPv4...\r";

$data = loadfile("ripe_ris_dump_v4");
my ($ris_peers,$routes) = ris_peers($data);
my $cnt=0;

open(DEBUG,">$conf->{blacklist}{debug}");
print DEBUG sprintf("%s\t%s\t%s\t%s\t%s\t%s\n",
"prefix", "origin ASN", "roa state", "RIS relevance", "ROA", "error" );

open(BL,">$conf->{blacklist}{cache4}");

foreach my $l ( split(/\n/, $data) ) {
if ( $l =~ /^(\d+)\t+(\d+\.\d+\.\d+\.\d+\/\d+)\t+(\d+)/ ) {
my $asn = $1;
my $prefix = $2;
my $peers = $3;
my $ret = check_route($valid, $asn, $prefix, $peers/$ris_peers);
print DEBUG sprintf("%s\t%s\t%s\t%s\t%s\t%s\n",
$ret->{prefix},
$ret->{asn},
$ret->{state},
$ret->{relevance},
exists $ret->{roa} ? ( defined $ret->{roa} ? $ret->{roa} : "n/a" ) : 'error',
exists $ret->{error} ? $ret->{error} : '-' );

if ( exists $conf->{blacklist}{filter}{$ret->{state}} && $ret->{relevance} > $conf->{blacklist}{filter}{$ret->{state}} ) {
print BL sprintf("%s #\t%s\t%s\t%s\t%s\t%s\n",
$ret->{prefix},
$ret->{asn},
$ret->{state},
$ret->{relevance},
exists $ret->{roa} ? ( defined $ret->{roa} ? $ret->{roa} : "n/a" ) : 'error',
exists $ret->{error} ? $ret->{error} : '-' );
}

if ( ! ( $cnt % 1000 ) ) {
printf("Loading routing dump IPv4... %0.2f%%\r", $cnt/$routes*100);
}
$cnt++;
}
}

close(BL);
close(DEBUG);

print "Loading routing dump IPv4... 100% \n";

print "Loading routing dump IPv6...\r";
$data = loadfile("ripe_ris_dump_v6");
($ris_peers,$routes) = ris_peers($data);
$cnt=0;

open(DEBUG,">>$conf->{blacklist}{debug}");
open(BL,">$conf->{blacklist}{cache6}");

foreach my $l ( split(/\n/, $data) ) {
if ( $l =~ /^(\d+)\t+([0-9a-f:]+\/\d+)\t+(\d+)/ ) {
my $asn = $1;
my $prefix = $2;
my $peers = $3;
my $ret = check_route($valid, $asn, $prefix, $peers/$ris_peers);
print DEBUG sprintf("%s\t%s\t%s\t%s\t%s\t%s\n",
$ret->{prefix},
$ret->{asn},
$ret->{state},
$ret->{relevance},
exists $ret->{roa} ? ( defined $ret->{roa} ? $ret->{roa} : "n/a" ) : 'error',
exists $ret->{error} ? $ret->{error} : '-' );

if ( exists $conf->{blacklist}{filter}{$ret->{state}} && $ret->{relevance} > $conf->{blacklist}{filter}{$ret->{state}} ) {
print BL sprintf("%s #\t%s\t%s\t%s\t%s\t%s\n",
$ret->{prefix},
$ret->{asn},
$ret->{state},
$ret->{relevance},
exists $ret->{roa} ? ( defined $ret->{roa} ? $ret->{roa} : "n/a" ) : 'error',
exists $ret->{error} ? $ret->{error} : '-' );
}

if ( ! ( $cnt % 1000 ) ) {
printf("Loading routing dump IPv6... %0.2f%%\r", $cnt/$routes*100);
}
$cnt++;
}
}

close(BL);
close(DEBUG);

$data = undef;
print "Loading routing dump IPv6... 100% \n";
}
elsif ( $cmd eq 'apply' ) {
mycmd("ipset destroy rpki4-tmp -exist");
mycmd("ipset create rpki4-tmp hash:net family inet hashsize 32768 maxelem 131072");
mycmd("ipset create rpki4 -exist hash:net family inet hashsize 32768 maxelem 131072");
mycmd("ipset destroy rpki6-tmp -exist");
mycmd("ipset create rpki6-tmp hash:net family inet6 hashsize 32768 maxelem 131072");
mycmd("ipset create rpki6 -exist hash:net family inet6 hashsize 32768 maxelem 131072");

open(IPS,"| ipset restore");
open(BL,"$conf->{blacklist}{cache4}");
while(<BL>) {
chomp;
s/ .*//;
print IPS "add rpki4-tmp $_\n";
}
close(BL);
open(BL,"$conf->{blacklist}{cache6}");
while(<BL>) {
chomp;
s/ .*//;
print IPS "add rpki6-tmp $_\n";
}
close(BL);
close(IPS);

mycmd("ipset swap rpki4 rpki4-tmp");
mycmd("ipset swap rpki6 rpki6-tmp");
mycmd("ipset destroy rpki4-tmp -exist");
mycmd("ipset destroy rpki6-tmp -exist");
}

sub ris_peers {
my ($data) = @_;
my $max = 1;
my $cnt = 0;
foreach my $l ( split(/\n/, $data) ) {
if ( $l =~ /^\d+\t+[^\t]+\t+(\d+)/ ) {
my $peers = $1;
$max = $peers if $peers > $max;
$cnt++;
}
}
return ($max,$cnt);
}

sub hexdump {
my($i)=@_;
print unpack("H*",$i);
print "\n";
}

sub check_route {
my($valid, $asn, $prefix, $peers) = @_;

my($ip,$maxlen) = split(/\//,$prefix);

my $msg;
my $state = "notfound";

for(my $len = $maxlen; $len>= 0; $len--) {
my $net = prefixmask($ip, $len);

my $print = "$prefix:AS$asn (RIS: $peers) matching to $net:";

# found matching network
if ( exists $valid->{$net} ) {
# found matching asn
if ( exists $valid->{$net}{$asn} ) {
# found matching netmask
if ( $len <= $valid->{$net}{$asn} ) {
#print "$print match $prefix => $valid->{$net}{$asn}\n";
$msg = "$net-$valid->{$net}{$asn}:$asn";
$state="found";
last;
}
else {
#print "$print invalid subnet (too small)\n";
$msg = "$net-$valid->{$net}{$asn}:$asn";
$state="toosmall";
last;
}
}
else {
my @asn;
foreach my $test ( keys %{$valid->{$net}} ) {
push @asn, $test if $valid->{$net}{$test} >= $len;
}
#print "$print asn not found (should be " . join(',',@asn) . ")\n";
$msg = "(should be " . join(',',@asn) . ")";
$state="aswrong";
}
}
}

my $ret = {
prefix => $prefix,
asn => $asn,
relevance => sprintf("%0.3f",$peers),
state => $state,
};

if ( $state eq "found" ) {
$ret->{roa} = $msg;
}
elsif ( $state eq "aswrong" ) {
$ret->{error} = $msg;
}
elsif ( $state eq "toosmall" ) {
$ret->{error} = "invalid length for ROA $msg";
}
elsif ( $state eq "notfound" ) {
$ret->{roa} = undef;
}
return $ret;
}

sub prefixmask {
my($prefix, $len) = @_;

$prefix =~ s/\/\d+//;

if ( $prefix =~ /:/ ) {
my @int = unpack("NNNN",inet_pton(AF_INET6, $prefix));
my @size;
if ( $len > 96 ) {
@size = ( 32, 32, 32, $len - 96 );
}
elsif ( $len > 64 ) {
@size = ( 32, 32, $len - 64, 0 );
}
elsif ( $len > 32 ) {
@size = ( 32, $len - 32, 0, 0 );
}
else {
@size = ( $len, 0, 0, 0 );
}

for(my $i=0; $i<4; $i++) {
$int[$i] -= $int[$i] % 2**(32-$size[$i]);
}

return sprintf("%s/%s", inet_ntop(AF_INET6, pack("NNNN", @int)), $len);

}
else {
my $int = unpack("N",inet_pton(AF_INET, $prefix));
my $size = 2**(32-$len);
$int -= $int % $size;
return sprintf("%s/%s", inet_ntop(AF_INET, pack("N", $int)), $len);
}
}

sub loadfile {
my($name) = @_;
my $file = "$conf->{basedir}/$conf->{$name}{cache}";

if ( !-r $file ) {
print STDERR "ERROR: File $file not found\n";
exit 1;
}

if ( $file =~ /\.gz$/ ) {
open(F,"gzip -cd $file |");
}
else {
open(F,$file);
}

my $d = '';
while(<F>) { $d .= $_; }
close(F);

return from_json($d) if $file =~ /\.json/;
return $d;
}

sub getfile {
my($name) = @_;
my $file = "$conf->{basedir}/$conf->{$name}{cache}";

print "$conf->{$name}{descr}\n";

my $dl=0;

if ( -r $file ) {
print " - file found\n";
if ( filetime($file) > $conf->{$name}{expires} ) {
print " - file expired\n";
$dl=1;
}
else {
print " - file still valid\n";
}
}
else {
print " - file not found\n";
$dl=1;
}

if ( $dl ) {
print " - downloading $conf->{$name}{url}\n";
system("wget --header=\"accept: application/json\" -q -T 10 -O $file \"$conf->{$name}{url}\"");
}

}

sub mycmd {
my($cmd) = @_;

print "CMD: $cmd\n";
system($cmd);
}

sub filetime {
my($f) = @_;
my $st = stat($f);
return time() - $st->mtime; # mtime
}

sub usage {
print "Usage: $0 <rpki|routes|genbl|apply|all>\n";
exit 1;
}

sub load_conf {
my($file) = @_;

my $f = '';

open(F,$file);
while(<F>) { $f.=$_; }
close(F);

return from_json($f);
}

Loading…
Cancel
Save