Files
ssh.net/src/Renci.SshNet/NetConfClient.cs
T
mus65 4e02502bdf Add .NET 10 target and make use of C#14 extension members (#1672)
* Add .NET 10 target

* fix IDE0031

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0031

* fix ca5399

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca5399

* fix ca1515

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1515

* fix ca2002

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2002

* fix ca1508

new false positives.

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1508

* fix ca2000

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2000

* fix ca2025

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2025

* fix ca1849

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1849

* fix Reverse() overloads

because of https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/10.0/csharp-overload-resolution

* supress CA2002

* Use extension members for ThrowHelpers

* use extension members for CryptoAbstractions

* use extension member for DateTime.UnixEpoch

* use extension members for string.Join etc

* use extension members for Convert.To/FromHexString

* disable CA1508

* Update .NET 10 RC2

* Workaround Build Regression in .NET 10 RC2

https://github.com/dotnet/sdk/issues/51265

* suppress new warnings introduced by merge

* Update to .NET 10 final release

* Revert "Workaround Build Regression in .NET 10 RC2"

This is fixed in the final release.

This reverts commit 5a59ac9aa8.

* fix new warnings with MSTest 4 + .NET 10

* use same Randomizer instance

* disable CA2000

* reduce CA1849 suppressions

and disable duplicate S6966

* disable preview analyzers

reverts 6c3c06d95a
2025-11-15 12:21:49 +01:00

320 lines
13 KiB
C#

#nullable enable
using System;
using System.Net;
using System.Threading;
using System.Xml;
using Renci.SshNet.Common;
using Renci.SshNet.NetConf;
namespace Renci.SshNet
{
/// <summary>
/// Contains operation for working with NetConf server.
/// </summary>
public class NetConfClient : BaseClient
{
private int _operationTimeout;
/// <summary>
/// Holds <see cref="INetConfSession"/> instance that used to communicate to the server.
/// </summary>
private INetConfSession? _netConfSession;
/// <summary>
/// Gets or sets the operation timeout.
/// </summary>
/// <value>
/// The timeout to wait until an operation completes. The default value is negative
/// one (-1) milliseconds, which indicates an infinite time-out period.
/// </value>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> represents a value that is less than -1 or greater than <see cref="int.MaxValue"/> milliseconds.</exception>
public TimeSpan OperationTimeout
{
get
{
return TimeSpan.FromMilliseconds(_operationTimeout);
}
set
{
_operationTimeout = value.AsTimeout(nameof(OperationTimeout));
}
}
/// <summary>
/// Gets the current NetConf session.
/// </summary>
/// <value>
/// The current NetConf session.
/// </value>
internal INetConfSession? NetConfSession
{
get { return _netConfSession; }
}
/// <summary>
/// Initializes a new instance of the <see cref="NetConfClient"/> class.
/// </summary>
/// <param name="connectionInfo">The connection info.</param>
/// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is <see langword="null"/>.</exception>
public NetConfClient(ConnectionInfo connectionInfo)
: this(connectionInfo, ownsConnectionInfo: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NetConfClient"/> 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 <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
public NetConfClient(string host, int port, string username, string password)
: this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NetConfClient"/> 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 <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is <see langword="null"/> or contains only whitespace characters.</exception>
public NetConfClient(string host, string username, string password)
: this(host, ConnectionInfo.DefaultPort, username, password)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NetConfClient"/> 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 <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <see langword="null"/> or contains only whitespace characters.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
public NetConfClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
: this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NetConfClient"/> 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 <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <see langword="null"/> or contains only whitespace characters.</exception>
public NetConfClient(string host, string username, params IPrivateKeySource[] keyFiles)
: this(host, ConnectionInfo.DefaultPort, username, keyFiles)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NetConfClient"/> class.
/// </summary>
/// <param name="connectionInfo">The connection info.</param>
/// <param name="ownsConnectionInfo">Specified whether this instance owns the connection info.</param>
/// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is <see langword="null"/>.</exception>
/// <remarks>
/// If <paramref name="ownsConnectionInfo"/> is <see langword="true"/>, then the
/// connection info will be disposed when this instance is disposed.
/// </remarks>
private NetConfClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo)
: this(connectionInfo, ownsConnectionInfo, new ServiceFactory())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NetConfClient"/> class.
/// </summary>
/// <param name="connectionInfo">The connection info.</param>
/// <param name="ownsConnectionInfo">Specified whether this instance owns the connection info.</param>
/// <param name="serviceFactory">The factory to use for creating new services.</param>
/// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="serviceFactory"/> is <see langword="null"/>.</exception>
/// <remarks>
/// If <paramref name="ownsConnectionInfo"/> is <see langword="true"/>, then the
/// connection info will be disposed when this instance is disposed.
/// </remarks>
internal NetConfClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory)
: base(connectionInfo, ownsConnectionInfo, serviceFactory)
{
_operationTimeout = Timeout.Infinite;
AutomaticMessageIdHandling = true;
}
/// <summary>
/// Gets the NetConf server capabilities.
/// </summary>
/// <value>
/// The NetConf server capabilities.
/// </value>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
public XmlDocument ServerCapabilities
{
get
{
if (_netConfSession is null)
{
throw new SshConnectionException("Client not connected.");
}
return _netConfSession.ServerCapabilities;
}
}
/// <summary>
/// Gets the NetConf client capabilities.
/// </summary>
/// <value>
/// The NetConf client capabilities.
/// </value>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
public XmlDocument ClientCapabilities
{
get
{
if (_netConfSession is null)
{
throw new SshConnectionException("Client not connected.");
}
return _netConfSession.ClientCapabilities;
}
}
/// <summary>
/// Gets or sets a value indicating whether automatic message id handling is
/// enabled.
/// </summary>
/// <value>
/// <see langword="true"/> if automatic message id handling is enabled; otherwise, <see langword="false"/>.
/// The default value is <see langword="true"/>.
/// </value>
public bool AutomaticMessageIdHandling { get; set; }
/// <summary>
/// Sends the receive RPC.
/// </summary>
/// <example>
/// <code>
/// var rpcXmlTemplate = "<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='1'>{0}</rpc>"'
/// rpc.LoadXml(String.Format(rpcXmlTemplate, "<get-config><source><running/></source></get-config>"));
/// var rpcResponse = client.SendReceiveRpc(rpc);
/// </code>
/// </example>
/// <param name="rpc">The RPC.</param>
/// <returns>
/// Reply message to RPC request.
/// </returns>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
public XmlDocument SendReceiveRpc(XmlDocument rpc)
{
if (_netConfSession is null)
{
throw new SshConnectionException("Client not connected.");
}
return _netConfSession.SendReceiveRpc(rpc, AutomaticMessageIdHandling);
}
/// <summary>
/// Sends the receive RPC.
/// </summary>
/// <example>
/// <code>
/// var rpcXmlTemplate = "<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='1'>{0}</rpc>"'
/// var rpcResponse = client.SendReceiveRpc(String.Format(rpcXmlTemplate, "<get-config><source><running/></source></get-config>"));
/// </code>
/// </example>
/// <param name="xml">The XML.</param>
/// <returns>
/// Reply message to RPC request.
/// </returns>
public XmlDocument SendReceiveRpc(string xml)
{
var rpc = new XmlDocument();
rpc.LoadXml(xml);
return SendReceiveRpc(rpc);
}
/// <summary>
/// Sends the close RPC.
/// </summary>
/// <returns>
/// Reply message to closing RPC request.
/// </returns>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
public XmlDocument SendCloseRpc()
{
if (_netConfSession is null)
{
throw new SshConnectionException("Client not connected.");
}
var 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 _netConfSession.SendReceiveRpc(rpc, AutomaticMessageIdHandling);
}
/// <summary>
/// Called when client is connected to the server.
/// </summary>
protected override void OnConnected()
{
base.OnConnected();
_netConfSession = CreateAndConnectNetConfSession();
}
/// <summary>
/// Called when client is disconnecting from the server.
/// </summary>
protected override void OnDisconnecting()
{
base.OnDisconnecting();
_netConfSession?.Disconnect();
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_netConfSession?.Dispose();
_netConfSession = null;
}
}
private INetConfSession CreateAndConnectNetConfSession()
{
var netConfSession = ServiceFactory.CreateNetConfSession(Session, _operationTimeout);
try
{
netConfSession.Connect();
return netConfSession;
}
catch
{
netConfSession.Dispose();
throw;
}
}
}
}