TCP报文及状态分析

0.背景

传输控制协议TCP(Transmission Control Protocol)

TCP和UDP协议的区别以及原理

简单总结下,TCP就是个位于传输层(四层)的面向连接的端对端可靠传输的全双工通讯协议

如何保证可靠馁,源自于三次握手和四次挥手。

  • 三次握手:确保双方收发能力正常(报文数据能成功交互),可以看下我这个视频

  • 四次挥手:确保数据传输完全(报文数据不丢失)。

咱们的HTTP协议作为应用层协议,也是基于TCP的,因为开发中经常会碰到,此处简单总结下。

1.netstat命令

netstat命令不多赘述,下面是几个常见的用法:

1.1 展示TCP连接状态

netstat -antpo

# -t 显示状态
# -n ip显示
# 展示对应的pid

image-20221120105025724

1.2 统计各TCP状态数

netstat -tnp |awk '{print $6}' | sort |uniq -c

# uniq 删除重复行
# uniq -c 统计重复行次数

image-20221120104323246

1.3 统计某个状态的数量

# wc -l
netstat -na|grep ESTABLISHED|wc -l

image-20221120105512949

2.TCP报文

image-20221120143740502

TCP三次握手中SYN,ACK,seq ack的含义TCP报文头详解,看的时候要注意下,ACK和ack是特意区分大小写的。


TCP报文:由TCP首部TCP数据两部分组成

  • TCP首部:20字节的固定长度可变长字段(选项和填充)组成

    • 数据偏移(首部总长度)

      由行4所在的前4位数据偏移决定,最大1111(十进制15),这里的长度单位是字节,所以最大是4*15=60字节(480位)。

    • 选项和填充

      由于前面5行的20字节是定长,所以这里还有40字节的信息可以选填。可选部分的信息不一定是4字节,填充位(补0)是为了使TCP首部为4字节的整数倍。

  • TCP数据:数据部分是可选的,比如三次握手和四次挥手。


现在,你对TCP报文已经有了个简要的认识,在开始讨论之前,我们先梳理几个问题。

2.1 为什么要学习TCP报文格式?

不管你啥TCP报文,到最后还不就是一串二进制的数据,例如下面这个:

1100011111101100000000011011101100100011101101111110000001100011000000000000000000000000000000001000000000000010111110101111000000011010001011110000000000000000000000100000010000000101101101000000000100000011000000110000100000000001000000010000010000000010

一模一样根本分不出来谁是谁啊?就跟看日志一样,单看到一个报文,我咋知道它是请求还是响应报文?

{
    "state": "0"
}

现在,告诉我,这是个请求报文还是个响应报文?

是吧,根本看不出来。

它搞这么多个规范和定义,就是为了区分,让我们能根据xx尽可能分析出更多的信息。

比如要区分上面的json报文是请求还是响应,我们可以在报文里加个direction字段,值用request/response,看到request,我就知道这是一个请求报文。

{
    "state": "0",
    "direction": "request"
}

协议,就是我们共同遵守的一个规则

我们可以简单的自己设计个协议,在第一位0表示请求,1表示响应,后面的表示数据包,像下面这样。

1000110001111

好,现在你知道我们的规则了,看到首位的1,根据规则它表示响应,所以,你能推断出这是一个响应的数据包。

学习报文格式,就是在学习这套规则。

我tm最烦那些到处copy名词copy概念的,必须给你结合报文展示下,现在我用wireshark随便抓个包。

image-20230420222151108

可以看到实际TCP报文是一个c7开头-02结尾的hex串,我把它复制出来。

# TCP报文hex串
c7ec01bb23b7e063000000008002faf01a2f0000020405b40103030801010402
# 转2进制
1100011111101100000000011011101100100011101101111110000001100011000000000000000000000000000000001000000000000010111110101111000000011010001011110000000000000000000000100000010000000101101101000000000100000011000000110000100000000001000000010000010000000010

image-20221120143740502

还记得这张图吗?我们按它的格式,32位一行来美化下。

# format
11000111111011000000000110111011
00100011101101111110000001100011
00000000000000000000000000000000
10000000000000101111101011110000
00011010001011110000000000000000
00000010000001000000010110110100
00000001000000110000001100001000
00000001000000010000010000000010

# 标志位在第4行对吧
1000 0000 0000 0010 1111 1010 1111 0000
# 然后,看上面的坐标,10-16是标志位对不.
00 0010
# 现在是不是可以分析出
ACK = 0
SYN = 1
FIN = 0

现在,你有没有get到为啥wireshark能给你提示了?

image-20230420223301227

我们根据SYN = 1,ACK = 0的信息,就可以推断出这是个TCP发起(第一次)握手请求报文。

为什么能推断出?这就是协议的作用:因为我们规定发起第一次握手时,报文中SYN = 1,ACK = 0。

那6个标志位后的窗口大小呢?是不是看下第4行的后面那串就行了。

# 标志位在第4行对吧
1000 0000 0000 0010 1111 1010 1111 0000
# 窗口取后16位
1111 1010 1111 0000

image-20230420223954393

是不是就对上了?

3.TCP的11种状态

由于tcp是一个面向连接的可靠传输,所以每一次的传输都会经历连接,传输,关闭的过程。

无论是哪个方向的传输,必须建立连接才行。在双方通信的过程中,tcp 的状态是不一样的。

下面是一张TCP状态的转换图

怎么看:从最上方的位置,“起点”开始分析。

image-20230420213513403

3.1 三次握手

3.1.1 三次握手的过程

image-20221120112250225

同步位SYN 确认位ACK 含义
1 0 第一次握手
1 1 第二次握手
0 1 第三次握手/数据传输报文

ACK、SYN和FIN这些大写的单词表示标志位(1位),其值要么是1,要么是0;

ack、seq小写的单词表示序号(32位、4字节)


简单介绍下几个名词

  • 同步位SYN

    这个玩意用来区分TCP的报文是不是建立连接的报文(是不是握手报文),只有在TCP一二次握手时才会置位1,二次握手完成后SYN这个标志位被置0。

  • 序列号seq(Sequence Number)

    用来标记数据段的位置。数据量大了肯定要分段发送。

    • 握手阶段

      • 第1次握手:本地随机产生ISN(Initial Sequence Number,初始序号值),即上图的x。
      • 第2次握手:服务器选择的另一个随机数,用于初始化这个TCP连接,既上图的y。
      • 第3次握手:这个阶段报文的数据部分是空的,所以固定为x+1。
    • 传输阶段

      上一次发送报文的seq加上该报文的数据长度。

      例如,你上一次发送的报文的序列号为x,该报文的数据长度为L,则下一次发送的报文的序列号应该是x+L。

  • 确认位ACK

    标识数据成功接收且确认号ack有效。当ACK=1时,接收到数据且确认号ack有效。ACK=0时,未接收到数据且ack无效。

    TCP规定,连接建立后的所有传输报文,都必须把ACK置为1。

    为啥要搞个ACK位呢?看下上面SYN的描述,我们可以结合SYN和ACK分析出TCP报文的类型。

    若SYN=1,ACK=0表示这是个请求连接的报文。若SYN=1,ACK=1表示这是个接受连接的报文。

  • 确认号ack

    假设对面扔过来一堆TCP的报文,你肯定疑惑,哇靠都一样的,哪个是我要的?所以,你得告诉对面怎么表达。

    上面不是有个seq吗?结合这个就能保证顺序,这个时候,就出现了seq和ack的计算规则。告诉对面,你希望收到的seq值是啥。

(1) 第1次握手
(2) 第2次握手
(3) 第3次握手

3.1.2 三次握手的5种状态

(1) CLOSED

没有任何连接状态

(2) LISTEN

侦听来自远方TCP端口的连接请求

(3) SYN_RCVD

收到/发送一个连接请求后,等待对方的确认。

(4) SYN_SENT

发送连接请求后,等待相匹配的连接。

(5) ESTABLISHED

连接已打开,数据可传送。

3.1.3 总结

3.2 四次挥手

3.2.1 四次握手的过程

(1) 第1次挥手
(2) 第2次挥手
(3) 第3次挥手
(4) 第4次挥手

3.2.2 四次挥手的6种状态

(1)
(2)
(3)
(4)

3.2.3 总结

4.wireshark分析

打开wireshark,然后本地用xshell连接腾讯云的服务器,捕捉数据包。

先简单看下wireshark的窗口

img

3.1 三次握手

tcp and ip.addr==175.178.189.195 and tcp.stream==5
# 这里用到了stream, Wireshark用Stream index来区分不同的请求.

image-20221120131320395

图里的就是三次握手阶段的报文,我们一个个来看。

3.1.1 一阶段握手

注意用[]括起来的是wireshark加上的注释信息

image-20221120152609313

38 a4 00 16 34 e7 d8 a7 00 00 00 00 80 02 fa f0 84 3c 00 00 02 04 05 b4 01 03 03 08 01 01 04 02
###### 格式化一下
行1: 38 a4 00 16 源端口 目的端口
行2: 34 e7 d8 a7 序列号
行3: 00 00 00 00 确认号
行4: 80 02 fa f0 数据偏移(首部长度)4+保留位6+标志位6+窗口16
行5: 84 3c 00 00 校验和+紧急指针
# 20字节(4字节*5行=20字节) 固定长度结束
行6: 02 04 05 b4
行7: 01 03 03 08 
行8: 01 01 04 02

##### 转换为2进制
行1: 1110 0010 1001 0000 0000 0000 0101 1000
行2: 1101 0011 1001 1111 0110 0010 1001 1100
行3: 0000 0000 0000 0000 0000 0000 0000 0010
行4: 1000 0000 0000 0010 1111 1010 1111 0000
行5: 1000 0100 0011 1100 0000 0000 0000 0000
# 20字节(32位*5行=160位) 固定长度结束
行1: 38 a4: 2字节,源端口 14500
行1: 00 16: 2字节,目的端口 22
行2: 34 e7 d8 a7: 4字节,序列号seq,887609511
行3: 00 00 00 00: 4字节,确认号ack,0
行4: 80 02 fa f0: 这里的信息有点多,拆分成2进制是1000 0000 0000 0010 1111 1010 1111 0000
					1.前面16位最后6个00 0010是标志位:URG=0,ACK=0,PSH=0,RST=0,SYN=1,FIN=0,由SYN=1,ACK=0 => 第一次握手
					2.后面16位是窗口大小: 64240(接收通告窗口Receiver Window,RWND,TCP滑动窗口协议,告诉对方本端的TCP接收缓冲区还能容纳多少字节					  的数据,这样对方就可以控制发送数据的速度,从而达到流量控制.窗口大小为一个16位字段,故窗口大小最大为65535)
行5: 84 3c 00 00: 1.前面16位是校验和,用CRC算法校验报文数据(头+数据),确保数据不被损坏 2.后面16位是紧急指针,配合紧急标志位URG使用.
行6、7、8: 可选项和填充,其中可选项的格式有点像那种TLV结构的,可选项格式:1字节类型信息(kind),1字节长度信息(length),n字节具体信息(info),有时候是只有kind的.

校验头和紧急指针不是本文重点,补充几链接做参考:

TCP头校验和计算算法详解

TCP 协议(紧急标志)

TCP报文-选项字段

好,下面结合wireshark的图来展示下:

image-20221120160524912

由SYN=1,ACK=0(seq=887609511),可以知道这是1阶段握手。

3.1.2 二阶段握手

image-20221120161042608

3.1.3 三阶段握手

image-20221120161410751

3.1.4 第一次数据传输

image-20221120162732159

3.1.5 第二次数据传输

image-20221120162755299

3.1.6 总结下上面几个阶段的seq和ack

image-20221120112250225

wireshark的相对值比较好用,直接参考这个。

#1次握手: client -> server
seq: 0 (本地随机的是: 887609511)

#2次握手: server -> client
seq: 0 
ack: 1 (本地随机的是: 1359191029)

#3次握手: client -> server
seq: 1
ack: 1

#第1次数据传输: server -> client (根据端口可以看到是server给client发的)
seq: 1
ack: 1

#第2次数据传输: client -> server
seq: 1
ack: 22

#第3次数据传输: client -> server
seq: 51
ack: 22

第2次传输时,ack送的是22,seq22的数据去哪了?

被我在这里找到了

image-20221120170603326

5.问题汇总

部分内容来自于和GPT的问答

Q:我怎么知道我是服务端还是客户端?

对于TCP的ESTABLISHED状态,如果我是客户端,那么在第二次握手时,我就进入了ESTABLISHED状态。但是如果我是服务端,我是在第三次握手的之后才会进入ESTABLISHED状态。现在我服务器有一个ESTABLISHED状态数过高的告警,我怎么知道我是服务端还是客户端?

如果您想确定您的服务器是作为客户端还是服务端与其他设备进行通信,则需要查看服务器上与其他设备建立的连接的本地和远程IP地址和端口号。

对于TCP连接,本地IP地址和端口是指服务器上的IP地址和端口,而远程IP地址和端口是指与服务器进行通信的远程设备的IP地址和端口。

  • 服务端

如果您的服务器的本地IP地址和端口号是固定的,而远程IP地址和端口号在不同的连接中不断变化,则您的服务器很可能是作为服务端在处理连接请求。因为在服务端,服务器通常监听一个固定的端口,等待客户端的连接请求,并在建立连接时分配临时端口号。

  • 客户端

相反,如果您的服务器的远程IP地址和端口号是固定的,而本地IP地址和端口号在不同的连接中不断变化,则您的服务器很可能是作为客户端在发起连接请求。因为在客户端,通常会为每个新的连接请求随机分配一个临时端口号。

您可以使用网络诊断工具(例如netstat、lsof或tcpdump)来查看当前连接的详细信息,以确定您的服务器是作为服务端还是客户端与其他设备进行通信。具体来说,您可以使用以下命令来查看当前连接的详细信息:

  • 使用netstat命令:
netstat -anp | grep ESTABLISHED

这将列出所有处于ESTABLISHED状态的连接,并显示与这些连接相关联的本地和远程IP地址和端口号,以及与这些连接相关联的进程ID。

  • 使用lsof命令:
lsof -i tcp | grep ESTABLISHED

这将列出所有处于ESTABLISHED状态的TCP连接,并显示与这些连接相关联的本地和远程IP地址和端口号,以及与这些连接相关联的进程名称。

请注意,在某些情况下,服务器可能会同时作为服务端和客户端与其他设备进行通信,因此在分析连接详细信息时要格外注意。


举例:

我在服务器上执行了grep ESTABLISHED后,查看到下方内容。

image-20230324152257040

现在,我们知道,10.7.12.1410.7.240.30在进行TCP通讯,我的本机ip是10.7.12.14,这个场景下,我是服务端还是客户端?

可以看到,此处10.7.12.14(本机)的端口固定为3009,而远端端口在不停变化,基于这个特点,我们能作出如下推论:

本机作为服务端,服务端口为3009,而10.7.240.30这个远程ip在请求我们的服务。

Q: seq是给我自己看的还是给对端看的?

序列号(seq)是TCP用于标识数据流中每个数据段的一个唯一标识符,是给对端看的。

序列号告诉对端接收到的数据段在整个数据流中的位置,使得对端可以重组数据,保证数据的正确性和可靠性。

假设你(计算机A)要向另一台计算机B发送一些数据,这些数据需要被拆分成多个TCP数据段进行传输。每个数据段都需要有一个序列号,以便计算机B在接收数据时可以按正确的顺序组合数据。

例如,假设你要发送的数据被分成三个数据段,它们的序列号可能是101、201和301。这样,当计算机B接收到数据时,它可以根据序列号将数据段重新组合成完整的数据,确保数据的正确性和可靠性。

Q:假设我收到了seq为201的,我怎么知道我还要等待一个seq为101的,而不是102或者103?

接收到一个数据段时,TCP 会将该数据段的序列号加上该数据段长度,得到下一个期望的序列号。

例如,在接收到序列号为 101,长度为 50 的数据段后,TCP 会期望下一个数据段的序列号是 151。

如果接收到了序列号为 152 的数据段,则 TCP 会认为数据段 101-150 已经丢失,会发送重传请求,要求发送方重新发送数据段 101-150。

posted @ 2022-11-20 17:09  羊37  阅读(592)  评论(0编辑  收藏  举报