TCP所面临的问题及解决方案
问题:
下层IP的交付为面向无连接和不可靠的协议,而TCP要向应用层提供面向连接的可靠的协议。
同时TCP要实现流量控制和拥塞控制
什么是面向连接:
需要确定传输的对方处于等待接受和发送的状态上,同时也需要维护传输数据的关系,例如数据流的顺序。其实也就是要确保对方能准确的收到原模原样的数据。
以实际例子类比,面向连接更像是打电话,而无连接则更像是发短信。
如何解决:
如何解决面向连接
解决确定传输对方的状态问题:在包内加上特殊的标记,用来专门创建断开和维护一个连接:syn,ack,fin,rst
解决数据顺序和重复问题:维护seq序列号字段
如何解决可靠性:
引入数据传输确认机制:维护确认字段Acknowledgement和ack状态,也就是停止等待协议。
但停止等待协议导致了带宽利用率不高的问题
如何解决带宽利用率不高:
引入了窗口确认机制和滑动窗口:可以在发送多个包之后一起确认
如何实现拥塞控制
引入了拥塞判断机制和拥塞窗口
TCP状态机——三次握手四次挥手
三次握手
为什么要三次?
从TCP要实现面向连接的目的来看,目的主要有两个:
1.确认对端在线,即一端请求另一端能立刻给出响应。也可以说是确认双方同时都有对数据接受和发送的能力。
2.面向连接的传输需要保证包的次序,因此两端都需要确认对方的起始序列号。从而对接受来的数据排序。
为什么两次不行:两次握手至少让一端无法确认对方是否了解自己的起始序列号。
eNSP模拟
在ensp中模拟如下的环境,并进行抓包分析。
可以很明显的看到三次握手
四次挥手
为什么是四次挥手,而不是两次?
如果对于单工通信来说,A发送,B接受,A向B发送结束的通知,B向A回复收到。这样拆除连接是完全可行的。
但对于全双工通信来说,一个方向的传输关闭之后,另一个方向的传输也有可能还在继续。两个方向需要分别关闭才可以。
类比一下(四次挥手):
A:我这边没数据传啦!
B:好的。
(可能B向A继续传了一些没传完的数据)
B:我这边的数据也传完了!
A:了解!
和上面握手的时候同样的环境进行抓包,很容易的捕捉到了四次挥手的过程。
如果A要切断自己发送的连接的时候,B也没有数据要发了,能合并成三次吗?
答:可以
可以看到再下图中挥手次数只有三次。其中中间的两次出现了合并的现象。
这是因为。TCP有Delay ACK的机制
阅读参考:https://blog.csdn.net/dog250/article/details/52664508
类比一下(三次挥手):
A:我这边没数据传啦!
B:好的。我这边的数据也传完了!
A:了解!
在中间有数据要传的情况下也能合并吗?
答:可以
类比一下(三次挥手带数据):
A:我这边没数据传啦!
B:好的。我这边的数据也只剩这最后一点了!(data)
A:了解,那我就关掉连接了!
各种状态以及可能引起的问题分析
搬运自:https://zorrozou.github.io/docs/tcp/wavehand/TCP_Wavehand.html
为什么要有TimeWait状态
要确保最后一个ACK送达。
如果最后一个ACK未送达。那么服务器会重发FIN,这个时候如果客户端已经关闭了连接。那么就会导致服务器一直超时重发无应答。
所以客户端维护一段时间的TimeWait状态。RFC规定要等待两倍的MSL。RFC规定要等待两倍的MSL(一个数据分片(报文)在网络中能够生存的最长时间),这个时间也被RFC规定为2分钟。但是实际Linux的实现并不用等这么久,Linux的实现中,TCP_TIME_WAIT等待时间被固定设置为60秒。
FIN_WAIT1驻留问题
主动关闭方发出fin之后会进入到FIN_WAIT1状态,这个状态会持续到对方回复ack为止。一般情况下,这个状态都不会持续太久,大概在一个rtt左右。但是在某些异常情况下,它可能会长时间存在于服务器上。如果出现这种情况,主要原因应该是对端行为异常,要么对端出现bug,要么可能是故意发起的攻击行为。在这种情况下,对应用程序进行重启一般不会缓解现象,因为此时应用程序已经认为TCP连接被关闭,这种TCP连接已经是单纯的内核行为,并不关联任何应用程序。我们把这种应用程序称为tcp orphans(tcp孤儿)。内核维护这种TCP状态需要消耗内存,所以如果这种连接多了会产生针对内存的DoS(拒绝服务)攻击。
FIN_WAIT2驻留问题
参考:https://blog.csdn.net/dog250/article/details/81256550
CLOSE_WAIT
正常情况下,被动关闭方接受到对端发来的fin之后,会立即发送ack,并进入CLOSE_WAIT状态。直到确认本端也没数据要发了,给对端发送fin之前,会一直维持这个状态。
大量遗留的CLOSE_WAIT状态是在业务上经常出现的问题,尤其是对于很多自己研发的软件来说尤其是如此。我们可以想像,什么情况下会有大量CLOSE_WAIT状态遗留?那就是tcp通知应用层说对方已经关闭了,你还有数据要发么?而应用层怎么样?压根没反应。所以,一般这种异常都是被动关闭方没有正确处理来自socket的事件而产生的问题。遇到这种问题是,请检查代码bug。
LAST_LCK
当被动关闭方发出要跟对方关闭连接的fin之后,本端会进入LAST_ACK状态,并持续到对端发来ack为止。那么如果对方没有发来ack,本端当重传次数超限后,会关闭连接。
一些搬运问题
搬运来源:https://zhuanlan.zhihu.com/p/86426969
1.ISN(Initial Sequence Number)是固定的吗?
三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
2.三次握手过程中可以携带数据吗?
其实第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手不可以携带数据。
假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。
也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。
3.SYN攻击是什么?
服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。
检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。
1 | netstat -n -p TCP | grep SYN_RECV |
常见的防御 SYN 攻击的方法有如下几种:
- 缩短超时(SYN Timeout)时间
- 增加最大半连接数
- 过滤网关防护
- SYN cookies技术