diff --git a/BasicEncodingRules.cs b/BasicEncodingRules.cs index 2ff8e95..0c64a16 100644 --- a/BasicEncodingRules.cs +++ b/BasicEncodingRules.cs @@ -33,9 +33,21 @@ namespace ln.snmp Number = number; } + public byte Firstbyte + { + get + { + return (byte)( + ((int)IdentifierClass << 6) | + (Constructed ? 0x20 : 0x00) | + (int)((Number < 31) ? Number : 0x1F) + ); + } + } + public override string ToString() { - return String.Format("[ASN.1 Type Class={0} Constructed={1} Number={2}]",IdentifierClass,Constructed,Number); + return String.Format("[ASN.1 Type Class={0} Constructed={1} Number={2} FirstByte=0x{3,02:X}]",IdentifierClass,Constructed,Number,Firstbyte); } } diff --git a/SNMPClient.cs b/SNMPClient.cs index 15bd84f..59ec3c3 100644 --- a/SNMPClient.cs +++ b/SNMPClient.cs @@ -9,24 +9,146 @@ // **/ using System; using System.Collections.Generic; -using Lextm.SharpSnmpLib; +using ln.snmp.types; +using System.Net.Sockets; +using System.Net; +using System.Threading; namespace ln.snmp { - public abstract class SNMPClient + public class SNMPClient : IDisposable { - public SNMPClient() - { + public static bool DEBUG = false; + public UdpClient LocalEndpoint { get; private set; } + public int Timeout { get; set; } = 1000; + + private Dictionary queuedRequests = new Dictionary(); + private bool shutdown = false; + private Thread ReceiverThread { get; set; } + + public SNMPClient(IPEndPoint localEndpoint) + { + LocalEndpoint = new UdpClient(localEndpoint); + ReceiverThread = new Thread(Receiver); + ReceiverThread.Start(); } - public abstract List Walk(ObjectIdentifier baseOID); - public abstract List Get(List baseOID); - - public virtual Variable Get(ObjectIdentifier oid) + public void Close() { - return Get(new List(new ObjectIdentifier[] { oid }))[0]; + shutdown = true; + LocalEndpoint.Close(); } + private void Receiver() + { + while (!shutdown) + { + try + { + IPEndPoint remoteEndpoint = null; + byte[] datagram = LocalEndpoint.Receive(ref remoteEndpoint); + + lock (queuedRequests) + { + if (queuedRequests.ContainsKey(remoteEndpoint)) + { + InternalRequest internalRequest = queuedRequests[remoteEndpoint]; + internalRequest.Response = datagram; + lock (internalRequest) + { + Monitor.PulseAll(internalRequest); + } + } + } + + + } + catch (SocketException se) + { + + } + catch (Exception e) + { + Console.WriteLine("Receiver(): {0}", e); + } + } + } + + private byte[] SendRequest(IPEndPoint remoteEndpoint,byte[] request,int timeout) + { + InternalRequest internalRequest = new InternalRequest(); + internalRequest.RemoteEndpoint = remoteEndpoint; + + lock(queuedRequests) + { + if (queuedRequests.ContainsKey(remoteEndpoint)) + throw new ArgumentException("Already pending request exists for this remote endpoint", nameof(remoteEndpoint)); + + queuedRequests.Add(remoteEndpoint,internalRequest); + + if (DEBUG) + Console.WriteLine("SNMPClient: Send: {0}", BitConverter.ToString(request)); + + LocalEndpoint.Send(request, request.Length, remoteEndpoint); + + lock (internalRequest) + { + Monitor.Exit(queuedRequests); + bool success = Monitor.Wait(internalRequest, timeout); + Monitor.Enter(queuedRequests); + if (!success) + { + throw new TimeoutException(); + } + } + queuedRequests.Remove(remoteEndpoint); + } + + return internalRequest.Response; + } + + + public Variable SNMPRequest(IPEndPoint remoteEndpoint,Sequence request) + { + byte[] response = SendRequest(remoteEndpoint, request.ToBytes(), Timeout); + + if (DEBUG) + Console.WriteLine("SNMPClient: Received: {0}", BitConverter.ToString(response)); + + Variable vreply = Variable.Read(response); + return vreply; + } + + + //public abstract List Walk(ObjectIdentifier baseOID); + //public abstract List Get(List baseOID); + + //public virtual Variable Get(ObjectIdentifier oid) + //{ + // return Get(new List(new ObjectIdentifier[] { oid }))[0]; + //} + + public void Dispose() + { + if (ReceiverThread != null) + { + if (ReceiverThread.IsAlive) + { + Close(); + if (!ReceiverThread.Join(250)) + { + ReceiverThread.Abort(); + } + } + ReceiverThread = null; + } + } + + class InternalRequest + { + public IPEndPoint RemoteEndpoint; + public byte[] Response; + } } } diff --git a/SNMPInterface.cs b/SNMPInterface.cs new file mode 100644 index 0000000..335e0b1 --- /dev/null +++ b/SNMPInterface.cs @@ -0,0 +1,142 @@ +using System; +using ln.snmp.types; +using System.Net; +using System.Collections.Generic; +using System.Linq; +using ln.snmp.channel; +namespace ln.snmp +{ + public enum SnmpVersion : int { V1 = 0, V2c = 1, V3 = 3 } + + public class SNMPInterface + { + public SnmpPDUChannel PDUChannel { get; set; } + + public IPEndPoint RemoteEndpoint { get; set; } + + public SnmpVersion SnmpVersion => PDUChannel.SnmpVersion; + + public SNMPInterface(SnmpPDUChannel PDUChannel,IPEndPoint remoteEndpoint) + { + this.PDUChannel = PDUChannel; + RemoteEndpoint = remoteEndpoint; + } + + + private List snmpRequest(IEnumerable objectIdentifiers) where T: PDU, new() + { + T pdu = new T(); + foreach (ObjectIdentifier oid in objectIdentifiers) + { + pdu.Add(new Sequence(new Variable[] { oid, NullValue.Instance })); + } + + GetResponse responsePDU = PDUChannel.RequestResponse(pdu, RemoteEndpoint) as GetResponse; + Sequence varBinds = responsePDU.VarBinds as Sequence; + List results = new List(); + + foreach (Variable varBind in varBinds.Items) + { + results.Add(varBind as Sequence); + } + + return results; + } + + public List snmpGet(IEnumerable objectIdentifiers) + { + return snmpRequest(objectIdentifiers); + } + public Variable snmpGet(ObjectIdentifier objectIdentifier) + { + return snmpGet(new ObjectIdentifier[] { objectIdentifier })[0].Items[1]; + } + public Variable snmpGet(string objectIdentifier) + { + return snmpGet(new ObjectIdentifier[] { new ObjectIdentifier(objectIdentifier) })[0].Items[1]; + } + public List snmpGet(IEnumerable objectIdentifiers) + { + return snmpGet(objectIdentifiers.Select((x) => new ObjectIdentifier(x))); + } + + public List snmpGetNext(IEnumerable objectIdentifiers) + { + return snmpRequest(objectIdentifiers); + } + public Sequence snmpGetNext(ObjectIdentifier objectIdentifier) + { + return snmpGetNext(new ObjectIdentifier[] { objectIdentifier })[0]; + } + public Sequence snmpGetNext(string objectIdentifier) + { + return snmpGetNext(new ObjectIdentifier[] { new ObjectIdentifier(objectIdentifier) })[0]; + } + public List snmpGetNext(IEnumerable objectIdentifiers) + { + return snmpGetNext(objectIdentifiers.Select((x) => new ObjectIdentifier(x))); + } + + + public List snmpGetBulk(ObjectIdentifier objectIdentifier) + { + List results = new List(); + List parts; + + do + { + parts = snmpRequest(new ObjectIdentifier[] { objectIdentifier }); + + foreach (Sequence ps in parts) + { + ObjectIdentifier oid = ps.Items[0] as ObjectIdentifier; + if (objectIdentifier.Contains(oid)) + results.Add(ps); + else + return results; + } + + + } while (parts.Count >= 32); + + return results; + } + public List snmpGetBulk(String objectIdentifier) + { + return snmpGetBulk(new ObjectIdentifier(objectIdentifier)); + } + + + + public List snmpWalk(ObjectIdentifier objectIdentifier) + { + if (SnmpVersion == SnmpVersion.V1) + { + List results = new List(); + ObjectIdentifier oiLast = objectIdentifier; + + while (objectIdentifier.Contains(oiLast)) + { + Sequence next = snmpGetNext(oiLast); + oiLast = next.Items[0] as ObjectIdentifier; + if (objectIdentifier.Contains(oiLast)) + results.Add(next); + } + return results; + } else if (SnmpVersion == SnmpVersion.V2c) + { + return snmpGetBulk(objectIdentifier); + } + else + { + throw new NotImplementedException(); + } + } + public List snmpWalk(string objectIdentifier) + { + return snmpWalk(new ObjectIdentifier(objectIdentifier)); + } + + + } +} diff --git a/SnmpError.cs b/SnmpError.cs new file mode 100644 index 0000000..f1ec56f --- /dev/null +++ b/SnmpError.cs @@ -0,0 +1,24 @@ +using System; +namespace ln.snmp +{ + public class SnmpError : Exception + { + public override string Message { get; } + public int Error { get; } + public int ErrorIndex { get; } + public String ObjectIdentifier { get; } + + public SnmpError(int error,int errorIndex,string objectIdentifier) + { + Error = error; + ErrorIndex = errorIndex; + ObjectIdentifier = objectIdentifier; + Message = ToString(); + } + + public override string ToString() + { + return String.Format("SNMP Error {0} (Index: {1} = {2}) was received",Error,ErrorIndex,ObjectIdentifier); + } + } +} diff --git a/channel/SnmpPDUChannel.cs b/channel/SnmpPDUChannel.cs new file mode 100644 index 0000000..119485c --- /dev/null +++ b/channel/SnmpPDUChannel.cs @@ -0,0 +1,20 @@ +using System; +using ln.snmp.types; +using System.Net; +namespace ln.snmp.channel +{ + public abstract class SnmpPDUChannel + { + public SNMPClient SNMPClient { get; set; } + + + public abstract SnmpVersion SnmpVersion { get; } + + public SnmpPDUChannel(SNMPClient client) + { + SNMPClient = client; + } + + public abstract PDU RequestResponse(PDU pdu, IPEndPoint remoteEndpoint); + } +} diff --git a/channel/SnmpV1Channel.cs b/channel/SnmpV1Channel.cs new file mode 100644 index 0000000..cf9cd4d --- /dev/null +++ b/channel/SnmpV1Channel.cs @@ -0,0 +1,48 @@ +using System; +using ln.snmp.types; +using System.Net; +using System.Collections.Generic; + +namespace ln.snmp.channel +{ + public class SnmpV1Channel : SnmpPDUChannel + { + public override SnmpVersion SnmpVersion => SnmpVersion.V1; + + public OctetString CommunityString { get; set; } + + + public SnmpV1Channel(SNMPClient client) + :base(client) + { + CommunityString = "public"; + } + public SnmpV1Channel(SNMPClient client,string communityString) + :base(client) + { + CommunityString = communityString; + } + + public override PDU RequestResponse(PDU pdu,IPEndPoint remoteEndpoint) + { + Integer version = new Integer((int)SnmpVersion); + + Sequence snmpRequest = new Sequence(); + snmpRequest.Add(version); + snmpRequest.Add(CommunityString); + snmpRequest.Add(pdu); + + Variable reply = SNMPClient.SNMPRequest(remoteEndpoint, snmpRequest); + + Sequence sreply = reply as Sequence; + PDU responsePDU = sreply.Items[2] as PDU; + + if (responsePDU.Error.LongValue != 0) + { + throw new SnmpError(responsePDU.Error, responsePDU.ErrorIndex, ((pdu.VarBinds.Items[(int)responsePDU.ErrorIndex - 1] as Sequence).Items[0] as ObjectIdentifier).AsString); + } + + return responsePDU; + } + } +} diff --git a/channel/SnmpV2Channel.cs b/channel/SnmpV2Channel.cs new file mode 100644 index 0000000..ad1dcb7 --- /dev/null +++ b/channel/SnmpV2Channel.cs @@ -0,0 +1,17 @@ +using System; +namespace ln.snmp.channel +{ + public class SnmpV2Channel : SnmpV1Channel + { + public override SnmpVersion SnmpVersion => SnmpVersion.V2c; + + public SnmpV2Channel(SNMPClient client) + : base(client) + { + } + public SnmpV2Channel(SNMPClient client, string communityString) + : base(client, communityString) + { } + + } +} diff --git a/ln.snmp.csproj b/ln.snmp.csproj index 865d4df..ace5cbf 100644 --- a/ln.snmp.csproj +++ b/ln.snmp.csproj @@ -45,12 +45,20 @@ + + + + + + + + \ No newline at end of file diff --git a/types/Counter64.cs b/types/Counter64.cs new file mode 100644 index 0000000..5e494ca --- /dev/null +++ b/types/Counter64.cs @@ -0,0 +1,24 @@ +using System; +namespace ln.snmp.types +{ + public class Counter64 : Integer + { + public ulong ULongValue { get => (ulong)LongValue; set => LongValue = (long)Value; } + + public Counter64() + : base(new Identifier(IdentifierClass.APPLICATION, false, 0x06)) + { + } + + public Counter64(uint value) + : this() + { + ULongValue = value; + } + + public override string ToString() + { + return String.Format("[Counter64 ULongValue={0}]", ULongValue); + } + } +} diff --git a/types/Integer.cs b/types/Integer.cs index a9aa70d..8247b8f 100644 --- a/types/Integer.cs +++ b/types/Integer.cs @@ -25,6 +25,11 @@ namespace ln.snmp.types LongValue = value; } + protected Integer(Identifier identifier) + :base(identifier) + { + } + public override byte[] Bytes { get => BasicEncodingRules.EncodeInteger(LongValue); @@ -32,5 +37,21 @@ namespace ln.snmp.types } public override object Value { get => LongValue; set => LongValue = (long)value; } + + public override string ToString() + { + return String.Format("[Integer LongValue={0}]", LongValue); + } + + + public static implicit operator int(Integer integer) + { + return (int)integer.LongValue; + } + public static implicit operator long(Integer integer) + { + return integer.LongValue; + } } + } diff --git a/types/NullValue.cs b/types/NullValue.cs index fefc8f1..5d999e6 100644 --- a/types/NullValue.cs +++ b/types/NullValue.cs @@ -29,5 +29,11 @@ namespace ln.snmp.types } } public override object Value { get => null; set => throw new NotImplementedException(); } + + public override string ToString() + { + return "[Null]"; + } + } } diff --git a/types/ObjectIdentifier.cs b/types/ObjectIdentifier.cs index e2f0cb3..2a07d30 100644 --- a/types/ObjectIdentifier.cs +++ b/types/ObjectIdentifier.cs @@ -32,6 +32,23 @@ namespace ln.snmp.types OIDValue = ioid; } + /* Check is b is part of the my subtree or is equal*/ + public bool Contains(ObjectIdentifier b) + { + int[] me = OIDValue; + int[] you = b.OIDValue; + + if (me.Length > you.Length) + return false; + + for (int n=0;n OIDValue = value as int[]; } + public string AsString + { + get => String.Join(".", OIDValue.Select((x) => x.ToString())); + } + + public override string ToString() + { + return String.Format("[ObjectIdentifier OID={0}]", AsString); + } + } } diff --git a/types/OctetString.cs b/types/OctetString.cs index 8051893..400166d 100644 --- a/types/OctetString.cs +++ b/types/OctetString.cs @@ -34,5 +34,21 @@ namespace ln.snmp.types get => StringValue; set => StringValue = value as string; } + + public override string ToString() + { + return String.Format("[OctetString StringValue={0}]", StringValue); + } + + public static implicit operator String(OctetString octetString) + { + return octetString.StringValue; + } + + public static implicit operator OctetString(String text) + { + return new OctetString(text); + } + } } diff --git a/types/PDU.cs b/types/PDU.cs index 01609af..bca1edc 100644 --- a/types/PDU.cs +++ b/types/PDU.cs @@ -24,7 +24,7 @@ namespace ln.snmp.types public PDU(Identifier identifier) :base(identifier) { - RequestID = new Integer(1); // Environment.TickCount + RequestID = new Integer(Environment.TickCount); Error = new Integer(); ErrorIndex = new Integer(); @@ -64,8 +64,40 @@ namespace ln.snmp.types public class GetRequest : PDU { - public GetRequest() : base(new Identifier(IdentifierClass.CONTEXT,true,0x00)) + public GetRequest() : base(new Identifier(IdentifierClass.CONTEXT, true, 0x00)) { } } + public class GetNextRequest : PDU + { + public GetNextRequest() : base(new Identifier(IdentifierClass.CONTEXT, true, 0x01)) + { + } + } + public class GetResponse : PDU + { + public GetResponse() : base(new Identifier(IdentifierClass.CONTEXT, true, 0x02)) + { + } + } + public class GetBulkRequest: PDU + { + public Integer NonRepeaters => Error; + public Integer MaxRepetitions => ErrorIndex; + + public GetBulkRequest() : base(new Identifier(IdentifierClass.CONTEXT, true, 0x05)) + { + NonRepeaters.LongValue = 0; + MaxRepetitions.LongValue = 32; + + } + } + public class SetRequest : PDU + { + public SetRequest() : base(new Identifier(IdentifierClass.CONTEXT, true, 0x03)) + { + } + } + + } diff --git a/types/Sequence.cs b/types/Sequence.cs index b5c2e5a..b419fce 100644 --- a/types/Sequence.cs +++ b/types/Sequence.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.IO; +using System.Text; namespace ln.snmp.types { public class Sequence : AbstractSequence @@ -93,5 +94,10 @@ namespace ln.snmp.types } } + public override string ToString() + { + return string.Format("[Sequence {0}]",String.Join(", ",Items.Select((x) => x.ToString()))); + } + } } diff --git a/types/Unsigned32.cs b/types/Unsigned32.cs new file mode 100644 index 0000000..265e789 --- /dev/null +++ b/types/Unsigned32.cs @@ -0,0 +1,64 @@ +// /** +// * File: Integer.cs +// * Author: haraldwolff +// * +// * This file and it's content is copyrighted by the Author and / or copyright holder. +// * Any use wihtout proper permission is illegal and may lead to legal actions. +// * +// * +// **/ +using System; +namespace ln.snmp.types +{ + public class Unsigned32 : Variable + { + public uint UIntValue { get; set; } + + public Unsigned32() + :base(new Identifier(IdentifierClass.APPLICATION, false, 0x02)) + { + } + + public Unsigned32(uint value) + :this() + { + UIntValue = value; + } + + protected Unsigned32(Identifier identifier) + : base(identifier) + { + + } + protected Unsigned32(Identifier identifier,uint value) + : base(identifier) + { + UIntValue = value; + } + + public override byte[] Bytes + { + get => BasicEncodingRules.EncodeInteger(UIntValue); + set => UIntValue = (uint)BasicEncodingRules.DecodeInteger(value); + } + public override object Value { get => UIntValue; set => UIntValue = (uint)value; } + + + public override string ToString() + { + return String.Format("[Unsigned32 UIntValue={0}]", UIntValue); + } + } + + public class Counter32 : Unsigned32 + { + public Counter32() + : base(new Identifier(IdentifierClass.APPLICATION, false, 0x01)) + { + } + public Counter32(uint value) + : base(new Identifier(IdentifierClass.APPLICATION, false, 0x01),value) + { + } + } +} diff --git a/types/Variable.cs b/types/Variable.cs index 7bcdb10..1b542b9 100644 --- a/types/Variable.cs +++ b/types/Variable.cs @@ -32,6 +32,14 @@ namespace ln.snmp.types stream.Write(payload, 0, payload.Length); } + public byte[] ToBytes() + { + MemoryStream memoryStream = new MemoryStream(); + Write(memoryStream); + return memoryStream.ToArray(); + } + + public static Variable Read(Stream stream) { Variable variable = null; @@ -47,6 +55,12 @@ namespace ln.snmp.types return variable; } + public static Variable Read(byte[] bytes) + { + MemoryStream stream = new MemoryStream(bytes); + return Read(stream); + } + public static void Write(Stream stream,Variable variable) { variable.Write(stream); @@ -70,12 +84,29 @@ namespace ln.snmp.types case 0x10: return new Sequence(); } - } else if (identifier.IdentifierClass == IdentifierClass.CONTEXT) + } + else if (identifier.IdentifierClass == IdentifierClass.CONTEXT) { switch (identifier.Number) { case 0x00: return new GetRequest(); + case 0x02: + return new GetResponse(); + case 0x03: + return new SetRequest(); + } + } + else if (identifier.IdentifierClass == IdentifierClass.APPLICATION) + { + switch (identifier.Number) + { + case 0x01: + return new Counter32(); + case 0x02: + return new Unsigned32(); + case 0x06: + return new Counter64(); } }