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.

asn.pl 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. #!/usr/bin/env perl
  2. use warnings;
  3. use strict;
  4. use Pod::Usage;
  5. use Getopt::Long;
  6. use File::Fetch;
  7. use LWP::Simple;
  8. use PerlIO::gzip;
  9. use File::Basename;
  10. use Net::MRT;
  11. use URI;
  12. use Data::Dumper;
  13. $LWP::Simple::ua->show_progress(1);
  14. $Net::MRT::USE_RFC4760 = -1;
  15. my %config = (
  16. asn_sources => [
  17. 'ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest',
  18. 'ftp://ftp.ripe.net/ripe/stats/delegated-ripencc-latest',
  19. 'ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest',
  20. 'ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest',
  21. 'ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest'
  22. ],
  23. bgp_sources => ['http://data.ris.ripe.net/rrc00/latest-bview.gz']
  24. );
  25. my $download_asn = 0;
  26. my $download_bgp = 0;
  27. my $download_target = "./";
  28. my $help = 0;
  29. my $man = 0;
  30. my $v4 = 1;
  31. my $v6 = 1;
  32. my $parse = 1;
  33. my $v4_zone = "asn.rspamd.com";
  34. my $v6_zone = "asn6.rspamd.com";
  35. my $v4_file = "asn.zone";
  36. my $v6_file = "asn6.zone";
  37. my $ns_servers = [ "asn-ns.rspamd.com", "asn-ns2.rspamd.com" ];
  38. GetOptions(
  39. "download-asn" => \$download_asn,
  40. "download-bgp" => \$download_bgp,
  41. "4!" => \$v4,
  42. "6!" => \$v6,
  43. "parse!" => \$parse,
  44. "target=s" => \$download_target,
  45. "zone-v4=s" => \$v4_zone,
  46. "zone-v6=s" => \$v6_zone,
  47. "file-v4=s" => \$v4_file,
  48. "file-v6=s" => \$v6_file,
  49. "ns-server=s@" => \$ns_servers,
  50. "help|?" => \$help,
  51. "man" => \$man
  52. ) or pod2usage(2);
  53. pod2usage(1) if $help;
  54. pod2usage( -exitval => 0, -verbose => 2 ) if $man;
  55. sub download_file {
  56. my ($u) = @_;
  57. print "Fetching $u\n";
  58. my $ff = File::Fetch->new( uri => $u );
  59. my $where = $ff->fetch( to => $download_target ) or die $ff->error;
  60. return $where;
  61. }
  62. if ($download_asn) {
  63. foreach my $u ( @{ $config{'asn_sources'} } ) {
  64. download_file($u);
  65. }
  66. }
  67. if ($download_bgp) {
  68. foreach my $u ( @{ $config{'bgp_sources'} } ) {
  69. download_file($u);
  70. }
  71. }
  72. if ( !$parse ) {
  73. exit 0;
  74. }
  75. my $v4_fh;
  76. my $v6_fh;
  77. if ($v4) {
  78. open( $v4_fh, ">", $v4_file ) or die "Cannot open $v4_file for writing: $!";
  79. print $v4_fh "\$SOA 43200 $ns_servers->[0] support.rspamd.com 0 600 300 86400 300\n";
  80. foreach my $ns ( @{$ns_servers} ) {
  81. print $v4_fh "\$NS 43200 $ns\n";
  82. }
  83. }
  84. if ($v6) {
  85. open( $v6_fh, ">", $v6_file ) or die "Cannot open $v6_file for writing: $!";
  86. print $v6_fh "\$SOA 43200 $ns_servers->[0] support.rspamd.com 0 600 300 86400 300\n";
  87. foreach my $ns ( @{$ns_servers} ) {
  88. print $v6_fh "\$NS 43200 $ns\n";
  89. }
  90. }
  91. # Now load BGP data
  92. my $networks = {};
  93. foreach my $u ( @{ $config{'bgp_sources'} } ) {
  94. my $parsed = URI->new($u);
  95. my $fname = $download_target . '/' . basename( $parsed->path );
  96. open( my $fh, "<:gzip", $fname )
  97. or die "Cannot open $fname: $!";
  98. while ( my $dd = eval { Net::MRT::mrt_read_next($fh) } ) {
  99. if ( $dd->{'prefix'} && $dd->{'bits'} ) {
  100. next if $dd->{'subtype'} == 2 and !$v4;
  101. next if $dd->{'subtype'} == 4 and !$v6;
  102. my $entry = $dd->{'entries'}->[0];
  103. my $net = $dd->{'prefix'} . '/' . $dd->{'bits'};
  104. if ( $entry && $entry->{'AS_PATH'} ) {
  105. my $as = $entry->{'AS_PATH'}->[-1];
  106. if ( ref($as) eq "ARRAY" ) {
  107. $as = @{$as}[0];
  108. }
  109. if ( !$networks->{$as} ) {
  110. if ( $dd->{'subtype'} == 2 ) {
  111. $networks->{$as} = { nets_v4 => [$net], nets_v6 => [] };
  112. }
  113. else {
  114. $networks->{$as} = { nets_v6 => [$net], nets_v4 => [] };
  115. }
  116. }
  117. else {
  118. if ( $dd->{'subtype'} == 2 ) {
  119. push @{ $networks->{$as}->{'nets_v4'} }, $net;
  120. }
  121. else {
  122. push @{ $networks->{$as}->{'nets_v6'} }, $net;
  123. }
  124. }
  125. }
  126. }
  127. }
  128. }
  129. # Now roughly detect countries
  130. foreach my $u ( @{ $config{'asn_sources'} } ) {
  131. my $parsed = URI->new($u);
  132. my $fname = $download_target . '/' . basename( $parsed->path );
  133. open( my $fh, "<", $fname ) or die "Cannot open $fname: $!";
  134. while (<$fh>) {
  135. next if /^\#/;
  136. chomp;
  137. my @elts = split /\|/;
  138. if ( $elts[2] eq 'asn' && $elts[3] ne '*' ) {
  139. my $as_start = int( $elts[3] );
  140. my $as_end = $as_start + int( $elts[4] );
  141. for ( my $as = $as_start ; $as < $as_end ; $as++ ) {
  142. my $real_as = $as;
  143. if ( ref($as) eq "ARRAY" ) {
  144. $real_as = @{$as}[0];
  145. }
  146. if ( $networks->{"$real_as"} ) {
  147. $networks->{"$real_as"}->{'country'} = $elts[1];
  148. $networks->{"$real_as"}->{'rir'} = $elts[0];
  149. }
  150. }
  151. }
  152. }
  153. }
  154. while ( my ( $k, $v ) = each( %{$networks} ) ) {
  155. if ($v4) {
  156. foreach my $n ( @{ $v->{'nets_v4'} } ) {
  157. # "15169 | 8.8.8.0/24 | US | arin |" for 8.8.8.8
  158. if ( $v->{'country'} ) {
  159. printf $v4_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, $v->{'country'}, $v->{'rir'};
  160. }
  161. else {
  162. printf $v4_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, 'UN', 'UN';
  163. }
  164. }
  165. }
  166. if ($v6) {
  167. foreach my $n ( @{ $v->{'nets_v6'} } ) {
  168. # "15169 | 8.8.8.0/24 | US | arin |" for 8.8.8.8
  169. if ( $v->{'country'} ) {
  170. printf $v6_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, $v->{'country'}, $v->{'rir'};
  171. }
  172. else {
  173. printf $v6_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, 'UN', 'UN';
  174. }
  175. }
  176. }
  177. }
  178. __END__
  179. =head1 NAME
  180. asn.pl - download and parse ASN data for Rspamd
  181. =head1 SYNOPSIS
  182. asn.pl [options]
  183. Options:
  184. --download-asn Download ASN data from RIR
  185. --download-bgp Download GeoIP data from Maxmind
  186. --target Where to download files (default: current dir)
  187. --zone-v4 IPv4 zone (default: asn.rspamd.com)
  188. --zone-v6 IPv6 zone (default: asn6.rspamd.com)
  189. --file-v4 IPv4 zone file (default: ./asn.zone)
  190. --file-v6 IPv6 zone (default: ./asn6.zone)
  191. --help Brief help message
  192. --man Full documentation
  193. =head1 OPTIONS
  194. =over 8
  195. =item B<--download-asn>
  196. Download ASN data from RIR.
  197. =item B<--download-bgp>
  198. Download GeoIP data from Ripe
  199. =item B<--target>
  200. Specifies where to download files.
  201. =item B<--help>
  202. Print a brief help message and exits.
  203. =item B<--man>
  204. Prints the manual page and exits.
  205. =back
  206. =head1 DESCRIPTION
  207. B<asn.pl> is intended to download ASN data and GeoIP data and create a rbldnsd zone.
  208. =cut