Add NetConf support, apply patch 10722

This commit is contained in:
olegkap_cp
2011-11-16 03:46:00 +00:00
parent aa10395ebc
commit 23d3eb8ae7
6 changed files with 471 additions and 1 deletions
@@ -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.
@@ -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)
{
}
}
}
@@ -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)
{
}
}
}
@@ -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);
}
}
}
@@ -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;
}
}
}
}
}
@@ -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" />