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.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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
  80. "\$SOA 43200 $ns_servers->[0] support.rspamd.com 0 600 300 86400 300\n";
  81. foreach my $ns (@{$ns_servers}) {
  82. print $v4_fh "\$NS 43200 $ns\n";
  83. }
  84. }
  85. if ($v6) {
  86. open( $v6_fh, ">", $v6_file ) or die "Cannot open $v6_file for writing: $!";
  87. print $v6_fh
  88. "\$SOA 43200 $ns_servers->[0] support.rspamd.com 0 600 300 86400 300\n";
  89. foreach my $ns (@{$ns_servers}) {
  90. print $v6_fh "\$NS 43200 $ns\n";
  91. }
  92. }
  93. # Now load BGP data
  94. my $networks = {};
  95. foreach my $u ( @{ $config{'bgp_sources'} } ) {
  96. my $parsed = URI->new($u);
  97. my $fname = $download_target . '/' . basename( $parsed->path );
  98. open( my $fh, "<:gzip", $fname )
  99. or die "Cannot open $fname: $!";
  100. while ( my $dd = eval { Net::MRT::mrt_read_next($fh) } ) {
  101. if ( $dd->{'prefix'} && $dd->{'bits'} ) {
  102. next if $dd->{'subtype'} == 2 and !$v4;
  103. next if $dd->{'subtype'} == 4 and !$v6;
  104. my $entry = $dd->{'entries'}->[0];
  105. my $net = $dd->{'prefix'} . '/' . $dd->{'bits'};
  106. if ( $entry && $entry->{'AS_PATH'} ) {
  107. my $as = $entry->{'AS_PATH'}->[-1];
  108. if (ref($as) eq "ARRAY") {
  109. $as = @{$as}[0];
  110. }
  111. if ( !$networks->{$as} ) {
  112. if ( $dd->{'subtype'} == 2 ) {
  113. $networks->{$as} = { nets_v4 => [$net], nets_v6 => [] };
  114. }
  115. else {
  116. $networks->{$as} = { nets_v6 => [$net], nets_v4 => [] };
  117. }
  118. }
  119. else {
  120. if ( $dd->{'subtype'} == 2 ) {
  121. push @{ $networks->{$as}->{'nets_v4'} }, $net;
  122. }
  123. else {
  124. push @{ $networks->{$as}->{'nets_v6'} }, $net;
  125. }
  126. }
  127. }
  128. }
  129. }
  130. }
  131. # Now roughly detect countries
  132. foreach my $u ( @{ $config{'asn_sources'} } ) {
  133. my $parsed = URI->new($u);
  134. my $fname = $download_target . '/' . basename( $parsed->path );
  135. open( my $fh, "<", $fname ) or die "Cannot open $fname: $!";
  136. while (<$fh>) {
  137. next if /^\#/;
  138. chomp;
  139. my @elts = split /\|/;
  140. if ( $elts[2] eq 'asn' && $elts[3] ne '*' ) {
  141. my $as_start = int( $elts[3] );
  142. my $as_end = $as_start + int( $elts[4] );
  143. for ( my $as = $as_start ; $as < $as_end ; $as++ ) {
  144. my $real_as = $as;
  145. if (ref($as) eq "ARRAY") {
  146. $real_as = @{$as}[0];
  147. }
  148. if ( $networks->{"$real_as"} ) {
  149. $networks->{"$real_as"}->{'country'} = $elts[1];
  150. $networks->{"$real_as"}->{'rir'} = $elts[0];
  151. }
  152. }
  153. }
  154. }
  155. }
  156. while ( my ( $k, $v ) = each( %{$networks} ) ) {
  157. if ($v4) {
  158. foreach my $n ( @{ $v->{'nets_v4'} } ) {
  159. # "15169 | 8.8.8.0/24 | US | arin |" for 8.8.8.8
  160. if ( $v->{'country'} ) {
  161. printf $v4_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, $v->{'country'}, $v->{'rir'};
  162. }
  163. else {
  164. printf $v4_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, 'UN', 'UN';
  165. }
  166. }
  167. }
  168. if ($v6) {
  169. foreach my $n ( @{ $v->{'nets_v6'} } ) {
  170. # "15169 | 8.8.8.0/24 | US | arin |" for 8.8.8.8
  171. if ( $v->{'country'} ) {
  172. printf $v6_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, $v->{'country'}, $v->{'rir'};
  173. }
  174. else {
  175. printf $v6_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, 'UN', 'UN';
  176. }
  177. }
  178. }
  179. }
  180. __END__
  181. =head1 NAME
  182. asn.pl - download and parse ASN data for Rspamd
  183. =head1 SYNOPSIS
  184. asn.pl [options]
  185. Options:
  186. --download-asn Download ASN data from RIR
  187. --download-bgp Download GeoIP data from Maxmind
  188. --target Where to download files (default: current dir)
  189. --zone-v4 IPv4 zone (default: asn.rspamd.com)
  190. --zone-v6 IPv6 zone (default: asn6.rspamd.com)
  191. --file-v4 IPv4 zone file (default: ./asn.zone)
  192. --file-v6 IPv6 zone (default: ./asn6.zone)
  193. --help Brief help message
  194. --man Full documentation
  195. =head1 OPTIONS
  196. =over 8
  197. =item B<--download-asn>
  198. Download ASN data from RIR.
  199. =item B<--download-bgp>
  200. Download GeoIP data from Ripe
  201. =item B<--target>
  202. Specifies where to download files.
  203. =item B<--help>
  204. Print a brief help message and exits.
  205. =item B<--man>
  206. Prints the manual page and exits.
  207. =back
  208. =head1 DESCRIPTION
  209. B<asn.pl> is intended to download ASN data and GeoIP data and create a rbldnsd zone.
  210. =cut