hana_shinのLinux技術ブログ

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

ssコマンドのRecv-Q,Send-Qの意味について(ESTABLISHED状態のとき)

1 はじめに

ssコマンドを実行すると、Recv-QとSend-Q という項目が表示されます。本記事では、Recv-QとSend-Qの意味について説明します。なお、ssコマンドの使い方は、ssコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ss -antpo4
State          Recv-Q         Send-Q                  Local Address:Port                   Peer Address:Port          Process
LISTEN         0              128                           0.0.0.0:22                          0.0.0.0:*              users:(("sshd",pid=842,fd=5))
ESTAB          0              0                      192.168.122.68:22                    192.168.122.1:51132          users:(("sshd",pid=1504,fd=5),("sshd",pid=1486,fd=5)) timer:(keepalive,49min,0)

結論を先に説明すると、それぞれ以下の意味になります。ESTABLISHED状態とLISTEN状態では意味が異なります。ここでは、ESTABLISHED状態のときのRecv-Q,Send-Qの意味について検証してみます。LISTEN状態のときは、別途記事を作成予定です。

項目 意味
Recv-Q カーネル受信バッファに残っている受信データのバイト数を表します。ユーザプロセスがカーネル受信バッファから受信データを全て読み出すと0になります
Send-Q カーネル送信バッファに残っている送信データのバイト数を表します。送信データに対してACKが返ると、カーネル送信バッファに残っている送信データが削除されます。送信データを全て削除すると0になります

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 事前準備

クライアントからサーバの11111番ポートにアクセスできるようにするため、TCPの11111番ポートを解放します。なお、firewall-cmdコマンドの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# firewall-cmd --add-port=11111/tcp

設定を確認します。TCPの11111番ポートへのアクセスを許可するルールが追加されたことがわかります。

[root@server ~]# firewall-cmd --list-ports
11111/tcp

4 Recv-Qの確認

サーバでncコマンドを実行します。なお、ncコマンドのインストール方法、使い方は、ncコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# nc -kl 11111

送信データのデータ長(TCPペイロード長)を確認するため、tsharkコマンドを実行します。なお、tsharkコマンドのインストール方法、使い方は、tsharkコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tshark -i enp1s0 -n -Y 'tcp.port==11111'

ncコマンドを実行すると、サーバ/クライアント間でTCPコネクションが確立されます。

[root@client ~]# nc 192.168.122.68 11111

サーバでssコマンドを実行すると、サーバ/クライアント間でTCPコネクションが確立され、Recv-Qの値が0であることがわかります(一番下の行)。

[root@server ~]# ss -na4 'sport == :11111'
Netid    State     Recv-Q    Send-Q          Local Address:Port             Peer Address:Port     Process
tcp      LISTEN    0         1                     0.0.0.0:11111                 0.0.0.0:*
tcp      ESTAB     0         0              192.168.122.68:11111         192.168.122.177:59746

Ctrl+zを押下して、ncプロセスを停止状態にします。

[root@server ~]# nc -kl 11111
^Z
[1]+  停止                  nc -kl 11111

psコマンドを実行すると、ncプロセスの状態が停止状態(STAT列がT)になっていることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C nc -o comm,pid,stat
COMMAND             PID STAT
nc                 1473 T

クライアントで12345と入力したあと、Enterキーを押下します。改行コードも含めて6バイト入力することになります。

[root@client ~]# nc 192.168.122.68 11111
12345

tsharkの実行結果を確認すると、クライアントからサーバに6バイト(Len=6)送信されていることがわかります。

[root@server ~]# tshark -i enp1s0 -n -Y 'tcp.port==11111'
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp1s0'
 1481 2624.783047786 192.168.122.177 → 192.168.122.68 TCP 72 59742 → 11111 [PSH, ACK] Seq=1 Ack=1 Win=229 Len=6 TSval=515145826 TSecr=1559586379
 1482 2624.783147350 192.168.122.68 → 192.168.122.177 TCP 66 11111 → 59742 [ACK] Seq=1 Ack=7 Win=227 Len=0 TSval=1562229283 TSecr=515145826

サーバでssコマンドを実行すると、Recv-Qの値が6になっていることがわかります。

[root@server ~]# ss -na4 'sport == :11111'
Netid    State     Recv-Q    Send-Q          Local Address:Port             Peer Address:Port     Process
tcp      LISTEN    0         1                     0.0.0.0:11111                 0.0.0.0:*
tcp      ESTAB     6         0              192.168.122.68:11111         192.168.122.177:59742

fgコマンドを実行して、停止しているncプロセスを再開します。

[root@server ~]# fg
nc -kl 11111

ncプロセスを再開すると、ncプロセスがカーネル受信バッファから受信データを読み出すので、Recv-Qの値が6から0になったことがわかります。

[root@server ~]# ss -na4 'sport == :11111'
Netid    State     Recv-Q    Send-Q          Local Address:Port             Peer Address:Port     Process
tcp      LISTEN    0         1                     0.0.0.0:11111                 0.0.0.0:*
tcp      ESTAB     0         0              192.168.122.68:11111         192.168.122.177:59746

5 Send-Qの確認

サーバでncコマンドを実行します。

[root@server ~]# nc -kl 11111

クライアントでncコマンドを実行すると、サーバ/クライアント間でTCPコネクションが確立されます。

[root@client ~]# nc 192.168.122.68 11111

サーバでssコマンドを実行すると、サーバ/クライアント間でTCPコネクションが確立され、Send-Qの値が0であることがわかります(一番下の行)。そして、カーネル送信バッファに送信データが残っていないことになります。

[root@server ~]# ss -na4 'sport == :11111'
Netid    State     Recv-Q    Send-Q          Local Address:Port             Peer Address:Port     Process
tcp      LISTEN    0         1                     0.0.0.0:11111                 0.0.0.0:*
tcp      ESTAB     0         0              192.168.122.68:11111         192.168.122.177:59750

クライアントでiptablesコマンドを実行します。このとき、クライアントからサーバへのACKパケットを廃棄する設定をします。

[root@client ~]# iptables -I OUTPUT -p tcp --dport 11111 --tcp-flags ALL ACK -j DROP

設定を確認すると、OUTPUTチェインの先頭(1番目)にACKパケットを廃棄する設定がされていることがわかります。

[root@client ~]# iptables -nvL OUTPUT --line-numbers
Chain OUTPUT (policy ACCEPT 216 packets, 25829 bytes)
num   pkts bytes target     prot opt in     out     source               destination       
1        0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x3F/0x10

サーバで12345と入力したあと、Enterキーを押下します。改行コードも含めて6バイト入力することになります。

[root@server ~]# nc -kl 11111
12345

サーバでssコマンドを実行します。サーバからクライアントに送信したデータに対してACKを受信できないので、カーネル送信バッファに送信データが(6バイト)残ったままになっていることがわかります。

[root@server ~]# ss -na4 'sport == :11111'
Netid    State     Recv-Q    Send-Q          Local Address:Port             Peer Address:Port     Process
tcp      LISTEN    0         1                     0.0.0.0:11111                 0.0.0.0:*
tcp      ESTAB     0         6              192.168.122.68:11111         192.168.122.177:59750

クライアントでiptablesコマンドを実行します。OUTPUTチェインの統計情報を確認すると、ACKパケットを9個廃棄していることがわかります。廃棄した9個のACKパケットの内訳ですが、1つは最初にサーバからクライアントに送信したデータに対するACKパケット、残りはサーバからクライアントへの再送データに対するACKパケットです。なお、iptablesコマンドの実行タイミングによって、ACKパケットの廃棄数は異なります。

[root@client ~]# iptables -nvL OUTPUT --line-numbers
Chain OUTPUT (policy ACCEPT 226 packets, 27413 bytes)
num   pkts bytes target     prot opt in     out     source               destination       
1        9   564 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x3F/0x10

ACKパケットを廃棄するルールを削除します。なお、引数に指定している1は、1番目のルールを意味しています。

[root@client ~]# iptables -D OUTPUT 1

OUTPUTのルールを確認します。設定したルールが削除されたことがわかります。

[root@client ~]# iptables -nvL OUTPUT --line-numbers
Chain OUTPUT (policy ACCEPT 257 packets, 30621 bytes)
num   pkts bytes target     prot opt in     out     source               destination       

ACKパケットを廃棄するルールを削除すると、Send-Q の値が0になることがわかります。

[root@server ~]# ss -na4 'sport == :11111'
Netid    State     Recv-Q    Send-Q          Local Address:Port             Peer Address:Port     Process
tcp      LISTEN    0         1                     0.0.0.0:11111                 0.0.0.0:*
tcp      ESTAB     0         0              192.168.122.68:11111         192.168.122.177:59750

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

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

1 tcp_tw_reuseとは?

tcp_tw_reuseは、TCPコネクション確立時に使用していたローカルポートをTIME-WAIT状態のソケットで使用できるかどうかを指定するカーネルパラメータです。

tcp_tw_reuseが1の場合、クライアントが時刻T1のときに使用していたローカルポート(P1)を使って、時刻T2でサーバとTCPコネクションを確立することができます。一方、tcp_tw_reuseが2(デフォルト値)の場合、このようなことはできません。クライアントがP1を使ってサーバとTCPコネクションを確立するには、時刻T3でソケットの状態がTIME-WAITからCLOSEDになってからです。なお、TCPの状態については、TCPの各種状態の作り方 - hana_shinのLinux技術ブログを参照してください。

  TIME       client                              server
   |           |                                   |
   |           |                                   |
  T1   IP1,P1  |<-- TCP established connection --->| IP2,P2
   |           |                                   |
   |           |                                   |
   |   close() |--------------- FIN -------------->|
   |           |<----------- FIN + ACK ------------|
   |    -*-    |--------------- ACK -------------->|
   |     |     |                                   |
   |     |     |                                   |
   |     |     |                                   |
   |     |     |                                   |
   |     |     |                                   |
   | TIME-WAIT |                                   |
   |   (60s)   |                                   |
   |     |     |                                   |
  T2     |     |                                   |
   |     |     |                                   |
   |     |     |                                   |
   |    -*-    |                                   |
   |           |                                   |
  T3   IP1,P1  |<-- TCP established connection --->| IP2,P2
   |           |                                   |
(*) IP1,IP2はIPアドレス、P1,P2はポート番号

(注)本パラメーラを変更すると、システム上の全てのアプリケーションに影響がでます。TCPコネクション単位でTIME-WAIT状態のソケットを再利用できるようにするには、ソケットオプション(SO_REUSEADDR)を使用してください。

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 事前準備

クライアントからサーバの11111番ポートにアクセスできるようにするため、TCPの11111番ポートを解放します。なお、firewall-cmdコマンドの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# firewall-cmd --add-port=11111/tcp

設定を確認します。11111番ポートへのアクセスを許可するルールが追加されたことがわかります。

[root@server ~]# firewall-cmd --list-ports
11111/tcp

カーネルパラメータ(ip_local_port_range)を変更して、クライアントのncプロセスが常に同じローカルポートを使用できるようにします。ここでは、ncプロセスが50001番のローカルポートを使用できるようにします。なお、ip_local_port_rangeの使い方は、カーネルパラメータの使い方(ip_local_port_range編) - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# sysctl -w net.ipv4.ip_local_port_range="50001 50001"
net.ipv4.ip_local_port_range = 50001 50001

変更したカーネルパラメータの値を確認します。

[root@client ~]# sysctl -n net.ipv4.ip_local_port_range
50001   50001

4 tcp_tw_reuseが2の場合(デフォルト時)の動作確認

tcp_tw_reuseのデフォルト値は以下のとおりです。

[root@client ~]# sysctl -n net.ipv4.tcp_tw_reuse
2

サーバでncコマンドを実行します。なお、ncコマンドのインストール方法、使い方は、ncコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# nc -kl 11111

クライアントでncコマンドを実行します。ncコマンドを実行すると、サーバ/クライアント間でTCPコネクションが確立されます。

[root@client ~]# nc 192.168.122.68 11111

クライアントでssコマンドを実行します。サーバ/クライアント間でTCPコネクションが確立されていることがわかります(ssコマンド実行結果の一番下の行)。そして、ncプロセスが使用しているローカルポート番号が50001であることがわかります。なお、ssコマンドの使い方は、ssコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# ss -natop4
State	Recv-Q	Send-Q	Local	Address:Port	Peer	Address:Port	Process
LISTEN	0	128	0.0.0.0:22	0.0.0.0:*	users:(("sshd",pid=830,fd=5))
ESTAB	0	0	192.168.122.177:22	192.168.122.1:60012	users:(("sshd",pid=1529,fd=5),("sshd",pid=1525,fd=5))	timer:(keepalive,88min,0)
ESTAB	0	0	192.168.122.177:22	192.168.122.1:59994	users:(("sshd",pid=1437,fd=5),("sshd",pid=1421,fd=5))	timer:(keepalive,44min,0)
ESTAB	0	0	192.168.122.177:50001	192.168.122.68:11111	users:(("nc",pid=1600,fd=3))

Ctrl+cを押下して、ncコマンドを終了します。

[root@client ~]# nc 192.168.122.68 11111
^C

ncコマンドの終了直後、ssコマンドを実行します。ソケットの状態を確認するとTIME-WAIT状態の残り時間が58秒であることがわかります。

[root@client ~]# ss -natop4
State     Recv-Q Send-Q   Local Address:Port     Peer Address:Port  Process
LISTEN    0      128            0.0.0.0:22            0.0.0.0:*      users:(("sshd",pid=830,fd=5))
ESTAB     0      0      192.168.122.177:22      192.168.122.1:60012  users:(("sshd",pid=1529,fd=5),("sshd",pid=1525,fd=5)) timer:(keepalive,98min,0)
ESTAB     0      0      192.168.122.177:22      192.168.122.1:59994  users:(("sshd",pid=1437,fd=5),("sshd",pid=1421,fd=5)) timer:(keepalive,54min,0)
TIME-WAIT 0      0      192.168.122.177:50001  192.168.122.68:11111  timer:(timewait,58sec,0)

ssコマンド実行直後、クライアントでncコマンドを実行します。TCPの状態がTIME-WAITなので、ncコマンドが終了してしまいます。TIME-WAIT状態のときは、ncコマンド終了直前に使用していたローカルポートを使ってTCPコネクションを確立することができないことがわかります。

[root@client ~]# nc 192.168.122.68 11111
[root@client ~]#

Ctrl+cを押下して60秒経過したあとssコマンドを実行します。60秒経過したのでTCPの状態がTIME-WAITのソケットがなくなったことがわかります。

[root@client ~]# ss -natop4
State   Recv-Q  Send-Q     Local Address:Port    Peer Address:Port  Process
LISTEN  0       128              0.0.0.0:22           0.0.0.0:*      users:(("sshd",pid=830,fd=5))
ESTAB   0       0        192.168.122.177:22     192.168.122.1:60012  users:(("sshd",pid=1529,fd=5),("sshd",pid=1525,fd=5)) timer:(keepalive,97min,0)
ESTAB   0       0        192.168.122.177:22     192.168.122.1:59994  users:(("sshd",pid=1437,fd=5),("sshd",pid=1421,fd=5)) timer:(keepalive,53min,0)

再度、ncコマンドを実行します。サーバとTCPコネクションを確立できることがわかります。

[root@client ~]# nc 192.168.122.68 11111

5 tcp_tw_reuseが1の場合の動作確認

TIME-WAIT状態のソケットを利用できるように、カーネルパラメータを変更します。

[root@client ~]# sysctl -w net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_reuse = 1

設定変更したカーネルパラメータの値を確認します。

[root@client ~]# sysctl -n net.ipv4.tcp_tw_reuse
1

サーバでncコマンドを実行します。

[root@server ~]# nc -kl 11111

クライアントでssコマンドを実行します。

[root@client ~]# nc 192.168.122.68 11111

クライアントでssコマンドを実行します。サーバ/クライアント間でTCPコネクションが確立されていることがわかります(ssコマンド実行結果の一番上の行)。

[root@client ~]# ss -nato4
State   Recv-Q  Send-Q     Local Address:Port      Peer Address:Port   Process
LISTEN  0       128              0.0.0.0:22             0.0.0.0:*
ESTAB   0       0        192.168.122.177:50001   192.168.122.68:11111
ESTAB   0       0        192.168.122.177:22       192.168.122.1:48296   timer:(keepalive,98min,0)
ESTAB   0       0        192.168.122.177:22       192.168.122.1:48298   timer:(keepalive,102min,0)

Ctrl+cを押下して、ncコマンドを終了します。

[root@client ~]# nc 192.168.122.68 11111
^C

ncコマンドの終了直後、ssコマンドを実行します。ソケットの状態を確認するとTCPがTIME-WAIT状態で、残り時間が58秒であることがわかります。

[root@client ~]# ss -nato4
State     Recv-Q  Send-Q     Local Address:Port      Peer Address:Port  Process
LISTEN    0       128              0.0.0.0:22             0.0.0.0:*
TIME-WAIT 0       0        192.168.122.177:50001   192.168.122.68:11111  timer:(timewait,58sec,0)
ESTAB     0       0        192.168.122.177:22       192.168.122.1:48296  timer:(keepalive,95min,0)
ESTAB     0       0        192.168.122.177:22       192.168.122.1:48298  timer:(keepalive,99min,0)

ssコマンド実行直後、クライアントでncコマンドを実行します。今度は、tcp_tw_reuseが有効になっているため、サーバ/クライアント間でTCPコネクションが確立できることがわかります。

[root@client ~]# nc 192.168.122.68 11111

クライアントでssコマンドを実行します。サーバ/クライアント間でTCPコネクションが確立されていることがわかります(ssコマンド実行結果の一番上の行)。

[root@client ~]# ss -nato4
State   Recv-Q  Send-Q     Local Address:Port      Peer Address:Port   Process
LISTEN  0       128              0.0.0.0:22             0.0.0.0:*
ESTAB   0       0        192.168.122.177:50001   192.168.122.68:11111
ESTAB   0       0        192.168.122.177:22       192.168.122.1:48296   timer:(keepalive,95min,0)
ESTAB   0       0        192.168.122.177:22       192.168.122.1:48298   timer:(keepalive,99min,0)

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ
TCPの状態遷移については、TCPの各種状態の作り方 - hana_shinのLinux技術ブログを参照してください。

ソケットオプションの使い方(SO_KEEPALIVE)

1 TCP Keep-Aliveとは?

1.1 概要

TCPコネクション確立状態において、相手の生存確認をするためのTCP Keep-Aliveという機能があります。相手からTCPパケットを受信したあと、一定時間相手からTCPパケットを受信しないと、相手が生存しているかどうかを確認するため、TCP Keep-Aliveパケットを相手に送信します。TCP Keep-Aliveパケットを規定回数送信しても、相手から応答がないとRSTパケットを送信してTCPコネクションを終了します。

なお、TCP Keep-Alive機能の設定方法は、以下のものがあります。

方法 備考
カーネルパラメータを変更する方法 カーネルパラメータの変更がシステム全体のアプリケーションに影響します。個人的な見解ですが、この方法はお勧めしません
ソケットオプションを使う方法 ソケットオプションを指定したTCPコネクションについてTCP Keep-Alive機能が有効になります。ソケットオプションを使うと、カーネルパラメータの設定内容をオーバライトします

1.2 ソケットオプション

ソケットオプション 意味
TCP_KEEPIDLE 相手からTCPパケットを受信してから、TCP Keep-Aliveパケットを送信するまでの時間(秒)をあらわします
TCP_KEEPINTVL TCP Keep-Aliveパケットに対して相手からACKパケットを受信しないと、TCP_KEEPINTVL間隔(秒)でTCP Keep-Aliveパケットを送信します
TCP_KEEPCNT TCP Keep-Aliveパケットの送信回数をあらわします。TCP Keep-Aliveパケットは、生存確認のため相手に送信するパケットです。TCP Keep-Aliveパケットは、TCPペイロード長が0バイトのパケットです。つまりTCPヘッダだけのTCPパケットです

1.3 シーケンス

TCP Keep-Aliveパケットに対して相手からACKパケットが返ってくる場合のシーケンスを以下に示します。相手からTCPパケット(データやACK等)を受信して、一定時間(TCP_KEEPIDLE)経過すると、TCP Keep-Aliveパケットを相手に送信します。そのあと、TCP_KEEPINTVL間隔でTCP Keep-Aliveパケットを送信します。TCP Keep-Aliveパケットに対してACKパケットが返ってくると、TCPコネクションは維持されます。

client                              server(11111)
   |                                   |
   |                                   |
   |------------ TCP packet ---------->| -*-
   |                                   |  |
   |                                   | TCP_KEEPIDLE
   |                                   |  |
   |<-------- TCP Keep-Alive ----------| -*-
   |--------------- ACK -------------->| -*-
   |                                   |  |
   |                                   | TCP_KEEPINTVL
   |                                   |  |
   |<-------- TCP Keep-Alive ----------| -*-
   |--------------- ACK -------------->|
   |                                   |

TCP Keep-Aliveパケットに対して相手からACKパケットが返ってこない場合のシーケンスを以下に示します。相手からACKパケットが返ってこない場合、TCP Keep-Aliveパケットを再送します。規定した回数TCP Keep-Aliveパケットを送信しても相手からACKパケットが返ってこないと、RSTパケットを送信してTCPコネクションを終了します。

      client                              server(11111)
         |                                   |
         |------------ TCP packet ---------->| -*-
         |                                   |  |
         |                                   | TCP_KEEPIDLE
         |                                   |  |
         |<-------- TCP Keep-Alive ----------| -*-
         |                                   |  |
         |                                   | TCP_KEEPINTVL
         |                                   |  |
         |<---- TCP Keep-Alive(Retry 1) -----| -*-
         |                                   |  |
 ====================================================
         |                                   |  |
         |                                   | TCP_KEEPINTVL
         |                                   |  |
         |<---- TCP Keep-Alive(Retry n) -----| -*-
         |                                   |
         |<-------------- RST ---------------|
         |                                   |

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

クライアントからサーバの11111番ポートにアクセスできるようにするため、TCPの11111番ポートを解放します。なお、firewall-cmdの使い方は、firewall-cmdの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# firewall-cmd --add-port=11111/tcp
success

ルールを確認します。11111番ポートへのアクセスを許可するルールが追加されたことがわかります。

[root@server ~]# firewall-cmd --list-ports
11111/tcp

3 テストプログラム

サーバとクライアントのテストプログラム(以降TP)を作成します。

3.1 サーバ側TP

サーバのTPは、以下のようになります。11111番ポートでTCPパケットを受信します。TPの引数に1を指定すると、TCP Keep-Aliveが有効になります。0を指定すると、TCP Keep-Aliveが無効になります。なお、処理を分かりやすくするため、意図的に全ての異常処理は記載していません。

[root@server ~]# cat sv.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

int main(int argc, char *argv[])
{
    int lfd, cfd, val;
    socklen_t len;
    struct sockaddr_in sv, cl;
    char buf[32];
    ssize_t n;

    lfd = socket(AF_INET, SOCK_STREAM, 0);

    sv.sin_family = AF_INET;
    sv.sin_port = htons(11111);
    sv.sin_addr.s_addr = INADDR_ANY;

    bind(lfd, (struct sockaddr *)&sv, sizeof(sv));
    listen(lfd, 5);
    memset(buf, 0, sizeof(buf));
    len = sizeof(cl);
    cfd = accept(lfd, (struct sockaddr *)&cl, &len);

    val = atoi(argv[1]);
    if(val == 1){
      setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE , (void *)&val, sizeof(val));

      val = 30;
      setsockopt(cfd, SOL_TCP, TCP_KEEPIDLE, (void *)&val, sizeof(val));
      val = 5;
      setsockopt(cfd, SOL_TCP, TCP_KEEPINTVL, (void *)&val, sizeof(val));
      val = 2;
      setsockopt(cfd, SOL_TCP, TCP_KEEPCNT, (void *)&val, sizeof(val));

    }

    while (1) {
        n = read(cfd, buf, sizeof(buf));
        if(n > 0) {
            continue;
        }
        else if(n == 0) {
            fprintf(stderr,"We received EOF.\n");
            close(cfd);
            return 0;
        }
        else {
            perror("read");
            return 1;
        }
    }
    close(lfd);
    return 0;
}

3.2 クライアント側TP

クライアントのTPは、以下のようになります。connectシステムコールを実行してTCPコネクションを確立したあと、600秒スリープします。なお、処理を分かりやすくするため、意図的に全ての異常処理は記載していません。

[root@client ~]# cat cl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    struct sockaddr_in server;
    int sock;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    server.sin_family = AF_INET;
    server.sin_port = htons(11111);
    server.sin_addr.s_addr = inet_addr("192.168.122.68");

    connect(sock, (struct sockaddr *)&server, sizeof(server));
    fprintf(stderr,"I will now sleep for 600 seconds.\n");
    sleep(600);
    close(sock);
    return 0;
}

3.3 コンパイル

サーバのTPをコンパイルします。

[root@server ~]# gcc -Wall -o sv sv.c

クライアントのTPをコンパイルします。

[root@client ~]# gcc -Wall -o cl cl.c

4 動作確認

4.1 相手から応答がない場合

サーバでTPを実行します。引数に1を指定してTCP Keep-Alive機能を有効にします。

[root@server ~]# ./sv 1

TCP Keep-Aliveの動作確認をするため、クライアントでtsharkコマンドを実行します。なお、tsharkコマンドのインストール方法、使い方は、tsharkコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tshark -i enp1s0 port 11111

クライアントでTPを実行します。

[root@client ~]# ./cl

iptablesコマンドを使って、TCP Keep-Aliveパケットに対するACKパケットを廃棄する設定をします。iptablesコマンドは、サーバがクライアントにTCP Keep-Aliveパケット送信する30秒以内に実行してください。なお、iptablesコマンドの使い方は、iptablesコマンドの使い方 - Qiitaを参照してください(hatenaに移行予定)。

[root@client ~]# iptables -I OUTPUT -p tcp --dport 11111 --tcp-flags ACK ACK -j DROP

tsharkコマンドの実行結果を確認します。TCPコネクション確立のあと(No1,2,3)、30秒後にサーバからクライアントにTCP Keep-Aliveを送信しています(No.4)。そのあと、もう一度TCP Keep-Aliveパケットを送信していますが(No.5)、クライアントからACKパケットの応答がないので、No.6でクライアントにRSTパケットを送信していることがわかります。

[root@server ~]# tshark -i enp1s0 port 11111
    1 0.000000000 192.168.122.177 → 192.168.122.68 TCP 74 51692 → 11111 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=522082979 TSecr=0 WS=128
    2 0.000172750 192.168.122.68 → 192.168.122.177 TCP 74 11111 → 51692 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=383746932 TSecr=522082979 WS=128
    3 0.001971330 192.168.122.177 → 192.168.122.68 TCP 66 51692 → 11111 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=522082981 TSecr=383746932
    4 30.271443982 192.168.122.68 → 192.168.122.177 TCP 66 [TCP Keep-Alive] 11111 → 51692 [ACK] Seq=0 Ack=1 Win=29056 Len=0 TSval=383777203 TSecr=522082981
    5 35.391063189 192.168.122.68 → 192.168.122.177 TCP 66 [TCP Keep-Alive] 11111 → 51692 [ACK] Seq=0 Ack=1 Win=29056 Len=0 TSval=383782323 TSecr=522082981
    6 40.511581482 192.168.122.68 → 192.168.122.177 TCP 66 11111 → 51692 [RST, ACK] Seq=1 Ack=1 Win=29056 Len=0 TSval=383787443 TSecr=522082981

クライアントでiptablesのOUTPUTチェインの統計情報を確認します。宛先ポート番号が11111番のACKパケットが2つ廃棄されていることがわかります。

[root@client ~]# iptables -nvL OUTPUT
Chain OUTPUT (policy ACCEPT 3031 packets, 211K bytes)
 pkts bytes target     prot opt in     out     source               destination
    2   104 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x10/0x10

iptablesの設定を削除します。

[root@client ~]# iptables -D OUTPUT 1

iptablesの設定を確認します。設定が削除されたことがわかります。

[root@client ~]# iptables -nvL OUTPUT
Chain OUTPUT (policy ACCEPT 3053 packets, 214K bytes)
 pkts bytes target     prot opt in     out     source               destination

4.2 相手から応答がある場合

サーバでTPを実行します。引数に1を指定してTCP Keep-Alive機能を有効にします。

[root@server ~]# ./sv 1

クライアントでtsharkコマンドを実行します。

[root@client ~]# tshark -i enp1s0 port 11111

クライアントでTPを実行します。

[root@client ~]# ./cl

tsharkコマンドの実行結果を確認します。サーバからクライアントへのTCP Keep-Aliveパケットに対してACKパケットの応答があるので、サーバからクライアントにRSTパケットは送信されず、TCPコネクションが維持されたままであることがわかります。

[root@server ~]# tshark -i enp1s0 port 11111
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp1s0'
    1 0.000000000 192.168.122.177 → 192.168.122.68 TCP 74 51704 → 11111 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=526571670 TSecr=0 WS=128
    2 0.000109383 192.168.122.68 → 192.168.122.177 TCP 74 11111 → 51704 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=388235623 TSecr=526571670 WS=128
    3 0.001454536 192.168.122.177 → 192.168.122.68 TCP 66 51704 → 11111 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=526571672 TSecr=388235623
    4 30.284162023 192.168.122.68 → 192.168.122.177 TCP 66 [TCP Keep-Alive] 11111 → 51704 [ACK] Seq=0 Ack=1 Win=29056 Len=0 TSval=388265907 TSecr=526571672
    5 30.285131057 192.168.122.177 → 192.168.122.68 TCP 66 [TCP Keep-Alive ACK] 51704 → 11111 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=526601956 TSecr=388235623
    6 60.492579664 192.168.122.68 → 192.168.122.177 TCP 66 [TCP Keep-Alive] 11111 → 51704 [ACK] Seq=0 Ack=1 Win=29056 Len=0 TSval=388296115 TSecr=526601956
    7 60.493456777 192.168.122.177 → 192.168.122.68 TCP 66 [TCP Keep-Alive ACK] 51704 → 11111 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=526632164 TSecr=388235623
    8 90.700236661 192.168.122.68 → 192.168.122.177 TCP 66 [TCP Keep-Alive] 11111 → 51704 [ACK] Seq=0 Ack=1 Win=29056 Len=0 TSval=388326323 TSecr=526632164
    9 90.701144264 192.168.122.177 → 192.168.122.68 TCP 66 [TCP Keep-Alive ACK] 51704 → 11111 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=526662372 TSecr=388235623

5 メモ

setsockoptシステムコールを実行すると、sock_setsockopt関数でSO_KEEPALIVEオプションの設定を有効にします。

net/core/sock.c
int sock_setsockopt(struct socket *sock, int level, int optname,
                    char __user *optval, unsigned int optlen)
{
-snip-
        case SO_KEEPALIVE:
                if (sk->sk_prot->keepalive)
                        sk->sk_prot->keepalive(sk, valbool);
                sock_valbool_flag(sk, SOCK_KEEPOPEN, valbool);
                break;

TCP_KEEPIDLE、TCP_KEEPINTVL、TCP_KEEPCNTは以下の関数で設定されます。

net/ipv4/tcp.c
static int do_tcp_setsockopt(struct sock *sk, int level,
                int optname, char __user *optval, unsigned int optlen)
{
-snip-
        case TCP_KEEPIDLE:
                err = tcp_sock_set_keepidle_locked(sk, val);
                break;
        case TCP_KEEPINTVL:
                if (val < 1 || val > MAX_TCP_KEEPINTVL)
                        err = -EINVAL;
                else
                        tp->keepalive_intvl = val * HZ;
                break;
        case TCP_KEEPCNT:
                if (val < 1 || val > MAX_TCP_KEEPCNT)
                        err = -EINVAL;
                else
                        tp->keepalive_probes = val;
                break;

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ
https://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO

httpstatコマンドの使い方

1 httpstatコマンドとは?

Webサイトに接続するとき、そのWebサイトを表示するまでの時間を表示するツールです。表示時間は、Webサイトに対するDNS名前解決時間、TCPコネクション確立時間、TLSハンドシェークにかかる時間、サーバでの処理時間、コンテンツの転送時間等からなります。httpstatコマンドは、これら個々の時間をグラフィカルに表示するツールです。

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 インストール方法

下記ページを参考にしてインストールしました。
https://snapcraft.io/install/httpstat/centos

snapdはepelリポジトリにあるので、epel-releaseパッケージをインストールします。

[root@server ~]# dnf -y install epel-release

次に、snapdパッケージをインストールします。

[root@server ~]# dnf -y install snapd

snapd.socketsサービスを有効化します。

[root@server ~]# systemctl enable --now  snapd.socket

snapd.socketsサービスの状態を確認します。activeであることがわかります。

[root@server ~]# systemctl is-active snapd.socket
active

/var/lib/snapd/snapへのシンボリックリンクを/snapという名前で作成します。

[root@server ~]# ln -s /var/lib/snapd/snap /snap

環境変数PATHを確認します。

[root@server ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

仮想マシンを再起動します。

[root@server ~]# shutdown -r now

環境変数PATHを確認します。PATHに/var/lib/snapd/snap/binが追加されたことがわかります。

[root@server ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/var/lib/snapd/snap/bin:/root/bin

httpstatコマンドをインストールします。

[root@server ~]# snap install httpstat
2022-08-15T08:05:19-04:00 INFO Waiting for automatic snapd restart...
httpstat 1.1.3 from Simos Xenitellis (simosx) installed

httpstatコマンドの版数を確認します。

[root@server ~]# httpstat --version
httpstat 1.1.3

4 オプション一覧

オプションは以下のとおりです。

[root@server ~]# httpstat -h
Usage: httpstat URL [CURL_OPTIONS]
       httpstat -h | --help
       httpstat --version

Arguments:
  URL     url to request, could be with or without `http(s)://` prefix

Options:
  CURL_OPTIONS  any curl supported options, except for -w -D -o -S -s,
                which are already used internally.
  -h --help     show this screen.
  --version     show version.

Environments:
  HTTPSTAT_SHOW_BODY    By default httpstat will write response body
                        in a tempfile, but you can let it print out by setting
                        this variable to `true`.
  HTTPSTAT_SHOW_SPEED   set to `true` to show download and upload speed.

5 基本的な使い方

google.co.jpにアクセスしてみます。実行結果より、DNS名前解決に2ミリ秒、TCPコネクション確立に7ミリ秒かかっていることがわかります。そして、Webサイトの表示まで、155ミリ秒かかっていることがわかります。

[root@server ~]# httpstat https://www.google.co.jp/

HTTP/1.1 200 OK
Date: Mon, 15 Aug 2022 12:33:56 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=Shift_JIS
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2022-08-15-12; expires=Wed, 14-Sep-2022 12:33:56 GMT; path=/; domain=.google.co.jp; Secure
Set-Cookie: AEC=AakniGO6QqLGEKYnZGFHeEFVZVPhQukN8PJICGv8dCSILo9T4DcQ4Lml2g; expires=Sat, 11-Feb-2023 12:33:56 GMT; path=/; domain=.google.co.jp; Secure; HttpOnly; SameSite=lax
Set-Cookie: NID=511=gwIyrsp8Ll8NNpoWS-NAsqjxFazfzK5XX0-yeJZEttW4ylX940nyG4lWC2PvTA1kSVwWXcFakwVEth28EpTDUCxvSV2Hnr9_wRFWKAqWsLeK72j5PrFtMTPtBxCi9mPPutixldm_X78C1RMuWD_z2qEWFxDjlkTTGOiwwxcvMeE; expires=Tue, 14-Feb-2023 12:33:56 GMT; path=/; domain=.google.co.jp; HttpOnly
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
Accept-Ranges: none
Vary: Accept-Encoding
Transfer-Encoding: chunked

Body stored in: /tmp/tmpfntrdrc1

  DNS Lookup   TCP Connection   SSL Handshake   Server Processing   Content Transfer
[     2ms    |       7ms      |     61ms      |       83ms        |        2ms       ]
             |                |               |                   |                  |
    namelookup:2ms            |               |                   |                  |
                        connect:9ms           |                   |                  |
                                    pretransfer:70ms              |                  |
                                                      starttransfer:153ms            |
                                                                                 total:155ms

[root@server ~]#

次に首相官邸のWebサイトにアクセスしてみます。

[root@server ~]# httpstat https://www.kantei.go.jp/jp/rekidainaikaku/index.html

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 88862
Connection: keep-alive
x-amz-id-2: UFaP3UaX/tWPbE6JCa1YJ1gXXx+aqyixuZbIDTTnFjNY5UULoBLw35roP5fW6MbyvGyWRk+IMTM=
x-amz-request-id: ZSHPNNMQ3HWZ01KD
x-amz-replication-status: COMPLETED
Last-Modified: Mon, 08 Aug 2022 15:12:32 GMT
x-amz-server-side-encryption: AES256
x-amz-meta-user-agent: AWSTransfer
x-amz-meta-user-agent-id: kantei-prd-apn01-sftpuser-kantei01-alaya01@s-c73fde2b48d346ed8
x-amz-version-id: _zdgdTcWZOI_4qbLIlrF40P9T2yQjTPU
Accept-Ranges: bytes
Server: none
Date: Mon, 15 Aug 2022 12:50:59 GMT
ETag: "8b8b555a32f6b9a380436f5ddb04e376"
X-Cache: Hit from cloudfront
Via: 1.1 1d52323084c6753f5a9a5a3fa0fda23c.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: NRT57-P3
X-Amz-Cf-Id: d0i96v7wIyX81KhgNqxlwMPYLuYQog5wZgUA64hKTTzC4B_xGpuw6A==
Age: 19

Body stored in: /tmp/tmpb3e5d4e0

  DNS Lookup   TCP Connection   SSL Handshake   Server Processing   Content Transfer
[     4ms    |       9ms      |     30ms      |       42ms        |       12ms       ]
             |                |               |                   |                  |
    namelookup:4ms            |               |                   |                  |
                        connect:13ms          |                   |                  |
                                    pretransfer:43ms              |                  |
                                                      starttransfer:85ms             |
                                                                                 total:97ms

[root@server ~]#

6 転送スピードを表示する方法(HTTPSTAT_SHOW_SPEED=true)

環境変数HTTPSTAT_SHOW_SPEEDにtrueを設定すると、実行結果の最後にspeed_download,speed_uploadが表示されるようになり、Webサイトにアクセスしたときのコンテンツの転送スピードが表示できます。

HTTPSTAT_SHOW_SPEEDにtrueを設定します。

[root@server ~]# export HTTPSTAT_SHOW_SPEED=true

google.co.jpにアクセスしてみます。コンテンツのダウンロードのスピードが 40.8 KiB/秒であることがわかります。

[root@server ~]# httpstat https://www.google.co.jp/

HTTP/1.1 200 OK
Date: Tue, 30 Aug 2022 11:40:14 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=Shift_JIS
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2022-08-30-11; expires=Thu, 29-Sep-2022 11:40:14 GMT; path=/; domain=.google.co.jp; Secure
Set-Cookie: AEC=AakniGNrZxtKHwvokrzHaZ_Z2fhi52HXvanrPJdfR3KBpKOV71kFKWYsEg; expires=Sun, 26-Feb-2023 11:40:14 GMT; path=/; domain=.google.co.jp; Secure; HttpOnly; SameSite=lax
Set-Cookie: NID=511=qn3ZQDxtpzksRgJiLjfh0wIlhWJtuWQVeJ1qNaOimgYEzkUSLJD0pjZGVXTKBhSndDqEb7Qpkw5RU--ft0Ioqv3yIQySgvSkwmygw34bMcSLHYX-l8adHy1nYl0ftHABLNc7XC9MpRobI3blr434XUeTOfQpL-m8ofJ01n9Xzbc; expires=Wed, 01-Mar-2023 11:40:14 GMT; path=/; domain=.google.co.jp; HttpOnly
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
Accept-Ranges: none
Vary: Accept-Encoding
Transfer-Encoding: chunked

Body stored in: /tmp/tmp_auatvri

  DNS Lookup   TCP Connection   SSL Handshake   Server Processing   Content Transfer
[    48ms    |      12ms      |     104ms     |       160ms       |       10ms       ]
             |                |               |                   |                  |
    namelookup:48ms           |               |                   |                  |
                        connect:60ms          |                   |                  |
                                    pretransfer:164ms             |                  |
                                                      starttransfer:324ms            |
                                                                                 total:334ms

speed_download: 40.8 KiB, speed_upload: 0.0 KiB
[root@server ~]#

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinの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コマンドの使い方は、https://hana-shin.hatenablog.com/entry/2021/12/18/203658:titlleを参照してください。

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

TCPの各種状態の作り方

1 はじめに

TCPは以下の状態があります。こここでは、ncコマンド、iptablesコマンドを使って、TCPの各種状態を作ってみます。

状態 意味
LISTEN プロセスが指定したポートでTCPパケットの到着を待っている状態です
ESTABLISHED 3 Way Handshake(SYN,SYN+ACK,ACKのやり取り)が完了した状態です
SYN-SENT SYNを送信して相手からSYN+ACKが返ってくるまでの状態です
SYN-RECEIVED SYN+ACKを送信して、相手からACKが返ってくるまでの状態です
FIN-WAIT-1 アクティブクローズ側がとる状態です。FINを送信して相手からACKが返ってくるまでの状態です
FIN-WAIT-2 アクティブクローズ側がとる状態です。FINを送信して相手からFIN+ACKを受信したあと、相手からFINを受信するまでの状態です
CLOSE-WAIT アプリケーションからTCPコネクションのクローズを待っている状態です。closeシステムコールを実行すると、コネクションをクローズします
LAST-ACK パッシブクローズ側がとる状態です。こちらから相手にFINを送信して、相手からACKを受け取るまでの状態です
TIME-WAIT TIME-WAITは、アクティブクローズ側がとる状態です。相手のFINに対してこちらが送信したACKを、相手が確実に受信できるようにするための状態です
CLOSING アクティブクローズ側がとる状態です。同時クローズをしたときに発生します。FIN-WAIT-1のときに、相手からFINを受信するとCLOSINGになります
CLOSED TCPコネクションが存在しない状態です

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の状態遷移

TCPのコネクション確立/開放時における、TCPの状態遷移を以下に示します。listen(),connect(),close()はシステムコールを表しています。11111はTCPのポート番号を表しています。

                   client                              server(11111)
                      |                                   |
                 |    |                                   |    |
                 |    |                                   |  CLOSED
                 |    |                                   |    |
                 |    |                                   |   -*-   <=== listen()
              CLOSED  |                                   |    |
                 |    |                                   |  LISTEN
                 |    |                                   |    |
 connect() ===> -*-   |-------------- SYN --------------->|   -*-
                 |    |                                   |    |
                 |    |                                   |    |
            SYN-SENT  |                                   |    |
                 |    |                                   |  SYN-RECEIVED
                 |    |                                   |    |
                 |    |                                   |    |
                 |    |<------------- SYN + ACK ----------|    |
                -*-   |-------------- ACK --------------->|   -*-
                 |    |                                   |    |
                 |    |                                   |    |
                 |    |                                   |    |
         ESTABLISHED  |                                   |  ESTABLISHED
                 |    |                                   |    |
                 |    |                                   |    |
                 |    |                                   |    |
   close() ===> -*-   |-------------- FIN --------------->|   -*-
                 |    |                                   |    |
                 |    |                                   |    |
          FIN-WAIT-1  |                                   |    |
                 |    |                                   |    |
                 |    |                                   |    |
                -*-   |<------------- ACK ----------------|  CLOSE-WAIT
                 |    |                                   |    |
                 |    |                                   |    |
          FIN-WAIT-2  |                                   |    |
                 |    |                                   |    |
                 |    |                                   |    |
                 |    |<------------- FIN ----------------|   -*-   <=== close()
                 |    |                                   |    |
                 |    |                                   |    |
                 |    |                                   |   LAST-ACK
                 |    |                                   |    |
                 |    |                                   |    |
                -*-   |-------------- ACK --------------->|   -*-
                 |    |                                   |    |
                 |    |                                   |    |
           TIME-WAIT  |                                   |    |
                 |    |                                   |    |
                 |    |                                   |    |
                -*-   |                                   |   CLOSED
                 |    |                                   |    |
                 |    |                                   |    |
              CLOSED  |                                   |    |
                 |    |                                   |    |

4 LISTEN状態の作り方

サーバでncコマンドを実行します。このとき、ncプロセスがTCPの11111番ポートでListenするようにします。なお、ncコマンドのインストール方法、使い方は、ncコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# nc -kl 11111

もう1つターミナルを開いて、ssコマンドを実行します。ソケットの状態を確認すると、TCPの状態(State列)がLISTEN状態であることがわかります。なお、ssコマンドのインストール方法、使い方は、ssコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ss -an4 'sport == :11111'
Netid        State         Recv-Q        Send-Q                Local Address:Port                  Peer Address:Port        Process
tcp          LISTEN        0             1                           0.0.0.0:11111                      0.0.0.0:*

5 ESTABLISHED状態の作り方

3 Way HandShakeを実行してTCPコネクションを確立すると、TCPの状態がESTABLISHEDになります。ESTABLISHEDになると、ユーザデータのやり取りが可能になります。なお、TCPコネクションの確立、解放シーケンスの詳細は、TCPコネクションの確立、解放シーケンス - hana_shinのLinux技術ブログを参照してください。

             client                              server(11111)
                |                                   |
      connect() |-------------- SYN --------------->|
                |<------------- SYN + ACK ----------|
         -*-    |-------------- ACK --------------->|   -*-
          |     |                                   |    |
    ESTABLISHED |                                   | ESTABLISHED
          |     |                                   |    |

サーバでncコマンドを実行します。このとき、TCPの11111番ポートでListenします。

[root@server ~]# nc -kl 11111

サーバでssコマンドを実行します。ソケットの状態を確認すると、TCPがLISTEN状態であることがわかります。

[root@server ~]# ss -an4 'sport == :11111'
Netid        State         Recv-Q        Send-Q                Local Address:Port                  Peer Address:Port        Process
tcp          LISTEN        0             1                           0.0.0.0:11111                      0.0.0.0:*

クライアントからサーバの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

サーバでssコマンドを実行します。ソケットの状態を確認すると、TCPがESTABLISHED状態であることがわかります。

[root@server ~]# ss -n4 'sport == :11111'
Netid        State        Recv-Q        Send-Q                Local Address:Port                   Peer Address:Port        Process
tcp          ESTAB        0             0                    192.168.122.68:11111               192.168.122.177:42842

次に、クライアントでssコマンドを実行します。ソケットの状態を確認すると、TCPがESTABLISHED状態であることがわかります

[root@client ~]# ss -n4 'dport == :11111'
Netid          State          Recv-Q          Send-Q                       Local Address:Port                        Peer Address:Port           Process
tcp            ESTAB          0               0                          192.168.122.177:42842                     192.168.122.68:11111

6 SYN-SENT状態の作り方

SYN-SENTは、SYNを送信して相手からSYN+ACKが返ってくるまでの状態です。ここでは、クライアントのINPUTチェインにSYN+ACKを廃棄(★)する設定をして、SYN-SENTの状態を作りだしてみます。SYN+ACKを廃棄する理由は、SYN+ACKを破棄せず受信してしまうと、クライアントがACKを送信してTCPの状態がESTABLISHEDになってしまうからです。他の方法として、クライアントのOUTPUTチェイン、または、サーバのINPUTチェインでSYNを破棄しても、同じ結果が得られます。

             client                              server(11111)
                |                                   |
 connect() -*-  |-------------- SYN --------------->|
            |   |                                   |
            |   |                                   |
        SYN-SENT|                                   |
            |   |                                   |
            |   ★ <----------- SYN + ACK ----------|

6.1 作成手順

クライアントでiptablesコマンドを実行します。このとき、サーバからのSYN+ACKパケットを破棄します。

[root@client ~]# iptables -I INPUT -p tcp --sport 11111 --tcp-flags SYN,ACK SYN,ACK -j DROP

設定したルールを確認します。

[root@client ~]# iptables -nvL INPUT 1
    0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:11111 flags:0x12/0x12

サーバでncコマンドを実行します。このとき、ncプロセスがTCPの11111番ポートでListenするようにします。

[root@server ~]# nc -kl 11111

サーバでssコマンドを実行します。ソケットの状態を確認すると、TCPがLISTEN状態であることがわかります。

[root@server ~]# ss -nat4 'sport == :11111'
State     Recv-Q     Send-Q         Local Address:Port          Peer Address:Port    Process
LISTEN    0          1                    0.0.0.0:11111              0.0.0.0:*

クライアントでncコマンドを実行します。このとき、サーバの11111番ポートにTCPコネクションを確立します。

[root@client ~]# nc 192.168.122.68 11111

クライントでssコマンドを実行します。ソケットの状態を確認すると、TCPがSYN-SENT状態であることがわかります。

[root@client ~]# ss -nat4 'dport == :11111'
State          Recv-Q       Send-Q               Local Address:Port                Peer Address:Port       Process
SYN-SENT       0            1                  192.168.122.177:44500             192.168.122.68:11111

6.2 後始末(iptablesの設定削除)

INPUTチェインに登録されているルールを確認します。ルールが1つ登録されていることがわかります。

[root@client ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 5544 packets, 20M bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       12   720 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:11111 flags:0x12/0x12

INPUTチェインの1番目のルールを削除します。

[root@client ~]# iptables -D INPUT 1

INPUTチェインに登録されているルールを確認します。INPUTチェインの1番目のルールが削除されたことがわかります。

[root@client ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 5576 packets, 20M bytes)
num   pkts bytes target     prot opt in     out     source               destination

7 SYN-RECEIVED状態の作り方

SYN-RECEIVEDは、SYN+ACKを送信して、相手からACKが返ってくるまでの状態です。ここでは、サーバのINPUTチェインでACKパケットを廃棄(下記★印)することで、サーバでSYN-RECEIVEDの状態を作りだしてみます。他の方法として、クライアントのINPUTチェインでSYN+ACK、またはOUTPUTチェインでACKを破棄しても、同じ結果が得られます。

             client                              server(11111)
                |                                   |
 connect() -*-  |-------------- SYN --------------->|   -*-
            |   |                                   |    |
            |   |                                   |    |
        SYN-SENT|                                   | SYN-RECEIVED
            |   |                                   |    |
            |   |<------------- SYN + ACK ----------|    |
           -*-  |-------------- ACK --------------->★
                |                                   |

7.1 作成手順

サーバでiptablesコマンドを実行します。このとき、クライアントからのACKパケットを破棄します。

[root@server ~]# iptables -I INPUT -p tcp --dport 11111 --tcp-flags ACK ACK -j DROP

設定したルールを確認します。

[root@server ~]# iptables -nvL INPUT 1
    0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x10/0x10

サーバでncコマンドを実行します。このとき、TCPの11111番ポートでListenします。

[root@server ~]# nc -kl 11111

サーバでssコマンドを実行します。ソケットの状態を確認すると、TCPがLISTEN状態であることがわかります。

[root@server ~]# ss -nat4 'sport == :11111'
State     Recv-Q     Send-Q         Local Address:Port          Peer Address:Port    Process
LISTEN    0          1                    0.0.0.0:11111              0.0.0.0:*

クライアントでncコマンドを実行します。このとき、サーバの11111番ポートにTCPコネクションを確立します。

[root@client ~]# nc 192.168.122.68 11111

クライントでssコマンドを実行します。ソケットの状態を確認すると、TCPがSYN-RECV状態であることがわかります。

[root@server ~]# ss -nat4 'sport == :11111'
State       Recv-Q    Send-Q        Local Address:Port           Peer Address:Port    Process
LISTEN      0         1                   0.0.0.0:11111               0.0.0.0:*
SYN-RECV    0         0            192.168.122.68:11111       192.168.122.177:44538

7.2 後始末(iptablesの設定削除)

INPUTチェインに登録されているルールを確認します。ルールが1つ登録されていることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 6533 packets, 26M bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       16   832 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x10/0x10

INPUTチェインの1番目のルールを削除します。

[root@server ~]# iptables -D INPUT 1

INPUTチェインに登録されているルールを確認します。INPUTチェインの1番目のルールが削除されたことが分かります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 6577 packets, 26M bytes)
num   pkts bytes target     prot opt in     out     source               destination

8 FIN-WAIT-1状態の作り方

アクティブクローズ側(*)がとる状態です。FINを送信して相手からACKが返ってくるまでの状態です。TCPコネクション確立後、クライアントでCtrl + cを押下して、ncコマンドを終了します。ncコマンドを終了すると、クライアンからサーバにFINが送信されます。このとき、サーバのINPUTチェインでFINを廃棄(下記★印)して、クライアントでFIN-WAIT-1の状態を作りだしてみます。
(*) TCPコネクションが確立した状態で、先にclose()を実行してFINパケットを送信した側です。

             client                              server(11111)
          |     |                                   |    |
          |     |                                   |    |
    ESTABLISHED |                                   | ESTABLISHED
          |     |                                   |    |
          |     |                                   |    |
Ctrl + c -*-    |-------------- FIN -------------->★   -*- 
          |     |                                   |
          |     |                                   |
     FIN-WAIT-1 |                                   |
          |     |                                   |
          |     |                                   |

8.1 作成手順

サーバでiptablesコマンドを実行します。このとき、クライアントからのFINパケットを破棄します。

[root@server ~]# iptables -I INPUT -p tcp --dport 11111 --tcp-flags FIN FIN -j DROP

設定したルールを確認します。

[root@server ~]# iptables -nvL INPUT 1
    0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x01/0x01

サーバでncコマンドを実行します。このとき、TCPの11111番ポートでListenします。

[root@server ~]# nc -kl 11111

クライアントでncコマンドを実行します。このとき、サーバの11111番ポートにTCPコネクションを確立します。

[root@client ~]# nc 192.168.122.68 11111

クライントでssコマンドを実行します。ソケットの状態を確認すると、TCPがESTABLISHED状態であることがわかります。

[root@client ~]# ss -nat4 'dport == :11111'
State       Recv-Q       Send-Q               Local Address:Port                  Peer Address:Port        Process
ESTAB       0            0                  192.168.122.177:44540               192.168.122.68:11111

クライアンでCtrl +cを押下して、ncプロセスを終了します。Ctrl +cを押下することで、クライアントからFINが送信されます。なお、FINの送信は、tcpdumpコマンド、tsharkコマンドを使って確認することができます。tcpdumpコマンドは、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログ、tsharkコマンドは、tsharkコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# nc 192.168.122.68 11111
^C

クライントでssコマンドを実行します。ソケットの状態を確認すると、TCPがFIN-WAIT-1状態であることがわかります。

[root@client ~]# ss -nat4 'dport == :11111'
State           Recv-Q       Send-Q               Local Address:Port                Peer Address:Port       Process
FIN-WAIT-1      0            1                  192.168.122.177:44540             192.168.122.68:11111

8.2 後始末(iptablesの設定削除)

INPUTチェインに登録されているルールを確認します。ルールが1つ登録されていることがわかります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 6659 packets, 26M bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       10   520 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:11111 flags:0x01/0x01

INPUTチェインの1番目のルールを削除します。

[root@server ~]# iptables -D INPUT 1

INPUTチェインに登録されているルールを確認します。INPUTチェインの1番目のルールが削除されたことが分かります。

[root@server ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 6680 packets, 26M bytes)
num   pkts bytes target     prot opt in     out     source               destination

9 FIN-WAIT-2状態の作り方

正常なTCPコネクションの開放シーケンスは以下のようになります。クライアントでCtrl + cを押下してncプロセスを終了するとFINが送信されます。そのあと、サーバのncプロセスが送信するデータがなければ、close()を実行してTCPコネクションをクローズします。このとき、サーバからクライアントにFINを送信します。

             client                              server(11111)
          |     |                                   |    |
          |     |                                   |    |
    ESTABLISHED |                                   | ESTABLISHED
          |     |                                   |    |
          |     |                                   |    |
          |     |                                   |    |
Ctrl + c -*-    |-------------- FIN --------------->|    |
          |     |                                   |    |
     FIN-WAIT-1 |                                   |    |
          |     |                                   |    |
         -*-    |<------------- ACK ----------------|   -*-
          |     |                                   |    |
          |     |                                   |    |
     FIN-WAIT-2 |                                   | CLOSE_WAIT
          |     |                                   |    |
          |     |                                   |    |
          |     |<------------- FIN ----------------|   -*-  <--- close()
          |     |                                   |    |
          |     |                                   |    |
          |     |                                   |  LAST_ACK
          |     |                                   |    |
          |     |                                   |    |
         -*-    |-------------- ACK --------------->|   -*-
          |     |                                   |    |
          |     |                                   |    |

次に、意図的にサーバのncプロセスがclose()を実行しない状況を作ってみます。
TCPコネクションが確立した後、サーバのncプロセスをCtrl+zを押下して停止します。次に、クライアントでCtrl+cを押下します。Ctrl+cを押下するとFINがサーバに送信されますが、ncプロセスが停止しているため、close()が実行されません。そのため、サーバからクライアントにFINが送信されず、クライアントはFIN-WAIT-2の状態のままになります。なお、このとき、サーバはCLOSE-WAIT状態となっています。

             client                              server(11111)
          |     |                                   |    |
          |     |                                   |    |
    ESTABLISHED |                                   | ESTABLISHED
          |     |                                   |    |
          |     |                                   |    | Ctrl + z
          |     |                                   |    |
Ctrl + c -*-    |-------------- FIN --------------->|    |
          |     |                                   |    |
     FIN-WAIT-1 |                                   |    |
          |     |                                   |    |
         -*-    |<------------- ACK ----------------|   -*-
          |     |                                   |    |
          |     |                                   |    |
     FIN-WAIT-2 |                                   | CLOSE_WAIT
          |     |                                   |    |
          |     |                                   |    |
          |     |                                   |    | Can't execute close()
          |     |                                   |    |

9.1 作成手順

サーバでncコマンドを実行します。このとき、TCPの11111番ポートでListenします。

[root@server ~]# nc -kl 11111

クライアントでncコマンドを実行します。このとき、サーバの11111番ポートにTCPコネクションを確立します。

[root@client ~]# nc 192.168.122.68 11111

クライントでssコマンドを実行します。ソケットの状態を確認すると、TCPがESTABLISHED状態であることがわかります。

[root@client ~]# ss -nat4 'dport == :11111'
State    Recv-Q     Send-Q           Local Address:Port            Peer Address:Port     Process
ESTAB    0          0              192.168.122.177:46320         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                 1612 T

クライアンでCtrl +cを押下して、ncコマンドを終了します。Ctrl +cを押下することで、クライアントからサーバに向けてFINが送信されます。

[root@client ~]# nc 192.168.122.68 11111
^C

クライントでssコマンドを実行します。ソケットの状態がFIN-WAIT-2状態であることがわかります。

[root@client ~]# ss -nat4 'dport == :11111'
State         Recv-Q    Send-Q         Local Address:Port          Peer Address:Port     Process
FIN-WAIT-2    0         0            192.168.122.177:46320       192.168.122.68:11111

10 CLOSE-WAIT状態の作り方

アプリケーションからTCPコネクションのクローズを待っている状態です。closeシステムコールを実行すると、コネクションをクローズすることができます。バグなどによって、closeシステムコールが実行されないと、CLOSE-WAITの状態が残ったままになります。

             client                              server(11111)
          |     |                                   |    |
          |     |                                   |    |
    ESTABLISHED |                                   | ESTABLISHED
          |     |                                   |    |
          |     |                                   |    | Ctrl + z ...(1)
(2)       |     |                                   |    |
Ctrl + c -*-    |-------------- FIN --------------->|   -*- 
          |     |                                   |    |
          |     |                                   |    |
     FIN-WAIT-1 |                                   |    |
          |     |                                   |    |
          |     |                                   |    |
         -*-    |<---------- FIN + ACK -------------|  CLOSE-WAIT  <--- close()
          |     |                                   |    |
          |     |                                   |    |
     FIN-WAIT-2 |                                   |    |
          |     |                                   |    |
          |     |                                   |    |

10.1 作成手順

サーバでncコマンドを実行します。このとき、TCPの11111番ポートでListenします。

[root@server ~]# nc -kl 11111

クライアントで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プロセスが停止していることがわかります。

[root@server ~]# ps -C nc -o comm,pid,state
COMMAND             PID S
nc                 1657 T

クライアンでCtrl +cを押下して、ncコマンドを終了します。Ctrl +cを押下することで、クライアントからサーバに向けてFINが送信されます。

[root@client ~]# nc 192.168.122.68 11111
^C

サーバでssコマンドを実行します。ソケットの状態がCLOSE-WAIT状態であることがわかります。

[root@server ~]# ss -nat4 'sport == :11111'
State         Recv-Q    Send-Q        Local Address:Port           Peer Address:Port     Process
LISTEN        0         1                   0.0.0.0:11111               0.0.0.0:*
CLOSE-WAIT    1         0            192.168.122.68:11111       192.168.122.177:46324

11 LAST-ACK状態の作り方

パッシブクローズ側(*)がとる状態です。こちらから相手にFINを送信(下記1)して、相手からACKを受け取る(下記2)までの状態です。
(*)TCPコネクションが確立した状態で、あとからclose()を実行する側です。

             client                              server(11111)
          |     |                                   |    |
          |     |                                   |    |
    ESTABLISHED |                                   | ESTABLISHED
          |     |                                   |    |
          |     |                                   |    |
          |     |                                   |    |
Ctrl + c -*-    |-------------- FIN --------------->|   -*- 
          |     |                                   |    |
          |     |                                   |    |
     FIN-WAIT-1 |                                   |    |
          |     |                                   |    |
          |     |                                   |    |
         -*-    |<------------- ACK ----------------|  CLOSE-WAIT
          |     |                                   |    |
          |     |                                   |    |
     FIN-WAIT-2 |                                   |    |
          |     |                                   |    |
          |     |                                   |    |
          |     |<------------- FIN ----------------|   -*-   <===(1)
          |     |                                   |    |
          |     |                                   |    |
          |     |                                   |  LAST-ACK
          |     |                                   |    |
          |     |                                   |    |
         -*-    |-------------- ACK ------------->  |   -*-   <===(2)
          |     |                                   |    |
          |     |                                   |  CLSOED
          |     |                                   |    |

11.1 作成手順

サーバでncコマンドを実行します。このとき、TCPの11111番ポートでListenします。

[root@server ~]# nc -kl 11111

クライアントでncコマンドを実行します。このとき、サーバの11111番ポートにTCPコネクションを確立します。

[root@client ~]# nc 192.168.122.68 11111

クライアントでiptablesコマンドを実行します。このとき、サーバからのFINパケットを破棄します。

[root@client ~]# iptables -I INPUT -p tcp --sport 11111 --tcp-flags FIN FIN -j DROP
[root@client ~]# iptables -nvL INPUT 1
    0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:11111 flags:0x01/0x01

クライアンでCtrl +cを押下して、ncコマンドを終了します。Ctrl +cを押下することで、クライアントからFINが送信されます。

[root@client ~]# nc 192.168.122.68 11111
^C
[root@server ~]# ss -nat4 'sport == :11111'
State          Recv-Q       Send-Q              Local Address:Port                 Peer Address:Port       Process
LISTEN         0            1                         0.0.0.0:11111                     0.0.0.0:*
LAST-ACK       0            1                  192.168.122.68:11111             192.168.122.177:40098

11.2 後始末(iptablesの設定削除)

[root@client ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 12880 packets, 31M bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       10   520 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp spt:11111 flags:0x01/0x01
[root@client ~]# iptables -D INPUT 1
[root@client ~]# iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 12916 packets, 31M bytes)
num   pkts bytes target     prot opt in     out     source               destination

12 TIME-WAIT状態の作り方

TIME-WAITは、アクティブクローズ側がとる状態です。相手のFINに対してこちらが送信したACKを、相手が確実に受信できるようにするための状態です。本検証環境では、TIME-WAITの状態は60秒間継続します。60秒経過すると、TIME-WAITからCLOSEDに状態遷移します。

12.1 作成手順

サーバでncコマンドを実行します。このとき、TCPの11111番ポートでListenします。

[root@server ~]# nc -kl 11111

サーバの11111番ポートにTCPコネクションを確立します。

[root@client ~]# nc 192.168.122.68 11111

クライアンでCtrl +cを押下して、ncプロセスを終了します。Ctrl +cを押下することで、クライアントからFINが送信されます。

[root@client ~]# nc 192.168.122.68 11111
^C

ncコマンドを終了した直後、クライアントでssコマンドを実行します。このとき、oオプションをつけて、TIME-WAIT状態の残り時間を表示してみます。TIME-WAIT状態の残り時間が57秒であることがわかります。

[root@client ~]# ss -no4 state time-wait
Netid            Recv-Q            Send-Q                           Local Address:Port                            Peer Address:Port             Process
tcp              0                 0                              192.168.122.177:44542                         192.168.122.68:11111             timer:(timewait,57sec,0)

クライアントでssコマンドを実行します。TIME-WAIT状態の残り時間が588ミリ秒であることがわかります。

[root@client ~]# ss -no4 state time-wait
Netid            Recv-Q            Send-Q                           Local Address:Port                            Peer Address:Port             Process
tcp              0                 0                              192.168.122.177:44542                         192.168.122.68:11111             timer:(timewait,588ms,0)

クライアントでssコマンドを実行します。TIME-WAIT状態のソケットが存在しないことがわかります。

[root@client ~]# ss -no4 state time-wait
Netid             Recv-Q             Send-Q                         Local Address:Port                           Peer Address:Port             Process

13 CLOSING状態の作り方

アクティブクローズ側がとる状態です。同時クローズをしたときに発生します。つまり、FIN-WAIT-1のときに、相手からFINを受信すると、CLOSINGになります。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

coredumpctlコマンドの使い方

1 coredumpctlコマンドとは

coredumpctl は systemd-coredump(8) によって保存されたコアダンプとメタデータを取得し処理するためのコマンドです。

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

coredumpctlコマンドの版数は以下のとおりです。

[root@server ~]# coredumpctl --version
systemd 239 (239-58.el8)
+PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=legacy

3 オプション一覧

オプションは以下のとおりです。

[root@server ~]# coredumpctl -h
coredumpctl [OPTIONS...]

List or retrieve coredumps from the journal.

Flags:
  -h --help              Show this help
     --version           Print version string
     --no-pager          Do not pipe output into a pager
     --no-legend         Do not print the column headers
     --debugger=DEBUGGER Use the given debugger
  -1                     Show information about most recent entry only
  -S --since=DATE        Only print coredumps since the date
  -U --until=DATE        Only print coredumps until the date
  -r --reverse           Show the newest entries first
  -F --field=FIELD       List all values a certain field takes
  -o --output=FILE       Write output to FILE
  -D --directory=DIR     Use journal files from directory

  -q --quiet             Do not show info messages and privilege warning
Commands:
  list [MATCHES...]  List available coredumps (default)
  info [MATCHES...]  Show detailed information about one or more coredumps
  dump [MATCHES...]  Print first matching coredump to stdout
  debug [MATCHES...] Start a debugger for the first matching coredump

4 事前準備

httpdにSIGSEGVシグナルを送信して、httpdのcoreファイルを作成します。

4.1 ユニット定義ファイル編集

オリジナルのユニット定義ファイルのコピーを作成します。

[root@server ~]# cp /usr/lib/systemd/system/httpd.service /usr/lib/systemd/system/httpd.service.org

ユニット定義ファイルを編集します。

[root@server ~]# vi /usr/lib/systemd/system/httpd.service

編集内容を確認します。ServiceセクションにLimitCORE(単位:バイト数)の設定を追記します。ここでは、coreファイルのサイズを無制限にする設定をします。

[root@server ~]# diff -Nur /usr/lib/systemd/system/httpd.service.org /usr/lib/systemd/system/httpd.service
--- /usr/lib/systemd/system/httpd.service.org   2022-08-06 21:16:33.117451068 +0900
+++ /usr/lib/systemd/system/httpd.service       2022-08-06 21:39:14.476014784 +0900
@@ -27,6 +27,7 @@
 KillSignal=SIGWINCH
 KillMode=mixed
 PrivateTmp=true
+LimitCORE=infinity

 [Install]
 WantedBy=multi-user.target

設定変更をsystemdに通知します。

[root@server ~]# systemctl daemon-reload

httpdサービスを起動します。

[root@server ~]# systemctl start httpd

httpdプロセスの状態を確認します。PID1581が親プロセス、その他はPID1581の子プロセスになります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C httpd -o comm,pid,ppid
COMMAND             PID    PPID
httpd              1581       1
httpd              1582    1581
httpd              1583    1581
httpd              1584    1581
httpd              1585    1581

PID1581のプロセスに対してSIGSEGVシグナルを送信します。

[root@server ~]# kill -SIGSEGV 1581

httpdプロセスがSIGSEGVを受信して終了したため、httpdプロセスが存在しないことがわかります。

[root@server ~]# ps -C httpd -o comm,pid,ppid
COMMAND             PID    PPID

5 core一覧をする方法(list)

listコマンドは、journalに保存されているcoreの一覧を表示します。

[root@server ~]# coredumpctl list
TIME                            PID   UID   GID SIG COREFILE  EXE
Sat 2022-08-06 21:56:38 JST    1581     0     0  11 present   /usr/sbin/httpd

COREFILE列の意味は以下のとおりです。

表示 意味
none coreファイルがjournalに保存されていない状態
- coreファイルが利用できない状態。たとえば、シグナルを受信したけど、プロセスが終了していないとき
present coreファイルを利用できる状態
journal coreファイルがjournalに保存されている状態
truncated coreファイルのサイズが大きいため、coreファイルが全てjournalに保存されていない状態
error coreファイルにアクセスできない状態。たとえば、パーミッションが不十分なとき
missing coreファイルがjournalに保存されたが、削除された状態

6 core出力したプロセスの情報を表示する方法(info)

[root@server ~]# coredumpctl info 1581
           PID: 1581 (httpd)
           UID: 0 (root)
           GID: 0 (root)
        Signal: 11 (SEGV)
     Timestamp: Sat 2022-08-06 21:56:37 JST (1h 5min ago)
  Command Line: /usr/sbin/httpd -DFOREGROUND
    Executable: /usr/sbin/httpd
 Control Group: /system.slice/httpd.service
          Unit: httpd.service
         Slice: system.slice
       Boot ID: 86d38764b1314c9280859d2b9c98de1a
    Machine ID: 971f5080a9c549438e6e9a21eeb90854
      Hostname: server
       Storage: /var/lib/systemd/coredump/core.httpd.0.86d38764b1314c9280859d2b9c98de1a.1581.1659790597000000.lz4
       Message: Process 1581 (httpd) of user 0 dumped core.

                Stack trace of thread 1581:
                #0  0x00007fe14920f1db __select (libc.so.6)
                #1  0x00007fe1498fd919 apr_sleep (libapr-1.so.0)
                #2  0x00005639fbf48b4d ap_wait_or_timeout (httpd)
                #3  0x00007fe13e3d8041 event_run (mod_mpm_event.so)
                #4  0x00005639fbf4815e ap_run_mpm (httpd)
                #5  0x00005639fbf40733 main (httpd)
                #6  0x00007fe149122ca3 __libc_start_main (libc.so.6)
                #7  0x00005639fbf4087e _start (httpd)

7 coreをファイルに出力する方法(dump)

journalに保存されているcoreをhttpd.coreという名前のファイルに保存します。

[root@server ~]# coredumpctl -o httpd.core dump

保存したcoreファイルを確認します。

[root@server ~]# ls -lh httpd.core
-rw-r--r--. 1 root root 3.5M  8月  6 23:35 httpd.core

debuginfoパッケージをインストールするため、httpdパッケージの版数を確認します。

[root@server ~]# rpm -qa|grep httpd
httpd-2.4.37-47.module_el8.6.0+2935+fb177b09.2.x86_64
-snip-

httpdのdebuginfoパッケージをインストールします。

[root@server ~]# dnf debuginfo-install httpd-2.4.37-47.module_el8.6.0+2935+fb177b09.2.x86_64

httpdの実行ファイルのパスと保存したcoreファイルを指定してgdbコマンドを実行します。

[root@server ~]# gdb /usr/sbin/httpd httpd.core

gdbを起動します。そして、btコマンドを実行します。httpdソースコードについては、引数が表示されていることがわかります。

(gdb) bt
#0  0x00007fe14920f1db in select () from /lib64/libc.so.6
#1  0x00007fe1498fd919 in apr_sleep () from /lib64/libapr-1.so.0
#2  0x00005639fbf48b4d in ap_wait_or_timeout (status=status@entry=0x7ffd93be01d8,
    exitcode=exitcode@entry=0x7ffd93be01dc, ret=ret@entry=0x7ffd93be01e0, p=<optimized out>, s=<optimized out>)
    at mpm_common.c:201
#3  0x00007fe13e3d8041 in server_main_loop (num_buckets=1, remaining_children_to_start=0) at event.c:2904
#4  event_run (_pconf=<optimized out>, plog=<optimized out>, s=<optimized out>) at event.c:3082
#5  0x00005639fbf4815e in ap_run_mpm (pconf=0x5639fc4c6a18, plog=0x5639fc4f3c38, s=0x5639fc4efcc0) at mpm_common.c:94
#6  0x00005639fbf40733 in main (argc=<optimized out>, argv=<optimized out>) at main.c:819

さらに、glibcのdebuginfoパッケージをインストールします。

[root@server ~]# dnf debuginfo-install glibc-2.28-189.1.el8.x86_64

再度、gdbを起動します。そして、btコマンドを実行します。glibcのdebuginfoパッケージをインストールしたことで、selectシステムコールの引数が表示されるようになったことがわかります。

(gdb) bt
#0  0x00007fe14920f1db in __GI___select (nfds=0, readfds=0x0, writefds=0x0, exceptfds=0x0, timeout=0x7ffd93be0110)
    at ../sysdeps/unix/sysv/linux/select.c:41
#1  0x00007fe1498fd919 in apr_sleep () from /lib64/libapr-1.so.0
#2  0x00005639fbf48b4d in ap_wait_or_timeout (status=status@entry=0x7ffd93be01d8,
    exitcode=exitcode@entry=0x7ffd93be01dc, ret=ret@entry=0x7ffd93be01e0, p=<optimized out>, s=<optimized out>)
    at mpm_common.c:201
#3  0x00007fe13e3d8041 in server_main_loop (num_buckets=1, remaining_children_to_start=0) at event.c:2904
#4  event_run (_pconf=<optimized out>, plog=<optimized out>, s=<optimized out>) at event.c:3082
#5  0x00005639fbf4815e in ap_run_mpm (pconf=0x5639fc4c6a18, plog=0x5639fc4f3c38, s=0x5639fc4efcc0) at mpm_common.c:94
#6  0x00005639fbf40733 in main (argc=<optimized out>, argv=<optimized out>) at main.c:819

8 特定のcoreを絞り込む方法

8.1 事前準備

もう1つcoreファイルを作成します。次は、viのcoreファイルを取得してみます。まず、coreファイルのサイズを無限に設定します。なお、恒久的な設定は、/etc/security/limits.confに対して行います。

[root@server ~]# ulimit -c unlimited
[root@server ~]# ulimit -c
unlimited

viを実行します。

[root@server ~]# vi aaa

もう1つターミナルを起動して、viのPIDを確認します。

[root@server ~]# ps -C vi
    PID TTY          TIME CMD
   2379 pts/0    00:00:00 vi

viプロセスに対してSIGSEGVシグナルを送信します。

[root@server ~]# kill -SIGSEGV 2379

coreの一覧を確認します。

[root@server ~]# coredumpctl list
TIME                            PID   UID   GID SIG COREFILE  EXE
Sun 2022-08-07 21:24:06 JST    1463     0     0  11 present   /usr/sbin/httpd
Sun 2022-08-07 21:38:18 JST    2379     0     0  11 present   /usr/bin/vi

8.2 本日出力されたcoreを確認する方法(today)

[root@server ~]# coredumpctl list -S today
TIME                            PID   UID   GID SIG COREFILE  EXE
Sun 2022-08-07 21:24:06 JST    1463     0     0  11 present   /usr/sbin/httpd
Sun 2022-08-07 21:38:18 JST    2379     0     0  11 present   /usr/bin/vi

8.3 特定の日時以降のcoreを確認する方法(-S)

[root@server ~]# coredumpctl list -S "2022-08-07 21:38:10"
TIME                            PID   UID   GID SIG COREFILE  EXE
Sun 2022-08-07 21:38:18 JST    2379     0     0  11 present   /usr/bin/vi