Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1411699
  • 博文数量: 241
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 2253
  • 用 户 组: 普通用户
  • 注册时间: 2012-04-11 22:27
个人简介

--

文章分类

全部博文(241)

文章存档

2021年(3)

2019年(6)

2018年(1)

2017年(9)

2016年(21)

2015年(50)

2014年(125)

2013年(26)

我的朋友

分类: 网络与安全

2013-09-28 10:12:32

tcp-ip-状态详解

本章继续讨论TCP/IP协议。我们首先讨论TCP/IP客户程序如何根据连接持续时间是否会大于报文段最大生存时间MSL来分配端口号,以及这个分配结果对TCPTIME_WAIT状态有什么影响。接下来我们研究TCP协议为什么要定义TIME_WAIT状态,因为人们对TCP协议的这一特点普遍缺乏理解。TCP/IP协议的重要优点之一就是在连接持续时间小于报文段最大生存时间MSL时,使协议的TIME_WAIT状态由240秒缩短至大约12秒。我们将讨论TCP/IP协议是如何实现这一点的,以及这样做的正确性。

本章最后我们将讨论TCP/IP协议的TAO,即TCP加速打开。它使TCP的客户-服务器事务能够跳过三次握手过程,从而节省了一次往返时间,这也正是TCP协议给我们带来的最大好处。

 

1           客户的端口号和TIME_WAIT状态

我们编写TCP客户程序的时候通常不关心如何选择端口号。大部分TCP客户程序都是让系统的TCP模块选择一个当前未使用的临时端口。然而,TCP协议根据事务速率和持续时间,对端口号的选择有额外的要求。

事务过程中的客户程序有两种可能的优化方式:

1)      不需要改动任何程序源代码,只要客户和服务器端都支持TCP协议,就可将TIME_WAIT的持续时间缩短到连接中重传超时的8倍,而不是原来的240秒。

2)      只修改客户程序,使其重用同一个端口号,这时不但TIME_WAIT状态的持续时间可以像前一种情况那样截断到连接中重传超时的8倍,而且,如果同一连接的另一个替身被创建,TIME_WAIT状态就会更快地终止。

 

2           设置TIME_WAIT状态的目的:TIME_WAIT状态是TCP协议中最容易被误解的特性之一。因为RFC793中只对该状态做了扼要的解释,后来的RFC1185,对TIME_WAIT做了详细说明。设置该状态的原因主要有两个:

1)  它实现了全双工的连接关闭。           2)  它使过时的重复报文段作废。

下面为什么TIME_WAIT状态要出现在执行主动关闭的一端:主动关闭端发出最后一个ACK报文段,而如果这个ACK丢失或是最后一个FIN丢失了,那么另一端将超时并重传最后的FIN报文段。因此,在主动关闭的一端保留连接的状态信息,这样它才能在需要的时候重传最后的确认报文段;否则,它收到最后的FIN报文段后就无法重传最后一个ACK,而只能发出RST报文段,从而造成虚假的错误信息。

------------------------华丽的分割线----------------------------------------------------------------------------

u       TIME_WAIT状态的自结束:RFC793中规定,处于TIME_WAIT状态的连接在收到RST后变迁到CLOSED状态,这称为TIME_WAIT状态的自结束。RFC1337中则建议不要用RST过早地结束。

-------------------------华丽的分割线----------------------------------------------------------------------------

3           TIME_WAIT状态的截断

采用TCP协议后,保持时间由报文段最大生存时间MSL2倍缩短为RTO(重传超时)8倍。那么,截断了该状态的保持时间后,对应于每一个原因都分别产生了什么样的后果?

3.1          TCP全双工关闭

花在TIME_WAIT状态的时间实际上应该根据RTO来定,而不是根据MSLTCP中选用乘数8是为了保证对方有足够的时间超时,并重传最后一个报文段。这样就导致双方都在等待截断的TIME_WAIT保持期过去。

       但是如果新的客户程序又使用了同一个插口对,TIME_WAIT8RTO的保持时间到期之前就会被截断。

3.2          过时的重复报文段失效

TIME_WAIT状态的截断是可行的,因为CC选项能够防止过时重复报文段错误地传递给后续连接。但截断的前提是连接的持续时间小于报文段最大生存时间MSL。保持时间截断是安全的原因是因为CC选项的值直到所有的过时重复报文段都消失以后才会重复。

无论应用程序采用哪种端口使用策略,如果两端的主机都支持TCP协议,而且连接的持续时间小于报文段最大生存时间MSL,那么TIME_WAIT状态的保持时间总是可以从2MSL截断到8RTO。这样就节约了资源。

 

4           利用TAO跳过三次握手

为了理解何以能跳过三次握手,我们需要先了解三次握手的目的。RFC793中对此只是做了一个简单的说明:“引入三次握手的主要原因是为了避免过时的重复连接在再次建连时造成的混乱。

       因此,TCP协议必须提供一种方法,使收到SYN报文段的一方能够不经过三次握手就保证这个SYN不是过时的重复报文段,从而使得随该SYN报文段一起传送过来的数据能立刻交付给上层的用户进程。这里的保护手段是客户发出的SYN报文段中附带的CC选项和服务器缓存的最近一次从该客户收到的合法CC值。

采用的方法就是RFC1644中所述的TAO测试:“如果某个特定客户主机的第一个SYN报文段(即只含SYN位而不含ACK位的报文段)中所携带的CC值大于缓存中的该客户CC值,CC值的单调递增特性可以确保这是一个新的SYN报文段,可以立即接收下来”。正是CC值的单调递增特性以及下面的两个假设确保了SYN报文段是新的,使得TCP协议能够跳过三次握手:

1)      所有的报文段都只有有限的MSL秒的生存期;

2)      tcp_ccgen计数器在2MSL的时间内的递增量不超过232次方减一。

下面介绍一些出错的情况:

4.1          失序的SYN报文段

服务器缓存的该客户的CC值为1。报文段1是从客户的端口1600发出的,携带的CC值为15,但它在网络上延迟了一段时间。报文段2是从客户的端口1601发出的,携带的CC值为5000。当报文段2到达服务器时,TAO测试成功(5000大于1),于是对该客户缓存的最新CC值改为5000,并把数据交付给进程。

当报文段1终于到达服务器时,TAO测试失败(15小于5000),于是服务器给出的响应也是一个SYN报文段,其中带有对所收到SYNACK,强迫执行三次握手过程。

4.2          翻转了符号位的CC

TAO测试失败后,服务器强迫执行三次握手;即使握手过程成功结束,服务器所记录的该客户CC值也不更新。从协议的角度出发,这样做是正确的,但却降低了效率。

服务器端TAO测试失败是很可能发生的,因为CC值是客户端生成的。对客户端来说,这是所有连接的全局变量;对服务器来说,则是“翻转了它们的符号位”。(参见滑动窗口)

客户在0时刻与服务器建立连接,CC值为1。服务器TAO测试成功,并把该CC值记录为该客户当前的CC值。接着该客户与其他服务器建立了2147483646个连接。在第120秒时,它与0时刻建立起连接的服务器又建立一个连接,但此时的CC值为2147483648。服务器收到SYN后,TAO测试失败(按模运算,21474836481小,如卷2的图24-26所示),然后三次握手过程验证了该SYN报文段,但是服务器当前记录的该客户的CC值仍然是1

这就意味着,从此开始到第240秒的这一段时间里,该客户向该服务器发送任何一个SYN都要经过三次握手过程。这里假设tcp_ccgen计数器持续以最快的速率递增。

这个问题的解决需要连接双方的共同努力。首先,不仅服务器要为每个客户记录其最新的CC值,而且客户也要记录发给每个服务器的最新CC值。这两个变量即为tao_cctao_ccsent

4.3          重复的SYN/ACK报文段

这一节解决的是:客户端又如何确定所收到的服务器响应(SYN/ACK报文段)不是过时和重复的呢?

TCP协议用CCecho选项对过时的重复SYN/ACK报文段问题提供完整的保护。客户端知道自己发出的SYN报文段中的CC值,而服务器必须在CCecho选项中把该值原样发回给客户端。如果服务器的响应中不含所期望的CCecho值,那么客户端就把该响应丢弃。

CC的值具有单调递增特性,而且在至多2MSL秒的时间内就循环一次,这就可以保证客户端不会接受过时的重复SYN/ACK报文段。

4.4          重传的S Y N报文段

重传的SYN报文段报文会使超时时间滞后,但是这并不影响TAO测试的正确性,但是会却降低了tcp_ccgen计数器的最大递增速率。


 
[img][/img]....看图看
 
图.. 

我们在使用netstat -n时就能看到当前端口的状态 
状态转换图中状态的描述: 
CLOSED:无连接是活动的或正在进行 
LISTEN:服务器在等待进入呼叫 
SYN_RECV:一个连接请求已经到达,等待确认 
SYN_SENT:应用已经开始,打开一个连接 
ESTABLISHED:正常数据传输状态 
FIN_WAIT1:应用说它已经完成 
FIN_WAIT2:另一边已同意释放 
ITMED_WAIT:等待所有分组死掉 
CLOSING:两边同时尝试关闭 
TIME_WAIT:另一边已初始化一个释放 
LAST_ACK:等待所有分组死掉 
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认; 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 完成三次握手,客户端与服务器开始传送数据,也就是ESTABLISHED状态,我们每次netstat -a 时看到最多的状态。 
在上述过程中,还有一些重要的概念: 未连接队列:在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于Syn_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。 
其中SYN,ASK是服务器客服端唯一标识使通信不被串掉 
当然我们三次握手时可能会timeout也就是会产生如此状态,这对我们查问题比较有用SYN_SENT发送信号----FIN ---ITMED_WAIT --重试---close 
到这里我们来理解下DOS攻击用TCP/IP语言我们可以称做SYN浑水,黑客一直去给服务器发SYN_SENT去握手,最后一直不去建立连接导致服务器一直在FIN ---ITMED_WAIT等待握手,并尝试N次连接如果期间不断发送就会造成很多连接导致服务器瘫痪。所以配置防火墙时要注意这些状况。注意:windows防火墙我们开放端口是要选择TCP还是UDP开放,如果选择错了连接也是无法进行的。 




主要部分,四次握手: 
断开连接其实从我的角度看不区分客户端和服务器端,任何一方都可以调用close(or closesocket)之类 
的函数开始主动终止一个连接。这里先暂时说正常情况。当调用close函数断开一个连接时,主动断开的 
一方发送FIN(finish报文给对方。有了之前的经验,我想你应该明白我说的FIN报文时什么东西。也就是 
一个设置了FIN标志位的报文段。FIN报文也可能附加用户数据,如果这一方还有数据要发送时,将数据附 
加到这个FIN报文时完全正常的。之后你会看到,这种附加报文还会有很多,例如ACK报文。我们所要把握 
的原则是,TCP 肯定会力所能及地达到最大效率,所以你能够想到的优化方法,我想TCP 都会想到。 
当被动关闭的一方收到FIN报文时,它会发送ACK确认报文(对于ACK这个东西你应该很熟悉了)。这里有个 
东西要注意,因为TCP 是双工的,也就是说,你可以想象一对TCP 连接上有两条数据通路。当发送FIN报文 
时,意思是说,发送FIN的一端就不能发送数据,也就是关闭了其中一条数据通路。被动关闭的一端发送 
了ACK后,应用层通常就会检测到这个连接即将断开,然后被动断开的应用层调用close关闭连接。 
我可以告诉你,一旦当你调用close(or closesocket),这一端就会发送FIN报文。也就是说,现在被动 
关闭的一端也发送FIN给主动关闭端。有时候,被动关闭端会将ACK和FIN两个报文合在一起发送。主动 
关闭端收到FIN后也发送ACK,然后整个连接关闭(事实上还没完全关闭,只是关闭需要交换的报文发送 
完毕),四次握手完成。如你所见,因为被动关闭端可能会将ACK和FIN合到一起发送,所以这也算不上 
严格的四次握手---四个报文段。 
在前面的文章中,我一直没提TCP 的状态转换。在这里我还是在犹豫是不是该将那张四处通用的图拿出来, 
不过,这里我只给出断开连接时的状态转换图,摘自: 

给出一个正常关闭时的windump信息: 
14 : 00 : 38.819856 IP cd - zhangmin. 1748 > 220.181 . 37.55 . 80 : F 1 : 1 ( 0 ) ack 1 win 65535 
14 : 00 : 38.863989 IP 220.181 . 37.55 . 80 > cd - zhangmin. 1748 : F 1 : 1 ( 0 ) ack 2 win 2920 
14 : 00 : 38.864412 IP cd - zhangmin. 1748 > 220.181 . 37.55 . 80 : . ack 2 win 65535 

补充细节: 
关于以上的四次握手,我补充下细节: 
1. 默认情况下(不改变socket选项),当你调用close( or closesocket,以下说close不再重复)时,如果 
发送缓冲中还有数据,TCP 会继续把数据发送完。 

2. 发送了FIN只是表示这端不能继续发送数据(应用层不能再调用send发送),但是还可以接收数据。 

3. 应用层如何知道对端关闭?通常,在最简单的阻塞模型中,当你调用recv时,如果返回0,则表示对端 
关闭。在这个时候通常的做法就是也调用close,那么TCP 层就发送FIN,继续完成四次握手。如果你不调用 
close,那么对端就会处于FIN_WAIT_2状态,而本端则会处于CLOSE_WAIT状态。这个可以写代码试试。 

4. 在很多时候,TCP 连接的断开都会由TCP 层自动进行,例如你CTRL+C终止你的程序,TCP 连接依然会正常关 
闭,你可以写代码试试。 



特别的TIME_WAIT状态: 
从以上TCP 连接关闭的状态转换图可以看出,主动关闭的一方在发送完对对方FIN报文的确认(ACK)报文后, 
会进入TIME_WAIT状态。TIME_WAIT状态也称为2MSL状态。 
什么是2MSL?MSL即Maximum Segment Lifetime,也就是报文最大生存时间,引用中的话:“ 
它(MSL)是任何报文段被丢弃前在网络内的最长时间。”那么,2MSL也就是这个时间的2倍。其实我觉得没 
必要把这个MSL的确切含义搞明白,你所需要明白的是,当TCP 连接完成四个报文段的交换时,主动关闭的 
一方将继续等待一定时间(2-4分钟),即使两端的应用程序结束。你可以写代码试试,然后用netstat查看下。 
为什么需要2MSL?根据中的说法,有两个原因: 
其一,保证发送的ACK会成功发送到对方,如何保证?我觉得可能是通过超时计时器发送。这个就很难用 
代码演示了。 
其二,报文可能会被混淆,意思是说,其他时候的连接可能会被当作本次的连接。直接引用 
的说法:The second is to provide a “buffering period” between the end of this connection 
and any subsequent ones. If not for this period, it is possible that packets from different 
connections could be mixed, creating confusion. 
TIME_WAIT状态所带来的影响: 
当某个连接的一端处于TIME_WAIT状态时,该连接将不能再被使用。事实上,对于我们比较有现实意义的 
是,这个端口将不能再被使用。某个端口处于TIME_WAIT状态(其实应该是这个连接)时,这意味着这个 TCP 
连接并没有断开(完全断开),那么,如果你bind这个端口,就会失败。 
对于服务器而言,如果服务器突然crash掉了,那么它将无法再2MSL内重新启动,因为bind会失败。解决这 
个问题的一个方法就是设置socket的SO_REUSEADDR选项。这个选项意味着你可以重用一个地址。 
对于TIME_WAIT的插曲: 
当建立一个TCP 连接时,服务器端会继续用原有端口监听,同时用这个端口与客户端通信。而客户端默认情况 
下会使用一个随机端口与服务器端的监听端口通信。有时候,为了服务器端的安全性,我们需要对客户端进行
验证,即限定某个IP某个特定端口的客户端。客户端可以使用bind来使用特定的端口。 
对于服务器端,当设置了SO_REUSEADDR选项时,它可以在2MSL内启动并listen成功。但是对于客户端,当使 
用bind并设置SO_REUSEADDR时,如果在2MSL内启动,虽然bind会成功,但是在windows平台上connect会失败。 
而在linux上则不存在这个问题。(我的实验平台:winxp, ubuntu7.10) 
要解决windows平台的这个问题,可以设置SO_LINGER选项。SO_LINGER选项决定调用close时,TCP 的行为。 
SO_LINGER涉及到linger结构体,如果设置结构体中l_onoff为非0,l_linger为0,那么调用close时TCP 连接 
会立刻断开,TCP 不会将发送缓冲中未发送的数据发送,而是立即发送一个RST报文给对方,这个时候TCP 连 
接就不会进入TIME_WAIT状态。 
如你所见,这样做虽然解决了问题,但是并不安全。通过以上方式设置SO_LINGER状态,等同于设置SO_DONTLINGER 
状态。 
断开连接时的意外: 
这个算不上断开连接时的意外,当TCP 连接发生一些物理上的意外情况时,例如网线断开,linux上的TCP 实现 
会依然认为该连接有效,而windows则会在一定时间后返回错误信息。 
这似乎可以通过设置SO_KEEPALIVE选项来解决,不过不知道这个选项是否对于所有平台都有效。 
 
阅读(1611) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~