forked from sshnet/SSH.NET
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNetConfSession.cs
More file actions
230 lines (193 loc) · 8.8 KB
/
Copy pathNetConfSession.cs
File metadata and controls
230 lines (193 loc) · 8.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using Renci.SshNet.Common;
namespace Renci.SshNet.NetConf
{
internal sealed partial class NetConfSession : SubsystemSession, INetConfSession
{
private const string Prompt = "]]>]]>";
private const string LengthPattern = @"\n#(?<length>\d+)\n";
private const string ReplyPattern = @"\n##\n";
private readonly StringBuilder _data = new StringBuilder();
private bool _usingFramingProtocol;
private EventWaitHandle _serverCapabilitiesConfirmed = new AutoResetEvent(initialState: false);
private EventWaitHandle _rpcReplyReceived = new AutoResetEvent(initialState: false);
private StringBuilder _rpcReply = new StringBuilder();
private int _messageId;
#if NET7_0_OR_GREATER
private static readonly Regex LengthRegex = GetLengthRegex();
private static readonly Regex ReplyRegex = GetReplyRegex();
[GeneratedRegex(LengthPattern)]
private static partial Regex GetLengthRegex();
[GeneratedRegex(ReplyPattern)]
private static partial Regex GetReplyRegex();
#else
private static readonly Regex LengthRegex = new Regex(LengthPattern, RegexOptions.Compiled);
private static readonly Regex ReplyRegex = new Regex(ReplyPattern, RegexOptions.Compiled);
#endif
/// <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 number of milliseconds to wait for an operation to complete, or -1 to wait indefinitely.</param>
public NetConfSession(ISession session, int 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)
{
_ = _data.Clear();
XmlNamespaceManager nsMgr = null;
if (automaticMessageIdHandling)
{
_messageId++;
nsMgr = new XmlNamespaceManager(rpc.NameTable);
nsMgr.AddNamespace("nc", "urn:ietf:params:xml:ns:netconf:base:1.0");
rpc.SelectSingleNode("/nc:rpc/@message-id", nsMgr).Value = _messageId.ToString(CultureInfo.InvariantCulture);
}
_rpcReply = new StringBuilder();
_ = _rpcReplyReceived.Reset();
var reply = new XmlDocument();
if (_usingFramingProtocol)
{
var command = new StringBuilder(rpc.InnerXml.Length + 10);
_ = command.AppendFormat(CultureInfo.InvariantCulture, "\n#{0}\n", rpc.InnerXml.Length);
_ = command.Append(rpc.InnerXml);
_ = command.Append("\n##\n");
SendData(Encoding.UTF8.GetBytes(command.ToString()));
WaitOnHandle(_rpcReplyReceived, OperationTimeout);
reply.LoadXml(_rpcReply.ToString());
}
else
{
SendData(Encoding.UTF8.GetBytes(rpc.InnerXml + Prompt));
WaitOnHandle(_rpcReplyReceived, OperationTimeout);
reply.LoadXml(_rpcReply.ToString());
}
if (automaticMessageIdHandling)
{
var replyId = rpc.SelectSingleNode("/nc:rpc/@message-id", nsMgr).Value;
if (replyId != _messageId.ToString(CultureInfo.InvariantCulture))
{
throw new NetConfServerException("The rpc message id does not match the rpc-reply message id.");
}
}
return reply;
}
protected override void OnChannelOpen()
{
_ = _data.Clear();
var message = string.Concat(ClientCapabilities.InnerXml, Prompt);
SendData(Encoding.UTF8.GetBytes(message));
WaitOnHandle(_serverCapabilitiesConfirmed, OperationTimeout);
}
protected override void OnDataReceived(byte[] data)
{
var chunk = Encoding.UTF8.GetString(data);
if (ServerCapabilities is null)
{
_ = _data.Append(chunk);
if (!chunk.Contains(Prompt))
{
return;
}
try
{
chunk = _data.ToString();
_ = _data.Clear();
ServerCapabilities = new XmlDocument();
ServerCapabilities.LoadXml(chunk.Replace(Prompt, string.Empty));
}
catch (XmlException e)
{
throw new NetConfServerException("Server capabilities received are not well formed XML", e);
}
var nsMgr = new XmlNamespaceManager(ServerCapabilities.NameTable);
nsMgr.AddNamespace("nc", "urn:ietf:params:xml:ns:netconf:base:1.0");
const string xpath = "/nc:hello/nc:capabilities/nc:capability[text()='urn:ietf:params:netconf:base:1.1']";
// Per RFC6242 section 4.1, If the :base:1.1 capability is advertised by both
// peers, the chunked transfer mechanism is used for the remainder of the NETCONF
// session. Otherwise, the old end-of-message based mechanism(see Section 4.3) is used.
// This will currently evaluate to false since we (the client) do not advertise 1.1 capability.
// Despite some code existing for the 1.1 framing protocol, it is thought to be incorrect or
// incomplete. The NETCONF code is practically untested at the time of writing.
_usingFramingProtocol = ServerCapabilities.SelectSingleNode(xpath, nsMgr) != null
&& ClientCapabilities.SelectSingleNode(xpath, nsMgr) != null;
_ = _serverCapabilitiesConfirmed.Set();
}
else if (_usingFramingProtocol)
{
var position = 0;
for (; ; )
{
var match = LengthRegex.Match(chunk.Substring(position));
if (!match.Success)
{
break;
}
var fractionLength = Convert.ToInt32(match.Groups["length"].Value, CultureInfo.InvariantCulture);
_ = _rpcReply.Append(chunk, position + match.Index + match.Length, fractionLength);
position += match.Index + match.Length + fractionLength;
}
#if NET7_0_OR_GREATER
if (ReplyRegex.IsMatch(chunk.AsSpan(position)))
#else
if (ReplyRegex.IsMatch(chunk.Substring(position)))
#endif // NET7_0_OR_GREATER
{
_ = _rpcReplyReceived.Set();
}
}
else
{
_ = _data.Append(chunk);
if (!chunk.Contains(Prompt))
{
return;
}
chunk = _data.ToString();
_ = _data.Clear();
_ = _rpcReply.Append(chunk.Replace(Prompt, string.Empty));
_ = _rpcReplyReceived.Set();
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
if (_serverCapabilitiesConfirmed != null)
{
_serverCapabilitiesConfirmed.Dispose();
_serverCapabilitiesConfirmed = null;
}
if (_rpcReplyReceived != null)
{
_rpcReplyReceived.Dispose();
_rpcReplyReceived = null;
}
}
}
}
}