Генерация списков делегированных IPv4- и IPv6-адресов по странам.
Предыстория
Обнаружилась в какой-то момент проблема, для решения которой необходимо было блокировать весь трафик от наших китайских братьев по разуму. Казалось бы, что может быть проще — найти список делегированных им сетей и прописать на фаерволе. Однако, во-первых, адекватность и свежесть, найденных списков, вызывала сильные сомнения. Во-вторых, скриптов, которые бы генерировали эти списки никто не выкладывал. Как следствие, пришлось изобретать очередной велосипед :(
Как всё непросто.
Итак, исходные данные лежат в следующих файликах:
Там много чего есть, но строки со словами reserved, available, summary нас не интересуют. Кроме того, нас интересуют только 2 типа объектов: ipv4 и ipv6.
Для IPv6 строка имеет вид ripencc|RU|ipv6|2001:640::|32|19991115|allocated
, т.е. регистратор, страна, тип объекта, IPv6-сеть, её маска, дата выдачи, и статус. Нам интересны 2е, 4е и 5е поля. Всё просто.
А вот IPv4… Строка имеет похожий вид ripencc|FR|ipv4|2.0.0.0|1048576|20100712|allocated
, т.е. регистратор, страна, тип объекта, IPv4-сеть, количество адресов в блоке, дата выдачи, и статус. Казалось бы всё также, кроме того, что маска заменяется на число хостов. Но в этом-то всё и дело!!! Это ни разу не CIDR!!! Вот конкретные примеры:
afrinic|ZA|ipv4|164.146.0.0|393216|19930312|allocated|F363E51A # тут вначале идёт сеть /15, а потом /14 ripencc|FR|ipv4|194.146.2.0|768|19950428|assigned # тут вначале идёт сеть /23, а потом /24 afrinic|ZA|ipv4|41.77.96.0|2048|20110701|allocated|F363BEDE # тут две сети: 41.77.96.0/21 + 41.77.104.0/21, но их нельзя аггрегировать в /20!
Более того, у меня было устойчивое представление, что LIR не может получить сеть размером менее, чем /22, и когда я увидел строки:
ripencc|UA|ipv4|193.164.232.96|32|20011203|assigned ripencc|RU|ipv4|193.164.232.128|32|20050905|assigned ripencc|CY|ipv4|193.164.232.160|32|20120914|assigned ripencc|FR|ipv4|193.164.232.192|32|20060309|assigned ripencc|GB|ipv4|193.164.232.224|32|20060310|assigned
я понял, что просто не будет :)
Велосипед.
Как итог, в моём воспалённом сознании родился вот такой скриптец:
- cidr.pl
#!/usr/bin/perl -w use strict; my $DIR='/tmp/cidr_temp'; system "rm -rf $DIR"; system "mkdir -p $DIR/countries"; chdir $DIR; system "wget", "-c", "ftp://ftp.ripe.net/pub/stats/afrinic/delegated-afrinic-extended-latest", "ftp://ftp.ripe.net/pub/stats/apnic/delegated-apnic-extended-latest", "ftp://ftp.ripe.net/pub/stats/arin/delegated-arin-extended-latest", "ftp://ftp.ripe.net/pub/stats/lacnic/delegated-lacnic-extended-latest", "ftp://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest" ; my %size2mask = ( 1 => 32, 2 => 31, 4 => 30, 8 => 29, 16 => 28, 32 => 27, 64 => 26, 128 => 25, 256 => 24, 512 => 23, 1024 => 22, 2048 => 21, 4096 => 20, 8192 => 19, 16384 => 18, 32768 => 17, 65536 => 16, 131072 => 15, 262144 => 14, 524288 => 13, 1048576 => 12, 2097152 => 11, 4194304 => 10, 8388608 => 9, 16777216 => 8, 33554432 => 4 ); my %countries = (); for my $i ( "afrinic", "apnic", "arin", "lacnic", "ripencc" ) { open INPUT, "delegated-${i}-extended-latest" || die "Cannot open input file: delegated-${i}-extended-latest\n"; while (<INPUT>) { next if (/reserved|available|summary/); next unless (/ipv4/); #ripencc|FR|ipv4|2.0.0.0|1048576|20100712|allocated my ($registry, $country, $ip_version, $first_ip, $count, $date, $status) = split /\|/; my @prefixes = sort {$b <=> $a} (keys %size2mask); while ( $count > 0 ) { while ($prefixes[0] > $count ) { shift @prefixes; } my @first_ip = (split /\./, $first_ip); my $bin_ip = ( ( ( ( ( $first_ip[0] << 8 ) + $first_ip[1] ) << 8 ) + $first_ip[2] ) << 8 ) + $first_ip[3]; if ( ($bin_ip & ( 0xffffffff << ( 32 - $size2mask{$prefixes[0]} ))) == $bin_ip ) { $countries{$country} = [] if ( not defined $countries{$country} ); push @{$countries{$country}}, ($first_ip . '/' . $size2mask{$prefixes[0]}); $first_ip = join '.', (($bin_ip + $prefixes[0] >> 24) & 0xff, ($bin_ip + $prefixes[0] >> 16) & 0xff, ($bin_ip + $prefixes[0] >> 8) & 0xff, ($bin_ip + $prefixes[0]) & 0xff); $count -= $prefixes[0]; } else { shift @prefixes; } } } close INPUT; } foreach my $country ( sort keys %countries ) { open DUMP, "| sort | aggregate | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n > $DIR/countries/${country}-ipv4.cidr" || die; map { print DUMP $_, "\n" } @{$countries{$country}}; close DUMP; } %countries = (); for my $i ( "afrinic", "apnic", "arin", "lacnic", "ripencc" ) { open INPUT, "delegated-${i}-extended-latest" || die "Cannot open input file: delegated-${i}-extended-latest\n"; while (<INPUT>) { next if (/reserved|available|summary/); next unless (/ipv6/); #ripencc|RU|ipv6|2001:640::|32|19991115|allocated my ($registry, $country, $ip_version, $network, $mask, $date, $status) = split /\|/; $countries{$country} = [] if ( not defined $countries{$country} ); push @{$countries{$country}}, ($network . '/' . $mask); } close INPUT; } foreach my $country ( sort keys %countries ) { open DUMP, "| sort > $DIR/countries/${country}-ipv6.cidr" || die; map { print DUMP $_, "\n" } @{$countries{$country}}; close DUMP; }
Из всего скрипта пояснения заслуживает только момент с выводом IPv4-адресов. Если, как в примере выше, в начале блока идёт «маленькая» сеть, а после неё идёт «большая», то надо либо в логике скрипта массив префиксов восстанавливать в исходное состояние каждый раз, как только мы запоминаем CIDR-сеть и пересчитываем значение first_ip, либо мы получаем набор мелких сетей, которые можно аггрегировать в более крупные. Я пошел вторым путём, т.к. пакет net-misc/aggregate (домашняя страница: http://dist.automagic.org) у меня уже стоял.
Результат.
И таки да, результаты работы скрипта доступны здесь: http://cidr.sabitov.ru/countries/ Обновляться они будут раз в неделю. На самом деле и это часто, но я уже прописал:)