412 lines · 16.5 KB · C#
RawDownload
// 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{    /// <summary>    /// Discoverer class to discover SNMP agents in the same network.    /// </summary>    public sealed class Discoverer    {        private int _active;        private int _bufferSize;        private int _requestId;        private static readonly UserRegistry Empty = new UserRegistry();        private readonly IList<Variable> _defaultVariables = new List<Variable> { 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;         /// <summary>        /// Occurs when an SNMP agent is found.        /// </summary>        public event EventHandler<AgentFoundEventArgs> AgentFound;         /// <summary>        /// Occurs when an exception is raised.        /// </summary>        /// <remarks>The exception is typical <see cref="SocketException"/> here.</remarks>        public event EventHandler<ExceptionRaisedEventArgs> ExceptionRaised;         /// <summary>        /// Discovers agents of the specified version in a specific time interval.        /// </summary>        /// <param name="version">The version.</param>        /// <param name="broadcastAddress">The broadcast address.</param>        /// <param name="community">The community.</param>        /// <param name="interval">The discovering time interval, in milliseconds.</param>        /// <remarks><paramref name="broadcastAddress"/> must be an IPv4 address. IPv6 is not yet supported here.</remarks>        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, byte[]>(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<Socket, byte[]> data = (Tuple<Socket, byte[]>)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]));            }        }         /// <summary>        /// Discovers agents of the specified version in a specific time interval.        /// </summary>        /// <param name="version">The version.</param>        /// <param name="broadcastAddress">The broadcast address.</param>        /// <param name="community">The community.</param>        /// <param name="interval">The discovering time interval, in milliseconds.</param>        /// <remarks><paramref name="broadcastAddress"/> must be an IPv4 address. IPv6 is not yet supported here.</remarks>        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;                }            }        }    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t