// SNMP message extension class.
// Copyright (C) 2008-2010 Malcolm Crowe, Lex Li, and other contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
#if NETSTANDARD2_0
using System.Runtime.InteropServices;
#endif
using System.Threading;
using System.Threading.Tasks;
using GSF.Net.Snmp.Security;
namespace GSF.Net.Snmp.Messaging
{
///
/// Extension methods for .
///
public static class SnmpMessageExtension
{
///
/// Gets the .
///
/// The .
///
public static SnmpType TypeCode(this ISnmpMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return message.Pdu().TypeCode;
}
///
/// Variables.
///
/// The .
public static IList Variables(this ISnmpMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
SnmpType code = message.TypeCode();
return code == SnmpType.Unknown ? new List(0) : message.Scope.Pdu.Variables;
}
///
/// Request ID.
///
/// The .
public static int RequestId(this ISnmpMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return message.Scope.Pdu.RequestId.ToInt32();
}
///
/// Gets the message ID.
///
/// The message ID.
/// The .
/// For v3, message ID is different from request ID. For v1 and v2c, they are the same.
public static int MessageId(this ISnmpMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return message.Header == Header.Empty ? message.RequestId() : message.Header.MessageId;
}
///
/// PDU.
///
/// The .
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pdu")]
public static ISnmpPdu Pdu(this ISnmpMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return message.Scope.Pdu;
}
///
/// Community name.
///
/// The .
public static OctetString Community(this ISnmpMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return message.Parameters.UserName;
}
#region sync methods
///
/// Sends an .
///
/// The .
/// Manager
public static void Send(this ISnmpMessage message, EndPoint manager)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
if (manager == null)
{
throw new ArgumentNullException(nameof(manager));
}
SnmpType code = message.TypeCode();
if ((code != SnmpType.TrapV1Pdu && code != SnmpType.TrapV2Pdu) && code != SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(
CultureInfo.InvariantCulture,
"not a trap message: {0}",
code));
}
using (Socket socket = manager.GetSocket())
{
message.Send(manager, socket);
}
}
///
/// Sends an .
///
/// The .
/// Manager
/// The socket.
public static void Send(this ISnmpMessage message, EndPoint manager, Socket socket)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
if (socket == null)
{
throw new ArgumentNullException(nameof(socket));
}
if (manager == null)
{
throw new ArgumentNullException(nameof(manager));
}
SnmpType code = message.TypeCode();
if ((code != SnmpType.TrapV1Pdu && code != SnmpType.TrapV2Pdu) && code != SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(
CultureInfo.InvariantCulture,
"not a trap message: {0}",
code));
}
byte[] bytes = message.ToBytes();
socket.SendTo(bytes, 0, bytes.Length, SocketFlags.None, manager);
}
///
/// Sends this and handles the response from agent.
///
/// The .
/// The time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period.
/// Port number.
/// User registry.
///
public static ISnmpMessage GetResponse(this ISnmpMessage request, int timeout, IPEndPoint receiver, UserRegistry registry)
{
// TODO: make more usage of UserRegistry.
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (receiver == null)
{
throw new ArgumentNullException(nameof(receiver));
}
SnmpType code = request.TypeCode();
if (code == SnmpType.TrapV1Pdu || code == SnmpType.TrapV2Pdu || code == SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "not a request message: {0}", code));
}
using (Socket socket = receiver.GetSocket())
{
return request.GetResponse(timeout, receiver, registry, socket);
}
}
///
/// Sends this and handles the response from agent.
///
/// The .
/// The time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period.
/// Port number.
///
public static ISnmpMessage GetResponse(this ISnmpMessage request, int timeout, IPEndPoint receiver)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (receiver == null)
{
throw new ArgumentNullException(nameof(receiver));
}
SnmpType code = request.TypeCode();
if (code == SnmpType.TrapV1Pdu || code == SnmpType.TrapV2Pdu || code == SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "not a request message: {0}", code));
}
using (Socket socket = receiver.GetSocket())
{
return request.GetResponse(timeout, receiver, socket);
}
}
///
/// Sends this and handles the response from agent.
///
/// The .
/// The time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period.
/// Agent.
/// The UDP to use to send/receive.
///
public static ISnmpMessage GetResponse(this ISnmpMessage request, int timeout, IPEndPoint receiver, Socket udpSocket)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (receiver == null)
{
throw new ArgumentNullException(nameof(receiver));
}
if (udpSocket == null)
{
throw new ArgumentNullException(nameof(udpSocket));
}
UserRegistry registry = new UserRegistry();
if (request.Version == VersionCode.V3)
{
registry.Add(request.Parameters.UserName, request.Privacy);
}
return request.GetResponse(timeout, receiver, registry, udpSocket);
}
///
/// Sends an and handles the response from agent.
///
/// The .
/// The time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period.
/// Agent.
/// The UDP to use to send/receive.
/// The user registry.
///
public static ISnmpMessage GetResponse(this ISnmpMessage request, int timeout, IPEndPoint receiver, UserRegistry registry, Socket udpSocket)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (udpSocket == null)
{
throw new ArgumentNullException(nameof(udpSocket));
}
if (receiver == null)
{
throw new ArgumentNullException(nameof(receiver));
}
if (registry == null)
{
throw new ArgumentNullException(nameof(registry));
}
SnmpType requestCode = request.TypeCode();
if (requestCode == SnmpType.TrapV1Pdu || requestCode == SnmpType.TrapV2Pdu || requestCode == SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "not a request message: {0}", requestCode));
}
byte[] bytes = request.ToBytes();
int bufSize = udpSocket.ReceiveBufferSize = Messenger.MaxMessageSize;
byte[] reply = new byte[bufSize];
// Whatever you change, try to keep the Send and the Receive close to each other.
udpSocket.SendTo(bytes, receiver);
udpSocket.ReceiveTimeout = timeout;
int count;
try
{
count = udpSocket.Receive(reply, 0, bufSize, SocketFlags.None);
}
catch (SocketException ex)
{
// IMPORTANT: Mono behavior.
if (IsRunningOnMono && ex.SocketErrorCode == SocketError.WouldBlock)
{
throw TimeoutException.Create(receiver.Address, timeout);
}
if (ex.SocketErrorCode == SocketError.TimedOut)
{
throw TimeoutException.Create(receiver.Address, timeout);
}
throw;
}
// Passing 'count' is not necessary because ParseMessages should ignore it, but it offer extra safety (and would avoid an issue if parsing >1 response).
ISnmpMessage response = MessageFactory.ParseMessages(reply, 0, count, registry)[0];
SnmpType responseCode = response.TypeCode();
if (responseCode == SnmpType.ResponsePdu || responseCode == SnmpType.ReportPdu)
{
int requestId = request.MessageId();
int responseId = response.MessageId();
if (responseId != requestId)
{
throw OperationException.Create(string.Format(CultureInfo.InvariantCulture, "wrong response sequence: expected {0}, received {1}", requestId, responseId), receiver.Address);
}
return response;
}
throw OperationException.Create(string.Format(CultureInfo.InvariantCulture, "wrong response type: {0}", responseCode), receiver.Address);
}
#endregion
#region async methods
#if NET471
///
/// Ends a pending asynchronous read.
///
/// The .
/// An that stores state information and any user defined data for this asynchronous operation.
///
[Obsolete("Please use GetResponseAsync and await on it.")]
public static ISnmpMessage EndGetResponse(this ISnmpMessage request, IAsyncResult asyncResult)
{
if (asyncResult == null)
{
throw new ArgumentNullException(nameof(asyncResult));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
SnmpMessageAsyncResult ar = (SnmpMessageAsyncResult)asyncResult;
Socket s = ar.WorkSocket;
int count = s.EndReceive(ar.Inner);
// Passing 'count' is not necessary because ParseMessages should ignore it, but it offer extra safety (and would avoid an issue if parsing >1 response).
ISnmpMessage response = MessageFactory.ParseMessages(ar.GetBuffer(), 0, count, ar.Users)[0];
SnmpType responseCode = response.TypeCode();
if (responseCode == SnmpType.ResponsePdu || responseCode == SnmpType.ReportPdu)
{
int requestId = request.MessageId();
int responseId = response.MessageId();
if (responseId != requestId)
{
throw OperationException.Create(string.Format(CultureInfo.InvariantCulture, "wrong response sequence: expected {0}, received {1}", requestId, responseId), ar.Receiver.Address);
}
return response;
}
throw OperationException.Create(string.Format(CultureInfo.InvariantCulture, "wrong response type: {0}", responseCode), ar.Receiver.Address);
}
///
/// Begins to asynchronously send an to an .
///
/// The .
/// Agent.
/// The user registry.
/// The UDP to use to send/receive.
/// The callback.
/// The state object.
///
[Obsolete("Please use GetResponseAsync and await on it.")]
public static IAsyncResult BeginGetResponse(this ISnmpMessage request, IPEndPoint receiver, UserRegistry registry, Socket udpSocket, AsyncCallback callback, object state)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (udpSocket == null)
{
throw new ArgumentNullException(nameof(udpSocket));
}
if (receiver == null)
{
throw new ArgumentNullException(nameof(receiver));
}
if (registry == null)
{
throw new ArgumentNullException(nameof(registry));
}
SnmpType requestCode = request.TypeCode();
if (requestCode == SnmpType.TrapV1Pdu || requestCode == SnmpType.TrapV2Pdu || requestCode == SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "not a request message: {0}", requestCode));
}
// Whatever you change, try to keep the Send and the Receive close to each other.
udpSocket.SendTo(request.ToBytes(), receiver);
int bufferSize = udpSocket.ReceiveBufferSize = Messenger.MaxMessageSize;
byte[] buffer = new byte[bufferSize];
// https://sharpsnmplib.codeplex.com/workitem/7234
if (callback != null)
{
AsyncCallback wrapped = callback;
callback = asyncResult =>
{
SnmpMessageAsyncResult result = new SnmpMessageAsyncResult(asyncResult, udpSocket, registry, receiver, buffer);
wrapped(result);
};
}
IAsyncResult ar = udpSocket.BeginReceive(buffer, 0, bufferSize, SocketFlags.None, callback, state);
return new SnmpMessageAsyncResult(ar, udpSocket, registry, receiver, buffer);
}
#endif
///
/// Sends an .
///
/// The .
/// Manager
public static async Task SendAsync(this ISnmpMessage message, EndPoint manager)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
if (manager == null)
{
throw new ArgumentNullException(nameof(manager));
}
SnmpType code = message.TypeCode();
if ((code != SnmpType.TrapV1Pdu && code != SnmpType.TrapV2Pdu) && code != SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(
CultureInfo.InvariantCulture,
"not a trap message: {0}",
code));
}
using (Socket socket = manager.GetSocket())
{
await message.SendAsync(manager, socket).ConfigureAwait(false);
}
}
///
/// Sends an .
///
/// The .
/// Manager
/// The socket.
public static async Task SendAsync(this ISnmpMessage message, EndPoint manager, Socket socket)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
if (socket == null)
{
throw new ArgumentNullException(nameof(socket));
}
if (manager == null)
{
throw new ArgumentNullException(nameof(manager));
}
SnmpType code = message.TypeCode();
if ((code != SnmpType.TrapV1Pdu && code != SnmpType.TrapV2Pdu) && code != SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(
CultureInfo.InvariantCulture,
"not a trap message: {0}",
code));
}
byte[] bytes = message.ToBytes();
SocketAsyncEventArgs info = SocketExtension.EventArgsFactory.Create();
info.RemoteEndPoint = manager;
info.SetBuffer(bytes, 0, bytes.Length);
using (SocketAwaitable awaitable1 = new SocketAwaitable(info))
{
await socket.SendToAsync(awaitable1);
}
}
///
/// Sends this and handles the response from agent.
///
/// The .
/// Port number.
/// User registry.
///
public static async Task GetResponseAsync(this ISnmpMessage request, IPEndPoint receiver, UserRegistry registry)
{
// TODO: make more usage of UserRegistry.
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (receiver == null)
{
throw new ArgumentNullException(nameof(receiver));
}
SnmpType code = request.TypeCode();
if (code == SnmpType.TrapV1Pdu || code == SnmpType.TrapV2Pdu || code == SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "not a request message: {0}", code));
}
using (Socket socket = receiver.GetSocket())
{
return await request.GetResponseAsync(receiver, registry, socket).ConfigureAwait(false);
}
}
///
/// Sends this and handles the response from agent.
///
/// The .
/// Port number.
///
public static async Task GetResponseAsync(this ISnmpMessage request, IPEndPoint receiver)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (receiver == null)
{
throw new ArgumentNullException(nameof(receiver));
}
SnmpType code = request.TypeCode();
if (code == SnmpType.TrapV1Pdu || code == SnmpType.TrapV2Pdu || code == SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "not a request message: {0}", code));
}
using (Socket socket = receiver.GetSocket())
{
return await request.GetResponseAsync(receiver, socket).ConfigureAwait(false);
}
}
///
/// Sends this and handles the response from agent.
///
/// The .
/// Agent.
/// The UDP to use to send/receive.
///
public static async Task GetResponseAsync(this ISnmpMessage request, IPEndPoint receiver, Socket udpSocket)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (receiver == null)
{
throw new ArgumentNullException(nameof(receiver));
}
if (udpSocket == null)
{
throw new ArgumentNullException(nameof(udpSocket));
}
UserRegistry registry = new UserRegistry();
if (request.Version == VersionCode.V3)
{
registry.Add(request.Parameters.UserName, request.Privacy);
}
return await request.GetResponseAsync(receiver, registry, udpSocket).ConfigureAwait(false);
}
///
/// Sends an and handles the response from agent.
///
/// The .
/// Agent.
/// The UDP to use to send/receive.
/// The user registry.
///
public static async Task GetResponseAsync(this ISnmpMessage request, IPEndPoint receiver, UserRegistry registry, Socket udpSocket)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (udpSocket == null)
{
throw new ArgumentNullException(nameof(udpSocket));
}
if (registry == null)
{
throw new ArgumentNullException(nameof(registry));
}
SnmpType requestCode = request.TypeCode();
if (requestCode == SnmpType.TrapV1Pdu || requestCode == SnmpType.TrapV2Pdu || requestCode == SnmpType.ReportPdu)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "not a request message: {0}", requestCode));
}
byte[] bytes = request.ToBytes();
int bufSize = udpSocket.ReceiveBufferSize = Messenger.MaxMessageSize;
// Whatever you change, try to keep the Send and the Receive close to each other.
SocketAsyncEventArgs info = SocketExtension.EventArgsFactory.Create();
info.RemoteEndPoint = receiver ?? throw new ArgumentNullException(nameof(receiver));
info.SetBuffer(bytes, 0, bytes.Length);
using (SocketAwaitable awaitable1 = new SocketAwaitable(info))
{
await udpSocket.SendToAsync(awaitable1);
}
int count;
byte[] reply = new byte[bufSize];
// IMPORTANT: follow http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx
SocketAsyncEventArgs args = SocketExtension.EventArgsFactory.Create();
IPAddress remoteAddress = udpSocket.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any;
EndPoint remote = new IPEndPoint(remoteAddress, 0);
try
{
args.RemoteEndPoint = remote;
args.SetBuffer(reply, 0, bufSize);
using (SocketAwaitable awaitable = new SocketAwaitable(args))
{
count = await udpSocket.ReceiveMessageFromAsync(awaitable);
}
}
catch (SocketException ex)
{
// IMPORTANT: Mono behavior (https://bugzilla.novell.com/show_bug.cgi?id=599488)
if (IsRunningOnMono && ex.SocketErrorCode == SocketError.WouldBlock)
{
throw TimeoutException.Create(receiver.Address, 0);
}
if (ex.SocketErrorCode == SocketError.TimedOut)
{
throw TimeoutException.Create(receiver.Address, 0);
}
throw;
}
// Passing 'count' is not necessary because ParseMessages should ignore it, but it offer extra safety (and would avoid an issue if parsing >1 response).
ISnmpMessage response = MessageFactory.ParseMessages(reply, 0, count, registry)[0];
SnmpType responseCode = response.TypeCode();
if (responseCode == SnmpType.ResponsePdu || responseCode == SnmpType.ReportPdu)
{
int requestId = request.MessageId();
int responseId = response.MessageId();
if (responseId != requestId)
{
throw OperationException.Create(string.Format(CultureInfo.InvariantCulture, "wrong response sequence: expected {0}, received {1}", requestId, responseId), receiver.Address);
}
return response;
}
throw OperationException.Create(string.Format(CultureInfo.InvariantCulture, "wrong response type: {0}", responseCode), receiver.Address);
}
#endregion
///
/// Tests if running on Mono.
///
///
public static bool IsRunningOnMono
{
get { return Type.GetType("Mono.Runtime") != null; }
}
///
/// Gets a value indicating whether it is
/// running on Windows.
///
/// true if is running on Windows; otherwise, false.
public static bool IsRunningOnWindows
{
get
{
#if NET471
return !IsRunningOnMono;
#elif NETSTANDARD2_0
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
#else
return false;
#endif
}
}
///
/// Gets a value indicating whether it is running on macOS.
///
/// true if is running on macOS; otherwise, false.
public static bool IsRunningOnMac
{
get
{
#if NET471
return IsRunningOnMono;
#elif NETSTANDARD2_0
return RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
#else
return false;
#endif
}
}
///
/// Gets a value indicating whether it is running on iOS.
///
/// true if is running on iOS; otherwise, false.
public static bool IsRunningOnIOS
{
get
{
#if NET471
return false;
#elif NETSTANDARD2_0
return RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
#elif XAMARINIOS1_0
return true;
#else
return false;
#endif
}
}
///
/// Packs up the .
///
/// The .
/// The length bytes.
///
internal static Sequence PackMessage(this ISnmpMessage message, byte[] length)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return ByteTool.PackMessage(
length,
message.Version,
message.Header,
message.Parameters,
message.Privacy.GetScopeData(message.Header, message.Parameters, message.Scope.GetData(message.Version)));
}
private sealed class SnmpMessageAsyncResult : IAsyncResult
{
private readonly byte[] _buffer;
public SnmpMessageAsyncResult(IAsyncResult inner, Socket socket, UserRegistry users, IPEndPoint receiver, byte[] buffer)
{
_buffer = buffer;
WorkSocket = socket;
Users = users;
Receiver = receiver;
Inner = inner;
}
public IAsyncResult Inner { get; private set; }
public Socket WorkSocket { get; private set; }
public UserRegistry Users { get; private set; }
public byte[] GetBuffer()
{
return _buffer;
}
public IPEndPoint Receiver { get; private set; }
public bool IsCompleted
{
get { return Inner.IsCompleted; }
}
public WaitHandle AsyncWaitHandle
{
get { return Inner.AsyncWaitHandle; }
}
public object AsyncState
{
get { return Inner.AsyncState; }
}
public bool CompletedSynchronously
{
get { return Inner.CompletedSynchronously; }
}
}
}
}