Linux TCP 异常行为

0 投票
2 回答
720 浏览
提问于 2025-04-18 02:43

我们正在分析不同操作系统下TCP的表现,比如Windows 8和Ubuntu 13.10。为此,我们使用了一个叫做Scapy的工具,它是用Python写的,可以用来制作数据包、通过网络发送这些数据包并分析返回的结果。

在我们的实验中,有一个假装是客户端的Scapy引导程序和一个监听的服务器。客户端会向服务器发送一系列TCP数据包,然后检查服务器的响应。这个服务器只是接受连接,其他什么也不做。我们的目标是建立一个简单而具体的服务器行为模型,忽略一些复杂的情况,比如重传、窗口控制,甚至数据交换。

在分析Windows 8上监听服务器的行为时,我们得到了一个相当不错的模型。然而,在Ubuntu上实验时,我们遇到了不确定的行为,这让我很难理解。我在这里附上了一张wireshark的日志图片,里面包含了几次相似输入数据包的“运行”。每次运行都是通过一个逐渐增加的端口来执行的。

Wireshark capture

奇怪的情况遵循以下模式:

client ---- SYN 0 _ ---> server [LISTENING]
client <- SYN+ACK 0 1 -- server [SYN_RCVD]

client -- ACK+FIN 1 1 -> server [SYN_RCVD]
client <--- ACK 1 2 ---- server [CLOSE_WAIT]

client ---- ACK 1 20 --> server [CLOSE_WAIT]
client <--- ACK 1 2 ---- server [CLOSE_WAIT] or no_response [CLOSE_WAIT]

有没有人能告诉我,为什么在收到一个无效的确认(确认一个从未存在的段)时,服务器会表现得不确定?也就是说,要么重新发送它之前发出的ACK,要么什么也不发送。这种行为是由某个配置参数引起的吗?在我们的设置中,我们使用的是默认设置。

顺便说一下,这里是简单的服务器代码:

while (true) {
   try {
      Socket socket = server.accept();
   } 
   catch (IOException e) {}
}

更新

我分析了这个模型,发现当在Windows 8上运行相同的序列时,我得到了一个超时。这与rfc793标准不符,该标准明确规定:

如果连接处于同步状态(已建立、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT),任何不可接受的段(超出窗口序列号或不可接受的确认号)只能引发一个空的确认段,包含当前的发送序列号和一个指示下一个期望接收的序列号的确认,并且连接保持在同一状态。

有没有人能对此提供一些见解?协议的实现是否应该遵循标准,还是说有一定程度的不合规是常见的?我想其中一些是不可避免的,因为标准有时没有规定时间限制,但在这里我们讨论的是控制流程中的不合规。

当然,我也有可能是做错了什么。:)

谢谢,Paul。

2 个回答

1

对感兴趣的朋友来说,我想我找到了“问题”的所在。这是一个小小的与规范不符的地方,需要修复。如果你查看处理收到的段的确认号的代码(可以在这里找到),会发现有一个对确认号是否合格的检查,这个检查是根据RFC 793RFC 5961来设定的。

根据RFC 5961的规定(它是基于RFC 793的),一个确认号只有在满足条件((SND.UNA - MAX.SND.WND) <= SEG.ACK <= SND.NXT)时才是可以接受的。在其他情况下,确认号就被认为不合格,这时应该发送一个ACK。

在代码中,只有当段落的确认号在((SND.UNA-(2^31-1)) <= SEG.ACK < SND.UNA - MAX.SND.WND)这个范围内时,才会发送ACK。如果段落的确认号在((SND.NXT+1 <= SEG.ACK <= SND.UNA - 2^31)这个范围内,他们会直接丢弃这个段,而不发送ACK,尽管在这种情况下,这个段的确认号也是无效的。下面贴出了相关的代码片段。谢谢大家。

 /* If the ack is older than previous acks
 * then we can probably ignore it.
 */
if (before(ack, prior_snd_una)) {
        /* RFC 5961 5.2 [Blind Data Injection Attack].[Mitigation] */
        if (before(ack, prior_snd_una - tp->max_window)) {
                tcp_send_challenge_ack(sk);
                return -1;
        }
        goto old_ack;
}

/* If the ack includes data we haven't sent yet, discard
 * this segment (RFC793 Section 3.9).
 */
if (after(ack, tp->snd_nxt))
        goto invalid_ack;
2

你的服务器是用Java写的吗?我猜你看到的“非确定性”现象可能是因为垃圾回收(GC)的时间问题。如果你手动调用一下 Socket#close(),或者在 InputStream#read() 上等待一下,这个问题可能就会消失。

撰写回答