三次握手 (Three-way Handshake) 过程
场景
我们在浏览器的地址栏中输入swsmile.info,以观察浏览器进程与这个主机的Web服务器进程进行三次握手的过程。
图示
![三次握手 (Three-way Handshake) 过程-6851119](assets/三次握手 (Three-way Handshake) 过程.png)
分析
通过添加过滤条件(ip.addr == 185.199.109.153 && tcp.port == 63901) || dns.qry.name == swsmile.info
,可以看到:
- 本机(192.168.16.227)向本地设置的DNS服务器(192.168.16.1)查询swsmile.info 主机的DNS信息;
- 该DNS服务器返回以下DNS查询信息:
swsmile.info: type CNAME, class IN, cname swsmile.github.io
swsmile.github.io: type A, class IN, addr 185.199.109.153
swsmile.github.io: type A, class IN, addr 185.199.110.153
swsmile.github.io: type A, class IN, addr 185.199.108.153
swsmile.github.io: type A, class IN, addr 185.199.111.153
总结来说,swsmile.info 主机的IP为185.199.109.153、185.199.110.153、185.199.108.153或185.199.111.153。
- 本机浏览器与IP为185.199.109.153的主机尝试进行TCP连接,并进行三次握手;
- 本机发送SYN报文段,Seq = 0;
- 185.199.109.153发送SYN+ACK报文段,Ack = 1,Seq = 0;
- 本机发送ACK报文段,Ack = 1;
- 三次握手完成,双方都认为连接建立完成,因此此后任何一方都可以向对方发送数据。
- 发送一个HTTP请求以请求主页的内容
- Ethernet II Frame总长度为762 ,其中Header长度14 bytes,Data区长度748 bytes
- IP datagram总长度为748 bytes,其中Header长度20 bytes,则Body为728 bytes
- TCP 包总长度为728 bytes,TCP Header长度为32 bytes(其中 options区域长度为12 bytes),TCP Body长度为696 bytes
- HTTP包的长度为696 bytes
- TCP 包总长度为728 bytes,TCP Header长度为32 bytes(其中 options区域长度为12 bytes),TCP Body长度为696 bytes
- IP datagram总长度为748 bytes,其中Header长度20 bytes,则Body为728 bytes
- Sequence number为1
- Acknowledgement number为1
- Ethernet II Frame总长度为762 ,其中Header长度14 bytes,Data区长度748 bytes
TCP Segment被重装配成HTTP包的过程
场景
我们再来看一个浏览器进程发送HTTP Request以请求一个图片文件,操作系统重装配(Reassemble)收到的多个TCP Segment最终生成一个完整的HTTP Response的过程。
这个HTTP 的Request如下所示:
GET /logo.jpg HTTP/1.1
Host: swsmile.info
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: http://swsmile.info/
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8,en-GB;q=0.7
Cookie: _ga=GA1.2.1893643200.1546350896; Hm_lvt_4481376a6b117446f0ad6f2b9b796afc=1554796946,1554900954,1555126437,1556590964; _gid=GA1.2.507500758.1556768292; Hm_lpvt_4481376a6b117446f0ad6f2b9b796afc=1556769331
通过在Wireshark中,添加过滤条件ip.addr == 185.199.109.153 && tcp.port == 63908
,可以看到:
图示
![TCP Segment被重装配成HTTP包的过程](assets/TCP Segment被重装配成HTTP包的过程.png)
分析
从宏观的层面来看,无非是浏览器进程发送一个GET请求,以获得 /logo.jpg
资源,Web服务器返回一个图片资源。
从微观层面来看,这个图片文件较大,因此这个HTTP Response,其实是被分割了若干个TCP segment(分别对应若干次TCP传输)进行传输的,当所有的这些TCP segment都达到操作系统内核后,由内核负责拼接这个TCP segment,最终获得了一个完整的HTTP Response。
我们先看看看这个HTTP Reponse的Header,它的长度为587 bytes:
HTTP/1.1 200 OK
Server: GitHub.com
Content-Type: image/jpeg
Last-Modified: Thu, 02 May 2019 03:12:12 GMT
ETag: "5cca600c-2bc4"
Access-Control-Allow-Origin: *
Expires: Thu, 02 May 2019 03:48:13 GMT
Cache-Control: max-age=600
X-GitHub-Request-Id: 2C04:4A4E:158055:1744D4:5CCA6624
Content-Length: 11204
Accept-Ranges: bytes
Date: Thu, 02 May 2019 04:23:26 GMT
Via: 1.1 varnish
Age: 0
Connection: keep-alive
X-Served-By: cache-tyo19946-TYO
X-Cache: HIT
X-Cache-Hits: 1
X-Timer: S1556771006.045095,VS0,VE94
Vary: Accept-Encoding
X-Fastly-Request-ID: 4f43f0d925d973c8a6304ea8011d62a1590057bc
通过Content-Length域,我们知道了这个图片文件的大小为11204 bytes。
在Wireshark中,标示出了这个HTTP Response其实是通过装配包号为284、285、287、288、291、292、294、295和296这9个TCP包中的Body获得的。
我们尝试验证一下,这九个TCP包的Body长度分别为1388、1388、1388、1388、1388、1388、1388、1388 和 709 bytes,加起来总共是11813 bytes。
前面提到,HTTP Reponse的Header的长度为587 bytes,图片文件的大小为11204 bytes。因此加起来587 + 11204 = 11791 bytes。按理说,将这九个TCP包的Body拼接起来后的长度应该等于HTTP Reponse的Header的长度加上图片文件的大小。
这里暂时存疑。
TCP传输中的Sequence Number设置问题
我们再来看看,如果与统一主机连续请求两个HTTP,这两个HTTP的Reponse对应的第一个TCP包的Sequence Number是如何设置的。
这里有四个HTTP包,分别是两对HTTP通信。
通过增加tcp && ip.addr == 158.199.142.239
过滤条件,我们摘取出以下关键TCP包。
300(TCP,HTTP,第一个HTTP Request)
Sequence number = 1
Acknowledge number = 302
#313(TCP,为第一个HTTP传输数据的第一个TCP)
Sequence number = 1
Acknowledge number = 302
…
#340(TCP,HTTP,第一个HTTP Response)
Sequence number = 21858
[TCP Segment Len: 1203]
[Next sequence number: 23061 (relative sequence number)]
17 Reassembled TCP Segments (#313, #314, #315, #316, #317 #318, #319, #320, #327, #328, #329, #330, #334, #335, #336, #339, #340)
Acknowledge number = 302
说明
- 对于#340,根据Sequence number = 21858和TCP Segment Len: 1203,因此,这次HTTP最后一个字节在整个流中的序号应该为21858 + 1203 - 1 = 23060;而这个HTTP Reponse第一个字节在整个流中的序号应该为1。因此,这个HTTP Reponse的长度为23060 - 1 + 1 = 23060。
- 在Wireshark中,可以查到这个HTTP Response 长度为23060 bytes,我们的计算得到验证。
- 这也说明,在这个HTTP的Response的所有TCP片段(segment)被全部传输到Client之前,该Server与该Client没有进行任何其他的包含真实传输内容的TCP通信(包含真实传输内容的TCP通信是指TCP Body的长度不为0的TCP通信,比如 [TCP Window Update]就不属于包含真实传输内容的TCP通信)。
#342(TCP,HTTP,第二个HTTP Request)
Sequence number = 302
Acknowledge number = 23061
#351(TCP,为第一个HTTP传输数据的第一个TCP)
Sequence number = 23061
Acknowledge number = 555
…
#2956(TCP,HTTP,第二个HTTP Response)
Sequence number = 2105274
[TCP Segment Len: 531]
[Next sequence number: 2105805 (relative sequence number)]
Acknowledge number = 555
说明
-
对于#2956,根据Sequence number = 2105274 和TCP Segment Len: 531,因此,这个HTTP Reponse最后一个字节在整个流中的序号应该为 2105274 + 531 - 1 = 2105804;而这个HTTP Reponse第一个字节在整个流中的序号应该为23061。因此,这个HTTP Reponse的长度为 2105804 - 23061 + 1 = 20822744。
-
在Wireshark中,可以查到这个HTTP Response 长度为20822744 bytes,我们的计算得到验证。
-
这同时也说明了,在这个HTTP的Response的所有TCP片段(segment)被全部传输到Client之前,该Server与该Client没有进行任何其他的包含真实传输内容的TCP通信。