HTTP与TCP协议-GET与POST请求抓包分析

本文共2667字。
Copyright: 知识共享署名 非商业性使用 相同方式共享 4.0 国际许可协议 | CC BY-NC-SA 4.0

首先我在服务器上部署了一个Web服务(注:容器的1423端口被绑定到了宿主机的8010端口),可以接收一个最简单的Get和Post请求,如下图

1

然后在终端对其发送Get和Post请求,都是成功的,如下图:

2

然后使用Wireshark对Get请求和Post请求分别抓包,进行分析。

本文先详细分析Get请求,对于Post请求仅指出与Get请求的区别。

Get请求抓包分析

在Wireshark抓包一次Get请求如下图,可以看出其先进行三次握手建立连接,然后发送请求,最后四次挥手断开连接。

3

下面对这10帧数据依次分析。

三次握手

前三帧进行了三次握手建立连接,从图3可以看出,数据报和上篇文章ICMP-Wireshark抓包分析描述的ICMP格式相似,最外层为Ethernet II数据包,内部为IPv4数据包,最内部包含TCP数据包。所以本文不再对Ethernet II和IPv4进行详细分析,直接看IPv4的Data部分,也就是TCP数据包。

4

一般地,TCP的帧格式如下

5

然后我们查看第一次握手的情况。

第一次握手

从图4可以看到,TCP报文如下,共44字节:

1
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的内容。

6

将上述内容填充至TCP的帧格式内,如下图:

7

主要看到,序列号码0xda9cf9fa,SYN为1。

其中:

  • 序列号码:表示当前报文段的数据的第一个字节的序号,此序号为随机生成(为什么?)。本次以序列号0xda9cf9fa向服务端发送了1字节的数据,那么下次向服务端发送的序列号就是0xda9cf9fb。序列号和确认号码ACK有重要关系,第二次握手时会说明。
  • SYN:为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步

第二次握手

TCP报文如下

1
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报文如下

1
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。
8

三次握手,双方分别确认了如下能力:

8.5

Get请求

在Wireshark中可以看到,HTTP协议是由TCP协议支撑。

9

先看下,在HTTP请求中,TCP字段头的信息:

10

分析可以得出,序列号码为0xda9cf9fb,ACK为0x57f4449a,SYN为0。与第三次握手的TCP报文相同,因为第三次握手为客户端向服务端发送数据并未得到服务端回应,所以客户端发起的Get请求,所以其ACK为第二次握手接收到的序列号Seq+1,序列号码应该为第二次握手接收到的ACK。

HTTP数据报共90字节,计算方法如下图:

11

在TCP报文部分,可以看到Wireshark已经给出了[TCP Segment Len: 90],但是仅根据TCP协议帧字段是计算不出来90的,推测Wireshark也是根据上图方法计算的。

接下来,看下HTTP报文本身。

12

一般地,HTTP请求帧格式如下:

13

可以看到,抓包得到的HTTP数据报如下

1
2
3
4
5
6
7
8
9
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帧进行一下分析说明。

12.5

第46帧是由服务端向客户端发送的,是为了告诉客户端:已经接收到来自客户端的序列号为0xda9cfa54及之前的数据包(也就是刚才发的Get请求,即第45帧),下一个数据包序列号请从0xda9cfa55开始。

在刚才的Get请求中,其向服务端发送数据包中的ACK字段为0x57f4449a,序列号为0xda9cf9fb,共发送了90字节数据,所以服务端会给客户端一个回应。

Get请求的响应

第47帧是Get请求的响应,如下图

14

一般地,HTTP响应帧格式如下:

15

可以看到,抓包得到的HTTP数据报如下

1
2
3
4
5
6
7
8
9
10
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帧是双方挥手断开连接的过程。

14

具体来说,

  • 第一次(第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请求的抓包:

16

可以看到只有HTTP请求帧有少许差别,本质上还是HTTP协议,不再赘述。