NetBSD Advent Calendar 2022 20日目の記事です。今日はNPF(NetBSD Packet Filter)の拡張機能を作成する手順を、ソースコードから調べてみようと思います。
NPF拡張機能のソースコード
18日目の記事で紹介した、NPFのrndblock拡張機能の実装を例にソースコードを見てみます。
ソースツリーに対し、"npf"と"rndblock"という文字列を含むファイル名で検索をかけてみます。どうやら npfext_rndblock.c
と npf_ext_rndblock.c
という2つのC言語ソースファイルが存在しているようです。
$ cd /usr/src
find . -type f | grep npf | grep rndblock | grep -v CVS
./lib/npf/ext_rndblock/Makefile
./lib/npf/ext_rndblock/npfext_rndblock.c
./lib/npf/ext_rndblock/shlib_version
./sys/modules/npf_ext_rndblock/Makefile
./sys/net/npf/npf_ext_rndblock.c
それぞれのファイルの役割はどうなっているのでしょうか? Makefile
があるので実際にビルドしてみます。
まずは /usr/src/lib/npf/ext_rndblock/
で make
を実行してみます。 /usr/src/lib/libnpf/
の内容(オブジェクトファイル)を参照するため、あらかじめ libnpf
ディレクトリで make
を実行しておきます。
# cd /usr/src/lib/libnpf/
# make
...
# cd /usr/src/lib/npf/ext_rndblock/
# ls
CVS/ Makefile npfext_rndblock.c shlib_versio
# make
...
# ls -l
total 42
drwxrwxr-x 2 root wsrc 512 Aug 5 01:10 CVS/
-rw-rw-r-- 1 root wsrc 112 Mar 11 2013 Makefile
lrwxr-xr-x 1 root wsrc 19 Dec 21 13:16 ext_rndblock.so@ -> ext_rndblock.so.0.0
lrwxr-xr-x 1 root wsrc 19 Dec 21 13:16 ext_rndblock.so.0@ -> ext_rndblock.so.0.0
-rwxr-xr-x 1 root wsrc 8296 Dec 21 13:16 ext_rndblock.so.0.0*
-rw-r--r-- 1 root wsrc 12916 Dec 21 13:16 ext_rndblock.so.0.map
-rw-r--r-- 1 root wsrc 3792 Dec 21 13:16 ext_rndblock_pic.a
-rw-rw-r-- 1 root wsrc 2959 Sep 29 2018 npfext_rndblock.c
-rw-r--r-- 1 root wsrc 3496 Dec 21 13:16 npfext_rndblock.pico
-rw-rw-r-- 1 root wsrc 80 Dec 10 2012 shlib_version
make
が完了すると、 ext_rndblock.so.0.0
という共有ライブラリが作成されています。
もう一方の /usr/src/sys/modules/npf_ext_rndblock/
でも make
してみます。
# cd /usr/src/sys/modules/npf_ext_rndblock/
# ls
CVS/ Makefile
# make
# compile npf_ext_rndblock/npf_ext_rndblock.o
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-gcc -O2 -g -std=gnu99 -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wno-sign-compare -Wsystem-headers -Wno-traditional -Wa,--fatal-warnings -Wreturn-type -Wswitch -Wshadow -Wcast-qual -Wwrite-strings -Wextra -Wno-unused-parameter -Wno-sign-compare -Wold-style-definition -Wsign-compare -Wformat=2 -Wno-format-zero-length -Werror -ffreestanding -fno-strict-aliasing -Wno-pointer-sign -mno-red-zone -mno-mmx -mno-sse -mno-avx -msoft-float -mcmodel=kernel -fno-omit-frame-pointer -I/usr/src/common/include --sysroot=/ -I/usr/src/sys/external/bsd/libnv/dist -I/usr/src/common/include -nostdinc -I. -I/usr/src/sys/modules/npf_ext_rndblock -isystem /usr/src/sys -isystem /usr/src/sys/arch -isystem /usr/src/sys/../common/include -D_KERNEL -D_LKM -D_MODULE -DSYSCTL_INCLUDE_DESCR -c /usr/src/sys/net/npf/npf_ext_rndblock.c
/usr/src/tooldir.NetBSD-9.3-amd64/bin/nbctfconvert -L VERSION npf_ext_rndblock.o
# link npf_ext_rndblock/npf_ext_rndblock.kmod
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-gcc --sysroot=/ -Wl,--warn-shared-textrel -Wl,-z,relro -nostdlib -r -Wl,-T,/usr/src/sys/../sys/modules/xldscripts/kmodule,-d -Wl,-Map=npf_ext_rndblock.kmod.map -o npf_ext_rndblock.kmod npf_ext_rndblock.o
/usr/src/tooldir.NetBSD-9.3-amd64/bin/nbctfmerge -t -L VERSION -o npf_ext_rndblock.kmod npf_ext_rndblock.o
#
# ls -l
total 70
drwxrwxr-x 2 root wsrc 512 Aug 5 01:10 CVS/
-rw-rw-r-- 1 root wsrc 245 Sep 29 2018 Makefile
lrwxr-xr-x 1 root wsrc 31 Dec 21 13:19 amd64@ -> /usr/src/sys/arch/amd64/include
lrwxr-xr-x 1 root wsrc 30 Dec 21 13:19 i386@ -> /usr/src/sys/arch/i386/include
lrwxr-xr-x 1 root wsrc 31 Dec 21 13:19 machine@ -> /usr/src/sys/arch/amd64/include
-rw-r--r-- 1 root wsrc 21448 Dec 21 13:19 npf_ext_rndblock.kmod
-rw-r--r-- 1 root wsrc 1919 Dec 21 13:19 npf_ext_rndblock.kmod.map
-rw-r--r-- 1 root wsrc 41768 Dec 21 13:19 npf_ext_rndblock.o
lrwxr-xr-x 1 root wsrc 29 Dec 21 13:19 x86@ -> /usr/src/sys/arch/x86/include
こちらでは npf_ext_rndblock.kmod
が生成されます。パケットフィルタなので、カーネル内で動作するはずであり、 npf_ext_rndblock.kmod
というカーネルモジュールが必要なのは理解できます。しかし、もう一方の ext_rndblock.so.0.0
は何のために利用されるのでしょうか?
実際にnpfext_rndblock.cのソースコードを見てみましょう。
/usr/src/lib/npf/ext_rndblock/npfext_rndblock.c
58 int
59 npfext_rndblock_param(nl_ext_t *ext, const char *param, const char *val)
60 {
61 enum ptype { PARAM_U32 };
62 static const struct param {
63 const char * name;
64 enum ptype type;
65 signed long min;
66 signed long max;
67 } params[] = {
68 { "mod", PARAM_U32, 1, LONG_MAX },
69 { "percentage", PARAM_U32, 1, 9999 },
70 };
71
72 if (val == NULL) {
73 return EINVAL;
74 }
75 for (unsigned i = 0; i < __arraycount(params); i++) {
76 const char *name = params[i].name;
77 long ival;
78
79 if (strcmp(name, param) != 0) {
80 continue;
81 }
82
83 /*
84 * Note: multiply by 100 and convert floating point to
85 * an integer, as 100% is based on 10000 in the kernel.
86 */
87 ival = (i == 1) ? atof(val) * 100 : atol(val);
88 if (ival < params[i].min || ival > params[i].max) {
89 return EINVAL;
90 }
91 assert(params[i].type == PARAM_U32);
92 npf_ext_param_u32(ext, name, ival);
93 return 0;
94 }
95
96 /* Invalid parameter, if not found. */
97 return EINVAL;
98 }
"mod" や "percentage" といったパラメータらしきものがあります。 rndblock
のフィルタ設定は以下のように行っており、設定ファイルに指定したパラメータ値をこの npfext_rndblock_param()
で取得しています。つまり、 ext_rndblock.so.0.0
は指定されたパラメータ値をユーザランド側で取得するという役割があるようです。
procedure "rndblock" {
rndblock: percentage 50.0
}
さらにコードを見てみると、 npfext_rndblock_construct()
という関数があります。最終的に npf_ext_construct("rndblock")
を呼び出す形になっています。
51 nl_ext_t *
52 npfext_rndblock_construct(const char *name)
53 {
54 assert(strcmp(name, "rndblock") == 0);
55 return npf_ext_construct(name);
56 }
npf_ext_construct()
は/usr/src/lib/libnpf/npf.cで以下のように定義されています。 nvlist_add_string()
がポイントなのでしょうが、このコードだけだとどういった処理が走るのか分からないですね…。
625 /*
626 * NPF EXTENSION INTERFACE.
627 */
628
629 nl_ext_t *
630 npf_ext_construct(const char *name)
631 {
632 nl_ext_t *ext;
633
634 ext = malloc(sizeof(*ext));
635 if (!ext) {
636 return NULL;
637 }
638 ext->ext_dict = nvlist_create(0);
639 nvlist_add_string(ext->ext_dict, "name", name);
640 return ext;
641 }
視点を変えて、拡張機能の機能名(この場合は"rndblock")から調べてみましょう。 rndblock
カーネルモジュールの実装である/usr/src/sys/net/npf/npf_ext_rndblock.cを見ると、 npf_ext_rndblock.kmod
が modload
コマンドでロードされた際に、 npf_ext_rndblock_init()
関数が呼ばれています。
137 __dso_public int
138 npf_ext_rndblock_init(npf_t *npf)
139 {
140 static const npf_ext_ops_t npf_rndblock_ops = {
141 .version = NPFEXT_RNDBLOCK_VER,
142 .ctx = NULL,
143 .ctor = npf_ext_rndblock_ctor,
144 .dtor = npf_ext_rndblock_dtor,
145 .proc = npf_ext_rndblock
146 };
147
148 /*
149 * Initialize the NPF extension. Register the "rndblock" extension
150 * calls (constructor, destructor, the processing routine, etc).
151 */
152 npf_ext_rndblock_id = npf_ext_register(npf, "rndblock",
153 &npf_rndblock_ops);
154 return npf_ext_rndblock_id ? 0 : EEXIST;
155 }
...
171 static int
172 npf_ext_rndblock_modcmd(modcmd_t cmd, void *arg)
173 {
174 npf_t *npf = npf_getkernctx();
175
176 switch (cmd) {
177 case MODULE_CMD_INIT:
178 return npf_ext_rndblock_init(npf);
179 case MODULE_CMD_FINI:
180 return npf_ext_rndblock_fini(npf);
181 case MODULE_CMD_AUTOUNLOAD:
182 /* Allow auto-unload only if NPF permits it. */
183 return npf_autounload_p() ? 0 : EBUSY;
184 default:
185 return ENOTTY;
186 }
187 return 0;
188 }
static const npf_ext_ops_t npf_rndblock_ops
で各処理の実体となる関数ポインタをセットしています。 npf_ext_ops_t.proc
に設定されている npf_ext_rndblock()
が rndblock
機能のメインとなる処理になっています。それ以外の .ctor
や .dtor
はどんな役割を持っているのでしょうか?
.ctor
に設定されている、 npf_ext_rndblock_ctor()
を見てみます。どうやら"ctor"は"constructor"の略語のようです。ただ、この関数はカーネルモジュール内から直接呼び出している個所がありません…。
65 /*
66 * npf_ext_rndblock_ctor: a constructor to parse and store any parameters
67 * associated with a rule procedure, which is being newly created.
68 */
69 static int
70 npf_ext_rndblock_ctor(npf_rproc_t *rp, const nvlist_t *params)
71 {
72 npf_ext_rndblock_t *meta;
73
74 /*
75 * Allocate and a associate a structure for the parameter
76 * and our meta-data.
77 */
78 meta = kmem_zalloc(sizeof(npf_ext_rndblock_t), KM_SLEEP);
79 meta->mod = dnvlist_get_number(params, "mod", 0);
80 meta->percentage = dnvlist_get_number(params, "percentage", 0);
81 npf_rproc_assign(rp, meta);
82
83 return 0;
84 }
npf_ext_rndblock_init()
のコードをよく見ると、以下のようなコメントがあります。これによると、 rndblock
拡張機能が「登録」された時にコンストラクタ処理が走る、と書かれています。つまり、カーネルモジュールをロードした時ではなく、パケットフィルタのルール設定時に rndblock
を使う、としたときにコンストラクタが呼ばれ、フィルタのルール設定のパースは ext_rndblock.so.0.0
内で行われるため、 npf_ext_rndblock_ctor()
はユーザランド側から呼び出されるという挙動になっています。
137 __dso_public int
138 npf_ext_rndblock_init(npf_t *npf)
139 {
...
148 /*
149 * Initialize the NPF extension. Register the "rndblock" extension
150 * calls (constructor, destructor, the processing routine, etc).
151 */
152 npf_ext_rndblock_id = npf_ext_register(npf, "rndblock",
153 &npf_rndblock_ops);
154 return npf_ext_rndblock_id ? 0 : EEXIST;
155 }
そう考えると、ユーザランド側で npf_ext_construct("rndblock")
となる呼び出しになるというのも納得ができます。
(カーネルモジュールのロード後に、ユーザランドからのトリガーでコンストラクタを呼び出す、というのは一瞬「?」となる処理フローではありますが…)
まとめ
NPFの rndblock
のソースコードを例にして、NPF拡張機能の実装を読み解いてみました。必要最低限の処理フローは見えてきたので、このノウハウをもとに簡単な拡張機能を作れそうです。