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