Skip to content

Commit 60069be

Browse files
committed
Cisco connection issue fix
Certain Cisco devices do not adhere to RFC4342 and do not reply if the client identifies first. Since identifcation can be in random order it will give random connection issues because the SSH_MSG_KEXINIT will not be sent if the client is faster. Since SSH.Net is not at fault and compatibility with Cisco (and possibly other) devices is something that can easily be supported I've written this modification. Added LazyIdentification to the ConnectionInfo object to allow late identification in ProtocolVersionExchange. Overloaded 'Start' function to keep the original functionality and tests intact. Highly likely fixes issues sshnet#752, sshnet#778, sshnet#469 and might help with sshnet#798, sshnet#767, sshnet#807
1 parent f072c5f commit 60069be

File tree

4 files changed

+50
-4
lines changed

4 files changed

+50
-4
lines changed

src/Renci.SshNet/Connection/IProtocolVersionExchange.cs

+12
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,17 @@ internal interface IProtocolVersionExchange
1818
/// The SSH identification of the server.
1919
/// </returns>
2020
SshIdentification Start(string clientVersion, Socket socket, TimeSpan timeout);
21+
22+
/// <summary>
23+
/// Performs the SSH protocol version exchange.
24+
/// </summary>
25+
/// <param name="clientVersion">The identification string of the SSH client.</param>
26+
/// <param name="socket">A <see cref="Socket"/> connected to the server.</param>
27+
/// <param name="timeout">The maximum time to wait for the server to respond.</param>
28+
/// <param name="lazyIdentification">Allow server to identify itself first.</param>
29+
/// <returns>
30+
/// The SSH identification of the server.
31+
/// </returns>
32+
SshIdentification Start(string clientVersion, Socket socket, TimeSpan timeout, bool lazyIdentification);
2133
}
2234
}

src/Renci.SshNet/Connection/ProtocolVersionExchange.cs

+27-3
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,27 @@ internal class ProtocolVersionExchange : IProtocolVersionExchange
3737
/// </returns>
3838
public SshIdentification Start(string clientVersion, Socket socket, TimeSpan timeout)
3939
{
40-
// Immediately send the identification string since the spec states both sides MUST send an identification string
41-
// when the connection has been established
42-
SocketAbstraction.Send(socket, Encoding.UTF8.GetBytes(clientVersion + "\x0D\x0A"));
40+
return Start(clientVersion, socket, timeout, false);
41+
}
42+
43+
/// <summary>
44+
/// Performs the SSH protocol version exchange.
45+
/// </summary>
46+
/// <param name="clientVersion">The identification string of the SSH client.</param>
47+
/// <param name="socket">A <see cref="Socket"/> connected to the server.</param>
48+
/// <param name="timeout">The maximum time to wait for the server to respond.</param>
49+
/// <param name="lazyIdentification">Allow server to identify itself first.</param>
50+
/// <returns>
51+
/// The SSH identification of the server.
52+
/// </returns>
53+
public SshIdentification Start(string clientVersion, Socket socket, TimeSpan timeout, bool lazyIdentification)
54+
{
55+
if (!lazyIdentification)
56+
{
57+
// Immediately send the identification string since the spec states both sides MUST send an identification string
58+
// when the connection has been established
59+
SocketAbstraction.Send(socket, Encoding.UTF8.GetBytes(clientVersion + "\x0D\x0A"));
60+
}
4361

4462
var bytesReceived = new List<byte>();
4563

@@ -71,6 +89,12 @@ public SshIdentification Start(string clientVersion, Socket socket, TimeSpan tim
7189
var identificationMatch = ServerVersionRe.Match(line);
7290
if (identificationMatch.Success)
7391
{
92+
if (lazyIdentification)
93+
{
94+
// Send identification only after server identification has been validated.
95+
SocketAbstraction.Send(socket, Encoding.UTF8.GetBytes(clientVersion + "\x0D\x0A"));
96+
}
97+
7498
return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"),
7599
GetGroupValue(identificationMatch, "softwareversion"),
76100
GetGroupValue(identificationMatch, "comments"));

src/Renci.SshNet/ConnectionInfo.cs

+10
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ public class ConnectionInfo : IConnectionInfoInternal
183183
/// </value>
184184
public int MaxSessions { get; set; }
185185

186+
/// <summary>
187+
/// Gets or sets if the client should identify itself later.
188+
/// </summary>
189+
/// <value>
190+
/// <c>false</c>, the default for strict RFC4253 compliance where both sides identify at the same time. <c>true</c> if the client should wait for the server identification.
191+
/// </value>
192+
public bool LazyIdentification { get; set; }
193+
186194
/// <summary>
187195
/// Occurs when authentication banner is sent by the server.
188196
/// </summary>
@@ -432,6 +440,8 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
432440
ProxyPassword = proxyPassword;
433441

434442
AuthenticationMethods = authenticationMethods;
443+
444+
LazyIdentification = false;
435445
}
436446

437447
/// <summary>

src/Renci.SshNet/Session.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ public void Connect()
588588
.Connect(ConnectionInfo);
589589

590590
var serverIdentification = _serviceFactory.CreateProtocolVersionExchange()
591-
.Start(ClientVersion, _socket, ConnectionInfo.Timeout);
591+
.Start(ClientVersion, _socket, ConnectionInfo.Timeout, ConnectionInfo.LazyIdentification);
592592

593593
// Set connection versions
594594
ServerVersion = ConnectionInfo.ServerVersion = serverIdentification.ToString();

0 commit comments

Comments
 (0)