Search code examples
c#snmpsharp-snmp

How to define table for GET


I'm writing an agent for one of our C# apps that exposes various application properties via SNMP GET using SharpSnmpLib, which I've successfully sent traps and retrieved GETs with in the past. I'm able to expose single properties, but I'm having trouble figuring out how to define a table in my application.

This table should exposes a set of GET parameters once for each instance of a stream concept within my application (the number of streams is fixed on startup), each stream should be represented as a row in the table.

It's easy enough to define this in the MIB, and I've successfully defined and implemented such tables in the past with C++ libraries, so I know it's possible.

Here's how I define a simple GET of the app version in the MIB:

dtStream      OBJECT IDENTIFIER ::= {dtApps 3}

dtVersion OBJECT-TYPE
    SYNTAX      DisplayString
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
        "The version number of the application"
    ::= {dtStream 1}

And in code:

public class StringVar : ScalarObject
{
    private ISnmpData _data;
    private string _value;
    public StringVar(ObjectIdentifier oid, string value) : base(oid)
    {
        Value = value;
    }
    public override ISnmpData Data
    {
        get { return _data; }
        set { throw new AccessFailureException(); }
    }
    public string Value
    {
        set { _data = new OctetString(_value = value); }
        get { return _value; }
    }
}
...
ObjectStore os = new ObjectStore();
os.Add(Version = new StringVar(OID_STREAM_VERSION, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()));

Here's an example MIB definition for the table I'm trying to create:

dtStreamTable OBJECT-TYPE
    SYNTAX      SEQUENCE OF DtStreamEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "Statistics on the status and performance of the streams"
    ::= {dtStream 5}

DtStreamEntry OBJECT-TYPE
    SYNTAX      DtStreamEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "Status of a particular stream."
    INDEX       {dtStreamID}
    ::= {dtStreamTable 1}

DtStreamEntry ::= SEQUENCE {
    dtmStreamID           Integer32,
    dtStreamName          DisplayString
    dtStreamHost          DisplayString
    dtStreamType          Integer32
}

dtStreamID OBJECT-TYPE
    SYNTAX      Integer32 (0..255)
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "Index of the stream entry in the table."
    ::= {DtStreamEntry 1}

dtStreamName OBJECT-TYPE
    SYNTAX      DisplayString
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
        "Name of the stream."
    ::= {DtStreamEntry 2}

dtStreamHost OBJECT-TYPE
    SYNTAX      DisplayString
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
        "Hostname that the stream is connected to."
    ::= {DtStreamEntry 3}

dtStreamType OBJECT-TYPE
    SYNTAX      Integer32(1..2)
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
        "Type of stream"
    ::= {DtStreamEntry 4}

It looks like this in iReasoning MIB Browser:

Screengrab

I've got as far as defining what I'd consider to be the "row", as a class derived from TableObject:

    class StreamEntry : TableObject
    {
        private readonly IList<ScalarObject> _elements = new List<ScalarObject>();

        public StreamEntry(int id, string name, string host, int type) 
            : base()
        {
            _elements.Add(StreamId = new IntVar(OID_STREAM_ID, id));
            _elements.Add(Name = new StringVar(OID_STREAM_NAME, name));
            _elements.Add(Host = new StringVar(OID_STREAM_HOST, host));
            _elements.Add(Type = new IntVar(OID_STREAM_TYPE, type));
        }

        public IntVar StreamId { get; private set; }
        public StringVar Name { get; private set; }
        public StringVar Host { get; private set; }
        public IntVar Type { get; private set; }

        protected override IEnumerable<ScalarObject> Objects
        {
            get { return _elements; }
        }
    }

After all that, I guess my question is, how do I associate these into a table that is returned with the OID for the table or entry?

I've tried adding multiple StreamEntry objects into the ObjectStore:

foreach(Stream s in Streams)
{
    os.Add(new StreamEntry(s.ID, s.Name, s.HostName, s.Type));
}

With this I can only retrieve the data for the first one (using individual GETs, or WALK on the dtStreamEntry in the MIB Browser).


Solution

  • Ok, I figured it out. All ScalarObjects need to be added to the ObjectStore in OID order - otherwise everything breaks.

    So I needed to create multiple StreamEntry objects (no longer having TableObject as a base class) within an enclosing StreamTable object which was derived from TableObject, and I needed to extract the ScalarObjects from each StreamEntry and sort them all by OID before returning them as the Objects collection override.

    I now have a working SNMP table. :)