Wireshark图解TCP三次握手与四次挥手

1. TCP 包头结构

  • 源端口、目标端口:计算机上的进程要和其他进程通信是要通过计算机端口的,而一个计算机端口 某个时刻只能被一个进程占用,所以通过指定源端口和目标端口,就可以知道是哪两个进程需要通 信。源端口、目标端口是用16位表示的,可推算计算机的端口个数为2^16个,即65536

  • 序列号:表示本报文段所发送数据的第一个字节的编号。在TCP连接中所传送的字节流的每一个字节都会按顺序编号。由于序列号由 32 位表示,所以每2^32个字节,就会出现序列号回绕,再次从 0 开始,作用是解决数据包在网络传输中的乱序问题

  • 确认号:表示接收方期望收到发送方下一个报文段的第一个字节数据的编号。也就是告诉发送方:我希望你(指发送方)下次发送的数据的第一个字节数据的编号为此确认号,发送方收到确认应答以后,说明在这个序号以前的数据包都已经被成功接收了,作用是确认数据包在传输过程中是否有丢失

  • 数据偏移:表示TCP报文段的首部长度,共4位,由于TCP首部包含一个长度可变的选项部分,需要指定这个TCP报文段到底有多长。它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。该字段的单位是32位(即4个字节为计算单位),4位二进制最大表示15,所以数据偏移也就 是TCP首部最大60字节

  • 控制位

    • URG:表示本报文段中发送的数据是否包含紧急数据。后面的紧急指针字段(urgent pointer)只 有当 URG=1 时才有效

    • ACK:表示是否前面确认号字段是否有效。只有当 ACK=1 时,前面的确认号字段才有效。TCP 规定,连接建立后,ACK必须为1,带 ACK 标志的 TCP 报文段称为确认报文段

    • PSH:提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间。如 果为1,则表示对方应当立即把数据提交给上层应用,而不是缓存起来,如果应用程序不将接收到 的数据读走,就会一直停留在TCP接收缓冲区中

    • RST:如果收到一个 RST=1 的报文,说明与主机的连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接。或者说明上次发送给主机的数据有问题,主机拒绝响应,带 RST 标志的 TCP 报文段称为复位报文段

    • SYN:在建立连接时使用,用来同步序号。当SYN=1,ACK=0时,表示这是一个请求建立连接的报文段;当 SYN=1,ACK=1 时,表示对方同意建立连接。SYN=1,说明这是一个请求建立连接或同意建立连接的报文。只有在前两次握手中SYN才置为 1, 标志的 TCP 报文段称为同步报文段

    • FIN:表示通知对方本端要关闭连接了,标记数据是否发送完毕。如果 FIN=1 ,即告诉对方:“我的数据已经发送完毕,你可以释放连接了”,带 FIN 标志的 TCP 报文段称为结束报文段

  • 窗口大小:表示现在允许对方发送的数据量,也就是告诉对方,从本报文段的确认号开始允许对方 发送的数据量,达到此值,需要ACK确认后才能再继续传送后面数据,由Window size value * Window size scaling factor(此值在三次握手阶段TCP选项Window scale协商得到)得出此值

  • 校验和:提供额外的可靠性

  • 紧急指针:标记紧急数据在数据字段中的位置

  • 选项部分:其最大长度可根据TCP首部长度进行推算。TCP首部长度用4位表示,选项部分最长 为:(2^4-1)*4-20=40字节

TCP包头常见选项:

  • 最大报文段长度MSS(Maximum Segment Size),通常1460字节

    指明自己期望对方发送TCP报文段时那个数据字段的长度。比如:1460字节。数据字段的长度加 上TCP首部的长度才等于整个TCP报文段的长度。MSS不宜设的太大也不宜设的太小。若选择太 小,极端情况下,TCP报文段只含有1字节数据,在IP层传输的数据报的开销至少有40字节(包括 TCP报文段的首部和IP数据报的首部)。这样,网络的利用率就不会超过1/41。若TCP报文段非常 长,那么在IP层传输时就有可能要分解成多个短数据报片。在终点要把收到的各个短数据报片装配 成原来的TCP报文段。当传输出错时还要进行重传,这些也都会使开销增大。因此MSS应尽可能 大,只要在IP层传输时不需要再分片就行。在连接建立过程中,双方都把自己能够支持的MSS写入 这一字段。 MSS只出现在SYN报文中。即:MSS出现在SYN=1的报文段中 MTU和MSS值的关系:MTU=MSS+IP Header+TCP Header
    通信双方最终的MSS值=较小MTU-IP Header-TCP Header

  • 窗口扩大 Window Scale

    为了扩大窗口,由于TCP首部的窗口大小字段长度是16位,所以其表示的最大数是65535。但是随 着时延和带宽比较大的通信产生(如卫星通信),需要更大的窗口来满足性能和吞吐率,所以产生 了这个窗口扩大选项

  • 时间戳 Timestamps

    可以用来计算RTT(往返时间),发送方发送TCP报文时,把当前的时间值放入时间戳字段,接收方 收到后发送确认报文时,把这个时间戳字段的值复制到确认报文中,当发送方收到确认报文后即可 计算出RTT。也可以用来防止回绕序号PAWS,也可以说可以用来区分相同序列号的不同报文。因 为序列号用32为表示,每2^32个序列号就会产生回绕,那么使用时间戳字段就很容易区分相同序 列号的不同报文

2. 三次握手

2.1 三次握手图解

2.2 使用 tcpdump 和 wireshark 解读三次握手过程

实际生产中我们可以直接使用 wireshark 进行抓包查看 TCP 的通信过程,本文中使用 tcpdump 和 wireshark 结合, 主要是想顺带演示一下 tcpdump,毕竟在生产中我们使用的大多是 Linux 服务器,查看简单的网络问题,使用命令行模式的 tcpdump 可能会更多一些。

实验环境

centos8 服务端 http 服务的信息如下

[root@centos8 ~]#hostname -I | awk '{print $2}'
10.0.0.11
[root@centos8 ~]# yum -y install httpd
[root@centos8 ~]# yum -y install tcpdump      # 需要配置 epel 源

# epel 源配置如下
[root@centos8 ~]#cat /etc/yum.repos.d/epel.repo
[epel]
name=epel
baseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/$releasever/Everything/x86_64
gpgcheck=0
enabled=1
[root@centos8 ~]#

centos7 的客户端机器信息

[root@centos7 ~]# hostname -I|awk '{print $2}'
10.0.0.12
[root@centos7 ~]#

在 centos8 机器上使用 tcpdump 抓包

[root@centos8 ~]#tcpdump -i eth1 -nn -c5 tcp port 80 -w tcp.pcap
dropped privs to tcpdump
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
5 packets captured
11 packets received by filter
0 packets dropped by kernel
[root@centos8 ~]#

在 cetons7 机器上访问 centos8 的 http 服务

[root@centos7 ~]# curl 10.0.0.11

因为 httpd 服务采用 TCP 协议通信,所以,首先需要三次握手

将保存下来的 tcp.pcap 文件下载到桌面上,使用 wireshark 打开(文件–打开)即可

2.2.1 TCP 第一次握手

2.2.2 TCP 第二次握手

2.2.3 TCP 第三次握手

2.2.4 客户端请求网页(发送 GET 请求)

2.2.5 服务端响应内容

3. 四次挥手

四次挥手关闭连接,关闭连接的目的:防止数据丢失;与应用层交互

四次挥手分为两种情况

  • 客户端(后服务端)主动断开连接

  • 客户端和服务端同时断开连接

3.1 客户端主动断开连接

3.2 客户端和服务端同时断开连接

3.3 使用 tcpdump 和 wireshark 解读四次挥手过程

此处仅演示客户端主动断开连接的情况

实验环境不变,只是这次为了方便演示我们使用 ssh 登录后退出来演示四次挥手的过程(我们只考虑正常的通信情况)

服务端 centos8 抓包

[root@centos8 ~]#tcpdump -i eth1 -nn tcp port 22 and host 10.0.0.12 -w tcp_wave.pcap
dropped privs to tcpdump
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
^C66 packets captured
66 packets received by filter
0 packets dropped by kernel
[root@centos8 ~]#

centos7 客户端登录 centos8 服务端,之后退出来演示 TCP 四次挥手的过程

[root@centos7 ~]# ssh root@10.0.0.11
root@10.0.0.11's password:
Last login: Tue Apr 20 09:59:38 2021 from 10.0.0.12
[root@centos8 ~]#exit
logout
Connection to 10.0.0.11 closed.
[root@centos7 ~]#
3.3.1 TCP 第一次挥手

3.3.2 TCP 第二次挥手

3.3.3 TCP 第三次挥手

3.3.4 TCP 第四次挥手

4. TCP 常见面试题

4.1 请描述 TCP 和 UDP 报文的区别和优缺点 ?
区别TCPUDP
概念全称是传输控制协议(Transmission Control Protocol),是面向连接的,可靠的,基于字节流的传输层通信协议,使用TCP协议的通信双方,在进行数据传输之前,必须使用“三报文握手”建立TCP连接全称是用户数据报协议(User Datagram Protocol),面向应用报文的,不提供复杂的控制机制,利用 IP 提供无连接的通信服务,即通信双方不需要提前建立连接就可以传输数据
服务对象每一条TCP连接只能有两个端点EP,只能是一对一通信支持一对一,一对多,多对一和多对多交互通信
可靠性TCP 是可靠的交付数据,数据可以无差错、不丢失、不重复、按序到达向上层提供无连接不可靠传输服务,即尽最大努力交付,也就是不可靠;不使用流量控制和拥塞控制
控制机制滑动窗口、拥塞控制、流量控制、重传等保证传输数据的可靠性和安全性没有任何控制机制,哪怕网络非常拥塞,也不会影响 UDP 的数据发送
首部开销首部最小20字节,最大60字节首部开销小,仅8字节
传送的数据单位协议TCP 报文段(segment)UDP 报文或用户数据报
传输方式TCP 是流式传输,没有边界,但保证顺序和可靠UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序
应用场景万维网,电子邮件,文件传输等多媒体应用
4.2 为什么需要 TCP 协议?TCP 工作在那一层 ?

IP 层是不可靠的,它不能保证网络包的按需按序交付,也不能保证网络包中数据的完整性。

网络中数据包的可靠性需要其由其上层(传输层)来负责

4.3 什么是 TCP 连接 ?

简单来说 TCP 连接由三部分组成:socket + 序列号 + 窗口大小

  • socket:由 IP 地址 + 端口号组成
  • 序列号:用来解决数据包乱序问题
  • 窗口大小:目的是流量控制,限速等
4.4 如何唯一确定一个 TCP 连接 ?

答:TCP 五元组可以唯一的确定一个连接,五元组包括如下:

  • 协议

  • 源 IP

  • 源端口

  • 目的 IP

  • 目的端口

源地址和目的地址的字段(32位)是在 IP 头部中,用于主机与主机之间的通信

源端口和目的端口的字段(16位)是在 TCP 头部中,用于进程与进程之间的通信

4.5 TCP 有哪些优化参数 ?
TCP 优化方法内核选项参考设置
增大处于 TIME_WAIT 状态的连接数量net.ipv4.tcp_max_tw_buckets1048576
增大连接跟踪表的大小net.netfilter.nf_conntrack_max1048576
缩短从 TIME_WAIT_2 到 TIME_WAIT 状态的超时时间net.ipv4.tcp_fin_timeout15
缩短连接跟踪表中处于 TIME_WAIT 状态连接的超时时间net.netfilter.nf_conntrack_tcp_timeout_time_wait30
允许 TIME_WAIT 状态占用的端口,还可以用到新建的连接中net.ipv4.tcp_tw_reuse1
增大本地端口的范围,可以支持更多连接,提高整体的并发能力net.ipv4.ip_local_port_range10000 65000
增大进程和系统的最大文件描述符数fs.nr_open(单个进程可分配的最大文件数),systemd 配置文件中的 LimitNOFILE(系统级)1048576
增大 TCP 半连接的最大数量net.ipv4.tcp_max_syn_backlog16384
增大 TCP 全连接的最大数量net.core.somaxconn16384
增⼤应用程序的 backloglisten 80 default backlog=10241024
开启 TCP SYN Cookies 绕开半连接数量限制的问题net.ipv4.tcp_syncookies1
减少 SYN_RECV 状态的连接重传 SYN+ACK 包的次数net.ipv4.tcp_synack_retries1
缩短最后一次数据包到 Keepalive 探测包的间隔时间net.ipv4.tcp_keepalive_time600
缩短发送 Keepalive 探测包的间隔时间net.ipv4.tcp_keepalive_intvl30
减少 Keepalive 探测失败后通知应用程序前的重试次数net.ipv4.tcp_keepalive_probes3
4.6 请描述 TCP 三次握手和四次挥手的过程?

见上图

4.7 TCP 为什么需要三次握手?为什么不是二次?四次行吗?

采用三报文握手主要是为了防止已失效(序列号超时或过期)的连接请求报文段突然又传送到了,因而产生错误。

客户端和服务端是双向通信的,如果是两次握手,只能保证一方初始序列号能被对方成功接收,没办法保证另外一方的初始序列号都能被确认接收

四次握手其实也是可以的,只不过服务器端可以将 ACK 和 SYN 一起发送给客户端,同样也可以同步双方的初始化序号,建立可靠的连接,所以没必要在增加一次通信开销了

4.8 TCP 为什么需要四次挥手?为什么不能是三次?

客户端和服务端是双向通信的,客户端向服务端发送 FIN 时,只是表示客户端没有数据要发送了,并不能代表服务端也没有数据要发送了,此时客户端仍然是可以接受数据的

服务器收到客户端的 FIN 报文后,回复一个 ACK 应答报文,表示同意断开连接

而此时服务端可能还有数据需要发送和处理,等服务端没有数据要发送时,会发送一个 FIN 报文给客户端,客户端收到 FIN 报文后,回复一个 ACK 应答报文,表示同意断开连接

以上可知,TCP 挥手的过程不能像握手那样,将 FIN 和 ACK 报文一起发送,所以不能使用三次挥手,而是四次挥手

4.9 握手过程中可以携带数据吗?

第三次握手是可以携带数据的,前两次握手是不可以携带数据的

4.10 SYN 攻击是占用服务端具体什么资源呢,SOCKET 连接吗 ?


半连接队列,可以通过修改内核参数来控制其队列大小

# 内核接收数据包的最大值
net.core.netdev_max_backlog
# 增⼤半连接队列
net.ipv4.tcp_max_syn_backlog = 1024
# 增大 somaxconn
echo 1024 > /proc/sys/net/core/somaxconn
# 增⼤应用程序的 backlog 的⽅式,以 Nginx 为例
server {
   listen 80 default backlog=1024;
   server_name localhost;
   ......
}
# 减少 SYN+ACK 重传次数
# 连接每个 SYN_RECV 时,如果失败的话,内核还会自动重试,并且默认的重试次数是 5 次
net.ipv4.tcp_synack_retries = 1
# 开启 tcp_syncookies 功能
net.ipv4.tcp_syncookies = 1

TCP SYN Cookies 也是一种专门防御 SYN Flood 攻击的方法。SYN Cookies 基于连接信息(包括源地址、源端口、目的地址、目的端口等)以及一个加密种子(如系统启动时间),计算出一个哈希值(SHA1),这个哈希值称为 cookie。

然后,这个 cookie 就被用作序列号,来应答 SYN+ACK 包,并释放连接状态。当客户端发送完三次握手的最后一次 ACK 后,服务器就会再次计算这个哈希值,确认是上次返回的 SYN+ACK 的返回包,才会进入 TCP 的连接状态。

因而,开启 SYN Cookies 后,就不需要维护半开连接状态了,进而也就没有了半连接数的限制。

4.11 某一天忽然发现服务器有大量的 TIME_WAIT 状态?为什么会这样?怎么样解决?

TIME_WAIT 是 TCP 四次挥手过程中,主动断开连接的一方在第三次挥手后才会有的状态

是正常挥手中必不可少的一个状态,原因是:

  • 防止因为网络原因收到旧的数据包
  • 保证被动断开连接的一方能正确的收到 ACK 从而正常的关闭连接

如果服务器有大量的 TIME_WAIT,则首先说明是服务器主动断开的连接,过多的 TIME_WAIT 会暂用大量的内存和端口资源,导致服务端无法再处理新的连接请求

解决办法是优化内核

# 增大处于 TIME_WAIT 状态的连接数量 
net.ipv4.tcp_max_tw_buckets = 1048576
# 增大连接跟踪表的大小 
net.netfilter.nf_conntrack_max = 1048576
# 缩短从 TIME_WAIT_2 到 TIME_WAIT 状态的超时时间 ,让系统尽快释放它们所占用的资源。
net.ipv4.tcp_fin_timeout = 15
# 缩短连接跟踪表中处于 TIME_WAIT 状态连接的超时时间  ,让系统尽快释放它们所占用的资源
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
# 增大本地端口的范围 ,这样就可以支持更多连接,提高整体的并发能力
net.ipv4.ip_local_port_range = 10000 65000
# 增大单个进程可分配的最大文件数
fs.nr_open = 1048576
# 增大系统的最大文件描述符数,或在应用程序的 systemd 配置文件中,配置 LimitNOFILE
fs.file-max = 1048576
# 开启 TIME_WAIT 重用,这样,被 TIME_WAIT 状态占用的端口,还能用到新建的连接中,只能用于客户端
net.ipv4.tcp_tw_reuse = 1
#  开启 TCP 时间戳(配合上面使用)
net.ipv4.tcp_timestamps=1
# 重置超时连接(可能会引起其他问题,慎用!!!)
net.ipv4.tcp_max_tw_buckets

注意:net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_tw_recycle 都依赖于 net.ipv4.tcp_timestamps,net.ipv4.tcp_timestamps 是 tcp 的一个选项,如果开启了它,那么就无法使用 tcp 的其他选项了,包括 net.ipv4.tcp_syncookies。

4.12 某一天忽然发现服务器有大量的 CLOSE_WAIT 状态?为什么会这样?怎么样解决?
4.13 TCP 双方已经建立了连接,后来,TCP 客户进程所在的主机突然出现了故障怎么办 ?

TCP服务器进程以后就不能再收到TCP客户进程发来的数据

因此,应当有措施使TCP服务器进程不要再白白等待下去

TCP 有一个保活机制,原理就是

TCP 服务器进程每收到一次 TCP 客户进程的数据,就重新设置并启动保活计时器(2小时定时)若保活计时器定时周期内未收到 TCP 客户进程发来的数据,则当保活计时器到时后,TCP 服务器进程就向 TCP 客户进程发送一个探测报文段,以后每隔 75 秒发送一次。若连续发送 9 个探测报文段后仍无 TCP 客户进程的响应,TCP 服务器进程就认为 TCP 客户进程所在主机除了故障,接着就关闭这个连接

可以在内核中设置,默认如下:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
4.14 TCP客户进程在发送完最后一个确认报文后,为什么不直接进入关闭状态?而是要进入时间等待状态(2MSL时间)?

因为时间等待状态以及处于该状态2MSL时长,可以确保TCP服务器进程可以收到最后一个TCP确认报文段而进入关闭状态

另外,TCP客户进程在发送完最后一个TCP确认报文段后,在经过2MSL时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的TCP连接中,不会出现旧连接中的报文段

4.15 如何查看 TCP 的状态,以及统计 TCP 各个状态的总数

ss 命令来自于 iproute 包,代替 netstat,netstat 通过遍历 /proc 来获取 socket 信息,ss 使用 netlink 与内核 tcp_diag 模块通信获取 socket 信息,用法和输出结果类似于 netstat 命令

# 查看 TCP 的通信状态
[root@centos8 ~]#ss -antp
State           Recv-Q      Send-Q  Local Address:Port  Peer Address:Port
LISTEN          0           128     0.0.0.0:80          0.0.0.0:*           users:(("nginx",pid=767,fd=8),("nginx",pid=766,fd=8))
LISTEN          0           128     0.0.0.0:22          0.0.0.0:*           users:(("sshd",pid=734,fd=4))
LISTEN          0           100     127.0.0.1:25        0.0.0.0:*           users:(("master",pid=849,fd=16))
ESTAB           0           0       10.0.0.11:22        10.0.0.10:49483     users:(("sshd",pid=1281,fd=5),("sshd",pid=1269,fd=5))
LISTEN          0           128     [::]:80             [::]:*              users:(("nginx",pid=767,fd=9),("nginx",pid=766,fd=9))
LISTEN          0           128     [::]:22             [::]:*              users:(("sshd",pid=734,fd=6))
LISTEN          0           100     [::1]:25            [::]:*              users:(("master",pid=849,fd=17))

# 统计 TCP 各个状态的总数
[root@centos8 ~]#ss -antp|awk 'NR>=2{status[$1]++}END{for(i in status){print i"\t"status[i]}}'
LISTEN	6
ESTAB	1
[root@centos8 ~]#

  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值