再探 TCP/IP
@[toc]
概念性的东西就不再赘述了,看着也累:温故Linux后端编程(四):膜拜《TCP/IP 卷一》
TCP连接的建立与终止
TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。
三次握手
为了建立一条TCP连接:
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:释放一个连接。(这个)
其中,双方的序号(Seq)的初始值(ISN)都是根据自身主机的时钟随机生成的,这也可以在一定程度上防止通信数据被伪造。
无论是建立连接还是终止连接,都会消耗一个序号,这从SYN包中seq值及对SYN应答包中的seq值、FIN包的seq值及对FIN应答包中的seq值可以看出来。
建立连接超时
注意:对于报文段2(服务端的SYN+ACK),同样会存在超时重传。
服务端在发送该报文段(报文段2)时,会启动超时重传定时器,定时器一到,就会重传。
对于建立连接时,SYN包丢失,==其重传间隔2倍方递增==。当重传达到最大次数后,即发生丢包。默认是5次,间隔为 1s+2s+4s+8s+16s+32s=63s。Windows(win7)上的实现,是只重传两次(即总共3次),间隔为3s,6s。即在01秒发第1包,则04秒发重传包,10秒重传包。(==SYN重传时间间隔,注意该时间间隔是OS实现时自身确定的,而与网络的实际状况(如畅通、拥塞)等无关==)
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。
为什么需要扰乱初始化序列号?
一台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的时候就会觉得没有规律可寻。
四次挥手
CLOSE_WAITE状态:TCP的半关闭
TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓的半关闭。
如果应用程序不调用 close而调用shutdown,且第2个参数值为1,则插口的API支持半关闭。然而,大多数的应用程序通过调用close终止两个方向的连接。
2MSL等待状态
==注意区分跟服务端的 close_wait状态==,这个是time_wait状态。之前面试就有遇到这两种状态搞混了的,巨尴尬。
为什么客户端在TIME-WAIT阶段要等2MSL?
为的是确认服务器端是否收到客户端发出的ACK确认报文。
当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。==MSL指的是Maximum Segment Lifetime:一段TCP报文在传输过程中的最大生命周期==。==2MSL即是服务器端发出为FIN报文和客户端发出的ACK确认报文所能保持有效的最大时长==。
RFC 793 [Postel 1981c] 指出MSL为2分钟。然而,实现中的常用值是30秒, 1分钟,或2分钟。总之,MSL是一个由OS指定的值,而不是根据网络的实际情况计算出来的。
(客户的 I P地址和端口号,服务器的 I P地址和端口号)不能再被使用。这个连接只能在 2 M S L结束后才能再被使用。
服务器端在1MSL内没有收到客户端发出的ACK确认报文,就会再次向客户端发出FIN报文;
如果客户端在2MSL内,再次收到了来自服务器端的FIN报文,说明服务器端由于各种原因没有接收到客户端发出的ACK确认报文。客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始2MSL的计时;否则客户端在2MSL内没有再次收到来自服务器端的FIN报文,说明服务器端正常接收了ACK确认报文,客户端可以进入CLOSED阶段,完成“四次挥手”。
所以,客户端要经历时长为2SML的TIME-WAIT阶段;这也是为什么客户端比服务器端晚进入CLOSED阶段的原因。
端口被占用
这时会引发一个问题:
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去建立连接,仍会提示连接失败
TCP的超时与重传
数据包的超时重传
对于数据包(ACK包不是数据包),也会超时重传,在发生超时重传时,TCP不是以固定的时间间隔来重传的,而是会再每次重传时都将下一次重传的间隔设置为上次重传间隔的2倍,因此重传间隔是倍数增加的。直到收到确认或者彻底失败。由于正常发送报文段时,重传定时器的超时值为EstimateRTT + 4 * DevRTT,因此第一重传时会将下一次的超时时间设置为2倍的该值,依次类推。
TCP数据包重传的次数也根据系统设置的不同而有区分,有些系统,一个报文只会被重传3次,如果重传三次后还未收到该报文的确认,那么就不再尝试重传,直接reset重置该TCP连接,但有些要求很高的业务应用系统,则会不断的重传被丢弃的报文,以尽最大可能保证业务数据的正常交互。
拥塞避免算法
慢启动算法是在一个连接上发起数据流的方法,但有时我们会达到中间路由器的极限,此时分组将被丢弃。拥塞避免算法是一种处理丢失分组的方法。
该算法假定由于分组受到损坏引起的丢失是非常少的(远小于 1 %),因此分组丢失就意味着在源主机和目的主机之间的某处网络上发生了拥塞。有两种分组丢失的指示:发送超时和接收到重复的确认。
拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是当拥塞发生时,我们希望降低分组进入网络的传输速率,于是可以调用慢启动来作到这一点。在实际中这两个算法通常在一起实现。
拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口 cwnd和一个慢启动门限ssthresh。这样得到的算法的工作过程如下:
- 对一个给定的连接,初始化cwnd为1个报文段,ssthresh为65535个字节。
- TCP输出例程的输出不能超过cwnd和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关。
- 当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少为 2个报文段)。此外,如果是超时引起了拥塞,则cwnd被设置为1个报文段(这就是慢启动)。
- 当新的数据被对方确认时,就增加cwnd,但增加的方法依赖于我们是否正在进行慢启动或拥塞避免。如果cwnd小于或等于ssthresh,则正在进行慢启动,否则正在进行拥塞避免。
慢启动一直持续到我们回到当拥塞发生时所处位置的半时候才停止,然后转为执行拥塞避免。
慢启动算法初始设置 cwnd为1个报文段,此后每收到一个确认就加 1。
那样,这会使窗口按指数方式增长:发送 1个报文段,然后是2个,接着是4个……。
拥塞避免算法要求每次收到一个确认时将 cwnd增加1/cwnd。与慢启动的指数增加比起来,这是一种加性增长(additive increase)。我们希望在一个往返时间内最多为 cwnd增加1个报文段(不管在这个 RTT中收到了多少个 ACK),然而慢启动将根据这个往返时间中所收到的确认的个数增加cwnd。
快速重传与快速恢复算法
由于我们不知道一个重复的 ACK是由一个丢失的报文段引起的,还是由于仅仅出现了几个报文段的重新排序,因此我们等待少量重复的ACK到来。假如这只是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的 ACK之前,只可能产生1 ~ 2个重复的ACK。
如果一连串收到 3个或3个以上的重复ACK,就非常可能是一个报文段丢失了。于是我们就重传丢失的数据报文段,而无需等待超时定时器溢出。这就是快速重传算法。
接下来执行的不是慢启动算法而是拥塞避免算法。这就是快速恢复算法。
这个算法通常按如下过程进行实现:
- 当收到第3个重复的ACK时,将ssthresh设置为当前拥塞窗口 cwnd的一半。重传丢失的报文段。设置cwnd为ssthresh加上3倍的报文段大小。
- 每次收到另一个重复的 ACK时,cwnd增加1个报文段大小并发送 1个分组(如果新的cwnd允许发送)。
- 当下一个确认新数据的ACK到达时,设置cwnd为ssthresh(在第1步中设置的值)。这个ACK应该是在进行重传后的一个往返时间内对步骤 1中重传的确认。另外,这个ACK也应该是对丢失的分组和收到的第 1个重复的ACK之间的所有中间报文段的确认。这一步采用的是拥塞避免,因为当分组丢失时我们将当前的速率减半。
重新分组
当TCP超时并重传时,它不一定要重传同样的报文段。相反, TCP允许进行重新分组而发送一个较大的报文段,这将有助于提高性能(当然,这个较大的报文段不能够超过接收方声明的MSS)。在协议中这是允许的,因为 TCP是使用字节序号而不是报文段序号来进行识别它所要发送的数据和进行确认。
- 点赞
- 收藏
- 关注作者
评论(0)