sshnet / SSH.NET

SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism.
http://sshnet.github.io/SSH.NET/
MIT License
3.95k stars 930 forks source link

Issue with NetConf : ArgumentOutOfRangeException #69

Open lafrank opened 8 years ago

lafrank commented 8 years ago

When an rpc-reply message is received, this exception is thrown in NetconfSession.OnDataReceived event handler. The cause is that the received data length is 1015 byte, and the decoded message string contains a magic number at the beginning as below:

1585

<?xml version="1.0"?>

.... This number is matched with a regex then is used to append this length of data to a stringbuilder, namely to _rpcreply object, like : ``` c# var match = Regex.Match(chunk.Substring(position), @"\n#(?\d+)\n"); if (!match.Success) { break; } var fractionLength = Convert.ToInt32(match.Groups["length"].Value); _rpcReply.Append(chunk, position + match.Index + match.Length, fractionLength); ``` where the value of position is the number at the beginning of the message, that is, 1585 in this example. It is obvious that the Append operation will fail as one can't append 1585 byte from an 1015 byte length string. What I can't figure out is where this magic number is coming from, as RFC6241 apparently does not contain such - lets call it - segment length element ? So where is it coming from, can anyone tell ?
lafrank commented 8 years ago

I found RFC6242 describing Netconf over SSH and chunked framing. The problem is that SSH.Net somehow split data - or whatever does splitting - and as a result the first call to OnDataReceived does not contain the full payload, only part of it.

lafrank commented 8 years ago

I suggest modifying NetconfSession.OnDataReceived() to capture the full frame payload before parsing it

      // code omitted      
      else if (_usingFramingProtocol)
      {
        _fullFrame.Append(chunk);
        if (Regex.IsMatch(chunk, @"\n##\n")) 
        {
          int position = 0;
          string payload = _fullFrame.ToString();
          for (;;)
          {
            var match = Regex.Match(payload.Substring(position), @"\n#(?<length>\d+)\n");
            if (!match.Success)
            {
              break;
            }
            int fractionLength = Convert.ToInt32(match.Groups["length"].Value);
            _rpcReply.Append(payload, position + match.Index + match.Length, fractionLength);
            position += match.Index + match.Length + fractionLength;
          }
          _fullFrame.Clear();
          _rpcReplyReceived.Set();
        }
      }
     // code omitted

An in class declaration add _fullFrame as below :

namespace Renci.SshNet.NetConf
{
  internal class NetConfSession : SubsystemSession, INetConfSession
  {
    private const string Prompt = "]]>]]>";

    private readonly StringBuilder _data = new StringBuilder();
    private bool _usingFramingProtocol;
    private EventWaitHandle _serverCapabilitiesConfirmed = new AutoResetEvent(false);
    private EventWaitHandle _rpcReplyReceived = new AutoResetEvent(false);
    private StringBuilder _rpcReply = new StringBuilder();
    private StringBuilder _fullFrame = new StringBuilder();

This modification preserves original functionality but fixes the issue with split payload.