TCP包的结构:TCP协议规定的数据报文的构造
TCP报文中的序列号seq和确认号到底指什么?
序列号seq(sequence numer)
序列号seq,其实TCP发送payload数据的每一个字节都是有编号的,每个报文的编号其实就是这个报文payload数据中的首个字节的编号,通俗的例子:
一共发了3个报文:P1(payload=aaa), P2(payload=bb), P3(payload=c),那么
P1.seq=0
P2.seq=3(P1.seq+p1.payload.length)
P3.seq=5(P2.seq+p2.payload.length)
确认号ack(acknowledge numer)
确认号ack,其实是对前一个对端发送来的报文的回复,ack=x+1说明之前对端发送来的x个字节的payload数据都已经收到了,请对端下次发送seq=x+1的报文吧,或者说叫期望从对端收到的下一字节的序号。通俗的例子:
A主机发来了P1(payload=aaa), B主机接收到P1报文后需要发回确认报文R1:
P1.seq=0
R1.ack=3(P1.seq+p1.payload.length)
tcpdump抓包分析
抓在80端口上的TCP包:
> tcpdump -S 'tcp and port 80'
21:33:01.704998 IP 192.168.31.72.61146 > 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http: Flags [S], seq 396038856, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 744881175 ecr 0,sackOK,eol], length 0
其中传输的Flags释义:
- [S]:SYN,表示开始连接
- [.]:没有标记,一般是确认
- [P]:PSH,表示数据推送
- [F]:FIN,表示结束连接
- [R] :RST,表示重启连接
抓包的一组TCP连接传输断开的数据报文:
> tcpdump -S 'tcp and port 80'
21:33:01.704998 IP 192.168.31.72.61146 > 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http: Flags [S], seq 396038856, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 744881175 ecr 0,sackOK,eol], length 0
21:33:01.717502 IP 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http > 192.168.31.72.61146: Flags [S.], seq 2345237966, ack 396038857, win 14400, options [mss 1412,nop,nop,sackOK,nop,wscale 7], length 0
21:33:01.717542 IP 192.168.31.72.61146 > 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http: Flags [.], ack 2345237967, win 4096, length 0
21:33:01.718011 IP 192.168.31.72.61146 > 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http: Flags [P.], seq 396038857:396039622, ack 2345237967, win 4096, length 765: HTTP: POST /mmtls/22207be4 HTTP/1.1
21:33:01.727958 IP 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http > 192.168.31.72.61146: Flags [.], ack 396039622, win 125, length 0
21:33:01.758550 IP 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http > 192.168.31.72.61146: Flags [P.], seq 2345237967:2345238289, ack 396039622, win 125, length 322: HTTP: HTTP/1.1 200 OK
21:33:01.758735 IP 192.168.31.72.61146 > 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http: Flags [.], ack 2345238289, win 4090, length 0
21:33:01.759551 IP 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http > 192.168.31.72.61146: Flags [F.], seq 2345238289, ack 396039622, win 125, length 0
21:33:01.759693 IP 192.168.31.72.61146 > 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http: Flags [.], ack 2345238290, win 4090, length 0
21:33:01.773281 IP 192.168.31.72.61146 > 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http: Flags [F.], seq 396039622, ack 2345238290, win 4096, length 0
21:33:01.786917 IP 101.165.151.61.dial.xw.sh.dynamic.163data.com.cn.http > 192.168.31.72.61146: Flags [.], ack 396039623, win 0, length 0
梳理一下:
报文传输方向 | Flags | seq | ack | payload length |
---|---|---|---|---|
C -> S | [S] | 396038856 | 0 | |
S -> C | [S.] | 2345237966 | 396038857 | 0 |
C -> S | [.] | (396038857) | 2345237967 | 0 |
C -> S | [P.] | 396038857(:396039622) | 2345237967 | 765 |
S -> C | [.] | (2345237967) | 396039622 | 0 |
S -> C | [P.] | 2345237967(:2345238289) | 396039622 | 322 |
C -> S | [.] | (396039622) | 2345238289 | 0 |
S -> C | [F.] | 2345238289 | 396039622 | 0 |
C -> S | [.] | (396039622) | 2345238290 | 0 |
C -> S | [F.] | 396039622 | 2345238290 | 0 |
S -> C | [.] | (2345238290) | 396039623 | 0 |
其中需要注意的点有:
- 为了避免过长的seq和ack,图中seq和ack只展示了后3位;
- tcpdump开启-S命令后,所有的seq和ack都将保留绝对值而不是相对值,易于清楚呈现出连接报文的详情;
- 报文4
seq 396038857:396039622 length 765
,这个意思是seq=396038857,其报文的payload length=765,所以对端ack的时候应该ack=396039622(即表示396039622前的字节我都收到了,下一次请给我编号为396039622及以后的字节),显示这个就不需要人读的时候再手工计算了,简单明了的展示清楚,不过第一次用的话会很懵。报文5ack 396039622
印证了这个观点; - 为什么syn报文明明payload length=0,对端ack还要加1呢?因为syn是建立连接的关键报文,而为了确保对方接收到,使用超时重传机制,TCP规定,只为有数据的TCP报文重传,SYN占据一个序号(可以认为只有一个字节数据的报文,即使其payload length=0),这样做TCP也会重传SYN报文;
- ack的payload length=0,可以看到ack报文发送后之后的报文seq并没有变;