hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

カーネルパラメータの使い方(tcp_fin_timeout編)



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技術ブログ