NetBSDカーネルのsysctl処理を追いかけてみた

# NetBSDカーネルのsysctl処理を追いかけてみた

今日のNetBSD Advent Calendarでは、NetBSDカーネル内部のsysctl処理を追いかけてみます。ちょうど同じ日が空いていたので、Linuxのsysctl処理も同様に追いかけてみています。よかったらそちらもご覧ください。

 * Linuxカーネルのsysctl処理を追いかけてみた
   * http://qiita.com/furandon_pig/items/78fa15a97e2a356ab9ba

## sysctl

sysctlはカーネルの内部状態を設定、参照するための機能です。例えば、IPパケットのフォワーディング等の設定も
sysctlで有効・無効にすることができます。NetBSDでは、以下のコマンドにより、IPパケットのフォワーディング設定を
確認できます。

```sh
kimiuso tsubaki $ sysctl net.inet.ip.forwarding 
net.inet.ip.forwarding = 0
```

## 環境について

今回はNetBSD-6.1.5-amd64の環境でソースコードを追いかけてみます。

## カーネルソースコードを追いかけてみる

まずは調べるための足がかりが欲しいので、"forwarding"でソースコードをgrepしてみます。
それっぽいファイルが引っかかりますね。

```sh
kimiuso tsubaki $ cd /usr/src/sys/
kimiuso sys $ find sys/netinet -type f | grep \\.c$ | xargs grep -n \"forwarding\"
sys/netinet/ip_input.c:1749:                   CTLTYPE_INT, "forwarding",
```

上記はsysctl_createv()の一部ですね。何となくforwarding関連のデータ構造を構築する関数のように見えます。

```c
/usr/src/sys/netinet/ip_input.c:
1724 static void
1725 sysctl_net_inet_ip_setup(struct sysctllog **clog)
1726 {
...
1747         sysctl_createv(clog, 0, NULL, NULL,
1748                        CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
1749                        CTLTYPE_INT, "forwarding",
1750                        SYSCTL_DESCR("Enable forwarding of INET datagrams"),
1751                        NULL, 0, &ipforwarding, 0,
1752                        CTL_NET, PF_INET, IPPROTO_IP,
1753                        IPCTL_FORWARDING, CTL_EOL);
```

sysctl_createv()を呼んでいるsysctl_net_inet_ip_setup()は、ip_init()から呼ばれています。

```c
/usr/src/sys/netinet/ip_input.c:
 292 /*
 293  * IP initialization: fill in IP protocol switch table.
 294  * All protocols not implemented in kernel go to raw IP protocol handler.
 295  */
 296 void
 297 ip_init(void)
 298 {
 ...
 302         sysctl_net_inet_ip_setup(NULL);
```

ip_init()はstruct protosw inetsw[]内でpr_initへの関数ポインタに設定されています。
メンバ変数pr_domainで指定してるinetdomainを見ると、どうやらPF_INETに紐付くデータ構造のようです。

```c
/usr/src/sys/netinet/in_proto.c:
214 const struct protosw inetsw[] = {
215 {       .pr_domain = &inetdomain,
216         .pr_init = ip_init,
217         .pr_output = ip_output,
218         .pr_fasttimo = ip_fasttimo,
219         .pr_slowtimo = ip_slowtimo,
220         .pr_drain = ip_drainstub,
221 },
...
467 struct domain inetdomain = {
468         .dom_family = PF_INET, .dom_name = "internet", .dom_init = NULL,
```

sysctl_createv()を追いかけてみます。まずは引数を把握するため、関数呼び出しと引数の意味を対応させてみます。

```c
sysctl_createv(
    clog,   // sysctl_createv(struct sysctllog **log
    0,      // int cflags
    NULL,   // const struct sysctlnode **rnode
    NULL,   // const struct sysctlnode **cnode
    CTLFLAG_PERMANENT|CTLFLAG_READWRITE,   // int flags
    CTLTYPE_INT,    // int type
    "forwarding",   // const char *namep
    SYSCTL_DESCR("Enable forwarding of INET datagrams"),  // const char *descr
    NULL,   // sysctlfn func
    0,      // u_quad_t qv
    &ipforwarding,  // void *newp
    0,      // size_t newlen
    CTL_NET,           // ...
    PF_INET,           // ...
    IPPROTO_IP,        // ...
    IPCTL_FORWARDING,  // ...
    CTL_EOL            // ...
);
```

sysctlfn funcに対応する引数はNULLになっています。てっきり値を設定する関数を用意し、関数ポインタ経由で
呼び出すのかと予想していましたが違うみたいです。

```c
1921 int
1922 sysctl_createv(struct sysctllog **log, int cflags,
1923                const struct sysctlnode **rnode, const struct sysctlnode **cnode,
1924                int flags, int type, const char *namep, const char *descr,
1925                sysctlfn func, u_quad_t qv, void *newp, size_t newlen,
1926                ...)
1927 {
...
1931         struct sysctlnode nnode, onode, *dnode;
...
1987         if (type == CTLTYPE_NODE) {
...
1993         } else if (flags & CTLFLAG_IMMEDIATE) {
...
2007         } else {
2008                 nnode.sysctl_data = newp;
2009         }
```

変数ipforwardingを引数として渡しています。typeとflagsの条件に引っかからない引数の組み合わせなので、
最終的にstruct sysctlnodeのメンバ変数nnode.sysctl_dataにipfowardingのアドレスが紐付けられます。

struct sysctlnodeを見ると、sysctl_dataの実体は_sud_dataで、外部データへのポインタになっています。

```c
sys/sysctl.h:
1267 struct sysctlnode {
1268         uint32_t sysctl_flags;          /* flags and type */
1269         int32_t sysctl_num;             /* mib number */
1270         char sysctl_name[SYSCTL_NAMELEN]; /* node name */
1271         uint32_t sysctl_ver;            /* node's version vs. rest of tree */
1272         uint32_t __rsvd;
1273         union {
1274                 struct {
1275                         uint32_t suc_csize;     /* size of child node array */
1276                         uint32_t suc_clen;      /* number of valid children */
1277                         __sysc_pad(struct sysctlnode*) _suc_child; /* array of child nodes */
1278                 } scu_child;
1279                 struct {
1280                         __sysc_pad(void*) _sud_data; /* pointer to external data */
1281                         __sysc_pad(size_t) _sud_offset; /* offset to data */
1282                 } scu_data;
1283                 int32_t scu_alias;              /* node this node refers to */
1284                 int32_t scu_idata;              /* immediate "int" data */
1285                 u_quad_t scu_qdata;             /* immediate "u_quad_t" data */
1286                 bool scu_bdata;                 /* immediate bool data */
1287         } sysctl_un;
1288         __sysc_pad(size_t) _sysctl_size;        /* size of instrumented data */
1289         __sysc_pad(sysctlfn) _sysctl_func;      /* access helper function */
1290         __sysc_pad(struct sysctlnode*) _sysctl_parent; /* parent of this node */
1291         __sysc_pad(const char *) _sysctl_desc;  /* description of node */
1292 };
...
1294 /*
1295  * padded data
1296  */
...
1298 #define sud_data        __sysc_unpad(_sud_data)
...
1305 /*
1306  * nested data (may also be padded)
1307  */
...
1311 #define sysctl_data     sysctl_un.scu_data.sud_data
```

sysctl_dataを設定する処理まで追いかけようと思ったのですが、時間的にキビシいので、今はこれが精一杯。
とりあえず、変数ipforwardingが紐づけられているという事まで分かったので、これに紐付くデータ更新処理の部分を
追いかければ良さそうです。

## まとめ

NetBSDとLinuxでsysctlの実装を少しだけ追いかけてみました。両者の実装を比較しながら解説してみたかったのですが、
それはもう少しソースコードを調べる必要がありそうです。