国を指定して ipfilter.dat を作成する例1:apnic.net のデータを利用した場合
- 「%03s」は間違い。正しくは「%03d」
2010-08-06 修正
- 数値をゼロ埋めしレコードを固定長に変更(こちらの書式が主流?)
- 改行文字の修正(LF ⇒ CR+LF)を awk スクリプト中に含めた(Linux 等で使う場合は printf の \r を削ってください)
2009-07-05 修正
- ネット上を探してみると、個人で作成されたIP範囲のデータもいろいろ見つかる。が、どうせなら自分の納得いくよう作成しようという企画
- awk は初心者なのでバグが残っている可能性があります...指摘頂けると幸いです
- エラー処理は自分用なのでいいかげんでよしとする
前提条件
1.元データをダウンロード
- wget コマンドでダウンロード(wget が使えるかどうかはコマンドプロンプトで"where wget.exe"する。見つからない場合、環境変数 PATH に Cygwin インストール先の bin ディレクトリを追加すること)
wget -nd -nc http://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest
オプション | 意味 |
---|---|
-nd | ディレクトリを作成しない |
-nc | 既にファイルがある場合はダウンロードしない |
- このデータは毎日更新されている
2.データ概要
- ファイルサイズは1メガ弱。2万行程度でパイプ(¦)区切り(psvファイル)
- 各フィールドについての資料(英語)はここ参照のこと。以下では利用するフィールドのみ説明
- 2番目のフィールドはCC=国コード
- 3番目のフィールドでIPv4かどうか判定できる(asnはAS番号の意)
- 4番目のフィールドはIPv4レコードの場合、開始IPアドレス
- 5番目のフィールドは開始IPアドレスから終了IPアドレスまでの差を10進数化した値
- IPアドレスでソート済み
冒頭のコメント略 2|apnic|20090627|20105|gpan|20090626|+1000 apnic|*|asn|*|3283|summary apnic|*|ipv4|*|16063|summary apnic|*|ipv6|*|759|summary apnic|JP|asn|173|1|20020801|allocated apnic|NZ|asn|681|1|20020801|allocated 中略 apnic|NZ|asn|2.115|1|20090623|allocated apnic|HK|asn|2.116|1|20090625|allocated apnic|JP|ipv4|58.0.0.0|131072|20050106|allocated apnic|IN|ipv4|58.2.0.0|65536|20050110|allocated 中略 apnic|KR|ipv4|222.251.128.0|32768|20051215|allocated apnic|VN|ipv4|222.252.0.0|262144|20040518|allocated apnic|CN|ipv6|240c::|28|20080721|allocated apnic|JP|ipv6|2001:200:2000::|35|20030423|allocated 中略 apnic|MY|ipv6|2407:f800::|32|20090609|allocated apnic|JP|ipv6|2408::|22|20071102|allocated
3.ipfilter.dat 形式に変換
# Make ipfilter, targeting specific CC records from delegated-apnic-latest # # Date : 2009-07-05 # Author : k2jp # BEGIN{ FS = "|"; # OFS = ","; CLS_A = 16777216; CLS_B = 65536; CLS_C = 256; match_counter = 0; } /^apnic\|CN\|ipv4\|/ || /^apnic\|HK\|ipv4\|/ || /^apnic\|TW\|ipv4\|/ || /^apnic\|JP\|ipv4\|/ { gsub(",", " ", $2); gsub(",", " ", $6); gsub(",", " ", $7); printf "%-s - %-s , %03d , [%s]%s_%s\r\n", FillByZero($4), IPrize( NUMrize($4) + int($5) - 1 ), 56, $2, $6, $7; ++match_counter; } END{ if(match_counter > 0) printf "### The total number of matched records in the APNIC list: %d\r\n", match_counter; } ############################## User-defined Functions ############################## function NUMrize(ip){ if( split(ip, IPs, ".") != 4 ) { print "[IP Field 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]); } function IPrize(num){ a = int(num/CLS_A); b = int( (num-a*CLS_A)/CLS_B ); c = int( (num - a*CLS_A - b*CLS_B)/CLS_C ); d = num - a*CLS_A - b*CLS_B - c*CLS_C; return sprintf("%03d.%03d.%03d.%03d", a, b, c, d ); } function FillByZero(ip){ gsub(" ", "", ip); if( split(ip, IPs, ".") != 4 ){ print "[ERROR]Total number of IP field is invalid"; exit 4; } return sprintf("%03d.%03d.%03d.%03d", int(IPs[1]), int(IPs[2]), int(IPs[3]), int(IPs[4]) ); }
流れとしては
- BEGIN ブロックで入力ファイルのセパレータ(FS)に ¦ を指定。定数定義とレコードカウンター(match_counter)を初期化
- /正規表現/ ブロックで正規表現にマッチする行(中国 ¦ 香港 ¦ 台湾 ¦ 日本)を処理対象に抽出し、終了IPアドレスを計算している。計算式は、10進数化した開始IPアドレス + 終了IPアドレスまでの差 - 1
- 終了時に処理した行数(match_counter)をコメント行として出力する
作成したawkスクリプトの実行は次のコマンドラインを実行するだけ
gawk -f apnic2ipfilter.awk delegated-apnic-latest
5000行弱が画面に出力されるハズ
058.000.000.000 - 058.001.255.255 , 056 , [JP]20050106_allocated 058.003.000.000 - 058.003.127.255 , 056 , [JP]20050304_allocated 058.003.128.000 - 058.003.255.255 , 056 , [JP]20060404_allocated 058.004.000.000 - 058.005.255.255 , 056 , [JP]20050118_allocated 058.012.000.000 - 058.013.255.255 , 056 , [JP]20050222_allocated 中 略 222.249.176.000 - 222.249.191.255 , 056 , [CN]20050511_allocated 222.249.192.000 - 222.249.255.255 , 056 , [CN]20050511_allocated 222.250.000.000 - 222.250.255.255 , 056 , [TW]20040525_allocated 222.251.000.000 - 222.251.127.255 , 056 , [TW]20040525_allocated ### The total number of matched records in the APNIC list: 4769
4.IPアドレスの範囲が連続しているレコードを連結してファイルサイズを減らす
- Part1 で出力されたデータを見てみると、連結可能なものが散見されます。例えば、終了IPアドレス 58.3.127.255 は次の開始IPアドレス 58.3.128.0 に連結してもよさそう(ファイルサイズも劇的に減ってエコです)。この処理をするのが次の awk スクリプト(ConnectRecords.awk)
# Connect redundantly separated Records # # Date : 2009-07-05 # Author : k2jp # Condition : Input records must be sorted in advance. There shuld NOT be overlaped IP ranges. # 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 = ""; } /^[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]); } if(match_counter == 1){ # The first record initialization IP_start = IPRange[1]; IP_end = IPRange[2]; level = int($2); comment = $3; }else if( NUMrize(IPRange[1]) != NUMrize(IP_end)+1 ){ # This record is NOT connectable. Print previous. # # Previous Record: IP_start|---------->IP_end # Current Record: IPRange[1]|--------->IPRange[2] # ++rec_counter; printf "%-s - %-s , %03d , %s\r\n", IP_start, IP_end, level, comment; IP_start = IPRange[1]; IP_end = IPRange[2]; level = int($2); comment = $3; }else{ # Connecting records # # Previous Record: IP_start|----->IP_end # Current Record: IPRange[1]|---->IPRange[2] # IP_end = IPRange[2]; level = level < int($2) ? level : int($2); comment = sprintf("%s_%s", comment, $3); } } /^#+ *total number of matched records/ { # Check the number of match_counter. The same number is expected. if( match_counter != int(substr($0, index($0, ":")+1 )) ) { print "[ERROR]match_counter error", match_counter, int(substr($0, index($0, ":")+1 )); exit match_counter; } } END{ if(match_counter > 0) { ++rec_counter; printf "%-s - %-s , %03d , %s\r\n", IP_start, IP_end, level, comment; # 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]); }
現在行以外に以前の行データを比較の為に保持するので、最初のスクリプトより面倒です
- BEGIN ブロックでは、セパレーター指定(FS)をカンマにして、定数定義及び短縮前(match_counter)短縮後(rec_counter)のカウンターを初期化。以前の行データを保持する IP_start 及び IP_end 変数の初期化等を行う
- /正規表現/ ブロックでは行の先頭が数字で始まる行に対し、
- 入力ファイルの最終行に入力行数がコメントされている場合、今回処理した行数と一致するか確認し、不一致なら何らかの問題が発生しているので処理をエラー終了
- END ブロックで、入力された有効行数(match_counter)と連結処理された行数(rec_counter)をコメント行にして追加
以上を Part 1 で処理した結果に適用したいのでパイプでつなぎ、結果をファイル(ipfilter.dat)に出力
gawk -f apnic2ipfilter.awk delegated-apnic-latest | gawk -f ConnectRecords.awk >ipfilter.dat
出力されたファイルの最終行を見てみると、4769行あった行数が1596行まで減ったので約65%削減できました
058.000.000.000 - 058.001.255.255 , 056 , [JP]20050106_allocated 058.003.000.000 - 058.005.255.255 , 056 , [JP]20050304_allocated_[JP]20060404_allocated_表示幅の都合で切捨 058.012.000.000 - 058.025.255.255 , 056 , [JP]20050222_allocated_[CN]20050224_allocated_表示幅の都合で切捨 058.030.000.000 - 058.063.255.255 , 056 , [CN]20050316_allocated_[CN]20050128_allocated_表示幅の都合で切捨 中 略 222.166.000.000 - 222.229.079.255 , 056 , [HK]20040421_allocated_[HK]20041119_allocated_表示幅の都合で切捨 222.229.096.000 - 222.230.255.255 , 056 , [JP]20060915_allocated_[JP]20041130_allocated_表示幅の都合で切捨 222.231.064.000 - 222.231.255.255 , 056 , [JP]20051102_allocated_[JP]20051103_allocated 222.240.000.000 - 222.251.127.255 , 056 , [CN]20040326_allocated_[CN]20040427_allocated_表示幅の都合で切捨 ### The total number of matched records: 4769 Compacted: 1596
5.使い方
作成した ipfilter.dat を対応アプリに読み込ませます
まとめ
2行で ipfilter.dat が作成できた
wget -nd -nc http://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest
gawk -f apnic2ipfilter.awk delegated-apnic-latest | gawk -f ConnectRecords.awk >ipfilter.dat
備考
- 第3フィールドが長すぎるかもしれない。これはレコードを連結した際に内容をつなげるように処理しているため。awk で出力(printf)しているところで substr(comment, 1, 50) とすれば50文字以内に限定することができる