$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

HTTP GETしたときのTCPパケットの様子を理解する

どうやってIPからMACアドレスを解決するか - ARPの挙動を調べた - $shibayu36->blog;データリンク層tracerouteの仕組みをtcpdumpとwiresharkで理解する - $shibayu36->blog;ネットワーク層について実践してみたので、続いてトランスポート層について実践してみたい。そこで今回はcurl http://www.example.com/したときのTCPパケットの様子を観察し、理解してみることにした。ネットワーク初心者であるので、正しいかは不明。また、概要をつかみたいだけなので、詳細はあまり立ち入らないことにする。


まずパケットキャプチャ。dig www.example.comすると、IPは93.184.216.34ということが分かるので、以下のコマンドでキャプチャしておく。

tcpdump -w dump.cap -n -i en0 'host 93.184.216.34'

その後dump.capをWiresharkに食わせる。

このパケットを順番一つずつ眺めてみる。なお、手元PCをクライアント、www.example.comをサーバと呼ぶ。

3 way handshake完了まで

1	0.000000	192.168.10.7	93.184.216.34	TCP	78	53451 → 80 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=32 TSval=635755241 TSecr=0 SACK_PERM=1

クライアントからサーバへのSYNパケット。MSSでTCPパケット一つの上限サイズを通知している。Winによりどれだけ並列に送ってよいか通知している。WSやSACK_PERMの意味についてはあまり調べなかった。Wireshark Q&A とかが参考になりそう。

2	0.120085	93.184.216.34	192.168.10.7	TCP	74	80 → 53451 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 SACK_PERM=1 TSval=467756484 TSecr=635755241 WS=512

サーバからクライアントへのSYN + ACKパケット。クライアントからのSYNに対するACK応答と、クライアントへのSYNを同時に行っている事がわかる。こちらもMSSやWinを通知している。

3	0.120245	192.168.10.7	93.184.216.34	TCP	66	53451 → 80 [ACK] Seq=1 Ack=1 Win=131744 Len=0 TSval=635755360 TSecr=467756484

サーバからクライアントへのSYN要求へのACK応答。ちなみにこのパケットの中身をもうちょっと見ると、Window size value: 4117というのが見えたので、4117並列で送ってもいいということなのかな?

この三つのパケットで、3way handshakeがなされたので、コネクションが確立される。ここで、MSSはどちらも1460だったので、TCPの1パケットのサイズは1460を上限とする。サイズが一致していなければ小さい方を使うはず。

ここまでのイメージは

HTTPデータのやり取り完了まで

続いて、リクエストの送信。

4	0.120685	192.168.10.7	93.184.216.34	HTTP	141	GET / HTTP/1.1 
Transmission Control Protocol, Src Port: 53451, Dst Port: 80, Seq: 1, Ack: 1, Len: 75
    Source Port: 53451
    Destination Port: 80
    [Stream index: 0]
    [TCP Segment Len: 75]
    Sequence number: 1    (relative sequence number)
    [Next sequence number: 76    (relative sequence number)]
    Acknowledgment number: 1    (relative ack number)
    1000 .... = Header Length: 32 bytes (8)
    Flags: 0x018 (PSH, ACK)
    Window size value: 4117
    [Calculated window size: 131744]
    [Window size scaling factor: 32]
    Checksum: 0xd192 [unverified]
    [Checksum Status: Unverified]
    Urgent pointer: 0
    Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
    [SEQ/ACK analysis]
    TCP payload (75 bytes)
Hypertext Transfer Protocol

HTTPのプロトコルで、GET /しているだけ。PSHフラグが立っているので、すぐさま上位のアプリケーション層に渡される?(第15回 信頼性のある通信を実現するTCPプロトコル(2):基礎から学ぶWindowsネットワーク(2/3 ページ) - @IT)サイズが1460を越えていないので、このパケットのみでリクエストの送信は完了。

次はHTTPレスポンスのデータのTCPパケット。

5	0.248376	93.184.216.34	192.168.10.7	TCP	1514	80 → 53451 [PSH, ACK] Seq=1 Ack=76 Win=144896 Len=1448 TSval=467756515 TSecr=635755360 [TCP segment of a reassembled PDU]
Transmission Control Protocol, Src Port: 80, Dst Port: 53451, Seq: 1, Ack: 76, Len: 1448
    Source Port: 80
    Destination Port: 53451
    [Stream index: 0]
    [TCP Segment Len: 1448]
    Sequence number: 1    (relative sequence number)
    [Next sequence number: 1449    (relative sequence number)]
    Acknowledgment number: 76    (relative ack number)
    1000 .... = Header Length: 32 bytes (8)
    Flags: 0x018 (PSH, ACK)
    Window size value: 283
    [Calculated window size: 144896]
    [Window size scaling factor: 512]
    Checksum: 0x23b3 [unverified]
    [Checksum Status: Unverified]
    Urgent pointer: 0
    Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
    [SEQ/ACK analysis]
    TCP payload (1448 bytes)
    [Reassembled PDU in frame: 7]
    TCP segment data (1448 bytes)

TCP segment dataが1448bytes返ってきている。MSSの値を越えないように送られてきているので、これだけではHTTPレスポンス全体ではない。このデータ送信でもAcknowledgment numberが76で返ってきているので、先程ローカルから送ったリクエストのパケットに対するACKの役割も担えていることが面白い。

続いて4番目のパケットのGET /のリクエストに対応するACK。5番目のHTTPレスポンスデータのTCPパケットでACKの役割も担えていたので、実質意味はなさそう。

6	0.248385	93.184.216.34	192.168.10.7	TCP	66	80 → 53451 [ACK] Seq=1 Ack=76 Win=144896 Len=0 TSval=467756515 TSecr=635755360

次がHTTPレスポンスデータの二つ目のパケット。5番目のパケットと合わせてレスポンスデータが揃ったのでHTTPとして認識されている。このデータもPSHフラグがONになっているので、すぐに上位の層に送られそう。

7	0.248387	93.184.216.34	192.168.10.7	HTTP	211	HTTP/1.1 200 OK  (text/html)
Frame 7: 211 bytes on wire (1688 bits), 211 bytes captured (1688 bits)
Ethernet II, Src: NecPlatf_8d:b1:70 (98:f1:99:8d:b1:70), Dst: Apple_42:64:b2 (8c:85:90:42:64:b2)
Internet Protocol Version 4, Src: 93.184.216.34, Dst: 192.168.10.7
Transmission Control Protocol, Src Port: 80, Dst Port: 53451, Seq: 1449, Ack: 76, Len: 145
    Source Port: 80
    Destination Port: 53451
    [Stream index: 0]
    [TCP Segment Len: 145]
    Sequence number: 1449    (relative sequence number)
    [Next sequence number: 1594    (relative sequence number)]
    Acknowledgment number: 76    (relative ack number)
    1000 .... = Header Length: 32 bytes (8)
    Flags: 0x018 (PSH, ACK)
    Window size value: 283
    [Calculated window size: 144896]
    [Window size scaling factor: 512]
    Checksum: 0xc810 [unverified]
    [Checksum Status: Unverified]
    Urgent pointer: 0
    Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
    [SEQ/ACK analysis]
    TCP payload (145 bytes)
    TCP segment data (145 bytes)
[2 Reassembled TCP Segments (1593 bytes): #5(1448), #7(145)]
Hypertext Transfer Protocol
Line-based text data: text/html

レスポンスデータの一つ目(5番目のパケット)に対応するクライアントからサーバへのACK。

8	0.248952	192.168.10.7	93.184.216.34	TCP	66	53451 → 80 [ACK] Seq=76 Ack=1449 Win=130304 Len=0 TSval=635755486 TSecr=467756515

レスポンスデータの一つ目(5番目のパケット)に対応するクライアントからサーバへのACK。なぜもう一度送られているのかがいまいちわからなかった。

9	0.248953	192.168.10.7	93.184.216.34	TCP	66	[TCP Dup ACK 8#1] 53451 → 80 [ACK] Seq=76 Ack=1449 Win=130304 Len=0 TSval=635755486 TSecr=467756515

レスポンスデータの二つ目(7番目のパケット)に対応するクライアントからサーバへのACK。

10	0.249033	192.168.10.7	93.184.216.34	TCP	66	53451 → 80 [ACK] Seq=76 Ack=1594 Win=130144 Len=0 TSval=635755486 TSecr=467756515

これでHTTPのデータのやり取りは終わり。この部分のイメージは


コネクション切断

最後にコネクション切断。

クライアントからサーバへのFIN。

11	0.249555	192.168.10.7	93.184.216.34	TCP	66	53451 → 80 [FIN, ACK] Seq=76 Ack=1594 Win=131072 Len=0 TSval=635755487 TSecr=467756515

サーバからクライアントへのACK/FIN。これまでのサーバからのACKの番号は76だったので、先程のFINに対応するようにACKの番号が1増えて77になってそう。

12	0.444726	93.184.216.34	192.168.10.7	TCP	66	80 → 53451 [FIN, ACK] Seq=1594 Ack=77 Win=144896 Len=0 TSval=467756546 TSecr=635755487

クライアントからサーバへのACK。こちらもACKの番号が1増えている。

13	0.444847	192.168.10.7	93.184.216.34	TCP	66	53451 → 80 [ACK] Seq=77 Ack=1595 Win=131072 Len=0 TSval=635755681 TSecr=467756546

これで全てのパケットのやり取りが終わった。

まとめ

今回はHTTP GETしたときのTCPパケットの様子を眺めてみた。これまでは単純に3way handshakeがなされているとか、データはパケットに分割されているとか、そういう浅い理解しか無かった。しかし、今回様子を実際に眺めることで、SYNの時にデータサイズや並列数も通知されるとか、パケットの到着順によりACKも兼ねてしまうことがあるとか、そのような現象を体感できた。

今回はwww.example.comへのアクセスだったので、このように非常にシンプルなパケットの様子になっているが、もう少しデータ量が大きいページへアクセスすると、途中で並列数を変更するパケットが流れる様子なども観察できた。機会があったらもう少し詳しく調べてみたい。