Bladeren bron

Add NetConf support, apply patch 10722

olegkap_cp 14 jaren geleden
bovenliggende
commit
23d3eb8ae7

+ 13 - 1
Renci.SshClient/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -112,6 +112,12 @@
     <Compile Include="..\Renci.SshNet\Common\Extensions.cs">
       <Link>Common\Extensions.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Common\NetConfServerException.cs">
+      <Link>Common\NetConfServerException.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Common\NetConfServerException.NET40.cs">
+      <Link>Common\NetConfServerException.NET40.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Common\ObjectIdentifier.cs">
       <Link>Common\ObjectIdentifier.cs</Link>
     </Compile>
@@ -403,6 +409,12 @@
     <Compile Include="..\Renci.SshNet\Messages\Transport\UnimplementedMessage.cs">
       <Link>Messages\Transport\UnimplementedMessage.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\NetConfClient.cs">
+      <Link>NetConfClient.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet\Netconf\NetConfSession.cs">
+      <Link>Netconf\NetConfSession.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\NoneConnectionInfo.cs">
       <Link>NoneConnectionInfo.cs</Link>
     </Compile>
@@ -697,7 +709,7 @@
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 28 - 0
Renci.SshClient/Renci.SshNet/Common/NetConfServerException.NET40.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.Serialization;
+
+namespace Renci.SshNet.Common
+{
+    /// <summary>
+    /// The exception that is thrown when there is something wrong with the server capabilities.
+    /// </summary>
+    [Serializable]
+    public partial class NetConfServerException : SshException
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SshAuthenticationException"/> class.
+        /// </summary>
+        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
+        /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
+        /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> parameter is null. </exception>
+        ///   
+        /// <exception cref="T:System.Runtime.Serialization.SerializationException">The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0). </exception>
+        protected NetConfServerException(SerializationInfo info, StreamingContext context)
+            : base(info, context)
+        {
+        }
+    }
+}

+ 41 - 0
Renci.SshClient/Renci.SshNet/Common/NetConfServerException.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Renci.SshNet.Common
+{
+    /// <summary>
+    /// The exception that is thrown when there is something wrong with the server capabilities.
+    /// </summary>
+    public partial class NetConfServerException : SshException
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="NetConfServerException"/> class.
+        /// </summary>
+        public NetConfServerException()
+        {
+
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="NetConfServerException"/> class.
+        /// </summary>
+        /// <param name="message">The message.</param>
+        public NetConfServerException(string message)
+            : base(message)
+        {
+
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="NetConfServerException"/> class.
+        /// </summary>
+        /// <param name="message">The message.</param>
+        /// <param name="innerException">The inner exception.</param>
+        public NetConfServerException(string message, Exception innerException) :
+            base(message, innerException)
+        {
+        }
+    }
+}

+ 190 - 0
Renci.SshClient/Renci.SshNet/NetConfClient.cs

@@ -0,0 +1,190 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.IO;
+using Renci.SshNet.Sftp;
+using System.Text;
+using Renci.SshNet.Common;
+using System.Globalization;
+using System.Threading;
+using Renci.SshNet.NetConf;
+using System.Xml;
+
+namespace Renci.SshNet
+{
+    /// <summary>
+    /// 
+    /// </summary>
+    public partial class NetConfClient : BaseClient
+    {
+        /// <summary>
+        /// Holds SftpSession instance that used to communicate to the SFTP server
+        /// </summary>
+        private NetConfSession _netConfSession;
+
+        /// <summary>
+        /// Gets or sets the operation timeout.
+        /// </summary>
+        /// <value>The operation timeout.</value>
+        public TimeSpan OperationTimeout { get; set; }
+
+        #region Constructors
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="connectionInfo">The connection info.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is null.</exception>
+        public NetConfClient(ConnectionInfo connectionInfo)
+            : base(connectionInfo)
+        {
+            this.AutomaticMessageIdHandling = true;
+            this.OperationTimeout = new TimeSpan(0, 0, 0, 0, -1);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="host">Connection host.</param>
+        /// <param name="port">Connection port.</param>
+        /// <param name="username">Authentication username.</param>
+        /// <param name="password">Authentication password.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
+        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        public NetConfClient(string host, int port, string username, string password)
+            : this(new PasswordConnectionInfo(host, port, username, password))
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="host">Connection host.</param>
+        /// <param name="username">Authentication username.</param>
+        /// <param name="password">Authentication password.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
+        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>
+        public NetConfClient(string host, string username, string password)
+            : this(host, 22, username, password)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="host">Connection host.</param>
+        /// <param name="port">Connection port.</param>
+        /// <param name="username">Authentication username.</param>
+        /// <param name="keyFiles">Authentication private key file(s) .</param>
+        /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>
+        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
+        public NetConfClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
+            : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SftpClient"/> class.
+        /// </summary>
+        /// <param name="host">Connection host.</param>
+        /// <param name="username">Authentication username.</param>
+        /// <param name="keyFiles">Authentication private key file(s) .</param>
+        /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>
+        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>
+        public NetConfClient(string host, string username, params PrivateKeyFile[] keyFiles)
+            : this(host, 22, username, keyFiles)
+        {
+        }
+
+        #endregion
+
+        /// <summary>
+        /// Gets NetConf server capabilities.
+        /// </summary>
+        public XmlDocument ServerCapabilities 
+        {
+            get
+            {
+                this.EnsureConnection();
+                return this._netConfSession.ServerCapabilities;
+            }
+        }
+
+        /// <summary>
+        /// Gets NetConf client capabilities.
+        /// </summary>
+        public XmlDocument ClientCapabilities
+        {
+            get
+            {
+                this.EnsureConnection();
+                return this._netConfSession.ClientCapabilities;
+            }
+        }
+
+        public bool AutomaticMessageIdHandling { get; set; }
+
+        public XmlDocument SendReceiveRpc(XmlDocument rpc)
+        {
+            this.EnsureConnection();
+            return this._netConfSession.SendReceiveRpc(rpc, this.AutomaticMessageIdHandling);
+        }
+
+        public XmlDocument SendReceiveRpc(string xml)
+        {
+            var rpc = new XmlDocument();
+            rpc.LoadXml(xml);
+            return SendReceiveRpc(rpc);
+        }
+
+        public XmlDocument SendCloseRpc()
+        {
+            this.EnsureConnection();
+
+            XmlDocument rpc = new XmlDocument();
+
+            rpc.LoadXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rpc message-id=\"6666\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><close-session/></rpc>");
+
+            return this._netConfSession.SendReceiveRpc(rpc, this.AutomaticMessageIdHandling);
+        }
+
+        /// <summary>
+        /// Called when client is connected to the server.
+        /// </summary>
+        protected override void OnConnected()
+        {
+            base.OnConnected();
+
+            this._netConfSession = new NetConfSession(this.Session, this.OperationTimeout);
+
+            this._netConfSession.Connect();            
+        }
+
+        /// <summary>
+        /// Called when client is disconnecting from the server.
+        /// </summary>
+        protected override void OnDisconnecting()
+        {
+            base.OnDisconnecting();
+
+            this._netConfSession.Disconnect();
+        }
+
+        /// <summary>
+        /// Releases unmanaged and - optionally - managed resources
+        /// </summary>
+        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (this._netConfSession != null)
+            {
+                this._netConfSession.Dispose();
+                this._netConfSession = null;
+            }
+
+            base.Dispose(disposing);
+        }
+    }
+}

+ 195 - 0
Renci.SshClient/Renci.SshNet/Netconf/NetConfSession.cs

@@ -0,0 +1,195 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Renci.SshNet.Channels;
+using Renci.SshNet.Common;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Globalization;
+using Renci.SshNet.Sftp.Responses;
+using Renci.SshNet.Sftp.Requests;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Messages.Connection;
+using System.Xml;
+using System.Text.RegularExpressions;
+
+namespace Renci.SshNet.NetConf
+{
+    internal class NetConfSession : SubsystemSession
+    {
+        private bool _usingFramingProtocol = false;
+        
+        private const string _prompt = "]]>]]>";
+        
+        private List<byte> _data = new List<byte>(32 * 1024);
+        
+        private EventWaitHandle _serverCapabilitiesConfirmed = new AutoResetEvent(false);
+        
+        private EventWaitHandle _rpcReplyReceived = new AutoResetEvent(false);
+        
+        private StringBuilder _rpcReply = new StringBuilder();
+
+        private int _messageId = 0;
+
+        /// <summary>
+        /// Gets NetConf server capabilities.
+        /// </summary>
+        public XmlDocument ServerCapabilities { get; private set; }
+
+        /// <summary>
+        /// Gets NetConf client capabilities.
+        /// </summary>
+        public XmlDocument ClientCapabilities { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="NetConfSession"/> class.
+        /// </summary>
+        /// <param name="session">The session.</param>
+        /// <param name="operationTimeout">The operation timeout.</param>
+        public NetConfSession(Session session, TimeSpan operationTimeout)
+            : base(session, "netconf", operationTimeout)
+        {
+            ClientCapabilities = new XmlDocument();
+            ClientCapabilities.LoadXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+                                                "<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">" +
+                                                    "<capabilities>" +
+                                                        "<capability>" +
+                                                            "urn:ietf:params:netconf:base:1.0" +
+                                                        "</capability>" +
+                                                    "</capabilities>" +
+                                                "</hello>");
+
+        }
+
+        public XmlDocument SendReceiveRpc(XmlDocument rpc, bool automaticMessageIdHandling)
+        {
+            XmlNamespaceManager ns = null;
+            if (automaticMessageIdHandling)
+            {
+                _messageId++;
+                ns = new XmlNamespaceManager(rpc.NameTable);
+                ns.AddNamespace("nc", "urn:ietf:params:xml:ns:netconf:base:1.0");
+                rpc.SelectSingleNode("/nc:rpc/@message-id", ns).Value = _messageId.ToString();
+            }
+            _rpcReply = new StringBuilder();
+            _rpcReplyReceived.Reset();
+            var reply = new XmlDocument();
+            if (_usingFramingProtocol)
+            {
+                StringBuilder command = new StringBuilder(rpc.InnerXml.Length + 10);
+                command.AppendFormat("\n#{0}\n", rpc.InnerXml.Length);
+                command.Append(rpc.InnerXml);
+                command.Append("\n##\n");
+                SendData(new ChannelDataMessage(this.ChannelNumber, Encoding.UTF8.GetBytes(command.ToString())));
+                this.WaitHandle(this._rpcReplyReceived, this._operationTimeout);
+                reply.LoadXml(_rpcReply.ToString());
+            }
+            else
+            {
+                SendData(new ChannelDataMessage(this.ChannelNumber, Encoding.UTF8.GetBytes(rpc.InnerXml + _prompt)));
+                this.WaitHandle(this._rpcReplyReceived, this._operationTimeout);
+                reply.LoadXml(_rpcReply.ToString());
+            }
+            if (automaticMessageIdHandling)
+            {
+                //string reply_id = rpc.SelectSingleNode("/nc:rpc-reply/@message-id", ns).Value;
+                string reply_id = rpc.SelectSingleNode("/nc:rpc/@message-id", ns).Value;
+                if (reply_id != _messageId.ToString())
+                {
+                    throw new NetConfServerException("The rpc message id does not match the rpc-reply message id."); 
+                }
+            }
+            return reply;
+        }
+
+        protected override void OnChannelOpen()
+        {            
+            string message = string.Format("{0}{1}", this.ClientCapabilities.InnerXml, _prompt);
+
+            this.SendData(new ChannelDataMessage(this.ChannelNumber, Encoding.UTF8.GetBytes(message)));
+
+            this.WaitHandle(this._serverCapabilitiesConfirmed, this._operationTimeout);
+        }
+
+        protected override void OnDataReceived(uint dataTypeCode, byte[] data)
+        {
+            string chunk = Encoding.UTF8.GetString(data);
+
+            if (this.ServerCapabilities == null)   // This must be server capabilities, old protocol
+            {
+                if (!chunk.EndsWith(_prompt))
+                {
+                    throw new NetConfServerException("Server capabilities is not the first message received");
+                }
+                try
+                {
+                    this.ServerCapabilities = new XmlDocument();
+                    this.ServerCapabilities.LoadXml(chunk.Replace(_prompt, ""));
+                }
+                catch (XmlException e)
+                {
+                    throw new NetConfServerException("Server capabilities received are not well formed XML", e);
+                }
+
+                XmlNamespaceManager ns = new XmlNamespaceManager(this.ServerCapabilities.NameTable);
+                
+                ns.AddNamespace("nc", "urn:ietf:params:xml:ns:netconf:base:1.0");
+                
+                this._usingFramingProtocol = (this.ServerCapabilities.SelectSingleNode("/nc:hello/nc:capabilities/nc:capability[text()='urn:ietf:params:netconf:base:1.1']", ns) != null);
+                
+                this._serverCapabilitiesConfirmed.Set();
+            }
+            else if (this._usingFramingProtocol)
+            {
+                int position = 0;
+
+                for (; ; )
+                {
+                    Match match = Regex.Match(chunk.Substring(position), @"\n#(?<length>\d+)\n");
+                    if (!match.Success)
+                    {
+                        break;
+                    }
+                    int fractionLength = Convert.ToInt32(match.Groups["length"].Value);
+                    this._rpcReply.Append(chunk, position + match.Index + match.Length, fractionLength);
+                    position += match.Index + match.Length + fractionLength;
+                }
+                if (Regex.IsMatch(chunk.Substring(position), @"\n##\n"))
+                {
+                    this._rpcReplyReceived.Set();
+                }
+            }
+            else  // Old protocol
+            {
+                if (!chunk.EndsWith(_prompt))
+                {
+                    throw new NetConfServerException("Server XML message does not end with the prompt " + _prompt);
+                }
+                this._rpcReply.Append(chunk.Replace(_prompt, ""));
+                this._rpcReplyReceived.Set();
+            }
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            base.Dispose(disposing);
+
+            if (disposing)
+            {
+                if (this._serverCapabilitiesConfirmed != null)
+                {
+                    this._serverCapabilitiesConfirmed.Dispose();
+                    this._serverCapabilitiesConfirmed = null;
+                }
+
+                if (this._rpcReplyReceived != null)
+                {
+                    this._rpcReplyReceived.Dispose();
+                    this._rpcReplyReceived = null;
+                }
+            }
+        }
+    }
+}

+ 4 - 0
Renci.SshClient/Renci.SshNet/Renci.SshNet.csproj

@@ -85,6 +85,7 @@
     <Compile Include="Common\ChannelEventArgs.cs" />
     <Compile Include="Common\ChannelOpenFailedEventArgs.cs" />
     <Compile Include="Common\ChannelRequestEventArgs.cs" />
+    <Compile Include="Common\NetConfServerException.NET40.cs" />
     <Compile Include="Common\DerData.cs" />
     <Compile Include="Common\ExceptionEventArgs.cs" />
     <Compile Include="Common\Extensions.cs">
@@ -93,6 +94,7 @@
     <Compile Include="Common\Extensions.NET40.cs">
       <SubType>Code</SubType>
     </Compile>
+    <Compile Include="Common\NetConfServerException.cs" />
     <Compile Include="Common\ObjectIdentifier.cs" />
     <Compile Include="Common\PipeStream.cs" />
     <Compile Include="Common\PortForwardEventArgs.cs">
@@ -219,6 +221,8 @@
     <Compile Include="Messages\Transport\ServiceAcceptMessage.cs" />
     <Compile Include="Messages\Transport\ServiceRequestMessage.cs" />
     <Compile Include="Messages\Transport\UnimplementedMessage.cs" />
+    <Compile Include="NetConfClient.cs" />
+    <Compile Include="Netconf\NetConfSession.cs" />
     <Compile Include="NoneConnectionInfo.cs" />
     <Compile Include="PasswordConnectionInfo.cs" />
     <Compile Include="PasswordConnectionInfo.NET40.cs" />