NPF(NetBSD Packet Filter)の拡張機能サンプルとしてパケットカウント機能を作成してみる

[NetBSD Advent Calendar 2022](https://qiita.com/advent-calendar/2022/netbsd) 25日目、いよいよ最終日の記事です。今日はNPF(NetBSD Packet Filter)で簡単なパケットカウントを行う拡張機能を作成してみようと思います。

[20日目](https://qiita.com/furandon_pig/items/8fd923d445b1fba9b6e9)と[21日目](https://qiita.com/furandon_pig/items/66d8c2c5118d97c9f999)、そして[23日目](https://qiita.com/furandon_pig/items/66a6278141218b860bbe)の記事でNPFの拡張機能の構造と実装を調べていました。今回はこれらの内容を元に、簡単な拡張機能のサンプルを作成してみます。

 * [NPF(NetBSD Packet Filter)の拡張機能の実装をソースコードから読み解いてみる](https://qiita.com/furandon_pig/items/8fd923d445b1fba9b6e9)
 * [NetBSDでname/valueリストを扱うlibnvを試してみる](https://qiita.com/furandon_pig/items/66d8c2c5118d97c9f999)
 * [NPF(NetBSD Packet Filter)で拡張機能のパラメータ値がカーネル側に渡されるまでを追いかけてみる](https://qiita.com/furandon_pig/items/66a6278141218b860bbe)

## パケットカウント拡張機能を作成する

パケットカウントのサンプルとして、 `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` のソースコードを元に、パケットカウント処理に置き換えた形になります。

```c
/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` 拡張機能の実装となる関数のプロトタイプ宣言を追加します。

```diff
--- 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` パラメータを受け取る形に変更しています。

```c
/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` コマンドを実行するだけです。

```sh
# 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` コマンドを実行するだけです。

```sh
# 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` という名前で参照できるよう、シンボリックリンクを張っておきます。

```sh
# 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` についても、あらかじめカーネルモジュールとして読み込んでおきます。

```sh
# 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を有効にします。

```sh
# /etc/rc.d/npf onestart
```

別のノードからpingコマンドを実行すると、カーネルメッセージに現在のカウント値とカウント上限値を超えた場合にカウンタがクリアされたという出力がされています！😃 これで無事にパケットカウント拡張機能が動作していることが確認できました。

![sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61137/fd65bd06-5ce0-a48a-94ae-ba804ad56f0d.gif)

## まとめ

一連のNPFの記事を参考に、拡張機能のサンプルとしてパケットカウント機能を作成してみました。これで拡張機能としてのひな型的な処理は用意できたので、よりNetBSDの内部機能を用いたパケット処理・操作機能へと理解を深められればと思います。

さいごに

今年も何とかNetBSD Advent Calendar 2022を完走することができました。
これもひとえに皆様のおかげです。初日の予約投稿の手順を間違えてしまい1日目が抜けたままでの開始となっており、参加者の皆様にはご迷惑をおかけしてしまいました。

[@yamori813](https://twitter.com/yamori813)様、[ONODERA Ryo](https://qiita.com/ryoon)様(投稿日順です)ありがとうございました。特に[@yamori813](https://twitter.com/yamori813)様には投稿が延びてしまった際にフォローしていただき、本当に助かりました。ありがとうございます。

個人的には今年はNPF(NetBSD Packet Filter)について細かく見てゆく記事を多く投稿していました。NetBSDには `libnv` のような、利用されてはいるけど意外と解説記事がない機能やライブラリがあるという事に気が付いたので、来年はNetBSDの新機能だけでなく、このような細かい機能の解説もできればと思います。

それでは良いお年を。来年もどうかよろしくお願いいたします。