TCP三次握手和四次挥手 wireshark抓包


前言

本文以一次 wireshark 抓包经历,理解TCP的三次握手和四次挥手

0.1 TCP报文结构

在这里插入图片描述
(1)序号(sequence number):Seq序号,占32位(bit),4个字节(byte), 用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。

(2)确认号(acknowledgement number):Ack序号,4个字节(byte), 只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。

(3)标志位(Flags):8位(bit), 1个字节(byte), 常见6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下:

  • URG:紧急指针(urgent pointer)有效。
  • ACK:确认序号有效。
  • PSH:接收方应该尽快将这个报文交给应用层。
  • RST:重置连接。
  • SYN:发起一个新连接。
  • FIN:释放一个连接。

0.2 TCP双向可靠性

  • 可靠性: 发出去的消息(SYN)一定要有确认(ACK)
  • 双向协议: 通信的双方都可以主动发生消息给对方

  如图.1所示:A 向 B 发送消息 SYN 基于TCP传输的可靠性,B会回复一个确认消息 ACK,作为收到消息SYN的回复;而对于B 也同样对 A 做一轮收发, 此时 AB双向收发能力正常。
在这里插入图片描述
  可以看到 AB 通信中间两步都是 B>>>A 那么这两个消息包可以合并成一个如图.2
在这里插入图片描述
很清晰可以看出来,这就是TCP建立连接时的三次握手

一、三次握手

在这里插入图片描述

注意: 图中的x为客户端初始化序列号;图中的y为服务端初始化序列号

1.1 三次握手释义

  握手之前主动打开连接的客户端结束CLOSED阶段,被动打开的服务器端也结束CLOSED阶段,并进入LISTEN阶段。随后开始“三次握手”:

(1)首先客户端向服务器端发送一段TCP报文,详细:
   - 标记位 SYN=1,表示“请求建立新连接”;
   - 序号为Seq=X(sequence=X(随机int) 一般为1);
   - 随后客户端进入SYN-SENT阶段。
(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,详细:
   - 标志位为SYN=1,ACK=1,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”,序号为Seq=y;
   - 确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;
   - 随后服务器端进入SYN-RCVD阶段。
(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。详细:
   - 标志位为ACK=1,表示“确认收到服务器端同意连接的信号”;
   - 序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
   - 确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;
   - 随后客户端进入ESTABLISHED阶段。
  服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。
  在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。

  此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。

1.2 丢包处理

  1. client第一个syn包丢失,没有收到server的ack,则client进行持续重传syn包。总尝试时间为75秒。参与文献《TCP/IP详解 卷1:协议》p178

  2. server收到了client的syn,并发出了syn+ack包,syn+ack包丢失。
    client方面,因为没收server的。将执行情况(1);
    server方面,超时时间内没有收到client的ack包(或者数据包),会持续发送syn+ack包;

  3. 当Client端收到Server的SYN+ACK应答后,其状态变为ESTABLISHED,并发送ACK包给Server;
    如果此时ACK在网络中丢失,那么Server端该TCP连接的状态为SYN_RECV,并且依次等待3秒、6秒、12秒后重新发送SYN+ACK包,以便Client重新发送ACK包,以便Client重新发送ACK包;
    Server重发SYN+ACK包的次数,可以通过设置/proc/sys/net/ipv4/tcp_synack_retries修改,默认值为5;
    如果重发指定次数后,仍然未收到ACK应答,那么一段时间后,Server自动关闭这个连接;
    第三次握手最终loss后,Client 最终跟 Server的通信有两种说法,抓包做个解析:
      1.Client认为这个连接已经建立,如果Client端向Server写数据,Server端将以RST包响应,方能感知到Server的错误。(存疑)
      2.如果此时client向server发送数据包,server能正常接收数据。并认为连接已正常。

应用层编写socket代码时,三次握手发生在client的connect,所以为了避免长时间(75秒)无响应连接,应设置为非阻塞socket,同时用select检测设置合适的超时时间。

二、四次挥手

在这里插入图片描述

2.1 四次挥手释义

挥手之前主动释放连接的客户端结束ESTABLISHED阶段。随后开始“四次挥手”:

(1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:

标记位为FIN,表示“请求释放连接“;
序号为Seq=U;
随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。
注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。

(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:

标记位为ACK,表示“接收到客户端发送的释放连接的请求”;
序号为Seq=V;
确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;
随后服务器端开始准备释放服务器端到客户端方向上的连接。
客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段

前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了

(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:

标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。
序号为Seq=W;
确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。
随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。

(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:

标记位为ACK,表示“接收到服务器准备好释放连接的信号”。
序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。
确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。
随后客户端开始在TIME-WAIT阶段等待2MSL

为什么要客户端要等待2MSL呢?见后文。

服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。

客户端等待完2MSL之后,结束TIME-WAIT阶段,进入CLOSED阶段,由此完成“四次挥手”。

后“两次挥手”既让客户端知道了服务器端准备好释放连接了,也让服务器端知道了客户端了解了自己准备好释放连接了。于是,可以确认关闭服务器端到客户端方向上的连接了,由此完成“四次挥手”。

与“三次挥手”一样,在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性,一旦出现某一方发出的TCP报文丢失,便无法继续"挥手",以此确保了"四次挥手"的顺利完成。

2.2 丢包处理

(1)、client发的FIN包丢了,对于client,因为没收对应的ACK包,应当一直重传(像普通包一样),直至到达上限次数,直接关闭连接;对于server,它应该无任何感知;

(2)、server回client的ACK包丢了,对于client,将执行(1),对于server将像丢普通的ack一样,再次收到FIN后,再发一个ACK包;

(3)、如果client收到ACK后,server直接跑路。client将永远停留在这个状态(半打开状态,就像client关闭了输出一样)。linux有tcp_fin_timeout这个参数,设置一个超时时间 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看,默认60s,可否修改看linux具体版本; windows 注册表有TcpTimedWaitDelay,win10默认值30s;

(4)、server发的FIN包丢了,对于server,像丢普通的包一样,重传。若此时client早已跑路且与其他人建立的连接,client应会不认识这个FIN包,直接回个RST包给server。

如若client没跑路,且没收到server的FIN包,如(3)描述;

(5)、client回复ACK后,按道理来说,可以跑路了,但防止回复的ACK包丢失(丢失后,server因为没收FIN的ACK,所以会再发一个FIN),将等待2MSL(最大报文存活时间)(RFC793定义了MSL为2分钟,Linux设置成了30s)为什么要这有TIME_WAIT?为什么不直接给转成CLOSED状态呢?主要有两个原因:1)TIME_WAIT确保有足够的时间让对端收到了ACK,如果被动关闭的那方没有收到Ack,就会触发被动端重发Fin,一来一去正好2个MSL,2)有足够的时间让这个连接不会跟后面的连接混在一起(你要知道,有些自做主张的路由器会缓存IP数据包,如果连接被重用了,那么这些延迟收到的包就有可能会跟新连接混在一起),这期间如若再收到server的FIN,则再回复ACK;

题外话

可以看到时三次握手第二步时,server收到client的SYN包并回复SYN+ACK包后,server会把这条连接放入“半连接队列”。这边有个问题,假设这个client是恶意的,client只发SYN包,收到SYN+ACK后不回复,那在server方面,会一直存有这条“半连接”,client数量达到一定程序,serve就炸了。这就是所谓的SYN FLOOD攻击;那怎么防止这种情况呢?有下面几种方法:(来自RFC 4987)

过滤
增加积压
减少SYN-RECEIVED定时
复用古老的半开通TCP
SYN缓存
SYN Cookie
混合方法
防火墙和代理

总结

MSL

MSL是报文在网络中最长生存时间,这是一个工程值(经验值),不同的系统中可能不同。场景:1. A发出ACK后,等待一段时间T,确保如果B重传FIN自己一定能收到分析:1. ACK从A到B最多经过1MSL,超过这个时间B会重发FIN2. B重发的FIN最多经过1MSL到达A结论:如果B重发了FIN,且网络没有故障(重发的FIN被丢弃或错误转发),那么A一定能在2MSL之内收到该FIN,因此A只需要等待2MSL。

通信所要解决的首要问题就是,保持通信双方的信息对称,使通信双方处于同步状态。先来一个例子:罗密欧大学期间写信给中学同学朱丽叶,信的内容如下:小叶子,我喜欢你!这封信发出之后,罗密欧无法知道朱丽叶能否收到,只有收到小叶子的回信,才能知道自己的信已经到达对方。三天之后,小叶子回信了,信的内容如下:小欧,来信已阅,我也喜欢你…此时,小叶子眼中双方的状态是:互相爱慕!如果小欧收到回信,小欧眼中双方的状态也是:互相爱慕!如果小欧没有收到回信,小欧眼中双方的状态是:单相思!小叶子为了杜绝小欧模棱两可的状态,使他与自己达成“互相爱慕”的共识,需要做以下工作:1)先耐心地等小欧的第三封信2)如果若干天没有收到回信,需要把自己的第二封信再次发出如果收到了小欧的回信,那么双方的状态终于同步了:“互相爱慕”!即使2)发生了,N天之后也可以达成同步状态。之后,双方可以甜言蜜语地谈恋爱了。TCP四次挥手也遵循相似的套路。主动断开的一侧为A,被动断开的一侧为B。第一个消息:A发FIN第二个消息:B回复ACK第三个消息:B发出FIN此时此刻:B单方面认为自己与A达成了共识,即双方都同意关闭连接。此时,B能释放这个TCP连接占用的内存资源吗?不能,B一定要确保A收到自己的ACK、FIN。所以B需要静静地等待A的第四个消息的到来:第四个消息:A发出ACK,用于确认收到B的FIN当B接收到此消息,即认为双方达成了同步:双方都知道连接可以释放了,此时B可以安全地释放此TCP连接所占用的内存资源、端口号。所以被动关闭的B无需任何wait time,直接释放资源。但,A并不知道B是否接到自己的ACK,A是这么想的:1)如果B没有收到自己的ACK,会超时重传FiN那么A再次接到重传的FIN,会再次发送ACK2)如果B收到自己的ACK,也不会再发任何消息,包括ACK无论是1还是2,A都需要等待,要取这两种情况等待时间的最大值,以应对最坏的情况发生,这个最坏情况是:去向ACK消息最大存活时间(MSL) + 来向FIN消息的最大存活时间(MSL)。这恰恰就是2MSL( Maximum Segment Life)。等待2MSL时间,A就可以放心地释放TCP占用的资源、端口号,此时可以使用该端口号连接任何服务器。为何一定要等2MSL?如果不等,释放的端口可能会重连刚断开的服务器端口,这样依然存活在网络里的老的TCP报文可能与新TCP连接报文冲突,造成数据冲突,为避免此种情况,需要耐心等待网络老的TCP连接的活跃报文全部死翘翘,2MSL时间可以满足这个需求(尽管非常保守)!

link1
link2

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值