「net.ipv4.tcp_tw_recycle」を有効にするのは(場合によっては)やめた方がいい

そもそも「tcp_tw_recycle」ってなに?

TIME_WAIT状態のソケット*1を高速に再利用するためのLinuxカーネル特有の仕組みらしい。

「/etc/sysctl.conf」でこいつ(net.ipv4.tcp_tw_recycle)を1にしてやって「sysctl -p」するだけで有効になります。

「TIME_WAITのソケットを少なくしてくれるんでしょ?いいじゃん!」という感じで設定してしまいそうになりますが・・・

なんでダメなの?

結論から言うと、こいつを有効にしたとき、同じグローバルIPのクライアントからの接続でかつTCPパケットにタイムスタンプ情報が入っている場合で、ほぼ同時にパケットを送ると、古いタイムスタンプの方のパケットを勝手にドロップしちゃいます。

どういうことなの・・・

tcp_tw_recycle」は、「同一IPからのパケットが到着したとき、使っていたソケットをすぐに開放する」というロジックの中でタイムスタンプ情報を使っているのだと思われます。

TCPパケットにタイムスタンプ情報が入っている本来の目的は、再送タイムアウトの間隔を動的に調整するためであったり、同一のTCPシーケンス番号でほぼ同時に来たパケットのうち、どっちが最新なのか(処理すべき物か)っていうのを判断するためだそうです。
そして、このタイムスタンプは端末内で付加され、Unixtimeではなく「端末の起動時間(の秒数?)」*2を送っているようです。

本来の目的であれば、前者に関してはパケット一個で整合性が取れていれば良く*3、後者に関してはTCPのシーケンス番号*4が被ったタイミングでタイムスタンプを比較すれば良いので、正常なパケットがサーバーに来た時点で破棄されるケースはほとんどないでしょう。

しかし、「tcp_tw_recycle」の仕組みでは、同一IPからくるパケットのうち、いずれかの(起動時間の短い)端末のパケットを(過去のものであるとみなして?)常に破棄するということになってしまうので大問題です。

あと、このタイムスタンプ情報は最近のOSから送られるパケットには(デフォルト設定で)大体入ってます*5

どういうケースでまずいの?

例えば、同じWAN内のWiFiで接続しているiPhoneAndroidとPCからほぼ同時に同じサーバーに接続したとすると、どれかひとつ*6は普通に接続出来ますが、それ以外の端末は(サーバー側で設定してある)タイムアウトまでずっと読み込み画面のままで固まってしまいます(最終的にはブラウザとかアプリ側が「接続できませんでした云々」のステータスを返します)。

特に、「同じ回線から複数の端末で接続する」ようなケースとして、テスト・デバッグ環境用のサーバーでは必ず無効にしておいた方がいいと思われます*7
上記のようなケースが多発するのでかなりひどいです。

というか・・・

これを有効にして恩恵を受けられるケースって果たしてあるんでしょうか・・・。

昔であれば、一般的な家庭であればグローバルな回線とPCがルーターを介さずに直でつながっていたり、3G回線とかも一応それぞれにIPが割り振られるので別にいいんですが、今だったら公衆WiFiとかで違う人々が(この設定を有効にしてあるサーバーの)同じサービスとかWebページ見ようとしたら一人しかまともに接続できなくなっちゃうんですよね。
それってデメリットでかすぎじゃないですかね・・・?

そもそも、TIME_WAITを早く再利用しないといけない環境ってことは、すげーパケットのやりとりが行われていて回線がパンクしそうってことなので、素直にサーバーを増やして通信を分散させるか、どうしてもこの仕組みを利用するんであれば「net.ipv4.tcp_fin_timeout」*8をかなり短くしてやったほうが良いです。

*1:通信が終わったけど同じポートを同時に使わないように一定時間待ってる状態のソケット

*2:いわゆるuptime

*3:つまりuptimeでも全然問題ない

*4:ちなみに32bit

*5:少なくともiOSAndroidからのパケットには入ってます

*6:前述した通りであれば「一番起動時間が長い端末」ということになる

*7:ただしデフォルトでは無効らしいのでカーネル設定を変更していなければ特に気にすることはありません

*8:TIME_WAIT状態の猶予時間