HTTP与TCP协议-GET与POST请求抓包分析
首先我在服务器上部署了一个Web服务(注:容器的1423端口被绑定到了宿主机的8010端口),可以接收一个最简单的Get和Post请求,如下图
然后在终端对其发送Get和Post请求,都是成功的,如下图:
然后使用Wireshark对Get请求和Post请求分别抓包,进行分析。
本文先详细分析Get请求,对于Post请求仅指出与Get请求的区别。
Get请求抓包分析
在Wireshark抓包一次Get请求如下图,可以看出其先进行三次握手建立连接,然后发送请求,最后四次挥手断开连接。
下面对这10帧数据依次分析。
三次握手
前三帧进行了三次握手建立连接,从图3可以看出,数据报和上篇文章ICMP-Wireshark抓包分析描述的ICMP格式相似,最外层为Ethernet II数据包,内部为IPv4数据包,最内部包含TCP数据包。所以本文不再对Ethernet II和IPv4进行详细分析,直接看IPv4的Data部分,也就是TCP数据包。
一般地,TCP的帧格式如下
然后我们查看第一次握手的情况。
第一次握手
从图4可以看到,TCP报文如下,共44字节:
c5 fd 1f 4a da 9c f9 fa 00 00 00 00 b0 02 ff ff 63 e5 00 00 02 04 05 b4 01 03 03 06 01 01 08 0a 09 ff 7d ac 00 00 00 00 04 02 00 00
先观察数据包的大小,资料偏移为0xb,即整个数据包大小为0xb个字,即11*4=44byte。因为数据包Header部分占有20字节(5个字),因为资料偏移大于5,就意味着第160位标红处的“选项”有内容要填充,必要时在末尾填充0,上述数据包在“选项”部分填充了24byte的内容。
将上述内容填充至TCP的帧格式内,如下图:
主要看到,序列号码0xda9cf9fa,SYN为1。
其中:
- 序列号码:表示当前报文段的数据的第一个字节的序号,此序号为随机生成(为什么?)。本次以序列号0xda9cf9fa向服务端发送了1字节的数据,那么下次向服务端发送的序列号就是0xda9cf9fb。序列号和确认号码ACK有重要关系,第二次握手时会说明。
- SYN:为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步
第二次握手
TCP报文如下
1f 4a c5 fd 57 f4 44 99 da 9c f9 fb a0 12 71 20 2b fa 00 00 02 04 05 90 04 02 08 0a cf b6 6b 9d 09 ff 7d ac 01 03 03 07
其Source Port为8081,Destination Port为50685,序列号码0x57f44499,ACK为0xda9cf9fb,SYN为1。
与第一次握手相比,SYN均为1,序列号码Seq为两个不同的无关值,第二次握手设置了ACK。
也就是说:
- 序列号码:表示当前报文段的数据的第一个字节的序号。本次以序列号0x57f44499向客户端发送了1字节的数据,那么下次向客户端发送的数据包的序列号就是0x57f4449a。
- ACK:本次确认号码为0xda9cf9fb,意味着告诉客户端我已经收到序列号为0xda9cf9fb之前数据报,下一次期望收到的的数据包序列号应为0xda9cf9fb(也就是本次确认号码)。
第三次握手
TCP报文如下
c5 fd 1f 4a da 9c f9 fb 57 f4 44 9a 80 10 08 03 c3 a6 00 00 01 01 08 0a 09 ff 7d c5 cf b6 6b 9d
其Source Port为50685,Destination Port为8081,序列号码为0xda9cf9fb,ACK为0x57f4449a,SYN为0。
与第二次握手相比,第三次握手的SYN成为了0,且ACK成为了第二次握手的Seq+1,序列号码Seq是第二次握手的ACK。
也就是说:
- 序列号码:表示当前报文段的数据的第一个字节的序号。本次发送的数据包序列号为0xda9cf9fb,与第二次握手时服务端期望接收到的序列号相同。
- ACK:本次确认号码为0x57f4449a,意味着第二次握手时客户端所发送的序列号为0x57f4449a之前数据报已经被服务端接收,下次期待接收到的数据包序列号应为0xda9cf9fb。
三次握手,双方分别确认了如下能力:
Get请求
在Wireshark中可以看到,HTTP协议是由TCP协议支撑。
先看下,在HTTP请求中,TCP字段头的信息:
分析可以得出,序列号码为0xda9cf9fb,ACK为0x57f4449a,SYN为0。与第三次握手的TCP报文相同,因为第三次握手为客户端向服务端发送数据并未得到服务端回应,所以客户端发起的Get请求,所以其ACK为第二次握手接收到的序列号Seq+1,序列号码应该为第二次握手接收到的ACK。
HTTP数据报共90字节,计算方法如下图:
在TCP报文部分,可以看到Wireshark已经给出了[TCP Segment Len: 90],但是仅根据TCP协议帧字段是计算不出来90的,推测Wireshark也是根据上图方法计算的。
接下来,看下HTTP报文本身。
一般地,HTTP请求帧格式如下:
可以看到,抓包得到的HTTP数据报如下
474554202f76312f696e666f2f68656c6c6f20485454502f312e310d0a486f73743a20666c7864752e636e3a383031300d0a557365722d4167656e743a206375726c2f372e36342e310d0a4163636570743a202a2f2a0d0a0d0a
---使用工具将HEX转为ASCII---
GET /v1/info/hello HTTP/1.1
Host: flxdu.cn:8010
User-Agent: curl/7.64.1
Accept: */*
和上图给出的HTTP请求帧格式一致。注意,请求头后面有一个空行,在Wireshark截图中也可以看到。
HTTP数据包到达服务器后,直接由服务端监听8010的进程接收、解析并响应。
Get请求之后的一次握手
从本文Wireshark截图可以看到,在Get请求(第45帧)发出之后,紧接着接收到了来自服务器的一次握手数据包(第46帧),然后才是服务器返回的数据(第47帧)。下面对第46帧进行一下分析说明。
第46帧是由服务端向客户端发送的,是为了告诉客户端:已经接收到来自客户端的序列号为0xda9cfa54及之前的数据包(也就是刚才发的Get请求,即第45帧),下一个数据包序列号请从0xda9cfa55开始。
在刚才的Get请求中,其向服务端发送数据包中的ACK字段为0x57f4449a,序列号为0xda9cf9fb,共发送了90字节数据,所以服务端会给客户端一个回应。
Get请求的响应
第47帧是Get请求的响应,如下图
一般地,HTTP响应帧格式如下:
可以看到,抓包得到的HTTP数据报如下
485454502f312e3120323030204f4b0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6a736f6e3b20636861727365743d7574662d380d0a446174653a205475652c203037204a756e20323032322031353a33313a303720474d540d0a436f6e74656e742d4c656e6774683a2031340d0a0d0a2248656c6c6f2c776f726c642122
---使用工具将HEX转为ASCII---
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 08 Jun 2022 21:31:07 GMT
Content-Length: 14
"Hello,world!"
和上图给出的HTTP响应帧格式一致。注意,Content-Length的值就是响应体的字节数。
HTTP数据包到达客户端后,直接由客户端监听50685的进程接收、解析并响应。
第48帧,是客户端告知服务端已接收到第47帧而发的数据包。
四次挥手
下图中,第49、50、51帧是双方挥手断开连接的过程。
具体来说,
- 第一次(第49帧):客户端向服务端发送带有FIN(Finish)字段和对48帧的ACK的TCP包,告知服务端请求连接终止。此时客户端处于
FIN-WAIT-1
状态,即“等待远程TCP的连接中断请求,或先前的连接中断请求的确认”; - 第二次(第50帧):服务端接收到客户端发送的第49帧,并且发现自己也没数据再要发送,就回复了FIN和对第49帧的ACK,告知客户端请求连接终止。此时客户端到服务端的连接释放。
- 第三次(第51帧):客户端接收到服务端发送的第50帧,向对方回复已接收到,然后客户端进入
TIME_WAIT
(时间等待)状态,TIME-WAIT
- 等待足够的时间以确保远程TCP接收到连接中断请求的确认;
**注意 !!!**这个时候由服务端到客户端的 TCP 连接并未释放掉,需要经过时间等待计时器设置的时间 2MSL(一个报文的来回时间) 后才会进入 CLOSED
状态(这样做的目的是确保服务端收到自己的 ACK 报文(即第52帧)。如果服务端在规定时间内没有收到客户端发来的 ACK 报文的话,服务端会重新发送 FIN 报文给客户端,客户端再次收到 FIN 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文给服务端)。服务端收到 ACK 报文之后,就关闭连接了,处于 CLOSED
状态。
在这里来看,好像是“三次挥手”。
对于真正的四次挥手,应该是:
- 第一次(第49帧):客户端向服务端发送带有FIN(Finish)字段和对48帧的ACK的TCP包,告知服务端请求连接终止。此时客户端处于
FIN-WAIT-1
状态,即“等待远程TCP的连接中断请求,或先前的连接中断请求的确认”; - 第二次(第50帧):服务端接收到客户端发送的第49帧,但是服务端对客户端还有信息要发,就只回复了第49帧的ACK(未回复FIN),告知客户端已知悉连接终止请求,此时客户端到服务端的连接释放;
- 第三次(第51帧):服务端也想中断请求了,向客户端发送一个FIN报文,告知客户端要断开连接请求;
- 第四次(第52帧):客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,此时客户端处于
TIME_WAIT
(时间等待)状态,然后等待时间2MSL后释放到服务端的连接。
所以本文的“三次挥手”是把四次挥手的第二次和第三次合并成一次了。
为什么客户端释放对服务端的连接之后,依然可以向服务端发送ACK?
因为FIN状态位表示:“发送后,当前客户端仍然会接收服务器的数据,但是不会接收他本地服务的数据(应用层的数据)”,所以客户端在发送Fin后仍然能发送ACK是不矛盾的,因为ACK不是本地服务的数据,是放在TCP头部的,而不是Body中。
Post 请求
如下图,对Post请求的抓包:
可以看到只有HTTP请求帧有少许差别,本质上还是HTTP协议,不再赘述。