// 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; } } } } }