0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NetBSDAdvent Calendar 2022

Day 23

NPF(NetBSD Packet Filter)で拡張機能のパラメータ値がカーネル側に渡されるまでを追いかけてみる

Posted at

NetBSD Advent Calendar 2022 23日目の記事です。今日はNPF(NetBSD Packet Filter)の拡張機能において、フィルタルールで設定した値がカーネルに渡されるまでを追いかけてみようと思います。

NPFの拡張機能における設定値

18日目の記事rndblock で紹介したように、NPFではフィルタルール側で拡張機能にパラメータを渡すことができます。以下の例では rndblock のパラメータとして、 50.0 を設定しています。

procedure "rndblock" {
  rndblock: percentage 50.0
}

拡張機能の実体(実際の処理)はカーネル空間になるため、ユーザランド側で設定したフィルタールール内の値を何らかの方法でカーネル側に渡す必要があります。これは21日目の記事で紹介した、 libnv の機能を利用して実現されています。

フィルタルールの値がカーネルに渡されるまでを追いかけてみる

ユーザランド側で npfctl reload を実行すると、その時点のフィルタルールがロードされ、拡張機能の定義が行われた場合は対応するパラメータがパースされます。先述した rndblock: percentage 50.0src/lib/npf/ext_rndblock/npfext_rndblock.c内の以下の関数でパースされます。

 59 int
 60 npfext_rndblock_param(nl_ext_t *ext, const char *param, const char *val)
 61 {
 62     enum ptype { PARAM_U32 };
 63     static const struct param {
 64         const char *    name;
 65         enum ptype  type;
 66         signed long min;
 67         signed long max;
 68     } params[] = {
 69         { "mod",    PARAM_U32,  1,  LONG_MAX    },
 70         { "percentage", PARAM_U32,  1,  9999        },
 71     };
 72
 73     if (val == NULL) {
 74         return EINVAL;
 75     }
 76     for (unsigned i = 0; i < __arraycount(params); i++) {
 77         const char *name = params[i].name;
 78         long ival;
 79
 80         if (strcmp(name, param) != 0) {
 81             continue;
 82         }
 83
 84         /*
 85          * Note: multiply by 100 and convert floating point to
 86          * an integer, as 100% is based on 10000 in the kernel.
 87          */
 88         ival = (i == 1) ? atof(val) * 100 : atol(val);
 89         if (ival < params[i].min || ival > params[i].max) {
 90             return EINVAL;
 91         }
 92         assert(params[i].type == PARAM_U32);
 93         fprintf(stderr, "===> add param '%s', %ld\n", params[i].name, ival);
 94         npf_ext_param_u32(ext, name, ival);
 95         return 0;
 96     }
 97
 98     /* Invalid parameter, if not found. */
 99     return EINVAL;
100 }

具体的には、以下の個所で拡張機能のパラメータ値が設定されます。

 94         npf_ext_param_u32(ext, name, ival);

npf_ext_param_u32()/usr/src/lib/libnpf/npf.cで定義されており、21日目の記事で紹介した libnv の関数が呼ばれています。

 643 void
 644 npf_ext_param_u32(nl_ext_t *ext, const char *key, uint32_t val)
 645 {
 646     nvlist_add_number(ext->ext_dict, key, val);
 647 }

設定された値は、 npf.cnpf_config_submit() (これは npfctl reload の処理から呼ばれる)内からさらに呼ばれる _npf_xfer_fd() で処理されます。

 338 int
 339 npf_config_submit(nl_config_t *ncf, int fd, npf_error_t *errinfo)
 340 {
 ...
 347     error = _npf_xfer_fd(fd, IOC_NPF_LOAD, ncf->ncf_dict, &resp);
 348     if (error) {
 349         return error;
 350     }
 ...
 354 }

_npf_xfer_fd() の中では、 nvlist_xfer_ioctl() が呼ばれます。関数名からすると、この中で ioctl() が発行され、ユーザランドからカーネル側に値が渡されるように見えます。

 239 static int
 240 _npf_xfer_fd(int fd, unsigned long cmd, nvlist_t *req, nvlist_t **resp)
 241 {
 ...
 272 #if !defined(_NPF_STANDALONE)
 273     case S_IFBLK:
 274     case S_IFCHR:
 275         if (ioctl(fd, IOC_NPF_VERSION, &kernver) == -1) {
 276             goto err;
 277         }
 278         if (kernver != NPF_VERSION) {
 279             errno = EPROGMISMATCH;
 280             goto err;
 281         }
 282         if (nvlist_xfer_ioctl(fd, cmd, req, resp) == -1) {
 283             goto err;
 284         }
 285         break;
 286 #else

nvlist_xfer_ioctl()/usr/src/sys/external/bsd/libnv/dist/nv_kern_netbsd.cで定義されています。この関数はデータの送受信の両方を受け持っており、引数の const nvlist_t *nvl nvlist_t **nvlp のそれぞれの値により、データの受信・送信を行います。

143 int
144 nvlist_xfer_ioctl(int fd, unsigned long cmd, const nvlist_t *nvl,
145     nvlist_t **nvlp)
146 {
147     nvlist_ref_t nref;
...
152     if (nvl) {
153         /*
154          * Sending: serialize the name-value list.
155          */
...
174     if (nvlp) {
175         nvlist_t *retnvl;
176
177         /*
178          * Receiving: unserialize the nvlist.
179          *
180          * Note: pages are mapped by nvlist_kern_copyout() for us.
181          */

今回はデータを受信する場合の処理フローを追いかけてみます。 ioctl() 経由で受信したデータは nvlist_ref_t 型の変数に格納されます。コメントを見ると"Receiving: unserialize the nvlist."とあるので、 libnv で複数のname/valueリストをシリアライズしておいて、 ioctl() でひとかたまりのデータとして受信する、というフローになっているようです。

165     /*
166      * Exchange the nvlist reference data.
167      */
168     if (ioctl(fd, cmd, &nref) == -1) {
169         free(buf);
170         return -1;
171     }
172     free(buf);
173
174     if (nvlp) {
175         nvlist_t *retnvl;
176
177         /*
178          * Receiving: unserialize the nvlist.
179          *
180          * Note: pages are mapped by nvlist_kern_copyout() for us.
181          */
182         if (nref.buf == NULL || nref.len == 0) {
183             errno = EIO;
184             return -1;
185         }
186         retnvl = nvlist_unpack(nref.buf, nref.len, nref.flags);
187         munmap(nref.buf, nref.len);
188         if (retnvl == NULL) {
189             errno = EIO;
190             return -1;
191         }
192         *nvlp = retnvl;
193     }
194     return 0;
195 }

ここまででユーザランドからカーネル側に値が渡されてきたので、あとはカーネルモジュール側の実装で dlvlist_t から値を取得するだけです。

/usr/src/sys/net/npf/npf_ext_rndblock.c:
 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の rndblock において、フィルタルールで設定した値がカーネル内の拡張機能側で取得されるまでの流れを追いかけてみました。 libnv でname/valueのリストを作成し、データをシリアライズしてひと固まりにしたのち、 ioctl() 経由でカーネル側に送信、デシリアライズして値を取得という処理フローになっていることが分かりました。

0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?