hana_shinのLinux技術ブログ

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

TCPコネクションの確立、解放シーケンス

1 はじめに

ncコマンドを使って、TCPコネクションの確立、解放の様子を確認してます。

2 検証環境

2.1 ネットワーク構成

サーバとクライアントの2台構成です。図中のens33はNICの名前です。

                          192.168.2.0/24
client(ens33) -------------------------------------(ens33) server
        .105                                       .100

2.2 版数

サーバ、クライアントともに下記版数です。

[root@server ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

3 TCPコネクション確立・解放時のシーケンスについて

サーバでtcpdumpを実行します。tcpdumpの使い方は、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tcpdump -i ens33 port 11111 -nn

もう1つターミナルを起動して、ncコマンドを実行します。ncコマンドの使い方は、ncコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# nc -kl 11111

さらに、もう1つターミナルを起動して。lsofコマンドを実行します。ncプロセスがTCPの11111番ポートでLISTENしていることがわかります。lsofコマンドの使い方は、lsofコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lsof -c nc -a -i4 -a -P
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nc      2396 root    4u  IPv4  37062      0t0  TCP *:11111 (LISTEN

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

[root@client ~]# nc 192.168.2.100 11111

このときのtcpdump実行結果を以下に示します。SYNパケットをやり取りして、TCPのコネクションを確立していることがわかります。このやり取りを3WayHandshakeと呼びます。

[root@server ~]# tcpdump -i ens33 port 11111 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
19:46:20.408109 IP 192.168.2.105.43068 > 192.168.2.100.11111: Flags [S], seq 2926220354, win 29200, options [mss 1460,sackOK,TS val 2223348 ecr 0,nop,wscale 7], length 0
19:46:20.408204 IP 192.168.2.100.11111 > 192.168.2.105.43068: Flags [S.], seq 2770685413, ack 2926220355, win 28960, options [mss 1460,sackOK,TS val 2513231 ecr 2223348,nop,wscale 7], length 0
19:46:20.408715 IP 192.168.2.105.43068 > 192.168.2.100.11111: Flags [.], ack 1, win 229, options [nop,nop,TS val 2223350 ecr 2513231], length 0

lsofコマンドを実行します。TCPコネクション確立後、ncプロセスは、Listen用にFD=4、データ送受信用にFD=5を使用していることがわかります。

[root@server ~]# lsof -c nc -a -i4 -a -nP
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nc      2396 root    4u  IPv4  37062      0t0  TCP *:11111 (LISTEN)
nc      2396 root    5u  IPv4  37186      0t0  TCP 192.168.2.100:11111->192.168.2.105:43068 (ESTABLISHED) 

次は、TCPコネクションを解放してみます。クライアントでCtrl+Cを押下してncコマンドを終了します。

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

tcpdumpの実行結果を確認します。Ctrl+Cを押下するとFINパケットが送信され、TCPコネクションが解放されていることがわかります。

[root@server ~]# tcpdump -i ens33 port 11111 -nn
-snip-
19:47:08.228235 IP 192.168.2.105.43068 > 192.168.2.100.11111: Flags [F.], seq 1, ack 1, win 229, options [nop,nop,TS val 2271169 ecr 2513231], length 0
19:47:08.228411 IP 192.168.2.100.11111 > 192.168.2.105.43068: Flags [F.], seq 1, ack 2, win 227, options [nop,nop,TS val 2561051 ecr 2271169], length 0
19:47:08.228876 IP 192.168.2.105.43068 > 192.168.2.100.11111: Flags [.], ack 2, win 229, options [nop,nop,TS val 2271170 ecr 2561051], length 0

これまでの結果をまとめると次のようになります。クライアントでncコマンドを実行するとSYNパケットが送信され、TCPコネクションが確立されます。そして、クライアントでCtrl+Cを押下すると、FINパケットが送信され、TCPコネクションが解放されます。

                 client                              server
                   |                                   |
                   |                                   |<-- nc -kl 11111
                   |                                   |
nc server 11111 -->|--------------- SYN -------------->|
                   |<-------------- SYN+ACK -----------|
                   |--------------- ACK -------------->|
                   |                                   |
                   |                                   |
                   |                                   |
   Ctrl + C押下 -->|--------------- FIN -------------->|
                   |<-------------- FIN+ACK -----------|
                   |--------------- ACK -------------->|
                   |                                   |

4 FD(ファイル・ディスクリプタ)について

次は、TCPコネクション確立時、データ送受信に使用するFDについて確認してみます。

サーバでncコマンドを実行します。straceコマンドを使うと、ncプロセスはsocketシステムコールを実行して、Listen用ソケット(FD=4)を作成していることがわかります。次にlistenシステムコールを実行しています。第1引数にListen用のFDを指定しています。以下の実行例ではFD=4であることがわかります。なお、straceコマンドの使い方は、straceコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# strace -tT -f -e trace=network nc -kl 11111
19:49:50 socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP) = 3 <0.000527>
19:49:50 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 <0.000321>
19:49:50 setsockopt(3, SOL_IPV6, IPV6_V6ONLY, [1], 4) = 0 <0.000284>
19:49:50 bind(3, {sa_family=AF_INET6, sin6_port=htons(11111), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0 <0.000624>
19:49:50 listen(3, 10)                  = 0 <0.000344>
19:49:50 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 4 <0.000329>
19:49:50 setsockopt(4, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 <0.000316>
19:49:50 bind(4, {sa_family=AF_INET, sin_port=htons(11111), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 <0.000297>
19:49:50 listen(4, 10)                  = 0 <0.000363>

次に、クライアントでncコマンドを実行します。

[root@client ~]# strace -tT -f -e trace=network nc 192.168.2.100 11111
-snip-

クライアントでncコマンドを実行すると、TCPコネクションが確立され、acceptシステムコールから復帰します。acceptシステムコールの戻り値は、データ送受信に使用するソケットのFDになります。以下の実行例ではFD=5であることがわかります。

[root@server ~]# strace -tT -f -e trace=network nc -kl 11111
-snip-
19:50:31 accept(4, {sa_family=AF_INET, sin_port=htons(43070), sin_addr=inet_addr("192.168.2.105")}, [128->16]) = 5 <0.000820>

次にクライアントで"11111"を入力します。クライアントは、標準入力(FD=0)から入力した文字列(11111)を、ソケット(FD=3)から送信していることがわかります。

[root@client ~]# strace -tT -f -e trace=network nc 192.168.2.100 11111
-snip-
11111
19:57:34 recvfrom(0, 0x7ffe36494b00, 8192, 0, 0x7ffe36494a80, [128]) = -1 ENOTSOCK (ソケットでないものにソケット 操作をしています) <0.000013>
19:57:34 sendto(3, "11111\n", 6, 0, NULL, 0) = 6 <0.000118>

クライアントで"11111"を入力すると、recvfromシステムコールから復帰していることがわかります。このとき、recvfromシステムコールの第1引数には、データ送受信用のFD=5を指定していることがわかります。

[root@server ~]# strace -tT -f -e trace=network nc -kl 11111
-snip-
19:57:13 accept(4, {sa_family=AF_INET, sin_port=htons(43078), sin_addr=inet_addr("192.168.2.105")}, [128->16]) = 5 <0.000307>
19:57:34 recvfrom(5, "11111\n", 8192, 0, NULL, NULL) = 6 <0.000302>
11111

5 ソケットの状態確認

次は、TCPコネクションの確立、解放時のソケットの状態について確認してみます。

5.1 コネクション確立時

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

[root@server ~]# nc -kl 11111

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

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

次に、クライアントでncコマンドを実行して、サーバとTCPコネクションを確立します。

[root@client ~]# nc 192.168.2.100 11111

そして、サーバとクライアントでssコマンドを実行します。

サーバ側は以下の結果になります。LISTEN状態のソケットは、クライアントからのTCPコネクション確立を待ち続けています。もう1つのソケットは、クライアントとの間でTCPコネクションを確立したので、ESTABLISH状態になっています。

[root@server ~]# ss -anot4 'sport == :11111'
State       Recv-Q Send-Q               Local Address:Port                              Peer Address:Port
LISTEN      0      10                               *:11111                                        *:*
ESTAB       0      0                    192.168.2.100:11111                            192.168.2.105:51876

クライアント側は以下の結果になります。サーバとの間でTCPコネクションを確立したので、ESTABLISH状態になっています。

[root@client ~]# ss -anot4 'dport == :11111'
State       Recv-Q Send-Q                                 Local Address:Port                                                Peer Address:Port
ESTAB       0      0                                      192.168.2.105:51876                                              192.168.2.100:11111

5.2 コネクション解放時

クライアントでncコマンドを終了した際のソケットの状態を確認してみます。

クライアントでCtrl+Cを押下してncコマンドを終了します。

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

ncコマンド終了直後にssコマンドを実行します。ソケットの状態を確認すると、TIME-WAIT状態であることがわかります。TIME-WAIT状態は、アクティブクローズ側がとる状態です。デフォルトで60秒間、この状態をとります。ssコマンド実行結果の右端を確認すると、58秒と表示されています。これは、TIME-WAIT状態の残り時間が58秒であることを表しています。

[root@client ~]# ss -anot4 'dport == :11111'
State       Recv-Q Send-Q                                 Local Address:Port                                                Peer Address:Port
TIME-WAIT   0      0                                      192.168.2.105:51876                                              192.168.2.100:11111               timer:(timewait,58sec,0)

しばらく時間をおいて、ssコマンドを実行すると以下の結果になりました。ssコマンド実行結果の右端を確認すると、1.972ms秒と表示されています。TIME-WAIT状態が終了する直前の状態であることがわかります。

[root@client ~]# ss -anot4 'dport == :11111'
State       Recv-Q Send-Q                                 Local Address:Port                                                Peer Address:Port
TIME-WAIT   0      0                                      192.168.2.105:51876                                              192.168.2.100:11111               timer:(timewait,1.972ms,0)

もう一度ssコマンドを実行すると、TIME-WAIT状態のソケットが消滅したことがわかります。

[root@client ~]# ss -anot4 'dport == :11111'
State       Recv-Q Send-Q                                 Local Address:Port                                                Peer Address:Port

Z 参考情報

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