NetBSD Advent Calendar 2022 25日目、いよいよ最終日の記事です。今日はNPF(NetBSD Packet Filter)で簡単なパケットカウントを行う拡張機能を作成してみようと思います。
20日目と21日目、そして23日目の記事でNPFの拡張機能の構造と実装を調べていました。今回はこれらの内容を元に、簡単な拡張機能のサンプルを作成してみます。
- NPF(NetBSD Packet Filter)の拡張機能の実装をソースコードから読み解いてみる
- NetBSDでname/valueリストを扱うlibnvを試してみる
- NPF(NetBSD Packet Filter)で拡張機能のパラメータ値がカーネル側に渡されるまでを追いかけてみる
パケットカウント拡張機能を作成する
パケットカウントのサンプルとして、 counter
という拡張機能を作成してみます。フィルタルール側でカウントアップ上限値を設定しておき、カーネル内のパケットフィルタ処理でルールにマッチしたパケットをカウントアップし、上限値を超えたらカウントをクリアする、という挙動にしてみます。
ソースコードの構成
必要なソースコードの構成は以下になります。rndblock
拡張機能のソースコードをコピーしてひな型とし、必要な機能を追加してゆくのがお手軽です。
/usr
`-src
`-sys
| `-modules
| `-npf_ext_counter # カーネルモジュールをビルドするディレクトリ
| `-Makefile
`-net
| `-npf
| |-npf_ext_counter.c # カーネル側のパケットカウント機能の実体
| `-npfkern.h # パケットカウントに用いる関数プロトタイプを追記する
`-lib
`-npf
`-ext_counter # ユーザランド側でのパケットカウント機能の実体
|-Makefile
|-npfext_counter.c
`-shlib_version
カーネル側のコード
上記のソースコードを順に見て行きます。カーネルモジュールとしての npf_ext_counter.kmod
をビルドするMakefileは以下になります。これは単に rndblock
からコピーしてきたMakefileについて、"rndblock"を"counter"に文字列置換するだけでOKです。
/usr/src/sys/modules/npf_ext_counter/Makefile:
.include "../Makefile.inc"
.PATH: ${S}/net/npf
KMOD= npf_ext_counter
SRCS= npf_ext_counter.c
CPPFLAGS+= -I${NETBSDSRCDIR}/sys/external/bsd/libnv/dist
.include <bsd.kmodule.mk>
カーネル側でのパケットカウント処理の実体である npf_ext_counter.c
は以下になります。こちらも rndblock
のソースコードを元に、パケットカウント処理に置き換えた形になります。
/usr/src/sys/net/npf/npf_ext_counter.c:
#include <sys/types.h>
#include <sys/cprng.h>
#include <sys/atomic.h>
#include <sys/module.h>
#include <sys/kmem.h>
#endif
#include "npf_impl.h"
// NPF extension module definition and the identifier.
NPF_EXT_MODULE(npf_ext_counter, "");
#define NPFEXT_COUNTER_VER 1
static void * npf_ext_counter_id;
// パケットカウントしたデータを管理する構造体。
// coutnerでカウント数、maxcountでユーザランド側から渡されたカウント上限値を管理する。
typedef struct {
unsigned long counter;
unsigned long maxcount;
} npf_ext_counter_t;
// counter拡張機能のコンストラクタ
static int
npf_ext_counter_ctor(npf_rproc_t *rp, const nvlist_t *params)
{
npf_ext_counter_t *meta;
meta = kmem_zalloc(sizeof(npf_ext_counter_t), KM_SLEEP);
meta->counter = 0; // カウンタを初期化する。
meta->maxcount = dnvlist_get_number(params, "maxcount", 0); // パケットカウントの上限値。
npf_rproc_assign(rp, meta);
printf("(*) current count= %ld\n", meta->counter);
printf("(*) maxcount= %ld\n", meta->maxcount);
return 0;
}
// counter拡張機能のデストラクタ
static void
npf_ext_counter_dtor(npf_rproc_t *rp, void *meta)
{
kmem_free(meta, sizeof(npf_ext_counter_t));
}
/*
* npf_ext_counter: main routine implementing the extension functionality.
*/
static bool
npf_ext_counter(npf_cache_t *npc, void *meta, const npf_match_info_t *mi,
int *decision)
{
npf_ext_counter_t *counter = meta;
// SMP構成を考慮し、パケットのカウントはアトミックに行う。
atomic_inc_ulong_nv(&counter->counter);
if (counter->counter > counter->maxcount) {
// パケットカウントの上限値を超えた場合はカウンタをクリアする。
counter->counter = 0;
printf("counter clear!\n");
} else {
printf("current count= %ld\n", counter->counter);
}
return true;
}
__dso_public int
npf_ext_counter_init(npf_t *npf)
{
static const npf_ext_ops_t npf_counter_ops = {
.version = NPFEXT_COUNTER_VER,
.ctx = NULL,
.ctor = npf_ext_counter_ctor,
.dtor = npf_ext_counter_dtor,
.proc = npf_ext_counter
};
npf_ext_counter_id = npf_ext_register(npf, "counter",
&npf_counter_ops);
return npf_ext_counter_id ? 0 : EEXIST;
}
__dso_public int
npf_ext_counter_fini(npf_t *npf)
{
return npf_ext_unregister(npf, npf_ext_counter_id);
}
#ifdef _KERNEL
static int
npf_ext_counter_modcmd(modcmd_t cmd, void *arg)
{
npf_t *npf = npf_getkernctx();
switch (cmd) {
case MODULE_CMD_INIT:
printf("-=> ok\n");
return npf_ext_counter_init(npf);
case MODULE_CMD_FINI:
return npf_ext_counter_fini(npf);
case MODULE_CMD_AUTOUNLOAD:
/* Allow auto-unload only if NPF permits it. */
return npf_autounload_p() ? 0 : EBUSY;
default:
return ENOTTY;
}
return 0;
}
#endif
npfkern.h
は元からあるヘッダファイルですが、ここには今回作成する counter
拡張機能の実装となる関数のプロトタイプ宣言を追加します。
--- a/sys/net/npf/npfkern.h
+++ b/sys/net/npf/npfkern.h
@@ -100,6 +100,9 @@ int npf_ext_normalize_fini(npf_t *);
int npf_ext_rndblock_init(npf_t *);
int npf_ext_rndblock_fini(npf_t *);
+int npf_ext_counter_init(npf_t *);
+int npf_ext_counter_fini(npf_t *);
+
ユーザランド側のコード
ユーザランド側の処理を実装する npfext_counter.c
は以下になります。こちらも rndblock
のコードを元に、 maxcount
パラメータを受け取る形に変更しています。
/usr/src/lib/npf/ext_counter/npfext_counter.c:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <npf.h>
int npfext_counter_init(void);
nl_ext_t * npfext_counter_construct(const char *);
int npfext_counter_param(nl_ext_t *, const char *, const char *);
int
npfext_counter_init(void)
{
/* Nothing to initialise. */
return 0;
}
nl_ext_t *
npfext_counter_construct(const char *name)
{
assert(strcmp(name, "counter") == 0);
return npf_ext_construct(name);
}
int
npfext_counter_param(nl_ext_t *ext, const char *param, const char *val)
{
enum ptype { PARAM_U32 };
static const struct param {
const char * name;
enum ptype type;
signed long min;
signed long max;
} params[] = {
{ "maxcount", PARAM_U32, 1, 1000 }
};
if (val == NULL) {
return EINVAL;
}
for (unsigned i = 0; i < __arraycount(params); i++) {
const char *name = params[i].name;
long ival;
if (strcmp(name, param) != 0) {
continue;
}
/*
* Note: multiply by 100 and convert floating point to
* an integer, as 100% is based on 10000 in the kernel.
*/
ival = (i == 1) ? atof(val) * 100 : atol(val);
if (ival < params[i].min || ival > params[i].max) {
return EINVAL;
}
assert(params[i].type == PARAM_U32);
fprintf(stderr, "===> add param '%s', %ld\n", params[i].name, ival);
npf_ext_param_u32(ext, name, ival);
return 0;
}
/* Invalid parameter, if not found. */
return EINVAL;
}
npfext_counter.c
の Makefile
は以下になります。単に"ext_rndblock"を"ext_counter"に置き換えただけです。
MOD= ext_counter
.include "${.CURDIR}/../mod.mk"
shlib_version
はデバイスノードの情報を記載したファイルになります。これは単に rndblock
にある同名のファイルをコピーしてくればOKです。
# $NetBSD: shlib_version,v 1.1 2012/12/10 00:32:25 rmind Exp $
major=0
minor=0
これでパケットカウント拡張機能に必要なソースコードが用意できました。
パケットカウント拡張機能のビルド
ユーザランド側で使用する共有オブジェクトのビルド
ユーザランド側のソースコードは、 ext_counter.so
という共有オブジェクトとしてビルドします。 Makefile
は用意してあるので単に make
コマンドを実行するだけです。
# cd /usr/src/lib/npf/ext_counter
# ls
Makefile npfext_counter.c shlib_version
#
# make
# compile ext_counter/npfext_counter.pico
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-gcc -O2 -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 -fPIE --sysroot=/ -c -fPIC npfext_counter.c -o npfext_counter.pico
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-objcopy -x npfext_counter.pico
# build ext_counter/ext_counter_pic.a
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-ar crs ext_counter_pic.a `NM=/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-nm NM=/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-nm MKTEMP=/usr/src/tooldir.NetBSD-9.3-amd64/bin/nbmktemp /usr/src/tooldir.NetBSD-9.3-amd64/bin/nblorder npfext_counter.pico | /usr/src/tooldir.NetBSD-9.3-amd64/bin/nbtsort -q`
# build ext_counter/ext_counter.so.0.0
rm -f ext_counter.so.0.0
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-gcc -shared -Wl,-soname,ext_counter.so.0 -Wl,--warn-shared-textrel -Wl,-Map=ext_counter.so.0.map --sysroot=/ -Wl,--warn-shared-textrel -Wl,-z,relro -o ext_counter.so.0.0.tmp -Wl,-rpath,/lib -L=/lib -Wl,-x -Wl,--whole-archive ext_counter_pic.a -Wl,--no-whole-archive -L/usr/src/lib/libnpf -lnpf
mv -f ext_counter.so.0.0.tmp ext_counter.so.0.0
ln -sf ext_counter.so.0.0 ext_counter.so.0.tmp
mv -f ext_counter.so.0.tmp ext_counter.so.0
ln -sf ext_counter.so.0.0 ext_counter.so.tmp
mv -f ext_counter.so.tmp ext_counter.so
#
# ls -l
total 42
-rw-r--r-- 1 root wsrc 111 Dec 25 07:01 Makefile
lrwxr-xr-x 1 root wsrc 18 Dec 25 08:54 ext_counter.so@ -> ext_counter.so.0.0
lrwxr-xr-x 1 root wsrc 18 Dec 25 08:54 ext_counter.so.0@ -> ext_counter.so.0.0
-rwxr-xr-x 1 root wsrc 8224 Dec 25 08:54 ext_counter.so.0.0*
-rw-r--r-- 1 root wsrc 12563 Dec 25 08:54 ext_counter.so.0.map
-rw-r--r-- 1 root wsrc 3124 Dec 25 08:54 ext_counter_pic.a
-rw-r--r-- 1 root wsrc 3005 Dec 25 07:19 npfext_counter.c
-rw-r--r-- 1 root wsrc 2832 Dec 25 08:54 npfext_counter.pico
-rw-r--r-- 1 root wsrc 80 Dec 25 06:59 shlib_version
ext_counter.so.0.0
という共有オブジェクトが作成されています。
カーネルモジュールのビルド
カーネルモジュールとして npf_ext_counter.kmod
を作成します。こちらも単に make
コマンドを実行するだけです。
# ls
Makefile
#
# make
# compile npf_ext_counter/npf_ext_counter.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_counter -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_counter.c
/usr/src/tooldir.NetBSD-9.3-amd64/bin/nbctfconvert -L VERSION npf_ext_counter.o
# link npf_ext_counter/npf_ext_counter.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_counter.kmod.map -o npf_ext_counter.kmod npf_ext_counter.o
/usr/src/tooldir.NetBSD-9.3-amd64/bin/nbctfmerge -t -L VERSION -o npf_ext_counter.kmod npf_ext_counter.o
#
# ls -l
total 72
-rw-r--r-- 1 root wsrc 243 Dec 20 12:31 Makefile
lrwxr-xr-x 1 root wsrc 31 Dec 25 08:55 amd64@ -> /usr/src/sys/arch/amd64/include
lrwxr-xr-x 1 root wsrc 30 Dec 25 08:55 i386@ -> /usr/src/sys/arch/i386/include
lrwxr-xr-x 1 root wsrc 31 Dec 25 08:55 machine@ -> /usr/src/sys/arch/amd64/include
-rw-r--r-- 1 root wsrc 21648 Dec 25 08:55 npf_ext_counter.kmod
-rw-r--r-- 1 root wsrc 1836 Dec 25 08:55 npf_ext_counter.kmod.map
-rw-r--r-- 1 root wsrc 41976 Dec 25 08:55 npf_ext_counter.o
lrwxr-xr-x 1 root wsrc 29 Dec 25 08:55 x86@ -> /usr/src/sys/arch/x86/include
パケットカウント拡張機能を利用するための前準備
拡張機能のインストール
NPFが拡張機能を利用する際に共有オブジェクトを読み込むため、ビルドした ext_counter.so.0.0
については所定のディレクトリに配置する必要があります。以下のように、作成した共有オブジェクトを /lib/npf
に配置し、 /lib/npf/ext_counter.so
という名前で参照できるよう、シンボリックリンクを張っておきます。
# cp ext_counter.so.0.0 /lib/npf/
# ln -s /lib/npf/ext_counter.so.0.0 /lib/npf/ext_counter.so
root@nbsdadvcal2022 ~ # ls -l /lib/npf/ext_counter.so*
lrwxr-xr-x 1 root wheel 27 Dec 25 07:32 /lib/npf/ext_counter.so@ -> /lib/npf/ext_counter.so.0.0
-rwxr-xr-x 1 root wheel 8224 Dec 25 07:28 /lib/npf/ext_counter.so.0.0*
カーネルモジュールの読み込み
npf_ext_counter.kmod
についても、あらかじめカーネルモジュールとして読み込んでおきます。
# cd /usr/src/sys/modules/npf_ext_counter
#
# modstat | egrep 'NAME|npf_ext_counter'
NAME CLASS SOURCE FLAG REFS SIZE REQUIRES
#
# modload ./npf_ext_counter.kmod
#
# modstat | egrep 'NAME|npf_ext_counter'
NAME CLASS SOURCE FLAG REFS SIZE REQUIRES
npf_ext_counter misc filesys - 0 407 npf
これで一通りの準備は完了です。
パケットカウントを試してみる
さっそくパケットカウント拡張機能を試してみます。まずはフィルタルールとして以下の設定を /etc/npf.conf
に記述します。 counter
拡張機能を定義し、"icmp rule"グループの中でICMPパケットをブロックするルールにマッチした際にパケットカウントを行うという設定になっています。
/etc/npf.conf:
$ext_if = "wm0"
procedure "counter" {
counter: maxcount 10
}
group "icmp rule" {
block in final on $ext_if proto icmp all apply "counter"
}
group default {
pass in on $ext_if all
pass out on $ext_if all
}
NPFを有効にします。
# /etc/rc.d/npf onestart
別のノードからpingコマンドを実行すると、カーネルメッセージに現在のカウント値とカウント上限値を超えた場合にカウンタがクリアされたという出力がされています!😃 これで無事にパケットカウント拡張機能が動作していることが確認できました。
まとめ
一連のNPFの記事を参考に、拡張機能のサンプルとしてパケットカウント機能を作成してみました。これで拡張機能としてのひな型的な処理は用意できたので、よりNetBSDの内部機能を用いたパケット処理・操作機能へと理解を深められればと思います。
さいごに
今年も何とかNetBSD Advent Calendar 2022を完走することができました。
これもひとえに皆様のおかげです。初日の予約投稿の手順を間違えてしまい1日目が抜けたままでの開始となっており、参加者の皆様にはご迷惑をおかけしてしまいました。
@yamori813様、ONODERA Ryo様(投稿日順です)ありがとうございました。特に@yamori813様には投稿が延びてしまった際にフォローしていただき、本当に助かりました。ありがとうございます。
個人的には今年はNPF(NetBSD Packet Filter)について細かく見てゆく記事を多く投稿していました。NetBSDには libnv
のような、利用されてはいるけど意外と解説記事がない機能やライブラリがあるという事に気が付いたので、来年はNetBSDの新機能だけでなく、このような細かい機能の解説もできればと思います。
それでは良いお年を。来年もどうかよろしくお願いいたします。