BruceChen7 / gitblog

My blog
6 stars 1 forks source link

TCP知识之TimeWait #21

Open BruceChen7 opened 4 years ago

BruceChen7 commented 4 years ago

资料来源

连接请求的关键信息

Syn-Flood攻击成立的关键在于服务器资源是有限的,而服务器收到请求会分配资源。通常来说,服务器用这些资源保存此次请求的关键信息,包括请求的来源和目(五元组),以及TCP选项,如最大报文段长度MSS、时间戳timestamp、选择应答使能Sack、窗口缩放因子Wscale等等。当后续的ACK报文到达,三次握手完成,新的连接创建,这些信息可以会被复制到连接结构中,用来指导后续的报文收发。

那么现在的问题就是服务器如何在不分配资源的情况下

time-wait

tcp重置攻击

BruceChen7 commented 4 years ago

time-wait

状态转移图

image image

上面实际不一定是只有 Client 才能进入 TIME-WAIT 状态,而是谁发起 TCP 连接断开先发的 FIN,谁最终就进入 TIME-WAIT 状态。

time-wait的作用 先看下对待time-wait的态度

The TIME_WAIT state is our friend and is there to help us (i.e., to let old duplicate segments expire in the network). Instead of trying to avoid the state, we should understand it.

image

虚线将两次连接分开,两次连接都使用的同一组 TCP Tuple,即 Source IP, Source Port, Destination IP, Destination Port 组合。第一次连接中 SEQ 为 3 的数据包出现了重发,第二次连接中刚好再次使用 SEQ 为 3 这个序号的时候,第一次连接中本来发丢(延迟)的 SEQ 为 3 的数据包在此时到达,就导致这个延迟了的 SEQ 为 3 的数据包被当做正确的数据而接收,之后如果还有SEQ 为 3 的正常数据包到达会被接收方认为是重复数据包而直接丢弃,导致 TCP 连接接收的数据错误

这种错误可能看上去很难发生,因为必须 TCP Tuple 一致,并且 SEQ 号必须 valid 时才会发生,但在高速网络下发生的可能性会增大(因为 SEQ 号会很快被耗尽而出现折叠重用),所以需要有个 TIME-WAIT 的存在减少上面这种情况的发生,并且 TIME-WAIT 的长度还要长一些

RFC 793 - Transmission Control Protocol 要求TIME-WAIT 长度要大于两个 MSL(Maximum segment lifetime - Wikipedia) 。MSL 是人为定下的值,就认为数据包在网络路由的时间不会超过这么长。等足两个 MSL 以保证上图第二次连接建立的时候之前发丢的 SEQ 为 3 的数据包已经在网络中丢失,不可能再出现在第二次连接中。

如果time-wait很短,主动断开连接的一方发送SYNC报文会怎样?

TIME WAIT 结束之后主动断开连接一方直接发出 SYN 而被动断开连接一方还处在 LAST ACK 状态,因为 SYN 是其不期待的数据会不会触发其回复 RST 导致主动断开连接一方 connect 失败而放弃建立连接?这篇文章,如果开启 TCP Timestamp 后处在 LAST ACK 一方会丢弃 SYN 不会回复 RST 而是等到 FIN 超时后重发 FIN,从而让主动断开连接一方回复 RST 后再次发起 SYN 最终能保证连接正常建立。

time-wait带来的问题

对于第一个问题可以考虑开启下面说的 tcp_tw_reuse 甚至 tcp_tw_recycle,也能调大 net.ipv4.ip_local_port_range 以获取更多的可用端口,还可以使用多个 IP 或 Server 开启多个 port 的方法来避免没有端口可用的情况。

对于内存上的开销,进入 TIME WAIT 后应用层实际已经将连接相关信息销毁了,只是在 kernel 还维护有连接相关信息,所以内存占用只发生在 kernel 内。正常状态下的 Socket 结构比较复杂,可以看看这里 struct tcp_sock,它里面使用的是struct inet_connection_sock。Linux 为了减少 TIME WAIT 连接的开销,专门构造了更精简的 Socket 数据结构给进入 TIME WAIT 状态的连接用,参看这里 struct tcp_timewait_sock ,它里面用的是inet_timewait_sock。可以看到 TIME WAIT 状态下连接的结构要比正常连接数据结构简单不少,在内核的数据结构最多百来字节,即使有 65535 个 TIME WAIT 的连接存在也占不了多少内存,几十 M 最多了。

对于 CPU 的开销一般也不大,主要是在建立连接时在 inet_csk_get_port 函数内查找一个可用端口上的开销。所以 TIME WAIT带来的最主要的副作用就是会占用端口,而端口数量有限,可能导致无法创建新连接的情况

减少time-wait端口占用方法

SO_LINGER

应用层来说调用 send() 后数据并没有实际写入网络,而是先放到一个 buffer 当中,之后慢慢的往网络上写。所以会出现应用层想要关闭一个连接时,连接的 buffer 内还有数据没写出去,需要等待这部分数据写出。调用close() 后等待 buffer 内数据全部写出去的时间叫做 Linger Time。Linger Time 结束后开始正常的 TCP 挥手过程。

除了正常的连接关闭之外,还有非正常的断开连接的方法,可以不让连接进入 TIME WAIT 状态。方法就是给 Socket 配置 SO_LIGNER,这样在调用 close() 关闭连接的时候主动断开连接一方不是等待 buffer 内数据发完之后再发送 FIN 而是根据 SO_LINGER 参数配置的超时时间,等到最多这个超时时间这么长后,如果连接 buffer 内还有数据就直接发送 RST 强制重置连接,对方会收到 Connection Reset by peer 的错误,同时会导致主动断开连接一方所有还未来得及发送的数据全部丢弃。如果还未到 SO_LINGER 配置的超时时间连接 buffer 内的数据就全部发完了,就还是发 FIN 走正常挥手逻辑,但这样主动断开连接一方还是会进入 TIME WAIT。所以如果主动断开连接时完全不想让连接进入 TIME WAIT 状态,可以直接将 SO_LINGER 设置为 0 ,这样调用 close() 后会直接发 RST,丢弃 buffer 内所有数据,并让连接直接进入 CLOSED 状态

tcp_tw_reuse、 tcp_tw_recycle 配置和 TCP Timestamp TCP Timestamp 机制,RFC 1323 和 RFC 7323 提出过一个优化,在 TCP option 中带上两个 timestamp 值:

TCP Timestamps option (TSopt): Kind: 8 Length: 10 bytes

-------+-------+---------------------+---------------------+
|Kind=8 |  10   |   TS Value (TSval)  |TS Echo Reply (TSecr)|
+-------+-------+---------------------+---------------------+
    1       1              4                     4

TCP 握手时,通信双方如果都带有 TCP Timestamp 则表示双方都支持 TCP Timestamp 机制,之后每个 TCP 包都需要将自己当前机器时间带在 TSval 中,并且在每次收到对方 TCP 包做ACK 回复的时候将对方的 TSval 作为 ACK 中 TSecr 字段返回给对方。这样通信双方就能在收到 ACK 的时候读取TSecr 值并根据当前自己机器时间计算 TCP Round Trip Time,从而根据网络状况动态调整 TCP 超时时间,以提高 TCP 性能。请注意这个 option 虽然叫做 Timestamp 但不是真实日期时间,而是一般跟操作系统运行时间相关的一个持续递增的值

除了对TCP Round Trip 时间做测量外,这个 timestamp 还有个功能就是避免重复收到数据包影响正常的 TCP 连接,这个功能叫做PAWS

PAWS (Protection Against Wrapped Sequences)