Help us understand the problem. What is going on with this article?

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のコネクション確立を確認する

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 +++
$ 
keikmobile
cassandra、ansible、serverspec等に興味があります!
http://keikmobile.hatenablog.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした