本エントリでは、tcpdumpコマンドを使用してコネクションの確立、終了を確認することにより、TCPのコネクションの理解を深めます。
TCPのコネクションを理解する目的
TCPのコネクションを理解すると主に以下のメリットがあります。
- NW関連(主にTCPコネクションまわり)の問題切り分け・トラブルシューティング力が向上する。
- NW関連(主にTCPコネクションまわり)のテストケース観点を習得できる。
- NW関連の共通言語が習得できる。
TCPのコネクション
TCPでデータを転送する場合、データの転送を行う前にコネクションを確立する必要があります。
TCPのコネクションを確立する際は、以下の3way handshake(3ウェイ・ハンドシェイク)で処理が行われます。
- TCPクライアント(TCP A)は、TCPサーバー(TCP B)にコネクション確立要求を送信する。(syn)
- コネクション確立要求を受けたTCPサーバー(TCP B)は、TCPクライアント(TCP A)に確認応答とコネクション確立要求を返します。(syn+ack)
- コネクション確立要求を受けたTCPクライアント(TCP A)は、TCPサーバー(TCP B)に確認応答を返します。(ack)
3way handshake(3ウェイ・ハンドシェイク)の詳細については以下のURLを参照ください。
- https://tools.ietf.org/html/rfc793#section-3.4
- https://ja.wikipedia.org/wiki/Transmission_Control_Protocol#コネクション確立
上記のパケットの流れをtcpdumpコマンドで実際に確認して行きます。
tcpdumpで理解するTCPのコネクション確立
0. 前準備
使用するネットワークコマンド
本エントリでは、以下のLinuxコマンドを使用します。
- tcpdump
- ネットワーク上のパケットを出力するために使用します。
- nc,telnet
- tcpサーバー/クライアントとして使用します。
- netstat
- コネクションの状態を確認するために使用します。
上記コマンドがインストールされていない場合、以下のコマンドでインストールします。
$ sudo yum -y install tcpdump nc telnet net-tools
1. TCPサーバの起動
ncコマンドのlオプションを使用し任意のport番号でTCPサーバを起動します。ここでは12345番を使います。
$ nc -lk 12345
別ターミナルを開き、netstatコマンドで12345 portがLISTENされている事を確認します。
$ netstat -anp | grep 12345
tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN <PID>/nc
- 1列目はprotcolを示します。tcpである事を確認してください。
- 4列目はlocalアドレスとportを示します。portが12345である事を確認してください
- 6列目はソケットの状態を示します。LISTENである事を確認してください。
これで、TCPコネクションを受け付ける準備が完了しました。
2. TCPクライアントの起動、コネクション確立。
telnetまたはncコマンドでTCPクライアントを起動し、ローカルで起動している1.のTCPサーバに接続します。
$ telnet 127.0.0.1 12345
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
別ターミナルを開き、netstatコマンドでncとtelnetのコネクションが確立されている(ESTABLISHになっている)事を確認します。
$ netstat -anp | grep 12345
tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN <PID>/nc
tcp 0 0 127.0.0.1:<port> 127.0.0.1:12345 ESTABLISHED <PID>/telnet
tcp 0 0 127.0.0.1:12345 127.0.0.1:<port> ESTABLISHED <PID>/nc
2行目がTCPクライアントであるtelnetのプロセスのソケットです。
- 5列目はForeignアドレスを示します。ncのLISTENしている12345 portである事を確認してください。
- 6列目はソケットの状態を示します。ESTABLISHである事を確認してください。
telnet実行中のターミナルに戻り、Ctrl+[とquitコマンドで接続をCloseします。
$ telnet 127.0.0.1 12345
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
^]
telnet> quit
Connection closed.
$
これで、コネクションの確立ができることのテストが完了しました。
3. tcpdumpでコネクション確立の3wayhandshake(3ウェイ・ハンドシェイク)を目視する
1.で起動したTCPサーバが起動している事を確認します。
$ netstat -anp | grep 12345
tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN <PID>/nc
tcpdumpコマンドでlocal interfaceのport12345を指定して実行します。
$ sudo tcpdump -i lo -nnn port 12345
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
telnetまたはncコマンドでTCPクライアントを起動し、ローカルで起動している1.のTCPサーバに接続します。
$ telnet 127.0.0.1 12345
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
tcpdumpの出力を確認します。
08:52:48.685189 IP 127.0.0.1.<port> > 127.0.0.1.12345: Flags [S], seq ...
08:52:48.685221 IP 127.0.0.1.12345 > 127.0.0.1.<port>: Flags [S.], seq ...
08:52:48.685237 IP 127.0.0.1.<port> > 127.0.0.1.12345: Flags [.], ack ...
上記のtcpdumpの出力フォーマットは以下です。
timestamp src > dst: flags
- timestamp: タイムスタンプ
- src: 送信元IP.ポート
- dst: 送信先IP.ポート
- flags: フラグ S (SYN), ‘.’ (ACK)
上記のパケットキャプチャはTCPクラアント・サーバーのそれぞれ以下の振る舞いを示します。
- TCPクライアント(telnet)がTCPサーバー(nc)にSYNパケットを送信する。(SYN-SENT)
- TCPサーバー(nc)がTCPクライアント(telnet)にSYN+ACKパケットが送信する。(SYN-RECEIVED)
- TCPクライアント(telnet)がTCPサーバー(nc)にACKパケットが送信する。(ESTABLISH)
以下はシーケンスの概要図です。
別ターミナルを開き、netstatコマンドでncとtelnetのコネクションが確立されている(ESTABLISHになっている)事を確認します。
$ netstat -anp | grep 12345
tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN <PID>/nc
tcp 0 0 127.0.0.1:<port> 127.0.0.1:12345 ESTABLISHED <PID>/telnet
tcp 0 0 127.0.0.1:12345 127.0.0.1:<port> ESTABLISHED <PID>/nc
4. tcpdumpでコネクションの終了を目視する
tcpdumpコマンドは終了しないまま、telnet実行中のターミナルに戻り、Ctrl+[とquitコマンドで接続をCloseします。
$ telnet 127.0.0.1 12345
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
^]
telnet> quit
Connection closed.
$
tcpdumpの出力を確認します。
09:11:41.290540 IP 127.0.0.1.<port> > 127.0.0.1.12345: Flags [F.], seq 1,
09:11:41.290800 IP 127.0.0.1.12345 > 127.0.0.1.<port>: Flags [F.], seq 1,
09:11:41.290814 IP 127.0.0.1.<port> > 127.0.0.1.12345: Flags [.], ack 2,
上記のtcpdumpの出力フォーマットは以下です。
timestamp src > dst: flags
- timestamp: タイムスタンプ
- src: 送信元IP.ポート
- dst: 送信先IP.ポート
- flags: フラグ F (FIN), ‘.’ (ACK)
TCPクライアント(telnet)、TCPサーバー(nc)双方で、FIN/ACKを送信し、コネクションを終了します。
シーケンスソース
note left of TCP A: CLOSED
note right of TCP B: LISTEN
TCP A->TCP B: syn
note left of TCP A: SYN-SENT
note right of TCP B: SYN-RECEIVED
TCP B->TCP A: syn + ack
note left of TCP A: ESTABLISHED
TCP A->TCP B: ack
note right of TCP B: ESTABLISHED
note over TCP A ,TCP B: ESTABLISHED
おまけ: straceコマンドでTCPのコネクション確立を確認する
straceコマンドに本エントリで使用したnc/telnetコマンドを渡して、コネクション確立要求を送信し、システムコールを確認します。
nc側では、socket, bind, listen, acceptシステムコールが確認できます。
$ strace -Ttt -e trace=network nc -l 12345
15:47:30.162392 socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 <0.000017>
15:47:30.162494 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 <0.000010>
15:47:30.162529 setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0 <0.000008>
15:47:30.162556 bind(3, {sa_family=AF_INET, sin_port=htons(12345), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 <0.000011>
15:47:30.162592 listen(3, 1) = 0 <0.000011>
15:47:30.162621 accept(3, {sa_family=AF_INET, sin_port=htons(48162), sin_addr=inet_addr("127.0.0.1")}, [16]) = 4 <3.104111>
15:47:41.423396 shutdown(4, SHUT_RD) = 0 <0.000009>
15:47:41.423670 +++ exited with 0 +++
$
──────────────────────────
telnet側では、socket, connectシステムコールが確認できます。
$ strace -Ttt -e trace=network telnet 127.0.0.1 12345
Trying 127.0.0.1...
15:47:33.266522 socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 <0.000018>
15:47:33.266617 setsockopt(3, SOL_IP, IP_TOS, [16], 4) = 0 <0.000010>
15:47:33.266650 connect(3, {sa_family=AF_INET, sin_port=htons(12345), sin_addr=inet_addr("127.0.0.1")}, 16) = 0 <0.000116>
Connected to 127.0.0.1.
Escape character is '^]'.
15:47:33.266976 setsockopt(3, SOL_SOCKET, SO_OOBINLINE, [1], 4) = 0 <0.000008>
telnet> quit
15:47:41.423280 shutdown(3, SHUT_RDWR) = 0 <0.000171>
Connection closed.
15:47:41.424360 +++ exited with 0 +++
$