RaspberryIO/Unosquare.Swan/Networking/SnmpClient.cs
2019-02-17 14:08:57 +01:00

280 lines
10 KiB
C#

namespace Unosquare.Swan.Networking
{
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// Represents a little SNMP client based on http://www.java2s.com/Code/CSharp/Network/SimpleSNMP.htm.
/// </summary>
public static class SnmpClient
{
private static readonly byte[] DiscoverMessage =
{
48, 41, 2, 1, 1, 4, 6, 112, 117, 98, 108, 105, 99, 160, 28, 2, 4, 111, 81, 45, 144, 2, 1, 0, 2, 1, 0, 48,
14, 48, 12, 6, 8, 43, 6, 1, 2, 1, 1, 1, 0, 5, 0,
};
/// <summary>
/// Discovers the specified SNMP time out.
/// </summary>
/// <param name="snmpTimeOut">The SNMP time out.</param>
/// <returns>An array of network endpoint as an IP address and a port number.</returns>
public static IPEndPoint[] Discover(int snmpTimeOut = 6000)
{
var endpoints = new List<IPEndPoint>();
Task[] tasks =
{
Task.Run(async () =>
{
using (var udp = new UdpClient(IPAddress.Broadcast.AddressFamily))
{
udp.EnableBroadcast = true;
await udp.SendAsync(
DiscoverMessage,
DiscoverMessage.Length,
new IPEndPoint(IPAddress.Broadcast, 161));
while (true)
{
try
{
var buffer = new byte[udp.Client.ReceiveBufferSize];
EndPoint remote = new IPEndPoint(IPAddress.Any, 0);
udp.Client.ReceiveFrom(buffer, ref remote);
endpoints.Add(remote as IPEndPoint);
}
catch
{
break;
}
}
#if NET452
udp.Close();
#endif
}
}),
Task.Delay(snmpTimeOut),
};
Task.WaitAny(tasks);
return endpoints.ToArray();
}
/// <summary>
/// Gets the name of the public.
/// </summary>
/// <param name="host">The host.</param>
/// <returns>
/// A string that contains the results of decoding the specified sequence
/// of bytes ref=GetString".
/// </returns>
public static string GetPublicName(IPEndPoint host) => GetString(host, "1.3.6.1.2.1.1.5.0");
/// <summary>
/// Gets the up-time.
/// </summary>
/// <param name="host">The host.</param>
/// <param name="mibString">The mibString.</param>
/// <returns>
/// A time interval that represents a specified number of seconds,
/// where the specification is accurate to the nearest millisecond.
/// </returns>
public static TimeSpan GetUptime(IPEndPoint host, string mibString = "1.3.6.1.2.1.1.3.0")
{
var response = Get(host, mibString);
if (response[0] == 0xff) return TimeSpan.Zero;
// If response, get the community name and MIB lengths
var commlength = Convert.ToInt16(response[6]);
var miblength = Convert.ToInt16(response[23 + commlength]);
// Extract the MIB data from the SNMP response
var datalength = Convert.ToInt16(response[25 + commlength + miblength]);
var datastart = 26 + commlength + miblength;
var uptime = 0;
while (datalength > 0)
{
uptime = (uptime << 8) + response[datastart++];
datalength--;
}
return TimeSpan.FromSeconds(uptime);
}
/// <summary>
/// Gets the string.
/// </summary>
/// <param name="host">The host.</param>
/// <param name="mibString">The mibString.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
public static string GetString(IPEndPoint host, string mibString)
{
var response = Get(host, mibString);
if (response[0] == 0xff) return string.Empty;
// If response, get the community name and MIB lengths
var commlength = Convert.ToInt16(response[6]);
var miblength = Convert.ToInt16(response[23 + commlength]);
// Extract the MIB data from the SNMP response
var datalength = Convert.ToInt16(response[25 + commlength + miblength]);
var datastart = 26 + commlength + miblength;
return Encoding.ASCII.GetString(response, datastart, datalength);
}
/// <summary>
/// Gets the specified host.
/// </summary>
/// <param name="host">The host.</param>
/// <param name="mibString">The mibString.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
public static byte[] Get(IPEndPoint host, string mibString) => Get("get", host, "public", mibString);
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="host">The host.</param>
/// <param name="community">The community.</param>
/// <param name="mibString">The mibString.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
public static byte[] Get(string request, IPEndPoint host, string community, string mibString)
{
var packet = new byte[1024];
var mib = new byte[1024];
var comlen = community.Length;
var mibvals = mibString.Split('.');
var miblen = mibvals.Length;
var cnt = 0;
var orgmiblen = miblen;
var pos = 0;
// Convert the string MIB into a byte array of integer values
// Unfortunately, values over 128 require multiple bytes
// which also increases the MIB length
for (var i = 0; i < orgmiblen; i++)
{
int temp = Convert.ToInt16(mibvals[i]);
if (temp > 127)
{
mib[cnt] = Convert.ToByte(128 + (temp / 128));
mib[cnt + 1] = Convert.ToByte(temp - ((temp / 128) * 128));
cnt += 2;
miblen++;
}
else
{
mib[cnt] = Convert.ToByte(temp);
cnt++;
}
}
var snmplen = 29 + comlen + miblen - 1;
// The SNMP sequence start
packet[pos++] = 0x30; // Sequence start
packet[pos++] = Convert.ToByte(snmplen - 2); // sequence size
// SNMP version
packet[pos++] = 0x02; // Integer type
packet[pos++] = 0x01; // length
packet[pos++] = 0x00; // SNMP version 1
// Community name
packet[pos++] = 0x04; // String type
packet[pos++] = Convert.ToByte(comlen); // length
// Convert community name to byte array
var data = Encoding.ASCII.GetBytes(community);
foreach (var t in data)
{
packet[pos++] = t;
}
// Add GetRequest or GetNextRequest value
if (request == "get")
packet[pos++] = 0xA0;
else
packet[pos++] = 0xA1;
packet[pos++] = Convert.ToByte(20 + miblen - 1); // Size of total MIB
// Request ID
packet[pos++] = 0x02; // Integer type
packet[pos++] = 0x04; // length
packet[pos++] = 0x00; // SNMP request ID
packet[pos++] = 0x00;
packet[pos++] = 0x00;
packet[pos++] = 0x01;
// Error status
packet[pos++] = 0x02; // Integer type
packet[pos++] = 0x01; // length
packet[pos++] = 0x00; // SNMP error status
// Error index
packet[pos++] = 0x02; // Integer type
packet[pos++] = 0x01; // length
packet[pos++] = 0x00; // SNMP error index
// Start of variable bindings
packet[pos++] = 0x30; // Start of variable bindings sequence
packet[pos++] = Convert.ToByte(6 + miblen - 1); // Size of variable binding
packet[pos++] = 0x30; // Start of first variable bindings sequence
packet[pos++] = Convert.ToByte(6 + miblen - 1 - 2); // size
packet[pos++] = 0x06; // Object type
packet[pos++] = Convert.ToByte(miblen - 1); // length
// Start of MIB
packet[pos++] = 0x2b;
// Place MIB array in packet
for (var i = 2; i < miblen; i++)
packet[pos++] = Convert.ToByte(mib[i]);
packet[pos++] = 0x05; // Null object value
packet[pos] = 0x00; // Null
// Send packet to destination
SendPacket(host, packet, snmplen);
return packet;
}
private static void SendPacket(IPEndPoint host, byte[] packet, int length)
{
var sock = new Socket(
AddressFamily.InterNetwork,
SocketType.Dgram,
ProtocolType.Udp);
sock.SetSocketOption(
SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout,
5000);
var ep = (EndPoint) host;
sock.SendTo(packet, length, SocketFlags.None, host);
// Receive response from packet
try
{
sock.ReceiveFrom(packet, ref ep);
}
catch (SocketException)
{
packet[0] = 0xff;
}
}
}
}