0

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

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

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

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

下面是事件链的比较:

老的:

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)回复“Will认证”。接下来是服务器发送验证子选项(并且客户端同意),然后是所有其他 telnet 选项。最后,在看到登录文本后,客户端发送“do echo”,他们都坐在那里直到 1 分钟后,此时服务器发送 TCP FIN 以关闭连接。那是“好旧”的版本。

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

6/13 更新:Wireshark(双腿)

我捕获了“中断”对话的两条腿并对其进行了分析。有趣的行为。底线:一旦服务器获得 TCP 连接,它就会发回 Telnet-DoAuth 邀请。 IdMappedPortTCP 保留该数据包,但尚未将其传递给客户端。一旦客户端最终发送第一次击键(几秒钟或几分钟后),Id 将其传递给服务器。THEN 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源代码,我发现唯一的区别是新版本在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 中的待处理数据。

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

4

1 回答 1

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之前,它不会退出。ReadTimeoutTIdTCPConnection.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()调用时,它确实会等待数据从客户端到达,然后再从服务器读取数据。为避免这种情况,您可以在or事件中设置一个非无限值,例如:CurrentReadBuffer()OutboundClientReadTimeoutTIdMappedPortTCP.OnConnectTIdMappedPortTCP.OnOutboundConnect

AThread.Connection.ReadTimeout := 1;

在 Indy 10 中,TIdMappedPortTCP通过避免在连接到服务器后对客户端数据进行初始等待来解决此问题。我现在已经TIdMappedPortTCP在 Indy 9 中进行了更新以执行相同的操作。

于 2017-09-27T21:46:45.047 回答