NetBSDカーネルのsysctl処理を追いかけてみた
今日のNetBSD Advent Calendarでは、NetBSDカーネル内部のsysctl処理を追いかけてみます。ちょうど同じ日が空いていたので、Linuxのsysctl処理も同様に追いかけてみています。よかったらそちらもご覧ください。
- Linuxカーネルのsysctl処理を追いかけてみた
sysctl
sysctlはカーネルの内部状態を設定、参照するための機能です。例えば、IPパケットのフォワーディング等の設定も
sysctlで有効・無効にすることができます。NetBSDでは、以下のコマンドにより、IPパケットのフォワーディング設定を
確認できます。
kimiuso tsubaki $ sysctl net.inet.ip.forwarding
net.inet.ip.forwarding = 0
環境について
今回はNetBSD-6.1.5-amd64の環境でソースコードを追いかけてみます。
カーネルソースコードを追いかけてみる
まずは調べるための足がかりが欲しいので、"forwarding"でソースコードをgrepしてみます。
それっぽいファイルが引っかかりますね。
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関連のデータ構造を構築する関数のように見えます。
/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()から呼ばれています。
/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に紐付くデータ構造のようです。
/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()を追いかけてみます。まずは引数を把握するため、関数呼び出しと引数の意味を対応させてみます。
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になっています。てっきり値を設定する関数を用意し、関数ポインタ経由で
呼び出すのかと予想していましたが違うみたいです。
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で、外部データへのポインタになっています。
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の実装を少しだけ追いかけてみました。両者の実装を比較しながら解説してみたかったのですが、
それはもう少しソースコードを調べる必要がありそうです。