ipfのパケットフィルタログを集計してみる

今日から[NetBSD Advent Calendar 2019](https://qiita.com/advent-calendar/2019/netbsd)が始まります。1日目はipfのパケットフィルタログを集計する手順を紹介してみようと思います。

## ipf

NetBSDには[ipf](https://netbsd.gw.com/cgi-bin/man-cgi?ipf++NetBSD-8.1)というパケットフィルタ機能が搭載されています。フィルタ状況のログは以下のように[ipmon](https://netbsd.gw.com/cgi-bin/man-cgi?ipmon++NetBSD-8.1)コマンドで確認することができます(IPアドレス等は `AAA` や `BBB` に置き換えています)。

```shell
$ sudo ipmon
...
01/12/2019 11:34:41.268129 pppoe0 @0:7 b AAA.AAA.AAA.AAA,58501 -> BBB.BBB.BBB.BBB,22 PR tcp len 20 44 -S IN
...
```

この例では、 `AAA.AAA.AAA.AAA` からssh(22番ポート)へのアクセスをブロックしたというログになっています。このログの内容をうまいこと集計できるようにしてみます。

## 集計の前に、まずはipfの設定手順を概観する

今回紹介するipfのログ集計は、以前のアドベントカレンダーにおいて、[NetBSD+Raspberry PiでPCルータを作る(ユーザランド設定編)](https://qiita.com/furandon_pig/items/a6f53de431eb19ea2154#%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%AB%E3%83%BC%E3%82%BF%E3%81%AE%E6%A7%8B%E6%88%90)の記事で紹介したネットワーク構成の環境で行っています。

```:ネットワーク構成(再掲)
 ご家庭LAN-------+------ 192.168.10.0/24
                |
                |usmsc0
        +-------+--------+
        |     NetBSD     |
        | (Raspberry Pi) |
        +-------+--------+
                |pppoe0(axe0)
                |
           -----+-------インターネット
```

前述の記事ではipfのフィルタ設定内容を紹介していなかったので、ここで簡単に設定内容を紹介します。

まずは `/etc/rc.conf` でipfを有効化します。

```:/etc/rc.conf
ipfilter=YES
```

`/etc/ipf.conf` には、パケットフィルタルールを列挙する形で設定します。先述した `ipmon` のsshをフィルタするルールは `block in log quick on pppoe0 from any to pppoe0/peer port = ssh` になります。

```:/etc/ipf.conf
block in log quick on pppoe0 proto icmp from any to pppoe0/peer icmp-type echo

block in log quick on pppoe0 from any to pppoe0/peer port = 25  # mail
block in log quick on pppoe0 from any to pppoe0/peer port = 53  # DNS
block in log quick on pppoe0 from any to pppoe0/peer port = 67  # DHCP
block in log quick on pppoe0 from any to pppoe0/peer port = ftp
block in log quick on pppoe0 from any to pppoe0/peer port = www
block in log quick on pppoe0 from any to pppoe0/peer port = ssh
block in log quick on pppoe0 from any to pppoe0/peer port = ldap
block in log quick on pppoe0 from any to pppoe0/peer port = ldaps
block in log quick on pppoe0 from any to pppoe0/peer port = 443
```

## ipfのログを集計する

ipfの設定手順とipmonによるパケットフィルタログの確認手順が把握できたので、いよいよipfのログを集計してみます。とりあえず、集計に必要な項目として以下の内容をログから抽出できればよさそうです。

 * タイムスタンプ
 * 送信元IPアドレス
 * 受信先IPアドレス(自IPアドレス)
 * 送信元ポート番号
 * 受信先ポート番号(自サービスのポート番号)
 * block/pass状態

### 集計用データベースを作成する

ログ集計を柔軟に行いたいので、これらの項目をデータベース(DB)に投入し、SQLで集計できるようにしておきます。DBは手軽に用意できる[SQLite](https://www.sqlite.org/index.html)を使用し、集計用のテーブル定義を以下のように作成します。

```sql:ipmon_table.sql
CREATE TABLE ipmon(
        timestamp VARCHAR(32) PRIMARY KEY, -- タイムスタンプ
        sip VARCHAR(16) NOT NULL,          -- 送信元IPアドレス
        dip VARCHAR(16) NOT NULL,          -- 受信先IPアドレス(自IPアドレス)
        sport VARCHAR(5) NOT NULL,         -- 送信元ポート番号
        dport VARCHAR(5) NOT NULL,         -- 受信先ポート番号(自サービスのポート番号)
        status VARCHAR(1) NOT NULL         -- block/pass状態
);
```


以下の手順でテーブルを作成します。

```shell
$ sqlite3 ipmon.sqlite3
sqlite> .read ipmon_table.sql
```

### ipmonの内容からデータを抽出する

`ipmon` コマンドが出力するログデータを処理したいので、出力内容を以下のように適当なファイルにリダイレクトしておきます。

```shell
$ sudo bash
# ipmon | tee /tmp/_ipmon.log
```

ログファイルが `/tmp/_ipmon.log` に書き出されるようになったので、以下のシェルスクリプトでログ内容をDBに投入するようにします。

```shell:ipmon_analyze.sh
#!/bin/sh

# ipmonのフォーマットを変換する。
# before) 03/11/2019 02:10:58.901477 pppoe0 @0:5 b 74.82.47.10,60311
# after)  153.232.11.74,21 PR tcp len 20 40 -S IN

IPMON_LOG=/tmp/_ipmon.log

cat <<_EOF
INSERT INTO
    ipmon(timestamp,sip,dip,sport,dport, status)
  VALUES
_EOF

for line in `cat $IPMON_LOG | sed -e "s/ /_SP_/g" | grep '/11/2019'`
do
        timestamp=`echo $line | sed -e "s/_SP_/ /g" -e "s/,/ /g" | awk '{ print $1" "$2 }'`
        sip=`      echo $line | sed -e "s/_SP_/ /g" -e "s/,/ /g" | awk '{ print $6 }'`
        dip=`      echo $line | sed -e "s/_SP_/ /g" -e "s/,/ /g" | awk '{ print $9 }'`
        sport=`    echo $line | sed -e "s/_SP_/ /g" -e "s/,/ /g" | awk '{ print $7 }'`
        dport=`    echo $line | sed -e "s/_SP_/ /g" -e "s/,/ /g" | awk '{ print $10 }'`
        status=`   echo $line | sed -e "s/_SP_/ /g" -e "s/,/ /g" | awk '{ print $5 }'`

        echo -n "('$timestamp','$sip','$dip','$sport','$dport','$status'),"
done | tee out.txt | sed -e "s/,$/;/"
```

上記のシェルスクリプトを実行し、DBにデータを投入します。

```shell 
$ ./ipmon_analyze.sh > out.sql
$ sqlite3 ipmon.sqlite3
sqlite> .read out.sql
```

## SQLで集計する

DBにデータを投入し、集計準備が整いました。あとはSQLを投入して集計結果を得るだけです。
集計結果を見やすくするため、あらかじめ以下の設定をSQLiteに投入しておきます。

```shell
$ sqlite3 ipmon.sqlite3
sqlite> -- SELECT結果でのカラム名表示と見やすい形に整形する設定。
sqlite> .headers on
sqlite> .mode column
```

### アクセスをブロックしたポート番号毎に集計する

アクセスをブロックしたポート番号毎に集計してみます。具体的なSQLは以下になります。

```sql
SELECT
    MAX(status) AS status,
    MAX(dport) AS protocol,
    COUNT(status) as count
  FROM
    ipmon
  GROUP BY
    dport
  ORDER BY
    count DESC
  ;
```

集計結果は以下です。ssh(22),http(80),https(443)等へのアクセスが多くブロックされていることが把握できます。

``` 
status      protocol    count     
----------  ----------  ----------
b           22          64        
b           80          32        
b           443         22        
b           icmp        16        
b           389         12        
b           5900        11        
b           53          9         
...
```

ポート番号389はパッと頭に浮かばなかったので `/etc/services` から調べてみました。どうやらLDAPへのアクセスのようですね。

```shell
$ grep -w 389 /etc/services 
ldap                389/tcp    # Lightweight Directory Access [Tim_Howes]                                        [Tim_Howes]
ldap                389/udp    # Lightweight Directory Access [Tim_Howes]                                        [Tim_Howes]
```

### 送信元IPアドレス毎に集計する

次に送信元IPアドレス毎のフィルタ結果を集計してみます。集計用SQLは以下になります。

```sql
SELECT
    MAX(status) AS status,
    MAX(dport) AS protocol,
    MAX(sip) as sip,
    COUNT(status) as count
  FROM
    ipmon
  GROUP BY
    sip
  ORDER BY
    count DESC
  ;
```

集計結果は以下です(IPアドレスは一部マスクしています)。個人用のPCルータへのアクセスなので、それほど多くはないにせよ同一のIPアドレスから何度かsshでの接続が試行されているようです。

```
status      protocol    sip            count
----------  ----------  -------------  ----------
b           22          80.XXX.XXX.XXX  12
b           22          185.XXX.XXX.XXX  5
b           53          146.XXX.XXX.XXX   4
b           22          185.XXX.XXX.XXX  4
b           22          88.XXX.XXX.XXX    4
...
```

## まとめ

ipfでのパケットフィルタ結果を集計する手順を紹介してみました。パケットフィルタの結果を定期的に集計できるようにしておくと、特定ポートへのアクセス増加といった日常と異なる状態の検出にも応用できそうです。
(あと単にパケットフィルタ結果を集計するだけでも楽しいという感じもします...)