LoginSignup
1
0

More than 1 year has passed since last update.

NPF(NetBSD Packet Filter)の拡張機能の実装をソースコードから読み解いてみる

Posted at

NetBSD Advent Calendar 2022 20日目の記事です。今日はNPF(NetBSD Packet Filter)の拡張機能を作成する手順を、ソースコードから調べてみようと思います。

NPF拡張機能のソースコード

18日目の記事で紹介した、NPFのrndblock拡張機能の実装を例にソースコードを見てみます。

ソースツリーに対し、"npf"と"rndblock"という文字列を含むファイル名で検索をかけてみます。どうやら npfext_rndblock.cnpf_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.kmodmodload コマンドでロードされた際に、 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拡張機能の実装を読み解いてみました。必要最低限の処理フローは見えてきたので、このノウハウをもとに簡単な拡張機能を作れそうです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0