1 tcp_fin_timeoutとは?
TCPコネクション開放する際、パッシブクローズ側のプロセスが、なんらかの理由でcloseシステムコール(以降close())を実行できないと(*)、アクティブクローズ側でFIN-WAIT-2状態のソケットが残ったままになります。このような状態を防ぐため、tcp_fin_timeoutというカーネルパラメータがあります。このパラメータのデフォルト値は60秒です。60秒経過すると、ソケットの状態がFIN-WAIT-2からCLOSEDに遷移します。なお、TCPの状態遷移については、TCPの各種状態の作り方 - hana_shinのLinux技術ブログを参照してください。
(*) close()を実行できない理由として、バグでclose()を実行しない、CPU負荷が高くCPU実行権を得られない場合等があります。
1.1 正常シーケンス(パッシブクローズ側がclose()を実行する場合)
パッシブクローズ側が正常にclose()を実行する場合のシーケンスを以下に示します。クライアントでCtrl+cを押下するとFINパケットをサーバに送信します。サーバは、クライアントに送信するデータがなければ、close()を呼び出してFINパケットをクライアントに送信します。クライアントは、FINパケットを受信すると、その応答としてACKパケットをサーバに送信します。このとき、クライアントのソケットの状態がFIN-WAIT-2 からTIME-WAITに遷移します。
client server(11111) | | | | | | | | ESTABLISHED | | ESTABLISHED | | | | | | | | | | | | Ctrl + c -*- |-------------- FIN --------------->| | | | | | FIN-WAIT-1 | | | | | | | -*- |<------------- ACK ----------------| -*- | | | | | | | | FIN-WAIT-2 | | CLOSE_WAIT | | | | | | | | | |<------------- FIN ----------------| -*- <--- close() | | | | | | | | | | | LAST_ACK | | | | | | | | -*- |-------------- ACK --------------->| -*- | | | | TIME-WAIT | | CLOSED | | | |
1.2 異常シーケンス(パッシブクローズ側がclose()を実行できない場合)
パッシブクローズ側のプロセスが、何らかの理由でclose()を実行できない場合のシーケンスを以下に示します。次のように実行して疑似的にそのような状態を作成してみます。まず、サーバでCtrl+zを押下してncプロセスを停止します。サーバのncプロセスは停止状態なので、クライアントからFINパケットを受信してもclose()を実行することができません。このため、アクティブクローズ側はFIN-WAIT-2状態のままとなります。
client server(11111) | | | | | | | | ESTABLISHED | | ESTABLISHED | | | | | | | | <- Ctrl +z | | | | | | | | Ctrl + c -*- |-------------- FIN --------------->| | | | | | FIN-WAIT-1 | | | | | | | -*- |<------------- ACK ----------------| -*- | | A | | | | | | | FIN-WAIT-2 | | | CLOSE_WAIT | | | | | | | | | | | | net.ipv4.tcp_fin_timeout | | Can't execute close() | | | | | | | | | | | | V | | -*- | --- | | | | | | CLOSED | | | | | | |
2 検証環境
2.1 ネットワーク構成
サーバとクライアントの2台構成です。図中のenp1s0はNICの名前です。
192.168.122.0/24 client(enp1s0) ------------------------------------------(enp1s0) server .177 .68
2.2 版数
サーバとクライアントのAlmaLinuxの版数は以下のとおりです。
[root@server ~]# cat /etc/redhat-release AlmaLinux release 8.6 (Sky Tiger)
カーネル版数は以下のとおりです。
[root@server ~]# uname -r 4.18.0-372.9.1.el8.x86_64
3 デフォルトの動作確認
tcp_fin_timeoutがデフォルトの60秒のときの動作を確認してみます。
サーバでncコマンドを実行します。このとき、TCPの11111番ポートでListenします。なお。ncコマンドの使い方は、ncコマンドの使い方(ネットワーク実験の幅が広がるなぁ~) - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# nc -kl 11111
lsofコマンドを実行します。ncプロセスがTCPの11111番ポートでListenしていることがわかります。なお、lsofコマンドの使い方は、lsofコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# lsof -c nc -a -i -a -nP COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nc 1524 root 3u IPv4 30240 0t0 TCP *:11111 (LISTEN)
クライアントからサーバの11111番ポートにアクセスできるようにするため、TCPの11111番ポートを解放します。なお、firewall-cmdの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# firewall-cmd --add-port=11111/tcp success
クライアントでncコマンドを実行します。このとき、サーバの11111番ポートにTCPコネクションを確立します。
[root@client ~]# nc 192.168.122.68 11111
サーバでCtrl + zを押下して、ncプロセスを停止します。ncプロセスは停止しているので、close()を実行することができません。
[root@server ~]# nc -kl 11111 ^Z [1]+ 停止 nc -kl 11111
サーバでpsコマンドを実行します。S列がTとなっているので、ncプロセスが停止していることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# ps -C nc -o comm,pid,state COMMAND PID S nc 1524 T
クライアントでカーネルパラメータ(tcp_fin_timeout)の値を確認します。tcp_fin_timeoutのデフォルト値は60秒であることがわかります。
[root@client ~]# sysctl -n net.ipv4.tcp_fin_timeout 60
クライアンでCtrl +cを押下してncプロセスを終了します。ncプロセスが終了する時、FINパケットがサーバに送信されます。tcpdumpコマンドを実行していると、そのことが確認できます。なお、tcpdumpコマンドの使い方は、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログを参照してください。
[root@client ~]# nc 192.168.122.68 11111 ^C
ncコマンドを終了直後にクライントでssコマンドを実行します。FIN-WAIT-2状態の残り時間が58秒であることがわかります。残り時間が60秒ではなく58秒なのは、Ctrll+cを押下してから、ssコマンドを実行するまでに2秒かかっているからです。なお、ssコマンドの使い方は、ssコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
[root@client ~]# ss -not4 'dport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process FIN-WAIT-2 0 0 192.168.122.177:32804 192.168.122.68:11111 timer:(timewait,58sec,0)
再度、クライントでssコマンドを実行します。FIN-WAIT-2状態の残りが約1.8ミリ秒であることがわかります。
[root@client ~]# ss -not4 'dport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process FIN-WAIT-2 0 0 192.168.122.177:32804 192.168.122.68:11111 timer:(timewait,1.751ms,0)
クライントでssコマンドを実行します。60秒経過するとFIN-WAIT-2状態のソケットがなくなったことがわかります。
[root@client ~]# ss -not4 'dport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
4 カーネルパラメータ変更後の動作確認
次は、tcp_fin_timeoutを30秒に変更してみます。30秒経過するとFIN-WAIT-2のソケットがなくなることを確認してみます。
サーバでncコマンドを実行します。このとき、TCPの11111番ポートでListenします。
[root@server ~]# nc -kl 11111
クライアントでncコマンドを実行します。このとき、サーバの11111番ポートにTCPコネクションを確立します。
[root@client ~]# nc 192.168.122.68 11111
サーバでCtrl + zを押下して、ncプロセスを停止します。ncプロセスが停止しているので、ncプロセスはclose()を実行することができません。
[root@server ~]# nc -kl 11111 ^Z [1]+ 停止 nc -kl 11111
tcp_fin_timeoutを30秒に変更してみます。
[root@client ~]# sysctl -w net.ipv4.tcp_fin_timeout=30 net.ipv4.tcp_fin_timeout = 30
tcp_fin_timeoutの値を確認します。30秒に変更されたことがわかります。
[root@client ~]# sysctl -n net.ipv4.tcp_fin_timeout 30
クライアンでCtrl +cを押下して、ncコマンドを終了します。Ctrl +cを押下することで、クライアントからサーバに向けてFINパケットが送信されます。
[root@client ~]# nc 192.168.122.68 11111 ^C
ncコマンドを終了直後にクライントでssコマンドを実行します。FIN-WAIT-2状態の残り時間が28秒であることがわかります。残り時間が30秒ではなく28秒なのは、Ctrll+cを押下してから、ssコマンドを実行するまでに2秒かかっているからです。
[root@client ~]# ss -no4 'dport == :11111' Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process tcp FIN-WAIT-2 0 0 192.168.122.177:32808 192.168.122.68:11111 timer:(timewait,28sec,0)
再度、クライントでssコマンドを実行します。FIN-WAIT-2状態の残りが約1.5ミリ秒であることがわかります。
[root@client ~]# ss -no4 'dport == :11111' Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process tcp FIN-WAIT-2 0 0 192.168.122.177:32808 192.168.122.68:11111 timer:(timewait,1.487ms,0)
クライントでssコマンドを実行します。30秒経過するとFIN-WAIT-2状態のソケットがなくなったことがわかります。
[root@client ~]# ss -no4 'dport == :11111' Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
Z 参考情報
私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ