Linux/複数IPを1サブネットにまとめる

複数のIPアドレスをサブネットへまとめる(2017-03-05)


不正アクセス元を調べると似たようなアドレスがあって
(おそらく同一グループが自分の持つ複数のIPから発信している・・・のでは?)
これらを1つのサブネットでまとめてフィルタしたい、
その為の perl スクリプトです。

先に、実行イメージを。
例えば、こんなイメージがあったとして

# cat ip_list.txt
xxx.185.200.218
xxx.185.200.215
xxx.185.200.254
(以下省略)

以下、ツールに渡すと1まとめにします。

# ./make_subnet.pl ip_list.txt | sort 
xxx.185.200.192/26 #xxx.185.200.218,xxx.185.200.215,xxx.185.200.254
  xxx.13.32.185/32 #xxx.13.32.185
   xxx.8.37.129/32 #xxx.8.37.129

一行目のように、3つのアドレスを1つのマスクで集約してます。
以下、 make_subnet.pl です。

# cat make_subnet.pl
#!/usr/bin/perl -w

# デバッグ文、作成過程では入れまくってます。
sub dbg_printf{
        return;  # ここをコメントアウト
        printf STDOUT "dbg:";
        printf STDOUT @_ ;
        $|=1;
}

# 現状はIPv4だけ、
# IPv6は必要になったら考えます

my @subnets = (0xffffffc0, 0xffffffe0, 0xfffffff0, 0xfffffff8, 0xfffffffc, 0xfffffffe, 0xffffffff );
              # 0000, 1000, 1100, 1110 という具合にマスクを増やしていく
              # とりあえず、/26bit ~ /32bit  の範囲でマスクする

my $file;
if($ARGV[0] eq '-') { $file = 'STDIN';}
else{ open($file, "< $ARGV[0]") or die "$! [$ARGV[0]]\n";}

my %sub_ip;
my %sub_mask;
while(<$file>){
    if(/^(\d+\.\d+\.\d+\.\d+)/){
        $ip=$1;
        if($ip =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/){
            $bin=$1*256*256*256
            +$2*256*256
            +$3*256
            +$4; # IP表記から数値に戻す

            $sub_ip{$bin} = $ip;
            $sub_mask{$bin} = 0xffffffff;
        }
    }
}
my %aa;
my %bb;
my %new_ip;
my %new_mask;
my $is_loop=1;
# 重複するサブネットをさらに集約する
while($is_loop){
    %aa = %bb = %sub_ip;
    %new_ip=();
    %new_mask=();
    $is_loop=0;
    foreach my $a (keys(%aa)){
        my $is_merge=0;
        foreach my $b (keys(%bb)){
            if($a == $b)
            {
                next;
            }
            for($i=$#subnets; $i>=0; $i--){
                my $mask = $subnets[$i];
                if($sub_mask{$a} <= $mask){
                    next;
                }
                if(($a & $mask) == ($b & $mask)){
                    dbg_printf("%s and %s masked 0x%x\n", $sub_ip{$a}, $sub_ip{$b}, $mask);
                    my $n = $a & $mask;
 
                    # 内訳 IPリストを再構成する
                    my @hosts = (split(/,/, $sub_ip{$a}), split(/,/, $sub_ip{$b}));
                    if(defined $new_ip{$n}){
                       # 既存IPリスト, サブネットを上書きしないようにする
                       @hosts = (@hosts, split(/,/, $new_ip{$n}));
                       if($new_mask{$n} < $mask){ $mask = $new_mask{$n}; }
                    }
                    # perldoc -q duplicate より、重複排除
                    my %uniq   = map { $_, 1 } @hosts;
 
                    $new_ip{$n} = join(",", keys %uniq);
                    $new_mask{$n} = $mask;
                    $is_loop++;
                    $is_merge++;
                    last;
                }
            }
        }
        if($is_merge==0){
           $new_ip{$a} = $sub_ip{$a};
           $new_mask{$a} = $sub_mask{$a};
        }
    }
    %sub_ip = %new_ip;
    %sub_mask = %new_mask;
 }
 
foreach my $key (keys(%sub_ip)){
    $key &= $sub_mask{$key};
    my $ip = sprintf("%d.%d.%d.%d",
               $key /(256*256*256),
               $key /(256*256)%256,
               $key /256%256,
               $key %256
             );
    my $bip=0; # サブネット数。2の何乗かをビット演算。
    for($p=$sub_mask{$key}; $p%2==0; $p>>=1){$bip++;}
    printf("%16s/%d #%s\n", $ip, 32-$bip, $sub_ip{$key} );
}

・・・現時点ではこれが精いっぱい。長い。
出力はサブネットと内訳が出るので、1列目を抜き出して使います。
( awk '{ print $1 }' とか、よしなに)

複数アドレスをどこまで集約するか(何bitでフィルタするか)は
さじ加減になってしまいますが・・・
必要に応じて上記「my @subnets」の条件を増減させてください。