You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rpki-home.pl 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. #!/usr/bin/perl
  2. use strict;
  3. use JSON;
  4. use Socket qw(AF_INET AF_INET6 inet_pton inet_ntop);
  5. use Data::Dumper;
  6. use File::Basename;
  7. use File::stat;
  8. use warnings;
  9. $|=1;
  10. my $conf = load_conf(dirname($0) . '/config.json') || die "Failed to load configuration";
  11. $conf->{basedir} = dirname($0);
  12. my ($cmd) = @ARGV;
  13. if ( !defined $cmd ) {
  14. usage();
  15. }
  16. elsif ( $cmd eq 'all' ) {
  17. foreach my $mode ( split(/ /,"rpki routes genbl apply" ) ) {
  18. system("$0 $mode");
  19. }
  20. }
  21. elsif ( $cmd eq 'rpki' ) {
  22. getfile("ripe_export");
  23. }
  24. elsif ( $cmd eq 'routes' ) {
  25. getfile("ripe_ris_dump_v4");
  26. getfile("ripe_ris_dump_v6");
  27. }
  28. elsif ( $cmd eq 'genbl' ) {
  29. print "Loading ROAs...";
  30. my $data = loadfile("ripe_export");
  31. my $valid = { };
  32. foreach my $roa ( @{$data->{roas}} ) {
  33. $roa->{asn} =~ s/^AS//;
  34. $roa->{asn} += 0;
  35. my ($ip,$minlen) = split(/\//, $roa->{prefix});
  36. $valid->{$roa->{prefix}}{$roa->{asn}} = exists $roa->{maxLength} ? $roa->{maxLength} : $minlen;
  37. }
  38. print "\n";
  39. #print Dumper($valid);
  40. my $route = { };
  41. print "Loading routing dump IPv4...\r";
  42. $data = loadfile("ripe_ris_dump_v4");
  43. my ($ris_peers,$routes) = ris_peers($data);
  44. my $cnt=0;
  45. open(DEBUG,">$conf->{blacklist}{debug}");
  46. print DEBUG sprintf("%s\t%s\t%s\t%s\t%s\t%s\n",
  47. "prefix", "origin ASN", "roa state", "RIS relevance", "ROA", "error" );
  48. open(BL,">$conf->{blacklist}{cache4}");
  49. foreach my $l ( split(/\n/, $data) ) {
  50. if ( $l =~ /^(\d+)\t+(\d+\.\d+\.\d+\.\d+\/\d+)\t+(\d+)/ ) {
  51. my $asn = $1;
  52. my $prefix = $2;
  53. my $peers = $3;
  54. my $ret = check_route($valid, $asn, $prefix, $peers/$ris_peers);
  55. print DEBUG sprintf("%s\t%s\t%s\t%s\t%s\t%s\n",
  56. $ret->{prefix},
  57. $ret->{asn},
  58. $ret->{state},
  59. $ret->{relevance},
  60. exists $ret->{roa} ? ( defined $ret->{roa} ? $ret->{roa} : "n/a" ) : 'error',
  61. exists $ret->{error} ? $ret->{error} : '-' );
  62. if ( exists $conf->{blacklist}{filter}{$ret->{state}} && $ret->{relevance} > $conf->{blacklist}{filter}{$ret->{state}} ) {
  63. print BL sprintf("%s #\t%s\t%s\t%s\t%s\t%s\n",
  64. $ret->{prefix},
  65. $ret->{asn},
  66. $ret->{state},
  67. $ret->{relevance},
  68. exists $ret->{roa} ? ( defined $ret->{roa} ? $ret->{roa} : "n/a" ) : 'error',
  69. exists $ret->{error} ? $ret->{error} : '-' );
  70. }
  71. if ( ! ( $cnt % 1000 ) ) {
  72. printf("Loading routing dump IPv4... %0.2f%%\r", $cnt/$routes*100);
  73. }
  74. $cnt++;
  75. }
  76. }
  77. close(BL);
  78. close(DEBUG);
  79. print "Loading routing dump IPv4... 100% \n";
  80. print "Loading routing dump IPv6...\r";
  81. $data = loadfile("ripe_ris_dump_v6");
  82. ($ris_peers,$routes) = ris_peers($data);
  83. $cnt=0;
  84. open(DEBUG,">>$conf->{blacklist}{debug}");
  85. open(BL,">$conf->{blacklist}{cache6}");
  86. foreach my $l ( split(/\n/, $data) ) {
  87. if ( $l =~ /^(\d+)\t+([0-9a-f:]+\/\d+)\t+(\d+)/ ) {
  88. my $asn = $1;
  89. my $prefix = $2;
  90. my $peers = $3;
  91. my $ret = check_route($valid, $asn, $prefix, $peers/$ris_peers);
  92. print DEBUG sprintf("%s\t%s\t%s\t%s\t%s\t%s\n",
  93. $ret->{prefix},
  94. $ret->{asn},
  95. $ret->{state},
  96. $ret->{relevance},
  97. exists $ret->{roa} ? ( defined $ret->{roa} ? $ret->{roa} : "n/a" ) : 'error',
  98. exists $ret->{error} ? $ret->{error} : '-' );
  99. if ( exists $conf->{blacklist}{filter}{$ret->{state}} && $ret->{relevance} > $conf->{blacklist}{filter}{$ret->{state}} ) {
  100. print BL sprintf("%s #\t%s\t%s\t%s\t%s\t%s\n",
  101. $ret->{prefix},
  102. $ret->{asn},
  103. $ret->{state},
  104. $ret->{relevance},
  105. exists $ret->{roa} ? ( defined $ret->{roa} ? $ret->{roa} : "n/a" ) : 'error',
  106. exists $ret->{error} ? $ret->{error} : '-' );
  107. }
  108. if ( ! ( $cnt % 1000 ) ) {
  109. printf("Loading routing dump IPv6... %0.2f%%\r", $cnt/$routes*100);
  110. }
  111. $cnt++;
  112. }
  113. }
  114. close(BL);
  115. close(DEBUG);
  116. $data = undef;
  117. print "Loading routing dump IPv6... 100% \n";
  118. }
  119. elsif ( $cmd eq 'apply' ) {
  120. mycmd("ipset destroy rpki4-tmp -exist");
  121. mycmd("ipset create rpki4-tmp hash:net family inet hashsize 32768 maxelem 131072");
  122. mycmd("ipset create rpki4 -exist hash:net family inet hashsize 32768 maxelem 131072");
  123. mycmd("ipset destroy rpki6-tmp -exist");
  124. mycmd("ipset create rpki6-tmp hash:net family inet6 hashsize 32768 maxelem 131072");
  125. mycmd("ipset create rpki6 -exist hash:net family inet6 hashsize 32768 maxelem 131072");
  126. open(IPS,"| ipset restore");
  127. open(BL,"$conf->{blacklist}{cache4}");
  128. while(<BL>) {
  129. chomp;
  130. s/ .*//;
  131. print IPS "add rpki4-tmp $_\n";
  132. }
  133. close(BL);
  134. open(BL,"$conf->{blacklist}{cache6}");
  135. while(<BL>) {
  136. chomp;
  137. s/ .*//;
  138. print IPS "add rpki6-tmp $_\n";
  139. }
  140. close(BL);
  141. close(IPS);
  142. mycmd("ipset swap rpki4 rpki4-tmp");
  143. mycmd("ipset swap rpki6 rpki6-tmp");
  144. mycmd("ipset destroy rpki4-tmp -exist");
  145. mycmd("ipset destroy rpki6-tmp -exist");
  146. }
  147. sub ris_peers {
  148. my ($data) = @_;
  149. my $max = 1;
  150. my $cnt = 0;
  151. foreach my $l ( split(/\n/, $data) ) {
  152. if ( $l =~ /^\d+\t+[^\t]+\t+(\d+)/ ) {
  153. my $peers = $1;
  154. $max = $peers if $peers > $max;
  155. $cnt++;
  156. }
  157. }
  158. return ($max,$cnt);
  159. }
  160. sub hexdump {
  161. my($i)=@_;
  162. print unpack("H*",$i);
  163. print "\n";
  164. }
  165. sub check_route {
  166. my($valid, $asn, $prefix, $peers) = @_;
  167. my($ip,$maxlen) = split(/\//,$prefix);
  168. my $msg;
  169. my $state = "notfound";
  170. for(my $len = $maxlen; $len>= 0; $len--) {
  171. my $net = prefixmask($ip, $len);
  172. my $print = "$prefix:AS$asn (RIS: $peers) matching to $net:";
  173. # found matching network
  174. if ( exists $valid->{$net} ) {
  175. # found matching asn
  176. if ( exists $valid->{$net}{$asn} ) {
  177. # found matching netmask
  178. if ( $len <= $valid->{$net}{$asn} ) {
  179. #print "$print match $prefix => $valid->{$net}{$asn}\n";
  180. $msg = "$net-$valid->{$net}{$asn}:$asn";
  181. $state="found";
  182. last;
  183. }
  184. else {
  185. #print "$print invalid subnet (too small)\n";
  186. $msg = "$net-$valid->{$net}{$asn}:$asn";
  187. $state="toosmall";
  188. last;
  189. }
  190. }
  191. else {
  192. my @asn;
  193. foreach my $test ( keys %{$valid->{$net}} ) {
  194. push @asn, $test if $valid->{$net}{$test} >= $len;
  195. }
  196. #print "$print asn not found (should be " . join(',',@asn) . ")\n";
  197. $msg = "(should be " . join(',',@asn) . ")";
  198. $state="aswrong";
  199. }
  200. }
  201. }
  202. my $ret = {
  203. prefix => $prefix,
  204. asn => $asn,
  205. relevance => sprintf("%0.3f",$peers),
  206. state => $state,
  207. };
  208. if ( $state eq "found" ) {
  209. $ret->{roa} = $msg;
  210. }
  211. elsif ( $state eq "aswrong" ) {
  212. $ret->{error} = $msg;
  213. }
  214. elsif ( $state eq "toosmall" ) {
  215. $ret->{error} = "invalid length for ROA $msg";
  216. }
  217. elsif ( $state eq "notfound" ) {
  218. $ret->{roa} = undef;
  219. }
  220. return $ret;
  221. }
  222. sub prefixmask {
  223. my($prefix, $len) = @_;
  224. $prefix =~ s/\/\d+//;
  225. if ( $prefix =~ /:/ ) {
  226. my @int = unpack("NNNN",inet_pton(AF_INET6, $prefix));
  227. my @size;
  228. if ( $len > 96 ) {
  229. @size = ( 32, 32, 32, $len - 96 );
  230. }
  231. elsif ( $len > 64 ) {
  232. @size = ( 32, 32, $len - 64, 0 );
  233. }
  234. elsif ( $len > 32 ) {
  235. @size = ( 32, $len - 32, 0, 0 );
  236. }
  237. else {
  238. @size = ( $len, 0, 0, 0 );
  239. }
  240. for(my $i=0; $i<4; $i++) {
  241. $int[$i] -= $int[$i] % 2**(32-$size[$i]);
  242. }
  243. return sprintf("%s/%s", inet_ntop(AF_INET6, pack("NNNN", @int)), $len);
  244. }
  245. else {
  246. my $int = unpack("N",inet_pton(AF_INET, $prefix));
  247. my $size = 2**(32-$len);
  248. $int -= $int % $size;
  249. return sprintf("%s/%s", inet_ntop(AF_INET, pack("N", $int)), $len);
  250. }
  251. }
  252. sub loadfile {
  253. my($name) = @_;
  254. my $file = "$conf->{basedir}/$conf->{$name}{cache}";
  255. if ( !-r $file ) {
  256. print STDERR "ERROR: File $file not found\n";
  257. exit 1;
  258. }
  259. if ( $file =~ /\.gz$/ ) {
  260. open(F,"gzip -cd $file |");
  261. }
  262. else {
  263. open(F,$file);
  264. }
  265. my $d = '';
  266. while(<F>) { $d .= $_; }
  267. close(F);
  268. return from_json($d) if $file =~ /\.json/;
  269. return $d;
  270. }
  271. sub getfile {
  272. my($name) = @_;
  273. my $file = "$conf->{basedir}/$conf->{$name}{cache}";
  274. print "$conf->{$name}{descr}\n";
  275. my $dl=0;
  276. if ( -r $file ) {
  277. print " - file found\n";
  278. if ( filetime($file) > $conf->{$name}{expires} ) {
  279. print " - file expired\n";
  280. $dl=1;
  281. }
  282. else {
  283. print " - file still valid\n";
  284. }
  285. }
  286. else {
  287. print " - file not found\n";
  288. $dl=1;
  289. }
  290. if ( $dl ) {
  291. print " - downloading $conf->{$name}{url}\n";
  292. system("wget --header=\"accept: application/json\" -q -T 10 -O $file \"$conf->{$name}{url}\"");
  293. }
  294. }
  295. sub mycmd {
  296. my($cmd) = @_;
  297. print "CMD: $cmd\n";
  298. system($cmd);
  299. }
  300. sub filetime {
  301. my($f) = @_;
  302. my $st = stat($f);
  303. return time() - $st->mtime; # mtime
  304. }
  305. sub usage {
  306. print "Usage: $0 <rpki|routes|genbl|apply|all>\n";
  307. exit 1;
  308. }
  309. sub load_conf {
  310. my($file) = @_;
  311. my $f = '';
  312. open(F,$file);
  313. while(<F>) { $f.=$_; }
  314. close(F);
  315. return from_json($f);
  316. }