digital 千里眼 @abp_jp

アナログな日常とデジタルの接点

複数の ipfilter.dat を統合(マージ)してオリジナル ipfilter.dat を作成する方法

  • 「%03s」は間違い。正しくは「%03d

2010-08-06 修正

たくさん ipfilter.dat 作ったりダウンロードしても読み込めるファイルは1つだったり...orz
ということで、複数の ipfilter.dat を統合する方法です

前提条件
  • ゼロ埋済み ipfilter.dat
  • 統合対象のファイルはサブディレクトリに置かれているものとします
1.連結
cat ./*/*.dat
  • 任意のサブディレクトリにある拡張子 dat のファイル全てを cat で結合

2.ソート

  • sort を使います。Windows の sort.exe もあるのでフルパスで Cygwin の sort を指定
cat ./*/*.dat | C:\cygwin\bin\sort.exe -t . -n -k 1 -k 2 -k 3 -k 4 -k 4.10 -k 5 -k 6 -k 7
オプション 意味
t 区切り文字を指定
n 数値として比較するように設定
k オプションでソートの優先度を指定

3.開始IPアドレスと終了IPアドレスが同一の行を統合

uniq を使い各行先頭から33文字目まで(IPアドレス部分)を比較し、同一と判定された場合は1行だけ残し隣接行を削除する

cat ./*/*.dat | C:\cygwin\bin\sort.exe -t . -n -k 1 -k 2 -k 3 -k 4 -k 4.10 -k 5 -k 6 -k 7 | uniq -w 33 -i

4.もうちょっと複雑な行の統合

gawk を使います(RemoveDuplicatedRecords.awk

# Remove duplicated records or merge redundant records into one record.
#
# Date      : 2009-06-24
# Author    : k2jp
# Condition : Input records must be sorted in advance.
#
BEGIN{
    FS               = " , ";
#    OFS              = ",";
    CLS_A            = 16777216;
    CLS_B            = 65536;
    CLS_C            = 256;
    match_counter    = 0;
    rec_counter      = 0;
    IP_start         = "";
    IP_end           = "";
    level            = 0;
    comment          = "";
    NUMrizedIPstart  = 0;
    NUMrizedIPend    = 0;
    NUMrizedIPRange1 = 0;
    NUMrizedIPRange2 = 0;
}

/^[0-9]/ {
    ++match_counter;
    if( split($1, IPRange, " - ") != 2) {
        print "[ERROR]split error";
        exit 3;
    }

    # trim white space
    for(eachIP in IPRange) {
        gsub(" ", "", IPRange[eachIP]);
    }
    
    # remove comma in the comment(comma shuld be used as a separator)
    gsub(",", " ", $3);
    
    if(match_counter != 1) {
        # calculate NUMrized IPs in advance for better performance
        NUMrizedIPstart  = NUMrize(IP_start);
        NUMrizedIPend    = NUMrize(IP_end);
        NUMrizedIPRange1 = NUMrize(IPRange[1]);
        NUMrizedIPRange2 = NUMrize(IPRange[2]);
    }
    
    if(match_counter == 1) {
        # The first record initialization
        IP_start = IPRange[1];
        IP_end   = IPRange[2];
        level    = int($2);
        comment  = substr($3, 1, 10);
    }else if( (NUMrizedIPstart == NUMrizedIPRange1) || ( (NUMrizedIPRange1 <= NUMrizedIPend+1) && (NUMrizedIPend <= NUMrizedIPRange2) ) ){
        #
        # Previous Record: Start IP|---->End IP    :Ignore
        # Current  Record: Start IP|------>End IP  :OK
        #
        # or
        #
        # Previous Record: Start IP|---->End IP    :Use Start IP
        # Current  Record: Start IP  |---->End IP  :Use End   IP
        #
        IP_end = IPRange[2];
        level = level < int($2) ? level : int($2);
        comment = sprintf("%s_%s", comment, substr($3, 1, 10));
    }else if( (NUMrizedIPRange1 <= NUMrizedIPend+1) && (NUMrizedIPRange2 < NUMrizedIPend) ) {
        #
        # Previous Record: Start IP|--------->End IP  :OK
        # Current  Record: Start IP  |---->End IP     :Ignore
        #
        level = level < int($2) ? level : int($2);
        comment = sprintf("%s_%s", comment, substr($3, 1, 10));
    }else {
        #
        # Previous Record: Start IP|---->End IP          :Print buffered record
        # Current  Record: Start IP        |---->End IP  :Set current record to the next previous
        #
        ++rec_counter;
        printf "%-s - %-s , %03d , %s\r\n", IP_start, IP_end, level, substr(comment, 1, 150);
        IP_start = IPRange[1];
        IP_end   = IPRange[2];
        level    = int($2);
        comment  = substr($3, 1, 10);
    }
}

END{
    if(match_counter > 0) {
        ++rec_counter;
        printf "%-s - %-s , %03d , %s\r\n", IP_start, IP_end, level, substr(comment, 1, 150);
        # print statistics at final line
        printf "### The total number of matched records: %d\tCompacted: %d\r\n", match_counter, rec_counter;
    }
}

############################## User-defined Functions ##############################
function NUMrize(ip){
    if( split(ip, IPs, ".") != 4 )
    {
        print "[ERROR]Total number of IP field is invalid";
        exit 4;
    }
    return int(IPs[1])*CLS_A + int(IPs[2])*CLS_B + int(IPs[3])*CLS_C + int(IPs[4]);
}

長くてスンマセン。細かく書くときりがないので else if の部分だけざっくりと説明

  • 事前にソートされているので開始IPアドレスは前行のレコードと同じ場合(1つ目のelse if)、前行のIP範囲は読み捨て(開始IPアドレスはどちらのを使っても同じ)
  • 開始IPアドレスがずれていて、終了IPアドレスが前行より現在行の方が後の場合(1つ目のelse if)、前行の開始IPアドレスは使うが終了IPアドレスは現在行を使う
  • 現在行のIP範囲が前行のIP範囲以内である場合(2つ目のelse if)、現在行は無視
  • 全行の終了IPアドレスが現在行の開始IPアドレスより前にある場合(3つ目のelse if)、変数に入っている統合済みのIPアドレス範囲を出力し、現在行を変数の初期値として再設定する

最終的にコマンドはこうなります(たった1行)

cat ./*/*.dat | C:\cygwin\bin\sort.exe -t . -n -k 1 -k 2 -k 3 -k 4 -k 4.10 -k 5 -k 6 -k 7 | uniq -w 33 -i | gawk -f RemoveDuplicatedRecords.awk >ipfilter.dat

手元のデータでやってみたところ、(重複が多かったので)行数ベースで7割程度を削減できた!!

まとめ
  1. ファイル連結
  2. ソート
  3. uniq で重複行の削除
  4. 冗長的な行を排除(RemoveDuplicatedRecords.awk


ipfilter.dat シリーズやっと終わった