CentOS 6 の kernel で TIME_WAIT の値を変更して kernel の rpm を作り直す

netstat とかで出てくる TIME_WAIT ですが、デフォルトだと 60 秒になっています
今回はそれを 5 秒に変更しようと思います
せっかくなので rpm できちんと作ります(kernel-firmware も作る)

kernel src 内の `TCP_TIMEWAIT_LEN` の値を変更して rpmbuild します

## 前提
* 今回は **CentOS 6.5 (kernel-2.6.32-431.11.2)** を例にしています
* 出来上がる version は **kernel-2.6.32-431.11.2.el6.timewait5** のようにしています


# 事前準備

[本家のwiki](http://wiki.centos.org/HowTos/Custom_Kernel)に従って

```bash
$ sudo yum groupinstall "Development Tools"
$ sudo yum install ncurses-devel
$ sudo yum install hmaccalc zlib-devel binutils-devel elfutils-libelf-devel
```

# rpmbuild 環境作成

いくつか方法があります

## その1

```bash
$ mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
$ echo '%_topdir %(echo $HOME)/rpmbuild' > ~/.rpmmacros
```

## その2

```bash
$ sudo yum install rpmdevtools
$ rpmdev-setuptree
```

* `rpmdev-setuptree` は mkdir してくれて `~/.rpmmacros` を自動で生成してくれます

# kernel の srpm を install

```bash
$ wget http://vault.centos.org/6.5/updates/Source/SPackages/kernel-2.6.32-431.11.2.el6.src.rpm
$ rpm -ivh kernel-2.6.32-431.11.2.el6.src.rpm
```

# TCP_TIMEWAIT_LEN の patch ファイルを作る

今回いじるのは kernel の src の中にある `include/net/tcp.h` というファイルになります

```c:include/net/tcp.h
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
                                  * state, about 60 seconds     */
```

## src を展開する

* `kernel-2.6.32-431.11.2.el6.src.rpm` を install したことによって `~/rpmbuild/SOURCES/linux-2.6.32-431.11.2.el6.tar.bz2` が配置されていると思います
* その src を一旦 `/tmp` とかに解凍します

```bash
$ tar jxf ~/rpmbuild/SOURCES/linux-2.6.32-431.11.2.el6.tar.bz2 --directory=/tmp
```

## tcp.h を編集して patch を作成する

```bash
$ cd /tmp/linux-2.6.32-431.11.2.el6
$ cp include/net/tcp.h include/net/tcp.h.org
$ vi include/net/tcp.h
$ diff -Naur include/net/tcp.h.org include/net/tcp.h > ~/rpmbuild/SOURCES/linux-kernel-timewait5.patch
```

* linux-kernel-timewait5.patch の中身は下記のようになると思います
* `~/rpmbuild/SOURCES/` に patch を置いて下さい

```diff
--- include/net/tcp.h.org       2014-04-09 19:46:13.823604279 +0900
+++ include/net/tcp.h   2014-04-09 19:46:44.227097917 +0900
@@ -111,7 +111,7 @@
                                 */


-#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
+#define TCP_TIMEWAIT_LEN (5*HZ) /* how long to wait to destroy TIME-WAIT
                                  * state, about 60 seconds     */
 #define TCP_FIN_TIMEOUT        TCP_TIMEWAIT_LEN
                                  /* BSD style FIN_WAIT2 deadlock breaker.
```

# spec を編集する

* `~/rpmbuild/SPECS` にある `kernel.spec` を編集します

```bash
$ cd ~/rpmbuild/SPECS
$ cp kernel.spec kernel_timewait5.spec
$ vi kernel_timewait5.spec
```

* 変更箇所は下記 3 つです

## define buildid

```diff
@@ -15,7 +15,7 @@
 # that the kernel isn't the stock distribution kernel, for example,
 # by setting the define to ".local" or ".bz123456"
 #
-# % define buildid .local
+%define buildid .timewait5

 %define distro_build 431.11.2
 %define signmodules 1
```

## patch1000 追加

```diff
@@ -610,6 +610,9 @@
 Source85: config-powerpc64-debug-rhel
 Source86: config-s390x-debug-rhel

+# TCP_TIMEWAIT_LEN patch (60 -> 5)
+Patch1000: linux-kernel-timewait5.patch
+
 # empty final patch file to facilitate testing of kernel patches
 Patch999999: linux-kernel-test.patch
```

## ApplyOptionalPatch 追加

```diff
@@ -932,6 +935,7 @@
 # Dynamically generate kernel .config files from config-* files
 make -f %{SOURCE20} VERSION=%{version} configs

+ApplyOptionalPatch linux-kernel-timewait5.patch include/net/tcp.h
 ApplyOptionalPatch linux-kernel-test.patch

 # Any further pre-build tree manipulations happen here.
```

# kernel-firmware の rpm を作成する

* kernel の package は依存関係で同じ version の kernel-firmware を要求されます
* これのせいで custom で kernel 作る時に以前と version 変えずに作ってしまうケースが多いようですが、横着しないで作ればいいと思います
* `norarch`じゃないと作ってくれないので `--target=noarch` で作成します

```bash
$ rpmbuild -ba --target=noarch --without doc --with kernel_abi_whitelists --with firmware kernel_timewait5.spec
```

* kernel-doc や kernel-abi-whitelists は今回は作成しないようにしました

これで `~/rpmbuild/RPMS/noarch/kernel-firmware-2.6.32-431.11.2.el6.timewait5.noarch.rpm` が作成されていると思います

## 謎の挙動

ちょっと話がズレますが

```spec:kernel.spec
# kernel-abi-whitelists
%define with_kernel_abi_whitelists %{?_with_kernel_abi_whitelists: 0} %{?!_with_kernel_abi_whitelists: 1}
```

* となっているので、`--with kernel_abi_whitelists` をつけると `_with_kernel_abi_whitelists` が 0 になる
* なので `--with kernel_abi_whitelists` で rpmbuild すると kernel-abi-whitelists が作成されない
* 逆に `--without kernel_abi_whitelists` で rpmbuild すると kernel-abi-whitelists が作成される

ということで **多分これ逆なんじゃないかな** と思うんですが、意図してそうなっているかは不明・・

* ちなみに似たような firmware の flag は 0 と 1 が逆になっている

```spec:kernel.spec
# kernel-firmware
%define with_firmware  %{?_with_firmware:     1} %{?!_with_firmware:     0}
```

# kernel の rpm を作成する

必要なものだけ rpm を作る

```bash
$ rpmbuild -ba --target=$(uname -m) --with baseonly --without debug --without debuginfo kernel_timewait5.spec
```

これで下記の rpm が作成されていると思います(x86_64 の場合)

```
~/rpmbuild/RPMS/kernel-2.6.32-431.11.2.el6.timewait5.x86_64.rpm
~/rpmbuild/RPMS/kernel-headers-2.6.32-431.11.2.el6.timewait5.x86_64.rpm
~/rpmbuild/RPMS/kernel-devel-2.6.32-431.11.2.el6.timewait5.x86_64.rpm
```

# 作成した rpm を適用する

* yum の repository に置いたり rpm で直接入れたりしてください
* 適用後、 `/boot/grub/grub.conf` に新しい kernel の設定が入っていることを確認してください
* reboot !!

# 補足

* 今回はいろいろ余計な rpm を作らないように `--without` とか多用してますが、必要に応じて flag を外して下さい
* 何も指定しないで default の flag でも全然問題はないでしょう

## `IP_VS_TCP_S_TIME_WAIT` も？

サーバーの用途によっては `IP_VS_TCP_S_TIME_WAIT` 等も変更したほうがいいかもしれませんね

```c:net/netfilter/ipvs/ip_vs_proto_tcp.c
static int tcp_timeouts[IP_VS_TCP_S_LAST+1] = {
        [IP_VS_TCP_S_NONE]              =       2*HZ,
        [IP_VS_TCP_S_ESTABLISHED]       =       15*60*HZ,
        [IP_VS_TCP_S_SYN_SENT]          =       2*60*HZ,
        [IP_VS_TCP_S_SYN_RECV]          =       1*60*HZ,
        [IP_VS_TCP_S_FIN_WAIT]          =       2*60*HZ,
        [IP_VS_TCP_S_TIME_WAIT]         =       2*60*HZ,
        [IP_VS_TCP_S_CLOSE]             =       10*HZ,
        [IP_VS_TCP_S_CLOSE_WAIT]        =       60*HZ,
        [IP_VS_TCP_S_LAST_ACK]          =       30*HZ,
        [IP_VS_TCP_S_LISTEN]            =       2*60*HZ,
        [IP_VS_TCP_S_SYNACK]            =       120*HZ,
        [IP_VS_TCP_S_LAST]              =       2*HZ,
};
```

## `TCP_TIMEOUT_INIT` も？

CentOS 6.5 では TCP 再送間隔の初期値が 1 秒なので特に変えないでいいと思いますが、古い OS とかだと 3 秒になっていることがありますので必要に応じて変えるのもいいと思います

```c:include/net/tcp.h
#define TCP_TIMEOUT_INIT ((unsigned)(1*HZ))     /* RFC2988bis initial RTO value */
```