【问题标题】:IdMappedPortTCP now requires "prodding" after telnet connectionIdMappedPortTCP 现在需要在 telnet 连接后“刺激”
【发布时间】:2017-06-09 05:23:55
【问题描述】:

多年来,我一直在特定程序中使用 IdMappedPortTCP 来允许通用端口转发。我正在测试升级的构建/组件环境,但遇到了问题。首先,这是新旧版本信息:

  • 操作系统:W2kSP4 --> 相同(嘿,为什么大家都在笑?)
  • 德尔福:5 --> 7
  • Indy 项目:9.0.0.14 --> 9.[最新 SVN]

我正在使用标准 Windows 控制台 telnet 客户端和 Linux 服务器将其插入 telnet 会话中对其进行测试,我发现行为发生了奇怪的变化。

  • 直接连接:客户端连接,立即看到服务器问候
  • Old Indy:与直接相同
  • New Indy:客户端连接,什么也看不见。按键,看到服务器问候+击键。

下面是事件链的对比:

旧:

6/08/2017  6:47:16 PM - DEBUG: MappedPort-Connect
6/08/2017  6:47:16 PM -   TCP Port Fwd: Connect: 127.0.0.1:4325 --> 127.0.0.1:23
6/08/2017  6:47:16 PM - DEBUG: MappedPort-OutboundConnect
6/08/2017  6:47:16 PM -   TCP Port Fwd: Outbound Connect: 192.168.214.11:4326 --> 192.168.210.101:23
6/08/2017  6:47:16 PM - DEBUG: MappedPort-OutboundData
6/08/2017  6:47:16 PM - DEBUG: MappedPort-Execute
6/08/2017  6:47:16 PM - DEBUG: MappedPort-OutboundData
6/08/2017  6:47:16 PM - DEBUG: MappedPort-Execute
6/08/2017  6:47:16 PM - DEBUG: MappedPort-OutboundData
...

新功能:

6/08/2017  6:41:34 PM - DEBUG: MappedPort-Connect
6/08/2017  6:41:34 PM -   TCP Port Fwd: Connect: 127.0.0.1:1085 --> 127.0.0.1:23
6/08/2017  6:41:34 PM - DEBUG: MappedPort-OutboundConnect
6/08/2017  6:41:34 PM -   TCP Port Fwd: Outbound Connect: 192.168.214.59:1086 --> 192.168.210.101:23
6/08/2017  6:47:36 PM - DEBUG: MappedPort-Execute
6/08/2017  6:47:36 PM - DEBUG: MappedPort-OutboundData
6/08/2017  6:47:36 PM - DEBUG: MappedPort-Execute
6/08/2017  6:47:36 PM - DEBUG: MappedPort-OutboundData
6/08/2017  6:47:36 PM - DEBUG: MappedPort-Execute

在第一个中,您在连接后立即看到 OutboundData。在第二个中,连接后什么都没有发生,直到我发送了一个击键(6 分钟后),此时您会看到 Execute,然后是第一个 OutboundData 事件。

这让我疑惑:是真的连接到服务器只延迟了输出,还是连接本身延迟了?

我的第一个结论是连接本身被延迟,这就是原因。服务器在登录提示符处有 1 分钟的超时。如果您连接并收到问候但只是坐在那里,服务器会在一分钟后断开连接。使用新的 Indy 版本,我在连接事件后坐了整整 6 分钟,然后毫无问题地得到服务器问候。

但是... NETSTAT 显示在记录连接事件后不久建立的与远程服务器的连接!所以,我只能得出这样的结论:确实建立了连接,但也许某些初始字符正在被“吃掉”,或者导致 getty 在击键之前无法参与?

有什么建议吗?您是否知道我可能会寻找的任何改变——我应该做但没有做的事情?任何见解都值得赞赏。

(除非有任何好的线索,我猜我侦查的下一步可能是用 WireShark 嗅探两台机器,看看连接后发生了什么。)

更新:Wireshark(单腿)

从机器外部捕获的数据包显示 MappedPort 和服务器之间的流量(但不是客户端和 MappedPort 之间的流量)显示 telnet 服务器发送“Do Authenticate”,客户端(通过 MappedPort)回复 w/一个“将认证”。接下来是服务器发送验证子选项(并且客户端同意),然后是所有其他 telnet 选项。最后,在看到登录文本后,客户端发送“do echo”,他们都坐在那里直到 1 分钟后,此时服务器发送 TCP FIN 以关闭连接。那是“好旧”的版本。

在新版本中,客户端不响应“Will Authenticate”,并且他们都无限期地坐在那里。 (嗯,我想知道这在服务器资源方面有什么关系——可能是很好的 DOS 攻击。不过,它一个旧的 telnet 守护进程,所以它现在可能已经修复了......)我终于发送了第一个按键,这就是它在那个数据包中发送的全部内容。然后客户端发送“将进行身份验证”(无需来自服务器的额外刺激),协商继续正常进行;来自服务器的最后一个数据包(包含回显参数)也包括输入的回显字符。因此,就好像客户端没有看到来自服务器的初始“进行身份验证”数据包,但是一旦您开始输入,就会继续并做出响应,就好像它刚刚听到它一样(一旦它发送了击键)。

6/13 更新:Wireshark(双腿)

我捕获了“断断续续”的对话的两条腿并对其进行了分析。有趣的行为。底线: 一旦服务器获得 TCP 连接,它就会发回 Telnet-DoAuth 邀请。 IdMappedPortTCP 会保留该数据包,但不会将其传递给客户端——目前还没有。一旦客户端最终发送第一次击键(几秒钟或几分钟后),Id 将其传递给服务器。 那么 Id 将从服务器获得的 DoAuth 数据包传递到客户端。

以下是对数据包的更详细统计:

65 11-59 TCP Syn
67 59-11 TCP SynAck
69 11-59 TCP Ack
71 59-101 TCP Syn
73 101-59 TCP SynAck
74 59-101 TCP Ack
76 101-59 DoAuth
77 59-101 TCP Ack
nothing for 23 seconds
79 11-59 Data:\r\n (I pressed Enter)
81 59-101 Data:\r\n
83 59-11 DoAuth
85 11-59 WillAuth
87 101-59 TCP Ack
88 59-101 WillAuth
90 101-59 TCP Ack
91 101-59 Authentication option
92 59-11 Authentication option
94 11-59 Authentication option reply
96 59-101 Authentication option reply
98 101-59 Will/do Encryption/terminal/env options
99 59-101 Will/do Encryption/terminal/env options
101 11-59 Don't encrypt
103 59-101 Don't encrypt
105 101-59 TCP Ack
106 59-11 TCP Ack
108 11-59 Won't/will list
110 59-101 Won't/will list
112 101-59 TCP Ack
113 101-59 Do window size
114 59-11 Do window size

数据包转储行格式: Pkt# From-To Payload

(不要介意数据包#跳过;客户端和代理都在运行捕获的机器托管的 VM 上运行,因此 Wireshark 看到了两个数据包副本。我只包括了 pkt# 所以我如果我愿意,可以稍后参考原始转储。)

从/到机器:

10 = Linux client (see below)
11 = Windows client
59 = proxy
101 = server

一个有趣的转移:Linux 客户端

虽然我的所有测试都使用了各种 Windows 客户端(因为那是生产中使用的),但我“意外地”使用了 Linux(因为那是我在我的工作站上运行的,我运行 Wireshark),因为它很方便。该客户的行为不同 - 更积极 - 从而避免了问题。这是一个转储的样子:

1 10-59 TCP Syn
2 59-10 TCP SynAck
3 10 59 TCP Ack
4 10-59 Do/Will list
5 59-101 TCP Syn
7 101-59 TCP SynAck
8 59-101 TCP Ack
10 59-101 Do/Will list
12 101-59 TCP Ack
13 101-59 DoAuth
14 59-10 DoAuth
15 10-59 TCP Auth
16 10-59 WontAuth
17 59-101 WontAuth
19 101-59 Will/Do list
20 59-10 Will/Do list
21 10-50 Do window size
22 59-101 Do window size

如您所见,客户端不会等待 telnet 服务器先说话——只要 TCP 连接建立,它就会发送一个完整的 Do/Will 列表。一旦 Id 打开该连接,这又会传递到服务器。服务器发回与之前最初所做的相同的“DoAuth”;不同之处在于,这一次,已经传递了来自客户端的流量,Id 立即传递它。然后客户端发送身份验证标志,事情就会继续进行。

所以,如果客户端先说话,IdMappedPortTCP 就可以了;只有当服务器先说话时,它才会保留它的消息,并且在客户端说什么之前不会将其传递给客户端。

9/27 更新:发现代码更改

降级到 9.0.0.14 解决了这个问题。对比两个版本的IdMappedPortTCP.pas源代码,我发现唯一的区别是新版本在procedure TIdMappedPortThread.OutboundConnect中增加了一段代码:

  DoOutboundClientConnect(Self);

  FNetData := Connection.CurrentReadBuffer;
  if Length(FNetData) > 0 then begin
    DoLocalClientData(Self);
    FOutboundClient.Write(FNetData);
  end;//if

except

(第一行和最后一行已经存在,仅显示上下文。)

我确认将该代码添加到 9.0.0.14 会产生问题。

我检查了 SVN 存储库,您在 2008 年 9 月 7 日添加了违规代码。提交评论是:

更新了 TIdMappedPortThread.OutboundConnect() 以检查待处理 OnOutboundConnect 之后入站客户端的 InputBuffer 中的数据 事件处理程序退出。

我不完全理解更改的原因或影响——显然你有充分的理由这样做——但它似乎确实产生了我描述的效果(“保持”服务器的初始输出直到客户端发送一些东西)。

【问题讨论】:

  • TIdMappedPortTCP 的行为没有改变。它必须是服务器端的更改。特别是如果服务器问候被延迟到客户端发送数据之前。 TIdMappedPortTCP 对这种行为没有影响,因为它只是监视套接字状态。使用数据包嗅探器(如 Wireshark)验证行为。或者直接telnet到服务器。
  • 谢谢,雷米。我希望你会出现!很高兴知道组件中没有已知的更改。不是服务器更改,因为 (1) 相同的行为更改发生在具有不同操作系统类型和版本的 6 台不同服务器上,以及 (2) 我正在针对同一台服务器同时测试两个版本。如果它不是 Indy 代码...我知道的其他不同之处是用于构建它的 Delphi 版本,只是不同的构建机器。一旦我对两条腿(client-fwd-server)进行同步数据包跟踪,我肯定会学到更多,希望是今天或周一。
  • 如果不捕获客户端和TIdMappedPortTCP 之间的关系,就很难诊断出问题是在客户端本身还是在TIdMappedPortTCP。附带说明一下,如果您升级到 Indy 10,它有一个特定于 Telnet 连接的TIdMappedTelnet 组件。 Indy 9 没有。尽管TIdMappedPortTCP 应该可以正常工作。
  • 从“6/13 更新:Wireshark(双腿)”开始添加更多信息。关于缩小延迟事件原因的最佳方法的想法?这就像第一个“从服务器获取数据”事件在从客户端获取数据之前不会触发。显然,从最后一句话的措辞来看,我对 Indy 的内部运作并不熟悉。 :-) 不知道如何才能更好地确定应该发生的事情。
  • 原来 Indy 9 也有 TIdMappedTelnet。你试过了吗?无论如何,您所描述的不是TIdMappedPortTCP 的运作方式。它不会保留等待客户端首先发送数据的服务器数据。两个连接(客户端到 Indy,Indy 到服务器)在单个线程循环中同时监控。在每次迭代中,如果客户端发送了任何数据,则将其发送到服务器,如果服务器已发送任何数据,则将其发送到客户端。他们不相互依赖。服务器可以先发送(例如问候和握手等)。

标签: delphi-7 indy-9


【解决方案1】:

在 Indy 9 中,TIdTCPConnection.CurrentReadBuffer() 调用 TIdTCPConnection.ReadFromStack() 然后返回存储在 TIdTCPConnection.InputBuffer 属性中的任何数据:

function TIdTCPConnection.CurrentReadBuffer: string;
begin
  Result := '';
  if Connected then begin
    ReadFromStack(False); // <-- here
  end;
  Result := InputBuffer.Extract(InputBuffer.Size);
end;

不管InputBuffer 中可能已经存在什么,ReadFromStack() 都会等待套接字接收 数据以附加到InputBuffer。在新数据实际到达或指定的ReadTimeout 间隔过去之前,它不会退出。 TIdTCPConnection.ReadTimeout 属性默认设置为 0,所以当CurrentReadBuffer() 调用ReadFromStack() 时,最终会使用无限超时:

function TIdTCPConnection.ReadFromStack(const ARaiseExceptionIfDisconnected: Boolean = True;
  ATimeout: Integer = IdTimeoutDefault; const ARaiseExceptionOnTimeout: Boolean = True): Integer;
// Reads any data in tcp/ip buffer and puts it into Indy buffer
// This must be the ONLY raw read from Winsock routine
// This must be the ONLY call to RECV - all data goes thru this method
var
  i: Integer;
  LByteCount: Integer;
begin
  if ATimeout = IdTimeoutDefault then begin
    if ReadTimeOut = 0 then begin
      ATimeout := IdTimeoutInfinite; // <-- here
    end else begin
      ATimeout := FReadTimeout;
    end;
  end;
  ...
end;

因此,当TIdMappedPortTCP.OutboundConnect() 在将其OutboundClient 连接到服务器后调用CurrentReadBuffer() 时,它确实会等待来自客户端的数据到达,然后再从服务器读取数据。为避免这种情况,您可以在TIdMappedPortTCP.OnConnectTIdMappedPortTCP.OnOutboundConnect 事件中设置一个非无限的ReadTimeout 值,例如:

AThread.Connection.ReadTimeout := 1;

在 Indy 10 中,此问题已在 TIdMappedPortTCP 中得到解决,方法是避免在连接到服务器后对客户端数据进行初始等待。我现在已经在 Indy 9 中更新了 TIdMappedPortTCP 来做同样的事情。

【讨论】:

  • 非常感谢!我在 SVN 存储库中看到了您的更改,并验证它解决了我的问题。
猜你喜欢
  • 2020-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-20
  • 2011-05-30
  • 2017-04-28
  • 1970-01-01
相关资源
最近更新 更多