パケットをキャプチャした結果を日付別に分類・圧縮するシェルスクリプト

書いた。初めてシェルスクリプトを1から書いた。割と大変だった。要tcpdump

2009/6/16 23:55 追記

ブクマが付いてて驚いた。自分用だからいいやとてきとーに書いたのを後悔。いくつかバグを修正しました。あとちょっとした説明を書いてみた。

ディレクトリ構成

.
-- capture.sh (シェルスクリプト)
-- filter.txt (パケットフィルタリングの条件を書いて置く)
-- nohup.out (シェルスクリプトの標準出力が保存される。自動的に生成される)
`-- packet (ログが保存されるディレクトリ。自動的に生成される) |-- 20090615.tar.bz2 (6/15のログを圧縮したもの) |-- 20090616 (6/16のログ。) | |-- packet.log.000 (パケットログ) | |-- packet.log.001 | |-- (中略) | |-- packet.log.150 | `-- packet_message.log (tcpdumpが出力するメッセージ) `-- 20090617 (6/17のログ) `-- (内容略)

こんな感じでログが保存されていきます。毎日日付が変わると,その日のログ用のディレクトリが作成され,新しいログはそこに保存されるようになります。
上の図は,6/17になった直後のディレクトリ構造を表していて,このあと20090616.tar.bz2が自動的に生成され,ディレクトリ20090616は削除されます。この圧縮処理ですが,1日分のログが数GBあるので結構時間がかかります。本当は日付が変わった直後じゃなくて人気が無くなる午前4時くらいにやった方がよいのかも。
ちなみにパケットのログは同じ文字列の繰り返しが多いためか,圧縮すると結構容量は小さくなります (1.5GB→431MB, 2.4GB→601MBで大体4倍の圧縮率)。

ソースコード

#!/bin/bash

message()
{
        echo "$(date +'%Y-%m-%d %T') " $@
}
touch_dir()
{
        if [ ! -d $1 ]
        then
                message "create_dir $1"
                mkdir $1
        fi
}

log_dir="packet"
touch_dir $log_dir

set_dir()
{
        dir_name="$(date +%Y%m%d)"
        dir_path="$log_dir/$dir_name"
}
kill_proc()
{
        message "kill process [$prev_ps]"
        if [ $1 != -1 ] && kill -0 $1 2> /dev/null
        then
                kill -s TERM $1
                wait $1
                message "terminate process [$1]"
        else
                message "process [$1] is already terminated"
        fi
}
when_term()
{
        kill_proc $prev_ps
        kill_proc $running_ps
        message "exit"
}


running_ps=-1
running_dir=""
running_path=""

trap 'when_term' CHLD INT TERM

while :
do
        set_dir
        # wait until dir_name is change
        until [ $dir_name != "$running_dir" ]
        do
                sleep 1m
                set_dir
        done

        # start new process with new dir_name
        prev_ps=$running_ps
        prev_dir=$running_dir
        prev_path=$running_path

        touch_dir $dir_path
        rename .log .old.log $dir_path/*

        message "start new process [dir: $dir_name]"
        /usr/sbin/tcpdump -F"filter.txt" -X -s 200 -i eth0 2> $dir_path/packet_message.log | split -C 10485760 -a 3 -d - $dir_path/packet.log. &

        running_ps=$!
        running_dir=$dir_name
        running_path=$dir_path
        message "running [$running_ps]"

        # kill previous process if it exists
        if [ $prev_ps -ne -1 ]
        then
                kill_proc $prev_ps

                # archive previous logs
                message "archive old [$prev_dir]"
                tar jcf "$log_dir/$prev_dir.tar.bz2" $prev_path

                # delete previous logs
                rm -rf "$prev_path"
        fi
done

以下のように実行する。

# nohup ./capture.sh &

このシェルスクリプトは意図的に終了させない限り動き続けます。終了させるときはプロセス番号を探してきて,kill $process_number してください。

バグとか

TERMのシグナルを受け取ると,子プロセスを終了し,メッセージを出力してからスクリプト本体が終了する,なんて風に書いたつもりなんですが,メッセージがなぜか出力されません。子プロセスはきちんと終了できているみたいです。nohupをつかって起動させている関係なのかもしれませんがよくわかりません。つかデバッグが難しくて原因が特定できません。まあ基本的に連続稼働させるものでkillするのはスクリプトを書き換えた時ぐらいなのであまり問題にはならないかと思います。