防止TCP套接字连接重试

6

如何防止TCP进行多个套接字连接尝试?

背景

我正在尝试粗略估计到客户端的往返时间(RTT)。我需要使用的高级协议没有办法确定RTT,也没有任何形式的no-op请求/响应流。因此,我试图直接从较低的层面获取信息。特别是,我知道客户端将主动拒绝特定端口上的TCP连接尝试。

Me -> Client: SYN
Client -> Me: ACK, RST

代码

  long lStartTime = System.nanoTime() / 1000000;
  long lEndTime;

  // Attempt to connect to the remote party.  We don't mind whether this
  // succeeds or fails.
  try
  {
    // Connect to the remote system.
    lSocket.connect(mTarget, MAX_PING_TIME_MS);

    // Record the end time.
    lEndTime = System.nanoTime() / 1000000;

    // Close the socket.
    lSocket.close();
  }
  catch (SocketTimeoutException|IOException lEx)
  {
    lEndTime = System.nanoTime() / 1000000;
  }

  // Calculate the interval.
  lInterval = lEndTime - lStartTime;
  System.out.println("Interval = " + lInterval);

问题

使用Wireshark,我发现对lSocket.connect的调用在放弃之前尝试连接套接字三次(都失败了),每次间隔似乎是任意的(通常约为300毫秒)。

Me -> Client: SYN
Client -> Me: ACK, RST
Me -> Client: SYN
Client -> Me: ACK, RST
Me -> Client: SYN
Client -> Me: ACK, RST

问题

是否有一种方法让TCP在仅经历一个SYN/RST对后放弃连接尝试?

我查看了一些Java代码。当我看到AbstractPlainSocketImpl中的注释时,我想知道我是否找到了解决方案...

/**
 * The workhorse of the connection operation.  Tries several times to
 * establish a connection to the given <host, port>.  If unsuccessful,
 * throws an IOException indicating what went wrong.
 */

但遗憾的是,在我查看的那个函数或任何其他(非本机)函数中,都没有循环/重试的证据。

这种重试行为实际上来自哪里?如何控制它?

替代方案

我可能也会接受替代方案,但不包括以下内容:

  • 使用ICMP回显请求(ping)。 我知道许多客户端不会响应它们。
  • 使用原始套接字。 其中一个平台是Windows,现在严重限制了使用原始套接字的能力。(我还认为Linux网络堆栈会不理智地跳入其中,如果应用程序尝试使用原始套接字来执行TCP。)
  • 除了作为最后的手段之外,不使用JNI。 我的代码需要在至少两个非常不同的操作系统上运行。

嗯,我没明白... 如果你收到一个RST,通常会得到ECONNREFUSED,Java会将其包装在SocketException中(不记得是哪个了) - fge
我会得到异常(最终进入IOException分支),但只有在某些底层层次尝试并失败3次后才会出现。 - Andrew Rose
1个回答

10
TCP连接重试是操作系统的套接字实现功能。配置此功能取决于平台。请参见https://security.stackexchange.com/questions/34607/why-is-the-server-returning-3-syn-ack-packets-during-a-syn-scan,了解这是什么以及为什么会发生这种情况。
在Windows上,您应该能够在注册表中修改重试计数: HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpMaxConnectRetransmissions 有关RTT的设置也在该文档中详细说明。
在Linux上,链接到Security文章的被接受的答案讨论了如何配置此参数。
在Linux系统中,查看/proc/sys/net/ipv4/中的特殊文件tcp_syn_retriestcp_synack_retries:它们包含内核在给定连接上发出SYN(分别是SYN+ACK)的次数...这很简单,只需执行echo 3 > tcp_synack_retries...请注意,这是一个系统范围的设置。您可以通过读取注册表设置(在Windows上)或读取特殊文件的内容(在Linux上)来读取当前值。此外,MSDN关于Windows上TCP连接RTT的解释如下:

TCP/IP会随着时间调整重传频率。每个接口的原始传输和第一次重传之间的延迟由TcpInitialRTT条目的值确定。默认情况下,它是三秒钟。此延迟在每次尝试后加倍。在最后一次尝试之后,TCP/IP等待一个等于最后一个延迟的两倍的时间间隔,然后放弃连接请求。

顺便提一下,关于原始套接字 - 是的,你会遇到极大的困难。另外,自Windows XP SP2以来,Windows在任何情况下都不允许你指定原始套接字的TCP协议号码(请参阅限制)。

此外,顺带一提:确保TCP连接没有被客户端前面的独立防火墙阻塞,否则你只能测量到防火墙的往返时间。


这回答了主要问题,谢谢。有趣的是,当远端发送RST而不是超时时,TcpInitialRTT和加倍方法似乎都不适用。有人有估算RTT的其他建议吗? - Andrew Rose
@AndrewRose 是的,我也注意到 TcpInitialRTT 的记录行为与您报告的行为不符;很抱歉我没有提及它而是将其搁置。这个我无法解释。也许您可以连接到一个您知道 开放的 TCP 端口(如果有的话),并测量完成握手所需的时间;必须进行来回交流。然而,总的来说,任何使用 TCP 的操作都会因为额外的来回复杂性相对于 ICMP 等协议而增加一些不准确性。 - Jason C
1
这个是否已经更新到2022年了? - Bharel
@Bharela 不确定;虽然微软有一个从未真正改变底层的习惯,但我必须测试才能确认。实际上,我可能永远不会这样做,哈哈。但如果有人想要测试,那可能会很有价值。 - Jason C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接