TCP的异常关闭与RST包

TCP的异常关闭与RST包

 

关闭TCP连接

正常关闭

 

 

 

半打开

如果一方已经关闭或异常终止连接,而另一方却不知道, 我们将这样的TCP连接称为半打开(Half-Open)。

任何一端的主机异常(如突然断电)都可能导致发生这种情况;
只要不在半打开的连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。
 
 
 
 

 

半关闭

半关闭:TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力;

 

 

1. 客户端发送FIN,另一端发送对这个FIN的ACK报文段。 此时客户端就处于半关闭。

2. 调用shutdown,shutdown的第二个参数为SHUT_WR时,为半关闭。

 

 

 

 

SO_LINGER

// SO_LINGER选项可用来改变close的缺省设置
struct linger {
     int l_onoff;       // SO_LINGER选项是否打开,缺省值为0 
     int l_linger;      // 如果l_onoff=1,延迟关闭的时间 
}

 

 
 
struct linger so_linger;
so_linger.l_onoff = TRUE;
so_linger.l_linger = 1;     // delay 1 second
z = setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);

 

a) 如果l_onoff=0,这是close调用的缺省关闭方式,close调用将在send buffer的数据将发给对方之后返回(但无需等对方的TCP确认);

 

b) 如果l_onoff=1 且 l_linger=0,这种关闭方式称为“强制”关闭,TCP将丢弃保留在send buffer中的残留数据,并发送一个RST给对方,这避免了TIME_WAIT状态;注意,这种方式是非正常的4次握手关闭连接;
 
 
c) 如果l_onoff=1 且 l_linger>0,这种关闭方式称为“优雅”关闭,这会阻塞调用进程,直到所剩数据发送完毕并得到对方TCP确认或超时(如下左图)。
如果延迟时间消耗完毕,仍有数据未发送(或未被对方确认),close将返回EWOULDBLOCK且缓冲区数据都将丢弃(如下右图)。
注意,如果socket设置了O_NONBLOCK状态,则close调用将立即返回WSAEWOULDBLOCK错误,send buffer中数据都将被丢弃;

 

   

 

 

 

 

 

RST

在TCP协议中RST表示复位,用来关闭异常的连接,在TCP的设计中它是不可或缺的。

发送RST包关闭连接时,不必等缓冲区的包都发出去,直接就丢弃缓存区的包发送RST包。而接收端收到RST包后,也不必发送ACK包来确认。

TCP报文中有一个RST标志位,如下图:

 

 

产生RST的原因

1、端口未打开

服务器程序端口未打开而客户端来连接,例如telnet一个未打开的TCP的端口可能会出现这种错误。

比如主机A向主机B发送一个SYN请求,表示想要连接主机B的40000端口,但是主机B上根本没有打开40000这个端口,于是B就向主机A发送了一个RST。这种情况很常见。特别是服务器程序core dump之后重启之前连续出现RST的情况会经常发生。

 

 

2、提前关闭

比如主机A和主机B正常建立连接后,A向B发送了FIN包要求关连接,B发送ACK后,网断了,A通过若干原因放弃了这个连接(例如进程重启)。网通了后,B又开始发数据包,A收到后表示压力很大,不知道这野连接哪来的,就发了个RST包强制把连接关了,B收到后会出现connect reset by peer错误。

 

3、在一个已关闭的连接上收到数据

 

 

4、请求超时

 

 

 


 

RST攻击

服务器A和服务器B之间建立了TCP连接,此时服务器C伪造了一个TCP包发给B,使B异常的断开了与A之间的TCP连接,这就是RST攻击。
那么伪造什么样的TCP包可以达成目的呢?我们至顶向下的看:

  • 假定C伪装成A发过去的包,这个包如果是RST包的话,毫无疑问,B将会丢弃与A的缓冲区上所有数据,强制关掉连接;
  • 如果发过去的包是SYN包,B会表示A已经发疯了(与OS的实现有关),正常连接时又来建新连接,B主动向A发个RST包,并在自己这端强制关掉连接;

这两种方式都能够达到复位攻击的效果。似乎挺恐怖,然而关键是C如何能伪造成A发给B的包呢?这里有两个关键因素,源端口和序列号。
一个TCP连接都是四元组,由源IP+源端口、目标IP+目标端口唯一确定一个连接。所以,如果C要伪造A发给B的包,要在上面提到的IP头和TCP头,把源IP、源端口、目标IP、目标端口都填对。这里B作为服务器,IP和端口是公开的,A是我们要下手的目标,IP当然知道,但A的源端口就不清楚了,因为这可能是A随机生成的。当然,如果能够对常见的OS如windows和linux找出生成source port规律的话,还是可以搞定的。
此外,伪造的TCP包里需要填序列号(SeqNum),如果序列号的值不在A之前向B发送时B的滑动窗口内,B是会主动丢弃的。所以我们要找到能落到当时的AB间滑动窗口的序列号。这个可以暴力解决,因为一个sequence长度是32位,取值范围0-4294967296,如果滑动窗口大小为65535的话,则最多只需要发65537(4294967296/65535=65537)个包就能有一个序列号落到滑动窗口内。RST包是很小的,IP头+TCP头也才40字节,算算我们的带宽就知道这实在只需要几秒钟就能搞定。

 

 

 

 

参考文档:

http://my.oschina.net/costaxu/blog/127394

http://www.cnblogs.com/lulu/p/4149562.html

http://blog.csdn.net/russell_tao/article/details/7228923

 

posted @ 2015-08-06 16:07  如果的事  阅读(7294)  评论(0编辑  收藏  举报