Edited at

tcpdumpで理解するTCPのコネクション

本エントリでは、tcpdumpコマンドを使用してコネクションの確立、終了を確認することにより、TCPのコネクションの理解を深めます。


TCPのコネクションを理解する目的

TCPのコネクションを理解すると主に以下のメリットがあります。


  • NW関連(主にTCPコネクションまわり)の問題切り分け・トラブルシューティング力が向上する。

  • NW関連(主にTCPコネクションまわり)のテストケース観点を習得できる。

  • NW関連の共通言語が習得できる。


TCPのコネクション

TCPでデータを転送する場合、データの転送を行う前にコネクションを確立する必要があります。

TCPのコネクションを確立する際は、以下の3way handshake(3ウェイ・ハンドシェイク)で処理が行われます。


  1. TCPクライアント(TCP A)は、TCPサーバー(TCP B)にコネクション確立要求を送信する。(syn)

  2. コネクション確立要求を受けたTCPサーバー(TCP B)は、TCPクライアント(TCP A)に確認応答とコネクション確立要求を返します。(syn+ack)

  3. コネクション確立要求を受けたTCPクライアント(TCP A)は、TCPサーバー(TCP B)に確認応答を返します。(ack)

established.png

3way handshake(3ウェイ・ハンドシェイク)の詳細については以下のURLを参照ください。

上記のパケットの流れを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クラアント・サーバーのそれぞれ以下の振る舞いを示します。


  1. TCPクライアント(telnet)がTCPサーバー(nc)にSYNパケットを送信する。(SYN-SENT)

  2. TCPサーバー(nc)がTCPクライアント(telnet)にSYN+ACKパケットが送信する。(SYN-RECEIVED)

  3. TCPクライアント(telnet)がTCPサーバー(nc)にACKパケットが送信する。(ESTABLISH)

以下はシーケンスの概要図です。

tcp3wayhandshack.png

別ターミナルを開き、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のコネクション確立(Connection refused)を確認する

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 +++
$