我宣布现在没人比我更懂 TCP。
见邮件列表 Two different places between TCP socket behavior and RFC documents
虽然没有肉眼可见的 vulunerability / exposure 但多少还是被承认为 BUG 了…对方锐意修复中。
问题1. 关闭时有待处理数据,但未能发送 RST
根据 RFC 2525 第 2.17 节,当 close()
一个套接字时,如果其接收缓冲区中仍有待读取的数据,应该发送 RST(复位)。
根据 RFC 1122:主机可以实现一种“半双工”TCP 关闭序列,…不能继续读取数据…如果接收缓冲区中仍有待处理数据时进行 CLOSE 调用,或者在调用 CLOSE 后接收到新数据,TCP 应该发送一个 RST 以表明数据已丢失。
然而,FreeBSD 的 TCP 套接字并非如此。以下是 TCPDUMP 的输出,显示在套接字有待处理数据时调用 close()
发出的是 FIN(结束)而非 RST:
1 2 3 4 5 6 7 A > B: Flags [S], seq 2636678338, win 65535, length 0 B > A: Flags [S.], seq 1969223298, ack 2636678339, win 65535, length 0 A > B: Flags [.], ack 1, win 1277, length 0 A > B: Flags [P.], seq 1:6, ack 1, win 1277, length 5 B > A: Flags [.], ack 6, win 1277, length 0 B > A: Flags [F.], seq 1, ack 6, win 1277, length 0 A > B: Flags [.], ack 2, win 1277, length 0
无论是 close()
、shutdown(SHUT_RDWR)
、shutdown(SHUT_RD)
,还是 SO_LINGER
设置为开启或关闭,都得到相同的结果。而在 Linux 上,同样的操作会得到以下结果:
1 2 3 4 5 6 A > B: Flags [S], seq 2879877684, win 65495, length 0 B > A: Flags [S.], seq 1538598692, ack 2879877685, win 65483, length 0 A > B: Flags [.], ack 1, win 512, length 0 A > B: Flags [P.], seq 1:6, ack 1, win 512, length 5 B > A: Flags [.], ack 6, win 512, length 0 B > A: Flags [R.], seq 1, ack 6, win 512, length 0
当然,虽然没有发送 RST,但是这里不会像预期的那样导致连接一直保持。因为它的 FIN segment 实际上重新打开了窗口。
1 2 3 4 5 6 7 A > B: Flags [.], ack 1, win 510, length 0 B > A: Flags [.], ack 66137, win 0, length 0 A > B: Flags [.], ack 1, win 510, length 0 B > A: Flags [.], ack 66137, win 0, length 0 A > B: Flags [.], ack 1, win 510, length 0 B > A: Flags [.], ack 66137, win 0, length 0 B > A: Flags [F.], seq 1, ack 66137, win 1027, length 0
我们通过发送大量数据,使得对面最终通告了 0 窗口。但在对面进行close()
后,它又打开了窗口。此时发送端就又可以发送数据,包括发送 FIN 了。
如果发送数据,而对面已经做了接受方向的关闭,则会得到一个 RST;如果发送 FIN,也就能完成四次挥手。看起来都能够使连接关闭。但这里的问题在于,打开接收窗口与关闭接受方向显然是矛盾的。尤其是,打开窗口邀请对面发送数据,而这数据是必定会被自己所拒绝的,这造成了一些不必要的网络流量以及处理资源的浪费。
另一方面,FIN 标志着优雅地关闭了连接。但这里显然并不优雅。我们需要用清晰的报文来标志异常。
数据发送/强制重置比起主动重置除了少量资源消耗以外,使得关闭这件事整体变得复杂了….
问题2. 在 SYN-RECEIVED 状态下对旧序列 segment 回复 RST 而非确认
根据 RFC 793 第 69 页:如果一个传入的 segment 不可接受,应该回复一个确认(这里“应该”没有大写)。
这应该适用于包括 SYN-RECEIVED 在内的所有状态,但 FreeBSD 的 TCP 套接字并非如此。我用手动构造的 TCP 分段发现了这个问题:
1 2 3 4 A > B: Flags [S], seq 1, win 8192, length 0 B > A: Flags [S.], seq 4054810353, ack 2, win 65535, length 0 A > B: Flags [.], ack 1, win 8192, length 0 B > A: Flags [R], seq 4054810354, win 0, length 0
预期的行为是发送一个空的确认:
1 2 3 4 A > B: Flags [S], seq 1, win 8192, length 0 B > A: Flags [S.], seq 3620804602, ack 2, win 65495, length 0 A > B: Flags [.], ack 1, win 8192, length 0 B > A: Flags [.], ack 1, win 65495, length 0
这与 Linux 的行为是一致的。
上文发送后,维护者似乎不是很明白,于是我作了如下解释:
考虑以下 TCP 握手序列:
套接字 A 向处于 TCP_LISTEN 状态的套接字 B 发送一个 SYN(同步)分段 <CTL=SYN><SEQ=x>
。
套接字 B 转换为 TCP_SYN_RECV 状态,并以 <CTL=SYN,ACK><SEQ=y><ACK=x+1>
进行响应。
套接字 A 没有发送预期的 <CTL=ACK><SEQ=x+1><ACK=y+1>
来完成三次握手,而是错误地发送了 <CTL=ACK><SEQ=x><ACK=y+1>
。
根据 RFC 文档,对这种窗口外 ACK 的恰当响应应是一个空的 ACK 分段 <CTL=ACK><SEQ=y+1><ACK=x+1>
。之后,套接字 B 应等待一个有效的 ACK,或者在必要时重新传输 SYN-ACK。
然而,在 FreeBSD 当前的实现中,它发送了一个 RST(复位)分段,格式为 <CTL=RST><SEQ=y+1>
,这会 prematurely 终止连接。
这种行为似乎偏离了 RFC 的指导,并可能导致不必要的连接重置。
对于窗口不同程度的偏离都观察到了这一现象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 1. expect seq=2, actual seq=0x80000001 2. expect seq=2, actual seq=0x80000002 3. expect seq=2, actual seq=0x80000003 4. expect seq=2, actual seq=0x90000000 5. expect seq=2000001 (0x1e8481), actual seq=1 6. expect seq=1, actual seq=2000001 (0x1e8481) All of them, under FreeBSD, give a RST reply. Here is the tcpdump of the first case. A > B: Flags [S], seq 1, win 8192, length 0 0x0000: 4500 0028 0000 4000 4006 0000 7f00 0001 0x0010: 7f00 0001 22b9 22b8 0000 0001 0000 0000 0x0020: 5002 2000 4c6e 0000 B > A: Flags [S.], seq 1643153760, ack 2, win 65535, options [mss 16344], length 0 0x0000: 4500 002c 0000 4000 4006 0000 7f00 0001 0x0010: 7f00 0001 22b8 22b9 61f0 8960 0000 0002 0x0020: 6012 ffff fe20 0000 0204 3fd8 A > B: Flags [.], seq 2147483648, ack 1, win 8192, length 0 0x0000: 4500 0028 0000 4000 4006 0000 7f00 0001 0x0010: 7f00 0001 22b9 22b8 8000 0001 61f0 8961 0x0020: 5010 2000 e10d 0000 B > A: Flags [R], seq 1643153761, win 0, length 0 0x0000: 4500 0028 0000 4000 4006 0000 7f00 0001 0x0010: 7f00 0001 22b8 22b9 61f0 8961 0000 0000 0x0020: 5004 0000 fe1c 0000