#!/usr/bin/perl # usage: # make-geoip-deny.pl RIRをダウンロードしてGEOIPDENYをipset # make-geoip-deny.pl -n RIRをダウンロードしてGEOIPDENYを生成 # make-geoip-deny.pl -r 保存済みのstoreファイルからGEOIPDENYをipset use Getopt::Std ; use Net::IPAddress ; my %opt = ( 's' => "GEOIPDENY" , 'f' => "/etc/ferm/ipset/ipset_GEOIPDENY.store" , 'd' => 0 , # for debug 'r' => 0 , # ipset をファイルから取り出す 'n' => 0 , # ファイル生成のみ 'F' => 0 , # ipset を消す ) ; getopts( "s:f:drnF" , \%opt ) ; my $wget_cmd = "/usr/bin/wget" ; my $ipset_cmd = "/usr/sbin/ipset" ; # RIR取得用URL my @rir_url = ( "ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest" , "ftp://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest" , "ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-latest" , "ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest" , "ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest" , ) ; # 拒否する国ドメイン my %deny_country = () ; foreach my $country ( ( 'AE' , 'AF' , 'AM' , 'AZ' , 'BD' , 'BG' , 'BR' , 'BY' , 'CL' , 'CN' , 'ID' , 'IN' , 'IL' , 'IR' , 'IQ' , 'JO' , 'KG' , 'KP' , 'KR' , 'KW' , 'KZ' , 'LB' , 'NL' , 'OM' , 'PS' , 'QA' , 'RO' , 'RU' , 'SA' , 'SY' , 'TJ' , 'TR' , 'UA' , 'UZ' , 'YE' ) ) { $deny_country{$country} = 1 ; } # IPアドレス範囲をサブネットマスク用に分割する sub split_ipaddr_rec { my ( $start , $end , $ip_start , $ip_end , $ip_bit ) = @_ ; my $mid = ($start + $end) / 2 ; if ( $start == $ip_start && $end == $ip_end ) { return ( { 'start' => $start , 'next' => $end , 'size' => $end - $start } ) ; } elsif ( $ip_start < $mid && $mid < $ip_end ) { # print "---\n" ; return ( split_ipaddr_rec( $start , $mid , $ip_start , $mid , $ip_bit + 1 ) , split_ipaddr_rec( $mid , $end , $mid , $ip_end , $ip_bit + 1 ) ) ; } elsif ( $ip_end <= $mid ) { return split_ipaddr_rec( $start , $mid , $ip_start , $ip_end , $ip_bit + 1 ) ; } elsif ( $mid <= $ip_start ) { return split_ipaddr_rec( $mid , $end , $ip_start , $ip_end , $ip_bit + 1 ) ; } } sub split_ipaddr { my ( $item ) = @_ ; my $start = $item->{'start'} ; my $end = $item->{'next'} ; my $size = $item->{'size'} ; return split_ipaddr_rec( 0 , 1 << 32 , $start , $end , 0 ) ; } sub log2 { my ( $size ) = @_ ; return int( log( $size ) / log( 2 ) ) ; } # 拒否する国ドメインのIPアドレス一覧を取り出す sub rir_deny_list { # $ipset{ip2num("192.168.0.0")} = { 'start' => ... , 'size' => ... , 'next' => ... } ; my %ipset = () ; foreach my $url ( @rir_url ) { # RIRのデータを開く my $cmd = "$wget_cmd -q -O - '$url'" ; print "$cmd\n" if ( $opt{'d'} ) ; open( FH , "$cmd |" ) or die( "Can't open '$cmd'" ) ; while( my $line = ) { my @item = split( /\|/ , $line ) ; if ( $item[1] =~ /^[A-Z][A-Z]$/ && exists( $deny_country{$item[1]} ) && $item[2] eq "ipv4" ) { # 拒否国ドメインでipv4だけ my $country = $item[1] ; my $ip = Net::IPAddress::ip2num( $item[3] ) ; my $size = $item[4] ; # ハッシュに記録 $ipset{$ip} = { 'start' => $ip , 'next' => $ip + $size , 'size' => $size } ; } } close( FH ) ; } return %ipset ; } # 隣接するアドレスブロックを連結 sub merge_list { my ( %ipset ) = @_ ; # 拒否リストのipsetの整理 foreach my $ip ( sort { $a <=> $b } keys( %ipset ) ) { if ( exists( $ipset{$ip} ) ) { for(;;) { my $ip_start = $ipset{$ip}->{'start'} ; my $ip_next = $ipset{$ip}->{'next'} ; my $ip_size = $ipset{$ip}->{'size'} ; if ( exists( $ipset{$ip_next} ) ) { # 次のブロックと連結できる my $nx_start = $ipset{$ip_next}->{'start'} ; my $nx_next = $ipset{$ip_next}->{'next'} ; my $nx_size = $ipset{$ip_next}->{'size'} ; # 現在のブロックを拡張 $ipset{$ip}->{'size'} += $nx_size ; $ipset{$ip}->{'next'} = $nx_next ; #print "merge" # .":".Net::IPAddress::num2ip( $ip_start ) # ."-".Net::IPAddress::num2ip( $nx_next ) # ."/".$ipset{$ip}->{'size'} # ."\n" ; # 次のブロックを消す delete( $ipset{$ip_next} ) ; } else { last ; } } } } return %ipset ; } # ipset をサブネットマスク用に分割 sub split_ipset { my ( %ipset ) = @_ ; my %ipset_ans = () ; foreach my $ip ( sort { $a <=> $b } keys %ipset ) { foreach my $item ( split_ipaddr( $ipset{$ip} ) ) { my $ip_start = $item->{'start'} ; $ipset_ans{$ip_start} = $item ; } } return %ipset_ans ; } my $set_name = $opt{'s'} ; my $file = $opt{'f'} ; if ( $opt{'F'} ) { # -F ipsetを削除するだけ system( "$ipset_cmd destroy $set_name" ) ; exit 0 ; } elsif ( $opt{'r'} && -f $opt{'f'} ) { # -r 保存済みのIPSETを使う if ( !$opt{'n'} ) { system( "$ipset_cmd destroy $set_name" ) ; system( "$ipset_cmd restore < $file" ) ; } exit 0 ; } else { my $text = "create $set_name hash:net family inet hashsize 16384 maxelem 65536\n" ; # IPSETを新しく生成 my %ipset_rir = rir_deny_list() ; print "ipset count before : ".(keys %ipset_rir)."\n" if ( $opt{'d'} ) ; my %ipset_merge = merge_list( %ipset_rir ) ; print "ipset count merge : ".(keys %ipset_merge)."\n" if ( $opt{'d'} ) ; my %ipset = split_ipset( %ipset_merge ) ; print "ipset count after : ".(keys %ipset)."\n" if ( $opt{'d'} ) ; foreach my $ip ( sort { $a <=> $b } keys %ipset ) { my $item = $ipset{$ip} ; my $ip_start = $item->{'start'} ; my $ip_next = $item->{'next'} ; my $ip_size = $item->{'size'} ; $text .= "add $set_name" ." ".Net::IPAddress::num2ip( $ip_start ) ."/".(32 - log2( $ip_size ))."\n" ; } if ( $opt{'d'} ) { print $text ; } else { open( FH , ">$file" ) or die( "Can't open $file." ) ; print FH $text ; close( FH ) ; if ( !$opt{'n'} ) { system( "$ipset_cmd restore < $file" ) ; } } } ### Local Variables: ### ### mode: perl ### ### tab-width: 4 ### ### End: ###