Linuxカーネルのsysctl処理を追いかけてみた
今日のLinux Advent Calendarでは、Linuxカーネル内部のsysctl処理を追いかけてみます。ちょうど同じ日が空いていたので、NetBSDのsysctl処理も同様に追いかけてみています。よかったらそちらもご覧ください。
- NetBSDカーネルのsysctl処理を追いかけてみた
sysctl
sysctlはカーネルの内部状態を設定、参照するための機能です。例えば、IPパケットのフォワーディング等の設定も
sysctlで有効・無効にすることができます。Linuxでは、以下のコマンドにより、IPパケットのフォワーディング設定を
確認できます。
[yuuna@localhost ~]$ sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 0
環境について
今回はFedora21の環境を利用して調べてみます。
[yuuna@localhost ~]$ cat /etc/redhat-release
Fedora release 21 (Twenty One)
[yuuna@localhost ~]$ uname -r
3.17.6-300.fc21.x86_64
Linuxカーネルのバージョンは3.17.6ですね。Fedoraにインストールされているのがvanillaカーネルだとは思えないけど、
以下のカーネスソースコードでsysctl処理を追いかけてみます。
カーネルソースコードを追いかけてみる
まずは調べるための足がかりが欲しいので、"forwarding"と"ipv4"でソースコードをgrepしてみます。
それっぽいファイルが引っかかりますね。
[yuuna@localhost linux-3.17.6]$ find . -type f | grep sysctl | xargs grep -w forwarding | grep -i ipv4
./kernel/sysctl_binary.c: { CTL_INT, NET_IPV4_CONF_FORWARDING, "forwarding" },
上記はstruct bin_table bin_net_ipv4_conf_vars_table[]の一部ですね。struct bin_tableは入れ子の配列になっているようなので、
どんどん手繰ってみます。
linux-3.17.6/kernel/sysctl_binary.c:
230 static const struct bin_table bin_net_ipv4_conf_vars_table[] = {
231 { CTL_INT, NET_IPV4_CONF_FORWARDING, "forwarding" },
...
258 static const struct bin_table bin_net_ipv4_conf_table[] = {
259 { CTL_DIR, NET_PROTO_CONF_ALL, "all", bin_net_ipv4_conf_vars_table },
...
330 static const struct bin_table bin_net_ipv4_table[] = {
331 {CTL_INT, NET_IPV4_FORWARD, "ip_forward" },
332
333 { CTL_DIR, NET_IPV4_CONF, "conf", bin_net_ipv4_conf_table },
...
723 static const struct bin_table bin_net_table[] = {
...
728 { CTL_DIR, NET_IPV4, "ipv4", bin_net_ipv4_table },
903 static const struct bin_table bin_root_table[] = {
...
906 { CTL_DIR, CTL_NET, "net", bin_net_table },
...
1223 static const struct bin_table *get_sysctl(const int *name, int nlen, char *path)
1224 {
1225 const struct bin_table *table = &bin_root_table[0];
最終的にstruct bin_table bin_root_table[]という配列に行き着きます。簡単に図示すると、以下のような階層になっています。
+------------------+
| bin_root_table[] |
+-+----------------+
| +-----------------+
+--| bin_net_table[] |
+-+---------------+
| +----------------------+
+--| bin_net_ipv4_table[] |
+-+--------------------+
| +--------------------------------+
+--| bin_net_ipv4_conf_vars_table[] |
+--------------------------------+
上記の階層は、ちょうど以下のようなsysctlにおける、ドットで区切った名前に対応しているようですね。
$ sysctl -a | grep forwarding | grep ipv4
net.ipv4.conf.all.forwarding = 0
net.ipv4.conf.all.mc_forwarding = 0
net.ipv4.conf.default.forwarding = 0
net.ipv4.conf.default.mc_forwarding = 0
net.ipv4.conf.enp0s3.forwarding = 0
net.ipv4.conf.enp0s3.mc_forwarding = 0
net.ipv4.conf.lo.forwarding = 0
net.ipv4.conf.lo.mc_forwarding = 0
struct bin_table bin_root_table[]を参照しているget_sysctl()関数を呼び出し元に向かって追いかけてみます。
linux-3.17.6/kernel/sysctl_binary.c:
1223 static const struct bin_table *get_sysctl(const int *name, int nlen, char *path)
1224 {
1225 const struct bin_table *table = &bin_root_table[0];
...
get_sysctl()を呼んでいるのはsysctl_getname()ですね。struct bin_table *をtablepに入れて返しています。
linux-3.17.6/kernel/sysctl_binary.c:
1278 static char *sysctl_getname(const int *name, int nlen, const struct bin_table **tablep)
1279 {
1280 char *tmp, *result;
1281
1282 result = ERR_PTR(-ENOMEM);
1283 tmp = __getname();
1284 if (tmp) {
1285 const struct bin_table *table = get_sysctl(name, nlen, tmp);
1286 result = tmp;
1287 *tablep = table;
1288 if (IS_ERR(table)) {
1289 __putname(tmp);
1290 result = ERR_CAST(table);
1291 }
1292 }
1293 return result;
1294 }
sysctl_getname()はbinary_sysctl()から呼ばれており、取得した&tableに対し、convert()を呼び出しています。
引数の内容からすると、この中で値を設定しているようです。
linux-3.17.6/kernel/sysctl_binary.c:
1296 static ssize_t binary_sysctl(const int *name, int nlen,
1297 void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
1298 {
1299 const struct bin_table *table = NULL;
...
1306 pathname = sysctl_getname(name, nlen, &table);
...
1329 result = table->convert(file, oldval, oldlen, newval, newlen);
struct bin_tableも一連の関数が定義されているsysctl_binary.cで定義されています。sysctlまわりの構造体や関数はsysctl_binary.cに
まとめられているようです。
linux-3.17.6/kernel/sysctl_binary.c:
41 struct bin_table {
42 bin_convert_t *convert;
43 int ctl_name;
44 const char *procname;
45 const struct bin_table *child;
46 };
struct bin_tableのメンバ変数を見ると、最初のメンバがconver()への関数ポインタのように見えます。
bin_net_ipv4_conf_vars_table[]を見ると、CTL_INTが指定されています。
linux-3.17.6/kernel/sysctl_binary.c:
230 static const struct bin_table bin_net_ipv4_conf_vars_table[] = {
231 { CTL_INT, NET_IPV4_CONF_FORWARDING, "forwarding" },
CTL_INTはマクロ定数なのでは...?と思って確認すると、実は関数になっていました。
実体はbin_intvec()で、引数として渡されたnewvalを設定している...はず...(この関数の中までは追いかけきれませんでした...)
bin_intvec()については、日を改めて追いかけてみます。
linux-3.17.6/kernel/sysctl_binary.c:
34 #define CTL_INT bin_intvec
...
971 static ssize_t bin_intvec(struct file *file,
972 void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
973 {
...
1013 if (newval && newlen) {
1014 unsigned __user *vec = newval;
1015 size_t length = newlen / sizeof(*vec);
1016 char *str, *end;
1017 int i;
1018
1019 str = buffer;
1020 end = str + BUFSZ;
1021 for (i = 0; i < length; i++) {
1022 unsigned long value;
1023
1024 result = -EFAULT;
1025 if (get_user(value, vec + i))
1026 goto out_kfree;
1027
1028 str += scnprintf(str, end - str, "%lu\t", value);
1029 }
1030
1031 result = kernel_write(file, buffer, str - buffer, 0);
まとめ
LinuxとNetBSDでsysctlの実装を少しだけ追いかけてみました。両者の実装を比較しながら解説してみたかったのですが、
それはもう少しソースコードを調べる必要がありそうです。