123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- #!/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);
- }
|