Ticket #5409 enhancement new
Sending SSH client version & KEX before receiving server version does not work with some SSH servers
|Reported by:||philmayers||Owned by:|
First of all - let me start by saying that I know what the SSH2 RFCs say, and I know that the behaviour below is legal per-spec. Nonetheless, using current versions, it is impossible to connect to some SSH servers, for example the SSH server in Cisco IOS 12.2(33)SXJ1.
I am hopeful that, despite this being a server bug, you will accept a workaround. I note that this workaround will make the Twisted Conch code work the same as the OpenSSH code, which from a compatibility point of view, can only be a good thing.
So: the current conch code has:
class SSHTransportBase(protocol.Protocol): def connectionMade(self): # code to send our version string & KEX
This can result in the following behaviour on the wire:
C: SYN S: SYN,ACK C: ACK C: PSH "SSH-2.0-Twisted\n<kex packet>" S: PSH "SSH-1.99-Cisco-1.25\n"
...and the connection hangs. Clearly IOS is buggy; it is obviously doing something where it is not starting to read on the TCP socket until after the banner has been written.
On faster Cisco platforms (e.g. newer 6500s) the Cisco box "wins the race" and the hang does not happen, but I'm sure the bug is still there.
Speaking from experience, Cisco WILL NOT FIX this bug in anything less than 3-5 years. There is no realistic chance of using Twisted to talk to affected boxes without a fix client-side.
The fix can be done with a relatively trivial sub-class e.g.
class ClientTransport(transport.SSHClientTransport): # We MUST NOT send our banner until after they've sent theirs bannerSent = False def connectionMade(self): log.msg('ssh connection made') def connectionMade2(self): transport.SSHClientTransport.connectionMade(self) def dataReceived(self, data): if not self.bannerSent: self.bannerSent = True self.connectionMade2() return transport.SSHClientTransport.dataReceived(self, data)
However, this is unsatisfactory for two reasons. Firstly, it requires overriding connectionMade but not calling the superclass method until later on. This might cause problems if the connectionMade method ever does more work. Secondly, the simple override of dataReceived above is naive, and does not actually parse the received data to ensure the banner has been wholly received.
Rather than ask for a hacky workaround, can I suggest that the transport base class and subclass be modified along the following lines:
class SSHTransportBase(protocol.Protocol): def sendVersionKex(self): """send our own version & KEX packet""" def bannerReceived(self, banner): """called when the banner is received; can be overridden""" def connectionMade(self): # any non client/server specific setup class SSHServerTransport(SSHTransportBase): def connectionMade(self): SSHTransportBase.connectionMade(self) # only send the the version/kex at connect in servers self.sendVersionKex() class SSHClientTransport(SSHTransportBase): def bannerReceived(self, banner): # wait for the banner to send version/kex in clients self.sendVersionKex()
If this proposal is acceptable, I'll work up a patch.