声明:文章部分内容源自网络摘抄,本文仅作备忘笔记使用
注意:
(A)不要将确认序号Ack与标志位中的ACK搞混了。
(B)确认方Ack=发起方Req+1,两端配对。
连接建立时,各端分配一块缓冲区用来存储接收的数据,并将缓冲区的尺寸发送给另一端接收方发送的确认信息中包含了自己剩余的缓冲区尺寸,剩余缓冲区空间的数量叫做窗口
粗的实线箭头表示正常的客户端状态变迁,粗的虚线箭头表示正常的服务器状态变迁。
服务端收到建立连接的SYN没有收到ACK包的时候处在SYN_RECV状态。有两个相关系统配置:
1,net.ipv4.tcp_synack_retries :INTEGER
决定服务端在放弃连接之前所送出的 SYN+ACK 数目。不应该大于255,默认值是5,对应于180秒左右时间。
通常我们不对这个值进行修改,因为我们希望TCP连接不要因为偶尔的丢包而无法建立。
2,net.ipv4.tcp_syncookies
一般服务器都会设置net.ipv4.tcp_syncookies=1来防止SYN Flood攻击。
被动关闭的服务端收到FIN后,但未发出ACK的TCP状态是CLOSE_WAIT。出现这种状况一般都是由于服务端代码的问题,如果你的服务器上出现大量CLOSE_WAIT,应该要考虑检查代码。
TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒。
TIME_WAIT状态下的socket不能被回收使用. 具体现象是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的socket, 甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket,停止服务。
和TIME_WAIT状态有关的系统参数有一般有3个,本厂设置如下:
net.ipv4.tcp_fin_timeout,默认60s,减小fin_timeout,减少TIME_WAIT连接数量。
net.ipv4.tcp_tw_reuse = 1表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
启动 —> SYN_SENT —> ESTABLISHED
ESTABLISHED —> FIN_WAIT1 —> FIN_WAIT2 —> CLOSING —> TIME_WAIT —> CLOSED
LISTEN —> SYN_RECV —> ESTABLISHED
ESTABLISHED —> CLOSE_WAIT —> LAST_ACK —> CLOSED
Seq(sequence number)顺序号码,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
Ack(acknowledge number)确认号码,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。
共6个,即URG、ACK、PSH、RST、SYN、FIN
(A)URG(urgent):紧急指针(urgent pointer)有效。
(B)ACK(acknowledgement):确认序号有效。
(C)PSH(push):接收方应该尽快将这个报文交给应用层。
(D)RST(reset):重置连接。
(E)SYN(synchronous):发起一个新连接。
(F)FIN:释放一个连接。
三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。
实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。
HTTPS在这个过程进行数字证书的加密以及密钥的生成
刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。
TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文
客户端发送syn包
进入SYN_SENT状态
等待服务器确认(第二次握手)
首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。
服务端就知道客户端发送正常,服务端接收正常
服务器收到syn包
确认客户的SYN
发送自己的SYN包
进去SYN_RECV状态
ACK=1,SYN=1,确认号是ack=x+1,初始化序列号 seq=y。这个报文也不能携带数据,但是同样要消耗一个序号。
客户端知道其服务器端收发正常,客户端收发正常,但是服务端不知道自己发送正常与否,客户端接收与否
客户端收到服务端的SYN+ACK包
向服务器发送确认包ACK
服务端和客户端进入ESTABLISHEN状态
确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1
ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
服务端与客户端都知道自己和对方收发正常
当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。
但未必被动方所有的数据都完整的发送给了主动方,所以被动方不会马上关闭SOCKET
它可能还需要发送一些数据给主动方后,再发送FIN报文给主动方,告诉主动方同意关闭连接,所以这里的ACK报文和FIN报文多数情况下都是分开发送的。
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。
这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。
收到一个 FIN只意味着这一方向上没有数据流动
一个TCP连接在收到一个FIN后仍能发送数据。
客户端发送连接释放报文–一个FIN=1
停止发送数据
释放数据报文首部
客户端进入FIN_WAIT_1状态
FIN报文段即使不携带数据,也要消耗一个序号。
服务端收到FIN
给客户端发送ACK=1,ack=u+1,seq=v
进入CLOAE_WAIT状态
TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
服务端发送FIN
关闭服务端到客户端的数据传送
服务端进入LAST_ACK状态
客户端收到服务端请求进入FIN_WAIT_2状态,等待服务器发送连接释放报文(此时客户端还需要接受服务器发送的最后的数据)
客户端收到FIN
客户端进去TIME_WAIE状态
发送ACK给服务端ACK=1,ack=w+1
服务端进入CLOASED状态
注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
服务器只要收到了客户端发出的确认,立即进入CLOSED状态。
同样,撤销TCB后,就结束了这次的TCP连接。
同时发起主动关闭的情况,具体流程如下图:
TCP全双工需要两方都关闭通信
FIN和ACK分开发送
发送FIN报文仅表示不在发送数据但还能接收数据
TCP的半关闭(half-close)造成的。
半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
第二次握手的时候服务端将ACK+FIN一同发送给客户端
第二次挥手和第三次挥手将ACK,FIN分开两步发送
ACK报文是用来应答的,SYN报文是用来同步的
需要三次握手才能确认双方的接收与发送能力是否正常
允许双方就初始序列号进行协商。初始化Sequence Number 的初始值。
通信的双方要互相通知对方自己的初始化的Sequence Number(缩写为ISN:Inital Sequence Number),所以叫SYN,Synchronize Sequence Numbers。这个号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输的问题而乱序(TCP会用这个序号来拼接数据)。
防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
第二次握手并未完全建立链接,服务器会把处于完成第二次握手状态的请求放在一个队列中。
服务器会把完成三次握手的请求放在全连接队列中,如果队列满了可能会出现丢包现象。
服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。
注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…
服务器端的资源分配是在二次握手时分配的
客户端的资源是在完成三次握手时分配的
服务器容易受到SYN洪泛攻击。
SYN攻击就是客户端在短时间内伪造大量不存在的IP地址,并向服务端不断地发送SYN包,服务端则回复确认包,并等待客户端确认,由于源地址不存在,因此服务端需要不断重发直至超时,这些伪造的SYN包将长时间占用半连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。
SYN 攻击是一种典型的 DoS/DDoS 攻击。
检测 SYN 攻击:服务器有大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。
在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。
netstat -n -p TCP | grep SYN_RECV
常见的防御 SYN 攻击的方法有如下几种:
缩短超时(SYN Timeout)时间
服务器端重试(再次发送SYN+ACK给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为SYN Timeout,一般来说这个时间是分钟的数量级(大约为30秒-2分钟)。
增加最大半连接数
过滤网关防护
SYN cookies技术
SYN Cookie是对TCP服务器端的三次握手协议作一些修改,专门用来防范SYN Flood攻击的一种手段。
它的原理是,在TCP服务器收到TCP SYN包并返回TCP SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。在收到TCP ACK包时,TCP服务器在根据那个cookie值检查这个TCP ACK包的合法性。如果合法,再分配专门的数据区进行处理未来的TCP连接。
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。
ISN随时间而变化,因此每个连接都将具有不同的ISN。
ISN可以看作是一个32比特的计数器,每4ms加1 。
这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。
三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。
如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
第三次握手可以携带数据,第一二次不可以
第一次握手不可以携带数据,会让服务器更加容易被攻击
第三次已经建立好连接所以可以携带数据
TCP连接的状态为SYN_RECV
SYN-ACK重传
重传一定次数后仍未收到客户端的ACK应答,一段时间后服务端自动关闭链接
客户端在接收到SYN+ACK包,它的TCP连接状态就为ESTABLISHED(已连接),表示该连接已经建立。
第三次握手中的ACK包丢失的情况下,客户端向服务端发送数据,服务端将以RST包(reset重置)响应,才能感知到服务端的错误。
TCP还设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去。
服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
TIME_WAIT状态也称为2MSL等待状态。
MSL(Maximum Segment Lifetime)最长报文段寿命
MSL是任何报文段被丢弃前在网络内的最长时间。
这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
TIME-WAIT状态停留时间为2倍MSL
在此期间,定义这个连接的插口(客户端的IP地址和端口号,服务端的IP地址和端口号)不能被使用。
客户端发送的最后一个ACK报文段能够到达服务端,最后客户端和服务端都能正常关闭
因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。
如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。
特殊情况:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。