NetBSD Advent Calendar 2022 15日目の記事です。今日はNPF(NetBSD Packet Filter)において、拡張機能として実装されているログ機能のソースコードを眺めてみます。
NPFのログ機能
12日目の記事で紹介したように、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でカーネルモジュールとしてビルドされます。
ロードされているカーネルモジュールを見てみると、たしかに npf_ext_log
というモジュールが存在しています。
( npf_ext_rndblock
というモジュールも気になりますね…)
$ 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にあり、200行にも満たないコンパクトな実装となっています。
$ wc -l /usr/src/sys/net/npf/npf_ext_log.c
194 /usr/src/sys/net/npf/npf_ext_log.c
実装の中身を見て行きましょう。カーネルモジュールということもあり、まずはモジュールがロードされる個所から読んでみます。前にNetBSDのカーネルモジュールサンプルを試してみるで解説したように、modload(8)でモジュールをロードすると、ソースコード的には case
文の中の MODULE_CMD_INIT
部分の処理が走ります。ここでは npf_ext_log_init()
が呼ばれています。
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()
で行われます。
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
などの扱い方は理解しておく必要がありますが)比較的シンプルな処理でログ機能が実装されていることが分かります。
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拡張機能を作成してみるのも勉強になりそうです。