4

我们面临一个问题,从一段时间后,特定的套接字连接被阻塞,客户端的 tcp 内核不断重传 [ACK] 数据包。

拓扑流程如下:

   Client A ←→ Switch A ← Router A:NAT ← .. Internet .. 
               → Router B:NAT → Switch B ←→ Server B

以下是 WireShark 捕获的数据包:
A) 服务器

1. 8013 > 6757 [PSH, ACK] Seq=56 Ack=132 Win=5840 Len=55     
2. 6757 > 8013 [ACK] Seq=132 Ack=111 Win=65425 Len=0     

B) 客户

//lines 3 and 4 are exactly the same as line 1 and 2      
3. 8013 > 13000 [PSH, ACK] Seq=56 Ack=132 Win=5840 Len=55      
4. 13000 > 8013 [ACK] Seq=132 Ack=111 Win=65425 Len=0     
5. 13000 > 8013 [PSH, ACK] Seq=132 Ack=111 Win=65425 Len=17     

[TCP Retransmission]          
6. 13000 > 8013 [PSH, ACK] Seq=132 Ack=111 Win=65425 Len=17         

8013 是服务器端口,6757 是客户端 NAT 端口。

为什么 TCP 内核继续发送 [ACK] 数据包告诉客户端它收到了数据包 1(参见数据包 4、5 和 6),即使服务器已经收到了一个 [ACK] 数据包(参见数据包 2)?发生问题时,连接的任何一方都不会关闭套接字。

在数据包 6 之后,连接丢失,我们无法再通过该套接字向服务器发送任何内容。

         psuedocode:  
         //client
         serverAddr.port =htons(8013) ;
         serverAddr.ip = inet_addr(publicIPB);
         connect(fdA, serverAddr,...);         

         //server
         listenfd = socket(,SO_STREAM,);
         localAddr.port = htons(8013);
         localAddr.ip = inet_addr(INADDR_ANY);
         bind(localAddr...)
         listen(listenfd, 100);

         ...
         //using select model
         select(fdSet, NULL, NULL, NULL);
         for(...)
         {
         if (FD_ISSET(listenfd))
            {
            ...
              }
         ...
         }

更新
UP1。以下是重现问题的具体步骤

  1. 给定三台计算机,分别是 PC1、PC2 和 PC3。这三个都在RouterA之后,而Server在RouterB之后。

  2. 给定两个用户,即 U1 和 U2。U1 从 PC1 登录,U2 从 PC3 登录。U1 和 U2 都会在自己和服务器之间建立一个 tcp 连接。现在 U1 能够通过其 tcp 连接向 Server 发送数据,然后 Server 将所有数据中继到 U2。直到这一刻一切正常。

    表示U1和Server之间TCP连接的Server端点对应的socket号:U1-OldSocketFd

  3. 不要注销U1,拔掉PC1的线。然后 U1 从 PC2 登录,现在它与服务器建立了新的 TCP 连接。

    表示U1和Server之间的TCP连接的Server端点对应的socket号:U1-NewSocketFd

    在服务器端,当它使用 U1 更新其 Session 时,它会调用close(U1-OldSocketFd).

4.1。在第 3 步之后大约 30 秒,我们发现 U1 无法通过其新的 TCP 连接向服务器发送任何数据。

4.2. 在步骤 3 中,如果 Server 没有close(U1-OldSocketFd) 立即调用(U1 和 Server 之间建立了相同的第二个新连接),而是 Server close(U1-OldSocketFd)在超过 70-80 秒后调用,则一切正常。

UP2。路由器 B 在端口 8013 上使用端口转发
。UP3。运行服务器的 Linux 操作系统的一些参数。

    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_tw_recycle = 1
4

2 回答 2

1

在数据包 1(与 3 相同)和 2(与 4 相同)过去后,您的客户端似乎正在向服务器传输 17 个字节的数据(数据包 5)。我不知道在第一次交换数据包之后多久数据包 5 出现,所以我不知道这种情况发生了多少时间。您的伪代码没有澄清它,因为它只显示了套接字初始化,它没有显示哪一方尝试在什么时间传输什么数据。在这种情况下,梯形图可能有助于表示您的协议交换。

在任何情况下,服务器显然都没有确认这 17 个字节的数据,因此它们被再次传输(数据包 6)。

除非您的网络或防火墙或 NAT 路由器或其他丢弃数据包的东西有问题,否则服务器应该没有任何理由能够接收 TCP 交换的早期部分但显然无法接收数据包 5 或 6 . 再次,在之前的数据交换和数据包 5 之间是否有大量的时间过去(例如,足够的时间让 NAT 路由器、防火墙或负载平衡器使连接失效)?

于 2013-03-04T01:06:29.897 回答
1

根据您重现问题和 UPD3 的步骤,这可能是由于

net.ipv4.tcp_tw_recycle = 1

原因是内核试图在到期时间之前回收 TIME_WAIT 连接(感谢 tw_recycle)。

此答案解释了 tw_reuse 和 tw_recycle 的行为方式(此处对 NAT 部分感兴趣)。

根据重现的步骤和观察4-1和4-2,当你立即调用fclose()时,连接进入TIME_WAIT状态,从那里tw_recycle可以承担并假设由于这边已经关闭了连接,socket可以回收。由于从服务器的角度来看,连接来自同一主机,因此 tw_recycle 启动。

当您在调用 fclose() 之前等待时,由于没有从服务器的 POV 触发断开连接,它将假定连接仍然存在,这会阻止 tw_recycle 启动,可能/可能会强制创建全新的连接。

根据1,为了免受协议 POV 的影响,您有两种情况:

  • 禁用 tw_reuse 和 tw_recycle
  • 启用 tw_reuse,启用 TCP 时间戳,禁用 tw_recycle

鉴于您的网络拓扑,tw_recycle 可能总是会触发无连接条件。

于 2014-01-17T16:01:02.350 回答