我在特定程序中使用IdMappedPortTCP来允许通用端口转发多年。我正在测试升级的构建/组件环境,我遇到了一个问题。首先,这里是旧的&新版本信息:
我通过使用标准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分钟后),此时您会看到执行,然后是第一个OutboundData事件。
这让我想知道:它是真的连接到服务器而只是延迟输出,还是连接本身被延迟了?
我的第一个结论是连接本身被推迟了,这就是原因。服务器在登录提示符下超时1分钟。如果您连接并获得问候语但只是坐在那里,服务器会在一分钟后断开连接。使用新的Indy版本,我在连接事件后坐了6分钟,然后得到服务器问候语没有问题。
然而...... NETSTAT显示连接事件记录后很快建立的与远程服务器的连接!所以,我还得出结论认为这种联系确实已经建立起来,但也许某些最初的角色正在被“吃掉”#34;或者是什么导致getty在获得击键之前不会参与?
有什么建议吗?你是否意识到我可能会寻找的任何改变 - 我应该做的事情但不是吗?任何见解都表示赞赏。
(除非有任何良好的线索,我想我的侦探的下一步可能是嗅探两台机器w / WireShark,看看连接后发生了什么。)
更新: Wireshark(单腿)
从机器外部捕获数据包,显示MappedPort和&之间的流量。服务器(但不是客户端和MappedPort之间的流量)显示telnet服务器发送" Do Authenticate",客户端(通过MappedPort)回复w / a"将验证" ;。接下来是服务器发送身份验证子选项(和客户端同意),然后是所有其他telnet选项。最后,在看到登录文本后,客户端发送"做回声"他们都坐在那里直到1分钟后,服务器发送TCP FIN来关闭连接。这是"好老"版本
在新版本中,客户端没有响应" Will Authenticate",他们都无限期地坐在那里。 (嗯,我想知道在服务器资源方面的优势是什么 - 可能是很好的DOS攻击。但是 是一个旧的telnet守护进程,所以它现在可能已经修复了。 ..)当我最后发送第一次击键时,那就是在该数据包中发送的全部内容。然后客户端发送"将验证" (没有来自服务器的额外刺激)并且协商继续完全正常;来自服务器的最后一个数据包(包含echo参数)也包括输入的回显字符。所以它就像客户没有看到最初的"做认证"来自服务器的数据包,但是一旦你开始输入,继续进行响应就好像它刚刚听到它一样(一旦它发出击键)。
6/13更新: Wireshark(双腿)
我抓住了"破碎的#34;谈话并分析它。有趣的行为。底线: 一旦服务器获得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
(不要介意数据包#skip;客户端和代理都在我运行捕获的机器托管的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立即传递它。然后客户端发送auth标志,事情就一直向前移动。
因此,如果客户首先说话,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仓库,并在9/7/2008添加了违规代码。提交评论是:
更新了TIdMappedPortThread.OutboundConnect()以检查待处理 OnOutboundConnect之后入站客户端的InputBuffer中的数据 事件处理程序退出。
我不完全理解变化的原因或影响 - 显然你有充分的理由这样做 - 但它确实产生了我所描述的效果("坚持&# 34;服务器的初始输出,直到客户端发送内容。)
答案 0 :(得分: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()
在将CurrentReadBuffer()
连接到服务器之后调用OutboundClient
时,它确实会等待数据从客户端到达,然后再从服务器读取数据。为避免这种情况,您可以在ReadTimeout
或TIdMappedPortTCP.OnConnect
事件中设置非无限TIdMappedPortTCP.OnOutboundConnect
值,例如:
AThread.Connection.ReadTimeout := 1;
在Indy 10中,通过避免在连接到服务器之后对客户端数据进行初始等待,在TIdMappedPortTCP
中修复了此问题。我现在更新了Indy 9中的TIdMappedPortTCP
来做同样的事情。