// Discoverer classes.
// 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.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using GSF.Net.Snmp.Security;
namespace GSF.Net.Snmp.Messaging
{
///
/// Discoverer class to discover SNMP agents in the same network.
///
public sealed class Discoverer
{
private int _active;
private int _bufferSize;
private int _requestId;
private static readonly UserRegistry Empty = new UserRegistry();
private readonly IList _defaultVariables = new List { new Variable(new ObjectIdentifier(new uint[] { 1, 3, 6, 1, 2, 1, 1, 1, 0 })) };
private const int Active = 1;
private const int Inactive = 0;
///
/// Occurs when an SNMP agent is found.
///
public event EventHandler AgentFound;
///
/// Occurs when an exception is raised.
///
/// The exception is typical here.
public event EventHandler ExceptionRaised;
///
/// Discovers agents of the specified version in a specific time interval.
///
/// The version.
/// The broadcast address.
/// The community.
/// The discovering time interval, in milliseconds.
/// must be an IPv4 address. IPv6 is not yet supported here.
public void Discover(VersionCode version, IPEndPoint broadcastAddress, OctetString community, int interval)
{
if (broadcastAddress == null)
{
throw new ArgumentNullException(nameof(broadcastAddress));
}
if (version != VersionCode.V3 && community == null)
{
throw new ArgumentNullException(nameof(community));
}
AddressFamily addressFamily = broadcastAddress.AddressFamily;
if (addressFamily == AddressFamily.InterNetworkV6)
{
throw new ArgumentException("IP v6 is not yet supported.", nameof(broadcastAddress));
}
byte[] bytes;
_requestId = Messenger.NextRequestId;
if (version == VersionCode.V3)
{
// throw new NotSupportedException("SNMP v3 is not supported");
Discovery discovery = new Discovery(Messenger.NextMessageId, _requestId, Messenger.MaxMessageSize);
bytes = discovery.ToBytes();
}
else
{
GetRequestMessage message = new GetRequestMessage(_requestId, version, community, _defaultVariables);
bytes = message.ToBytes();
}
using (UdpClient udp = new UdpClient(addressFamily))
{
#if (!CF)
udp.EnableBroadcast = true;
#endif
#if NET471
udp.Send(bytes, bytes.Length, broadcastAddress);
#else
AsyncHelper.RunSync(() => udp.SendAsync(bytes, bytes.Length, broadcastAddress));
#endif
int activeBefore = Interlocked.CompareExchange(ref _active, Active, Inactive);
if (activeBefore == Active)
{
// If already started, we've nothing to do.
return;
}
_bufferSize = udp.Client.ReceiveBufferSize = Messenger.MaxMessageSize;
#if ASYNC
Task.Factory.StartNew(() => AsyncBeginReceive(udp.Client));
#else
Task.Factory.StartNew(() => AsyncReceive(udp.Client));
#endif
Thread.Sleep(interval);
Interlocked.CompareExchange(ref _active, Inactive, Active);
#if NET471
udp.Close();
#endif
}
}
#if ASYNC
private void AsyncBeginReceive(Socket socket)
{
while (true)
{
// If no more active, then stop.
if (Interlocked.Exchange(ref _active, _active) == Inactive)
{
return;
}
byte[] buffer = new byte[_bufferSize];
EndPoint remote = new IPEndPoint(IPAddress.Any, 0);
IAsyncResult iar = null;
try
{
iar = socket.BeginReceiveFrom(buffer, 0, _bufferSize, SocketFlags.None, ref remote, AsyncEndReceive, new Tuple(socket, buffer));
}
catch (SocketException ex)
{
// ignore WSAECONNRESET, http://bytes.com/topic/c-sharp/answers/237558-strange-udp-socket-problem
if (ex.SocketErrorCode != SocketError.ConnectionReset)
{
// If the SnmpTrapListener was active, marks it as stopped and call HandleException.
// If it was inactive, the exception is likely to result from this, and we raise nothing.
long activeBefore = Interlocked.CompareExchange(ref _active, Inactive, Active);
if (activeBefore == Active)
{
HandleException(ex);
}
}
}
if (iar != null)
{
iar.AsyncWaitHandle.WaitOne();
}
}
}
private void AsyncEndReceive(IAsyncResult iar)
{
// If no more active, then stop. This discards the received packet, if any (indeed, we may be there either
// because we've received a packet, or because the socket has been closed).
if (Interlocked.Exchange(ref _active, _active) == Inactive)
{
return;
}
//// We start another receive operation.
//AsyncBeginReceive();
Tuple data = (Tuple)iar.AsyncState;
byte[] buffer = data.Item2;
try
{
EndPoint remote = data.Item1.AddressFamily == AddressFamily.InterNetwork ? new IPEndPoint(IPAddress.Any, 0) : new IPEndPoint(IPAddress.IPv6Any, 0);
int count = data.Item1.EndReceiveFrom(iar, ref remote);
HandleMessage(buffer, count, (IPEndPoint)remote);
}
catch (SocketException ex)
{
// ignore WSAECONNRESET, http://bytes.com/topic/c-sharp/answers/237558-strange-udp-socket-problem
if (ex.SocketErrorCode != SocketError.ConnectionReset)
{
// If the SnmpTrapListener was active, marks it as stopped and call HandleException.
// If it was inactive, the exception is likely to result from this, and we raise nothing.
long activeBefore = Interlocked.CompareExchange(ref _active, Inactive, Active);
if (activeBefore == Active)
{
HandleException(ex);
}
}
}
}
#else
private void AsyncReceive(Socket socket)
{
while (true)
{
// If no more active, then stop.
if (Interlocked.Exchange(ref _active, _active) == Inactive)
{
return;
}
try
{
var buffer = new byte[_bufferSize];
EndPoint remote = new IPEndPoint(IPAddress.Any, 0);
var count = socket.ReceiveFrom(buffer, ref remote);
Task.Factory.StartNew(()=> HandleMessage(buffer, count, (IPEndPoint)remote));
}
catch (SocketException ex)
{
if (ex.SocketErrorCode != SocketError.ConnectionReset)
{
// If the SnmpTrapListener was active, marks it as stopped and call HandleException.
// If it was inactive, the exception is likely to result from this, and we raise nothing.
var activeBefore = Interlocked.CompareExchange(ref _active, Inactive, Active);
if (activeBefore == Active)
{
HandleException(ex);
}
}
}
}
}
#endif
private void HandleException(Exception exception)
{
ExceptionRaised?.Invoke(this, new ExceptionRaisedEventArgs(exception));
}
private void HandleMessage(byte[] buffer, int count, IPEndPoint remote)
{
foreach (ISnmpMessage message in MessageFactory.ParseMessages(buffer, 0, count, Empty))
{
SnmpType code = message.TypeCode();
if (code == SnmpType.ReportPdu)
{
ReportMessage report = (ReportMessage)message;
if (report.RequestId() != _requestId)
{
continue;
}
if (report.Pdu().ErrorStatus.ToErrorCode() != ErrorCode.NoError)
{
continue;
}
AgentFound?.Invoke(this, new AgentFoundEventArgs(remote, null));
}
if (code != SnmpType.ResponsePdu)
{
continue;
}
ResponseMessage response = (ResponseMessage)message;
if (response.RequestId() != _requestId)
{
continue;
}
if (response.ErrorStatus != ErrorCode.NoError)
{
continue;
}
AgentFound?.Invoke(this, new AgentFoundEventArgs(remote, response.Variables()[0]));
}
}
///
/// Discovers agents of the specified version in a specific time interval.
///
/// The version.
/// The broadcast address.
/// The community.
/// The discovering time interval, in milliseconds.
/// must be an IPv4 address. IPv6 is not yet supported here.
public async Task DiscoverAsync(VersionCode version, IPEndPoint broadcastAddress, OctetString community, int interval)
{
if (broadcastAddress == null)
{
throw new ArgumentNullException(nameof(broadcastAddress));
}
if (version != VersionCode.V3 && community == null)
{
throw new ArgumentNullException(nameof(community));
}
AddressFamily addressFamily = broadcastAddress.AddressFamily;
if (addressFamily == AddressFamily.InterNetworkV6)
{
throw new ArgumentException("IP v6 is not yet supported.", nameof(broadcastAddress));
}
byte[] bytes;
_requestId = Messenger.NextRequestId;
if (version == VersionCode.V3)
{
// throw new NotSupportedException("SNMP v3 is not supported");
Discovery discovery = new Discovery(Messenger.NextMessageId, _requestId, Messenger.MaxMessageSize);
bytes = discovery.ToBytes();
}
else
{
GetRequestMessage message = new GetRequestMessage(_requestId, version, community, _defaultVariables);
bytes = message.ToBytes();
}
using (Socket udp = new Socket(addressFamily, SocketType.Dgram, ProtocolType.Udp))
{
udp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
SocketAsyncEventArgs info = SocketExtension.EventArgsFactory.Create();
info.RemoteEndPoint = broadcastAddress;
info.SetBuffer(bytes, 0, bytes.Length);
using (SocketAwaitable awaitable1 = new SocketAwaitable(info))
{
await udp.SendToAsync(awaitable1);
}
int activeBefore = Interlocked.CompareExchange(ref _active, Active, Inactive);
if (activeBefore == Active)
{
// If already started, we've nothing to do.
return;
}
_bufferSize = udp.ReceiveBufferSize;
await Task.WhenAny(
ReceiveAsync(udp),
Task.Delay(interval));
Interlocked.CompareExchange(ref _active, Inactive, Active);
try
{
udp.Shutdown(SocketShutdown.Both);
}
catch (SocketException ex)
{
// This exception is thrown in .NET Core <=2.1.4 on non-Windows systems.
// However, the shutdown call is necessary to release the socket binding.
if (!SnmpMessageExtension.IsRunningOnWindows && ex.SocketErrorCode == SocketError.NotConnected)
{
}
}
}
}
private async Task ReceiveAsync(Socket socket)
{
while (true)
{
// If no more active, then stop.
if (Interlocked.Exchange(ref _active, _active) == Inactive)
{
return;
}
int count;
byte[] reply = new byte[_bufferSize];
SocketAsyncEventArgs args = SocketExtension.EventArgsFactory.Create();
try
{
EndPoint remote = new IPEndPoint(IPAddress.Any, 0);
args.RemoteEndPoint = remote;
args.SetBuffer(reply, 0, _bufferSize);
using (SocketAwaitable awaitable = new SocketAwaitable(args))
{
count = await socket.ReceiveMessageFromAsync(awaitable);
}
await Task.Factory.StartNew(() => HandleMessage(reply, count, (IPEndPoint) args.RemoteEndPoint))
.ConfigureAwait(false);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.ConnectionReset)
{
continue;
}
// If the SnmpTrapListener was active, marks it as stopped and call HandleException.
// If it was inactive, the exception is likely to result from this, and we raise nothing.
int activeBefore = Interlocked.CompareExchange(ref _active, Inactive, Active);
if (activeBefore == Active)
{
HandleException(ex);
}
}
catch (NullReferenceException)
{
args.UserToken = SocketAsyncEventArgsFactory.DisposedMessage;
}
}
}
}
}