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

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

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

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

## sysctl

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

```sh
[yuuna@localhost ~]$ sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 0
```
## 環境について

今回はFedora21の環境を利用して調べてみます。

```sh
[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処理を追いかけてみます。

 * https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.17.6.tar.xz

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

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

```sh
[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は入れ子の配列になっているようなので、
どんどん手繰ってみます。

```c
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における、ドットで区切った名前に対応しているようですね。

```sh
$ 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()関数を呼び出し元に向かって追いかけてみます。

```c
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に入れて返しています。

```c
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()を呼び出しています。
引数の内容からすると、この中で値を設定しているようです。

```c
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に
まとめられているようです。

```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が指定されています。

```c
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()については、日を改めて追いかけてみます。

```c
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の実装を少しだけ追いかけてみました。両者の実装を比較しながら解説してみたかったのですが、
それはもう少しソースコードを調べる必要がありそうです。