三次握手 && 四次挥手
Posted 看,未来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了三次握手 && 四次挥手相关的知识,希望对你有一定的参考价值。
愿打开这篇文章能帮到你。
文章目录
TCP连接的建立与终止
TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。
三次握手
为了建立一条TCP连接:
1) 请求端(通常称为客户)发送一个 SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN)。这个SYN段为报文段1。
2) 服务器发回包含服务器的初始序号的 SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
3) 客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。
这三个报文段完成连接的建立。这个过程也称为三次握手( three-way handshake)
1)序号(sequence number):seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
2)确认号(acknowledgement number):ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。
3)标志位(Flags):共6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下:
URG:紧急指针(urgent pointer)有效。
ACK:确认序号有效。(这个)
PSH:接收方应该尽快将这个报文交给应用层。
RST:重置连接。
SYN:发起一个新连接。(这个)
FIN:释放一个连接。(这个)
为什么需要扰乱初始化序列号?
一台PC它的ISN有自己的增长规律,如果它在ASA里面,有些攻击者他会不断的去连接PC,PC这时候就会不断的回复SYN,ACK。攻击者就能得到不同节点时间段的ISN,然后用这些不同时间段的ISN来计算出PC的ISN增长规律,通过判断ISN增长规律,攻击者就有可能判断出你的操作系统。
一台PC和某个主机已经建立了TCP连接,攻击者想做一个会话劫持,它需要伪装PC的IP地址、端口号、协议号。这些伪装了还是不行,还得知道你的序列号在什么范围。所以攻击者这时候也会通过多次探测来猜测ISN的增长规律,当得到ISN的增长规律会就会把PC干掉,攻击者自己连接到相应的主机。
所以ASA就要防止这些攻击者通过多次试探来获取PC的ISN增长规律,因为一旦PC的ISN增长规律被获取后,攻击者就可以对我的操作系统进行判断,而且还有可能造成更严重的会话劫持。所以ASA会对初始化序列号进行扰乱。
当PC在T1时刻发送SYN的时候,经过ASA,ASA会把T1时刻的ISN随机加一个数发走,在T2时刻发送SYN的时候,经过ASA,ASA又会把T2时刻的ISN随机加一个会减去一个数发走。由于ASA总是会在ISN的数上随机加上一个或者减去一个数,这样的话,攻击者在看你的ISN的时候就会觉得没有规律可寻。
syn攻击
SYN攻击是指发送方不断发送连接请求(第一个TCP包,SYN),待服务端发送回应(第2个TCP包,SYN+ACK),发送方收到服务端的回应后,却不再发送第3个TCP包。这样会造成服务端存在大量的打开的TCP连接(处于open状态,但并不是established状态),这样会消耗服务端大量的系统资源(Socket内核资源)。这就是SYN攻击。
服务端在收到客户端发来的SYN报文段后,会回复SYN+ACK报文段,此时这条连接已处于半打开状态,会将该半打开状态的连接放入一个队列(listen监听队列)。
需要注意的是,该回复的报文段(SYN+ACK)同样存在超时重传,如果一定时间内未收到客户端发来的ACK报文段,服务端则会重传SYN+ACK报文段。达到最大重传次数后,将该条半打开连接从队列中移除。
SYN攻击就是利用这个特点,不断发起SYN报文段,但却不回复对服务端SYN+ACK报文段的确认。造成服务端维护较多的半打开连接,消耗系统资源。而正常的连接请求,却因为资源不足而无法响应或响应缓慢。
发送方的实现方式有两种(使用WinPCap可实现):
发送使用真实的IP地址发关TCP连接请求,但在收到服务端回复后,不再发送第3个TCP包。
发送方再发送给TCP服务端的连接请求中,使用的是虚假的IP地址,这样服务端回复给了虚假的IP地址。
这两种方式,对服务端来说,TCP服务端在发送了第2个TCP包后,都会一直得不到应答,直到超时。服务端会维持大量的这种状态的SOCKET。
预防SYN攻击
题外话啊扯一点。
1、过滤网关防护
2、加固TCP/IP协议栈进行防范
最后一招,也是最重要的一招:请专业的人来,咱做开发的不能不了解这些,但是咱毕竟是开发,术业有专攻。
四次挥手
建立一个连接需要三次握手,而终止一个连接要经过 4次握手。这由TCP的半关闭(half-close)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。
这原则就是当一方完成它的数据发送任务后就能发送一个 FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端已经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果。
收到一个FIN只意味着在这一方向上没有数据流动。一个 TCP连接在收到一个 FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的 TCP应用程序这样做。
TCP的半关闭
TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓的半关闭。
为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据传送,因此发送一个文件结束( FIN)给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束(FIN)。
如果应用程序不调用 close而调用shutdown,且第2个参数值为1,则插口的API支持半关闭。然而,大多数的应用程序通过调用close终止两个方向的连接。
图显示了一个半关闭的典型例子。
普通问题解答
TCP状态变迁图
2MSL等待状态
TIME_WAIT状态也称为2MSL等待状态。每个具体 TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。我们知道这个时间是有限的,因为 TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
RFC 793 [Postel 1981c] 指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟,或2分钟。
在实际应用中,对 IP数据报TTL的限制是基于跳数,而不是定时器。
对一个具体实现所给定的 MSL值,处理的原则是:当 TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在 TIME_WAIT状态停留的时间为 2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的 FIN)。
这种2MSL等待的另一个结果是这个 TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的 IP地址和端口号)不能再被使用。这个连接只能在 2MSL结束后才能再被使用。
遗憾的是,大多数 TCP实现(如伯克利版)强加了更为严格的限制。在 2MSL等待期间,插口中使用的本地端口在默认情况下不能再被使用。
某些实现和API提供了一种避开这个限制的方法。使用插口API时,可说明其中的SO_REUSEADDR选项。它将让调用者对处于2MSL等待的本地端口进行赋值,但我们将看到TCP原则上仍将避免使用仍处于2MSL连接中的端口。
在连接处于2MSL等待时,任何迟到的报文段将被丢弃。因为处于 2MSL等待的、由该插口对(socket pair)定义的连接在这段时间内不能被再用,因此当要建立一个有效的连接时,来自该连接的一个较早替身( incarnation)的迟到报文段作为新连接的一部分不可能不被曲解(一个连接由一个插口对来定义。一个连接的新的实例( instance)称为该连接的替身)。我们说图18-13中客户执行主动关闭并进入 TIME_WAIT是正常的。服务器通常执行被动关闭,不会进入TIME_WAIT状态。这暗示如果我们终止一个客户程序,并立即重新启动这个客户程序,则这个新客户程序将不能重用相同的本地端口。这不会带来什么问题,因为客户使用本地端口,而并不关心这个端口号是什么。
然而,对于服务器,情况就有所不同,因为服务器使用熟知端口。如果我们终止一个已经建立连接的服务器程序,并试图立即重新启动这个服务器程序,服务器程序将不能把它的这个熟知端口赋值给它的端点,因为那个端口是处于 2MSL连接的一部分。在重新启动服务器程序前,它需要在1 ~ 4分钟。
端口被占用
这时会引发一个问题:
1.如果发起断开连接的这一方是客户端,通常客户端不需要指定本地端口号,在断开连接后,重启程序,会重新随机选择一个本地端口再去连接服务器。客户端程序正常。
2.如果发起断开连接的这一方是服务端,通常服务端使用熟知的(固定的)监听端口号,在断开连接后,重启程序,服务端监听本地端口会失败,会提示端口被使用。这是因为该端口在上次主动断开连接后,还处理2MSL的time_wait状态。解决方法是设置监听端口的SO_REUSEADDR选项。注意:即使服务端设置了SO_REUSEADDR选项,使用服务端可以重用处理time-wait状态的端口,但仍不允许存在相同的连接。什么意思呢?
例如,服务器S 3.3.3.3 监听 3000端口,客户端 4.4.4.4,端口4000连接到S。这时,S主动断开连接,然后,重启了服务(服务端SOCKET设置了SO_REUSEADDR选项)。这时,如果客户端仍使用4000去建立连接,仍会提示连接失败
解决CLOSE_WAIT、TIME_WAIT连接状态过多正确姿势
像这种啊,你现在问我我肯定是不会背的,不过让我抽出手来拿几个项目练练,嘿嘿,就OK了。
先来看下一台生产环境中的各种tcp状态的连接数:
netstat -n| awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
然后我们来分析下占比比较高的几种状态产生的原因和解决方法:
-
CLOSE_WAIT
咳咳,这就,很奇葩啊,是你的用户量巨大吗?不然就是服务器出问题了。如部分情况下不会执行socket的close方法,解决方法是查程序。 -
TIME_WAIT
time_wait是一个需要特别注意的状态,他本身是一个正常的状态,只在主动断开那方出现,每次tcp主动断开都会有这个状态的,维持这个状态的时间是2个msl周期(2分钟),设计这个状态的目的是为了防止我发了ack包对方没有收到可以重发。那如何解决出现大量的time_wait连接呢?千万不要把tcp_tw_recycle改成1,正确的姿势应该是降低msl周期,也就是tcp_fin_timeout值,同时增加time_wait的队列,防止满了。
为什么不能把tcp_tw_recycle改成1?
改成1的后果是会导致一个路由器后面用户有人能连你服务器有人telnet不通。
原因是tcp_tw_recycle改成1后会启用tcp的tcp_timestamps功能,这个功能简单说就是所有的通信包是时间戳递增的,如果收到了同一个IP下时间戳小的包那就说明是个老数据包,就会丢弃这个包。
而一个路由器下每台电脑的时间戳不是完全一致的,有的电脑的时间戳会小,导致这些电脑发出的通信包被直接丢弃了。
正确的tcp优化姿势
(回头做毕设的时候试一下)
修改/etc/sysctl.conf 文件,内容如下:
net.nf_conntrack_max = 1000000
#发送keepalive的心跳
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_tw_reuse = 0
#不能改成1 否则NAT后面的客户端可能连接不上
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_fin_timeout = 30
#timewait队列最大数量
net.ipv4.tcp_max_tw_buckets = 262144
#建立连接握手过程的ack重发次数从5到3
net.ipv4.tcp_synack_retries = 3
#缓存syn请求数量 解决messsage日志中syn溢出日志
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_syncookies = 1
#linux收包缓存
net.core.rmem_default=262144
net.core.wmem_default=262144
net.core.rmem_max=4194304
net.core.wmem_max=4194304
net.core.somaxconn=1024
vm.overcommit_memory = 1
vm.max_map_count=262144
为什么握手只要三次,挥手要四次?
TCP释放连接时之所以需要“四次挥手”,是因为FIN释放连接报文与ACK确认接收报文是分别由第二次和第三次"握手"传输的。
为何建立连接时一起传输,释放连接时却要分开传输?
建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。
在TCP里可以让数个Application共享一个Port么?
在TCP里可以让数个Application共享一个Port么?
专治难题
以下建立在非恶意攻击的前提下
第一次握手失败
出师未捷身先死哈。。。
当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT 状态。
在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发超时重传机制。
不同版本的操作系统可能超时时间不同,有的 1 秒的,也有 3 秒的,这个超时时间是写死在内核里的,如果想要更改则需要重新编译内核,比较麻烦。
而且其重传间隔2倍方递增。
当客户端在 1 秒后没收到服务端的 SYN-ACK 报文后,客户端就会重发 SYN 报文,那到底重发几次呢?
在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries内核参数控制,这个参数是可以自定义的,默认值一般是 5。
通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。没错,每次超时的时间是上一次的 2 倍。
当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。
所以,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。
第二次握手失败
注意:对于报文段2(服务端的SYN+ACK),同样会存在超时重传。
如果第二次握手丢了,那就好玩了。
因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文,所以,如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文。
如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文。
在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries内核参数决定,默认值是 5。
因此,当第二次握手丢失了,客户端和服务端都会重传。
(预知后事如何,了解一下TCP报文丢弃机制)
第三次握手失败
此时客户端状态进入到 ESTABLISH 状态。
因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。
注意,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。
第一次挥手失败
如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。
当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,直接进入到 close 状态。
第二次挥手失败
如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。
这里提一下,当客户端收到第二次挥手,也就是收到服务端发送的 ACK 报文后,客户端就会处于 FIN_WAIT2 状态,在这个状态需要等服务端发送第三次挥手,也就是服务端的 FIN 报文。
对于 close 函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。
这意味着对于调用 close 关闭的连接,如果在 60 秒后还没有收到 FIN 报文,客户端(主动关闭方)的连接就会直接关闭。
第三次挥手失败
内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。
服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。
如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。
第四次挥手失败
在 Linux 系统,TIME_WAIT 状态会持续 60 秒后才会进入关闭状态。
然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。
如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。
这个悲伤逆流成河啊。三次握手,四次挥手,哈哈。。。
还记得那天晚上,我们躺在草坪上看星星,你问我的那个问题吗?我给你的答复,很快我就要达成了。你的答复是啥来着?我忘了,忘咯。
以上是关于三次握手 && 四次挥手的主要内容,如果未能解决你的问题,请参考以下文章
TCP为什么是三次握手,为什么不是两次或者四次 && TCP四次挥手