拡張機能として実装されているNPF(NetBSD Packet Filter)のログ機能のソースコードを眺めてみる

[NetBSD Advent Calendar 2022](https://qiita.com/advent-calendar/2022/netbsd) 15日目の記事です。今日はNPF(NetBSD Packet Filter)において、拡張機能として実装されているログ機能のソースコードを眺めてみます。

## NPFのログ機能

[12日目の記事](https://qiita.com/furandon_pig/items/c41e59209938534e377f)で紹介したように、NPFでのログ機能は拡張機能として実装されています。フィルタルールの `procedure` としてログ出力を宣言しておき、マッチしたルールで `apply` することでログに記録されるのでした(この例では `/var/log/npflog0.pcap` ファイルに記録されます)。

```
procedure "log" {
    log: npflog0
}

group "icmp rule" {
  block in  final on $ext_if proto icmp all apply "log"
  block out final on $ext_if proto icmp all apply "log"
}
```

## 拡張機能としてのログ機能

拡張機能として実装されていることもあり、NPFのログ機能は[src/sys/modules/npf_ext_log](https://github.com/NetBSD/src/tree/trunk/sys/modules/npf_ext_log)でカーネルモジュールとしてビルドされます。

ロードされているカーネルモジュールを見てみると、たしかに `npf_ext_log` というモジュールが存在しています。
( `npf_ext_rndblock` というモジュールも気になりますね…)

```sh
$ modstat | egrep 'NAME|npf'
NAME                       CLASS    SOURCE   FLAG  REFS    SIZE REQUIRES
if_npflog                  driver   builtin  -        0       - -
npf                        misc     builtin  -        4       - bpf
npf_alg_icmp               misc     builtin  -        0       - npf
npf_ext_log                misc     builtin  -        0       - npf
npf_ext_normalize          misc     builtin  -        0       - npf
npf_ext_rndblock           misc     builtin  -        0       - npf
```

### ログ機能のソースコード

ソースコードの実体は[src/sys/net/npf/npf_ext_log.c](https://github.com/NetBSD/src/blob/trunk/sys/net/npf/npf_ext_log.c)にあり、200行にも満たないコンパクトな実装となっています。

```sh
$ wc -l /usr/src/sys/net/npf/npf_ext_log.c
     194 /usr/src/sys/net/npf/npf_ext_log.c
```

実装の中身を見て行きましょう。カーネルモジュールということもあり、まずはモジュールがロードされる個所から読んでみます。前に[NetBSDのカーネルモジュールサンプルを試してみる](https://qiita.com/furandon_pig/items/647c21cbe21eeb3aafc2)で解説したように、[modload(8)](https://man.netbsd.org/modload.8)でモジュールをロードすると、ソースコード的には `case` 文の中の `MODULE_CMD_INIT` 部分の処理が走ります。ここでは `npf_ext_log_init()` が呼ばれています。

```c
176 static int
177 npf_ext_log_modcmd(modcmd_t cmd, void *arg)
178 {
179     npf_t *npf = npf_getkernctx();
180
181     switch (cmd) {
182     case MODULE_CMD_INIT:
183         return npf_ext_log_init(npf);
184     case MODULE_CMD_FINI:
185         return npf_ext_log_fini(npf);
186         break;
187     case MODULE_CMD_AUTOUNLOAD:
188         return npf_autounload_p() ? 0 : EBUSY;
189     default:
190         return ENOTTY;
191     }
```

`npf_ext_log_init()` では、管理用の構造体 `npf_ext_ops_t` に必要な関数ポインタをセットします。 `npf_log_ctor()` と `npf_log_dtor()` はNPFのログ出力に必要なメモリ確保などの処理を行っており、実際のログ出力処理は `npf_log()` で行われます。

```c
155 __dso_public int
156 npf_ext_log_init(npf_t *npf)
157 {
158     static const npf_ext_ops_t npf_log_ops = {
159         .version    = NPFEXT_LOG_VER,
160         .ctx        = NULL,
161         .ctor       = npf_log_ctor,
162         .dtor       = npf_log_dtor,
163         .proc       = npf_log
164     };
165     npf_ext_log_id = npf_ext_register(npf, "log", &npf_log_ops);
166     return npf_ext_log_id ? 0 : EEXIST;
167 }
```

`npf_log()` を見てみます。関数の中ではログに記録するパケットについて受信・送出の判定などを行った後、 `bpf_mtap2()` でログ出力(pcapファイルへの出力)を行っています。こうやって見ると、( `buf` や `mbuf` などの扱い方は理解しておく必要がありますが)比較的シンプルな処理でログ機能が実装されていることが分かります。

```c
 82 static bool
 83 npf_log(npf_cache_t *npc, void *meta, const npf_match_info_t *mi, int *decision)
 84 {
 85     struct mbuf *m = nbuf_head_mbuf(npc->npc_nbuf);
 86     const npf_ext_log_t *log = meta;
 87     struct psref psref;
 88     ifnet_t *ifp;
 89     struct npfloghdr hdr;
 ...
132     KERNEL_LOCK(1, NULL);
133
134     /* Find a pseudo-interface to log. */
135     ifp = if_get_byindex(log->if_idx, &psref);
136     if (ifp == NULL) {
137         /* No interface. */
138         KERNEL_UNLOCK_ONE(NULL);
139         return true;
140     }
141
142     ifp->if_opackets++;
143     ifp->if_obytes += m->m_pkthdr.len;
144     if (ifp->if_bpf) {
145         /* Pass through BPF. */
146         bpf_mtap2(ifp->if_bpf, &hdr, NPFLOG_HDRLEN, m, BPF_D_OUT);
147     }
148     if_put(ifp, &psref);
149
150     KERNEL_UNLOCK_ONE(NULL);
151
152     return true;
153 }
```

## まとめ

NPFのログ出力機能の実装をソースコードから眺めてみました。拡張機能ということもあり、NetBSDではカーネルモジュールの形で実装されています。ソースコードの分量や実装内容のシンプルさから、このコードを元に簡単なNPF拡張機能を作成してみるのも勉強になりそうです。