ファイル内にセグメント範囲内のIPを含むか判定する

# 目標
$ commandx "192.168.0.1/24" access_log
のようにコマンドを入力して、access_logファイル内に 192.168.0.1/24 セグメント範囲内IPを含むかどうか判定するコマンド、またはスクリプトを作成する。

# 経緯
当初、grep + 正規表現のみのワンライナーでできると思っていましたが、方法がわからず諦める。

# スクリプト
そこで、
[ネットワークアドレスの秘密 実際のコード例](https://www.skyarch.net/blog/?p=7423)
の関数を使わせていただいてスクリプトを作ってみた。

```bash:ipsearch.sh
#!/bin/bash

# 使い方: sh <本スクリプトファイル> 引数1 引数2
# 説明:  調査対象ファイル内に 特定範囲のIPアドレス(CIDR形式のIPアドレス セグメン
ト)が含まれているかどうかを判定する。
#
# 引数1: CIDR形式 "192.168.0.1/24" 調査対象IPアドレスセグメント
# 引数2: 調査対象ファイルパス /var/log/messages ログファイルなど
# 戻り値: IPアドレスが含まれている場合: 含まれているIPアドレス を返す
#         含まれない場合、1つも含まれないことを返す
# 実行例: sh <本スクリプトファイル> "192.168.0.1/24" /var/log/messages
#     戻り値: 対象IPが含まれる。IP: 192.168.0.5
#             (または、"1つも含まれていない")

# IPアドレス表記 -> 32bit値 に変換
function ip2decimal(){
    local IFS=.
    local c=($1)
    printf "%s\n" $(( (${c[0]} << 24) | (${c[1]} << 16) | (${c[2]} << 8) | ${c[3]} ))
}

# 32bit値 -> IPアドレス表記 に変換
function decimal2ip(){
    local n=$1
    printf "%d.%d.%d.%d\n" $(($n >> 24)) $(( ($n >> 16) & 0xFF)) $(( ($n >> 8) & 0xFF)) $(($n & 0xFF))
}

# CIDR 表記のネットワークアドレスを 32bit値に変換
function cidr2decimal(){
    printf "%s\n" $(( 0xFFFFFFFF ^ ((2 ** (32-$1))-1) ))
}

# 指定された個数だけ連続したIPアドレスの一覧を表示
function iplist(){
    local num=$(ip2decimal $1)
    local max=$(($num + $2 - 1))

    while :
    do
        decimal2ip $num
        [[ $num == $max ]] && break || num=$(($num+1))
    done
}

# 特定のIPアドレスが特定のネットワークに含まれるか調べる
# 戻り値0:含まれる, 1:含まれない
function ipwith(){
   local addr=$1
   local mask=$2

   local num=$(ip2decimal $3)
   local net=$(( $(ip2decimal $addr) & $(ip2decimal $mask) ))
   local brd=$(( $(ip2decimal $addr) | (0xFFFFFFFF ^ $(ip2decimal $mask)) ))

   [ $net -le $num -a $num -le $brd ] && return 0 || return 1
}

# 上記参照: https://www.skyarch.net/blog/?p=7423


# 以下メイン処理部分 ####################################

# "192.168.0.1/24" のような引数を分離する
para1=`echo $1 | cut -d "/" -f1`
para2=`echo $1 | cut -d "/" -f2`
# 対象ファイル(引数 = $2) からIPアドレス箇所のみを行単位で抽出して配列変数para3_arrayに代入
para3_array=`grep -e "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" $2 -o`

# flag 0:IP範囲に含まれる、1:含まれない
flag=1
for para3_el in $para3_array; do
    #echo $para3_el
    ipwith $para1 $(decimal2ip $(cidr2decimal $para2)) $para3_el
    if [ $? = "0" ]; then
       echo "対象IPが含まれる。IP:" $para3_el
       flag=0
       # break
    fi
done

if [ $flag = "1" ]; then
    echo "1つも含まれていない "
fi
```

# 実行例
$ sh ipsearch.sh "192.168.0.1/24" access_log
対象IPが含まれる。IP: 192.168.0.6

$ sh ipsearch.sh "192.168.0.1/24" access2_log
1つも含まれていない

# 結果・感想
とりあえずは、該当IPアドレス範囲が対象ファイルに含まれているかは判定できるようになりました。
ですが、非常に遅いです！
/var/log/messages に対して実行すると数分待たされる。
一瞬で結果が出るようにならいものか。

# 補足1
"192.168.0.1/24"のセグメントを検索するだけなら、
$ grep -e "192\.168\.0\." access_log
を実行した方が遥かに早いです。

#補足2
[grepcidr: IPアドレスをサブネットで一括grep](http://yunazuno.hatenablog.com/entry/2014/08/09/235302) にgrepcidrと言うツールがあるのを発見しました。
grepcidr がコンパイル、インストールできれば用は事足りますが、コンパイル不可の環境では、本スクリプトが有用です。

さらに、rgxg を使えば、CIDR表記IPセグメントを正規表現に変換してくれることもわかりました。
参照：[正規表現ジェネレーターコマンド『rgxg』を使ってみる](https://orebibou.com/2016/02/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%BC%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%80%8Ergxg%E3%80%8F%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6/)
rgxg もコンパイル、インストールが必要です。

ご意見、改善点などありましたら、ご遠慮なく仰ってください。