国別IPアドレス割当一覧を取得するシェルスクリプトを作った

国別にファイヤーウォールで許可/拒絶したいと思ったがIPv6の国別割当を公開しているところは少なく、ほしい形で公開しているところがなかったため自分で作ってみた。ついでなのでここで公開することにした。

 

0.概要

  1. 目標
  2. 試行
  3. 最終実装
  4. まとめ

1. 目標

  1. 以下のアドレスから割当データを取得して国ごとに割り当てられているIPアドレスを分別する。
  1. 変更がなければ以前のデータを使用することで無駄な通信を減らす。
  2. シェルスクリプトでどこまで出来るか試す。

2. 試行

とりあえずarinのデータ(9MB)を使って試してみた。
元データは’|’区切りのデータで不要な部分もあることからgrepやcut等でフィルタして必要なデータに加工した。
このフィルタ部分は以下のワンライナーとなった。

 grep ipv|grep -e "allocated" -e "assigned"|cut -d'|'  --fields=2-5 |sed -e"s/|/ /g"

このあとに出力されるデータは “国コード,IP種別,IPアドレス,サブネットまたは割当個数”がスペース区切りで出力される。
IPSetなどではCIDR表記にするのが一般的である。そのようにするためにIPv4では割当個数表記であるためその部分を変換する必要がある。
そこで、最初にこの変換部分を作成する。
変換手法としては割り当て個数を底を2とする対数より求める。この計算にはbcを用いることにした。
しかし、bcのlog計算には底が自然対数の時しかない。
そこでlog(x)/log(2)とすることで対応する。
また、いちいちlog(2)の計算するのも無駄なので予め計算しておき定数として渡すことにした。
四捨五入にはprintfを用いた。

printf '%.f \n' $(echo "32-l(${SUBNET})/$_LOGBASE2"|bc -l)

次にこれをどのように呼び出すと効率よく処理できるかを検証した。
以下にそのスクリプトの内容を示す。

A.sh

#!/bin/bash
#COUNTRY,IPTYPE,ADDRESS,SUBNET

if [ $2 = 'ipv4' ];then
   printf '%.f \n' $(echo "32-l(${4})/l(2)"|bc -l)
else
    
echo $4
fi

B.sh

#!/bin/bash
#COUNTRY,IPTYPE,ADDRESS,SUBNET
LOGBASE2=$(echo 'l(2)'|bc -l)
while read COUNTRY IPTYPE ADDRESS SUBNET
do
if [ $IPTYPE = 'ipv4' ];then
SUBNET=$(printf '%.f \n' $(echo "32-l(${SUBNET})/$LOGBASE2"|bc -l))
fi
echo $ADDRESS/$SUBNET # >> ./result/$COUNTRY-$IPTYPE.txt
done

A.shでは外からxargs -l1 -P6で呼び出して並列化を図ってみた。
B.shではパイプで渡してwhile readを用いてみた。

結果は以下のとおりとなった。

real user sys
A 0m16.149s 1m18.921s 0m12.182s
B 0m39.869s 0m42.735s 0m6.777s

純処理時間ベースで考えるとBはAの半分近い処理時間となることからAではForkの処理が重かったことがわかる。
一方でAでは6並列で動作することでBの半分の時間で処理することができた。
今回の用途では5箇所から取得し並列させることを考えた時、総データ量は3倍の30MBほどであること、一番大きいのはRIPENCCの12MBであることから並列化しやすいBの方式を採用することにした。

2. 最終実装

これらの処理部とキャッシュの比較、作業ディレクトリの確認等の処理をまとめて以下のスクリプトにした。
ARINがmd5として公開している形式が違うため汚い実装となったが意外と短くまとめられた。
しかし、変換部がボトルネックとなっているので余裕ができたらGo言語などで書き直したい。

getIpCountry.sh

#!/bin/bash

WORKDIR=$(pwd)
DATADIR=$WORKDIR/data
CACHEDIR=$WORKDIR/cache

###############################################################################
_LOGBASE2=$(echo 'l(2)'|bc -l)

function _postProcess () {
    cat -|grep ipv|grep -e "allocated" -e "assigned"|cut -d'|'  --fields=2-5 |sed -e"s/|/ /g"
}

function distIp () {
while read COUNTRY IPTYPE ADDRESS SUBNET
do
if [ $IPTYPE = 'ipv4' ];then
    SUBNET=$(printf '%.f \n' $(echo "32-l(${SUBNET})/$_LOGBASE2"|bc -l))
fi
echo $ADDRESS/$SUBNET $IPTYPE>> $DATADIR/$COUNTRY
done
}

function hashCheck () {
    cd $CACHEDIR
    while read URL
    do
    (
    URLBase=$(basename $URL) 
    DownPath=$CACHEDIR/$URLBase
    test -e $DownPath&&curl -s --output $DownPath.md5 $URL.md5&&(cat $DownPath.md5|md5sum -c 2>/dev/null||cat $DownPath.md5|sed -E "s/delegated-(.)*-extended-(.)*/$URLBase/g"|md5sum -c 2>/dev/null)||(echo cache miss $URLBase;mv $DownPath $DownPath.old 2>/dev/null;curl -s --output $DownPath $URL)||mv $DownPath.old $DownPath 2>/dev/null
    cat $DownPath|_postProcess|distIp
    )&
done
}

function _init () {
    test -d $DATADIR&&test -w $DATADIR||chmod u+w $DATADIR||mkdir -pZ $DATADIR||exit 1
    test -d $CACHEDIR&&test -w $CACHEDIR||chmod u+x $CACHEDIR||mkdir -pZ $CACHEDIR||exit 1
    rm -f $DATADIR/*
    rm -f $CACHEDIR/*.md5
}

_init||exit 1
hashCheck << EOL
ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-latest
ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
ftp://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest
ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest
ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest
EOL
wait

4. まとめ

このスクリプトを作る上で以下のことがわかった。

  • &&,||は左から順に処理されていく。まとめるときは()で括る。
  • md5sum -c でハッシュと比較できる。
  • シェルはデータの加工にこそ有効
  • 変数への代入はコストが大きい。

 

国別IPアドレス割当一覧を取得するシェルスクリプトを作った」への1件のフィードバック

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください