MibService and Strongly Typed Objects

by Administrator 29. September 2008 16:11

Using the MibService it is possible to generate strongly typed objects for the nodes that are loaded into the MibService.  This is done by calling the GenerateCode method and passing the following arguments:

void GenerateCode(Node node, string namespaceName, string outputPath)

node – a node to be converted into code, all child nodes of this node will be generated too.
namespace
-  the namespace the generated code will be created in.
outputPath
– the path the generated code will be saved at.  

The code generated by the MibService has the following format each node encountered during generation is turned into a class; its name is the name of the node.  If the name of the node is a reserved word in C# then ‘Node’ is appended to it.  Each child Node, Variable, and Table is exposed as a property returning the type created for it. 

   1: public class NodeName : Node {
   2:       //Child nodes are exposed as properties returning their type.
   3:       public ChildNode ChildNode { get; }
   4:       //Instance definition of Variable
   5:       public string scalar { get; }
   6:       //Static definition of Variable
   7:       public static ObjectIdentifierVariable scalarVariable { get; }
   8:       //Tables are exposed as properties returning their type.
   9:       public MibTableNode MibTableNode { get; }
  10:   }
  11:  
  12:   public class ChildNode : Node {
  13:       //May contain other Nodes,Variables, or Tables
  14:   }
  15:  
  16:   public class MibTableNode : MibTable, IEnumerable<MibTableRowNode> {
  17:       //MibTable.Rows is hidden with a new strongly typed signature.
  18:       new public Collection<MibTableRowNode> Rows { get; }
  19:   }
  20:  
  21:   public class MibTableRowNode : MibTableRow {
  22:       //Instance definition of Variable
  23:       public EnumVar enumVar { get; }
  24:       //Static definition of Variable
  25:       public static EnumerationVariable scalarVariable { get; }
  26:   }
  27:   public enum EnumVar { Option1 = 1, Option2 = 2 }

The strongly typed MibTables hide their base classes Rows property with a signature returning a collection of the generated Mib TableRows class.  Each generated MibTable also implements IEnumerable where T is the generated MibTableRows class.  EnumerationVariables are turned into enumerations and properties that use the enumeration return the generated type for the enum.

Strongly Typed Objects Usage Scenarios:

The Nstrument library ships with RFC1213 and HOST-RESOURCES Mibs already generated.  They are located in the ‘Nstrument.Snmp.Mib.Compiled’ namespace.  You can use these generated classes in a couple of different ways:

Using Static Variable Definitions:

The static variables that were defined during the code generation process were defined to support the following usage scenario:

   1: public void UsingStaticVariables() {
   2:      SnmpService snmpService = new SnmpService();
   3:      SnmpSap snmpSap = snmpService.CreateSap("192.168.1.203", "public");
   4:      Response response = snmpSap.Get(
   5:          new Collection<Variable> {
   6:              SystemNode.SysNameVariable,
   7:              Interfaces.IfNumberVariable 
   8:      });
   9:      string systemName = response[SystemNode.SysNameVariable].ValueAsString; 
  10:      string totInterface = response[Interfaces.IfNumberVariable].ValueAsString; 
  11: }

Writing your code using this format has the following advantages:

· No object identifiers are defined as strings “1.3.6.1.2.1.2.1.0“. The code is easier to read and understand its function.
· It’s efficient, were only fetching the object identifiers that were going to use.

Using Strongly Typed Tables:

The generated tables can be used as in the following example:

   1: public void StronglyTypedTables() {
   2:      SnmpService snmpService = new SnmpService();
   3:      SnmpSap snmpSap = SnmpService.CreateSap("192.168.1.203", "public");
   4:  
   5:      Mib2 mib2 = new Mib2(snmpSap);
   6:      foreach (IfEntry ifEntry in mib2.Interfaces.IfTable) {
   7:          if (ifEntry.IfType != IfType.SoftwareLoopback) {
   8:              Console.WriteLine(ifEntry.IfDescr + " Speed = " + ifEntry.IfSpeed);
   9:          }
  10:      }
  11:  }
Note above I did not create an instance of a IfTable but instead a Mib2 object and accessed IFTable thru it.  This can be useful if you’re accessing multiple objects in the Mib as you only have to declare one variable.   Also you can use Intellisence  to help find what you are looking for in the Mib.  Another advantage of using the generated code is the use of the EnumerationVariable  in the following line:
if (ifEntry.IfType != IfType.SoftwareLoopback)

Anyone reading this code won’t be wondering under what condition we will be branching.

Using LINQ to SNMP:

Because the generated tables implement IEnumerable they can be used in LINQ queries.  The following example is identical to the last one only it uses LINQ:

   1: public void BasicLinqQuery() {
   2:       SnmpService snmpService = new SnmpService();
   3:       SnmpSap snmpSap = snmpService.CreateSap("192.168.1.203", "public");
   4:  
   5:       IfTable ifTable = new IfTable(snmpSap);
   6:       var interfaces = from inter in ifTable
   7:                        where inter.IfType != IfType.SoftwareLoopback
   8:                        select inter;
   9:       foreach (IfEntry entry in interfaces) {
  10:           Console.WriteLine(entry.IfDescr + " Speed = " + entry.IfSpeed);
  11:       } 
  12:   }
  13:  

From the examples shown in this post you can see that using the classes generated by the MibService in your Snmp code have the following advantages:

· Code is easier to read and maintain.
· Values can be accessed as their actual data types as opposed to strings.
· Can be used with LINQ.

Feedback appreciated,
Craig R.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Using the MibService

by Administrator 29. September 2008 15:59

The MibService performs two functions:
Mib parsing - converts Mib files into an object graph.  This graph consists of 6 different object types (Node, MibTable, MibTableRow, Variable, Notification, and Group) all of these classes derive from NodeBase.
Strongly typed class generator – creates C# classes that contain properties representing the actual Snmp variables.

Loading Mib files:

To use the MibService you have to create an instance and pass it a directory where the Mibs are located.  Notice I said ‘Mibs’, Mib files can contain references to other Mib files.  In order to load a Mib all references to included Mibs need to be loaded first.  A FileNotFoundException will be thrown if a required Mib cannot be located.  The name of the missing Mib can be read from the Exception.Message property.   Mibs are loaded into the MibService by calling the Load method and passing the name of the Mib to load.  Each loaded Mib is turned into a graph of objects.  This graph consists of 6 node types:

Node – can contain other Nodes, Tables and Variables 
Table – defines a table.
TableRow – defines the columns in a table.
Variable – defines a named symbol.
Notification – defines a trap which contains a collection of Variables.
Group – defines a named collection of Variables

The MibService exposes the graph of objects it has loaded thru the Nodes collection and the following methods:

   1: public NodeBase GetNodeByName(string name)
   2: public T GetNodeByName(string name)
   3:  
   4: public NodeBase GetNodeByOid(string objectIdentifier)
   5: public T GetNodeByOid(string objectIdentifier)
   6:  
   7: public Collection GetNodesByType()

These methods exist on the MibService, Module, and NodeBase objects so you can begin your search for specific nodes from different scopes depending on your needs.  It’s worth noting that the GetNodeByName and GetNodeByOid functions return null if the node your searching for cannot be found.  You can load multiple Mibs into the Mib service by repeatedly calling load with a new Mib Name.  Each loaded Mib is inserted into the existing graph of objects contained by the MibService.  The following diagram depicts the relationships between the MibService, Module and the Nodes.

MibServiceRelationships]

Working with loaded modules:

The following example shows how to load the RFC1213 mib and dump all of it’s nodes in a hierarctical fashion.   Using the children collection on NodeBase we can recursivly call DumpNode for each child node.

   1: public void DumpTree() {
   2:     string mibsPath = @"C:\Program Files\Nstrument\SnmpLibrary\Mibs"; 
   3:     MibService mibService = new MibService(mibsPath);
   4:     Module module = mibService.Load("RFC1213-MIB");
   5:     DumpNode(module.Nodes[0], 0);
   6: }
   7:  
   8:  void DumpNode(NodeBase node, int depth) {
   9:      string padding = string.Empty;
  10:      for (int idx = 0; idx < depth; idx++) { padding += "\t"; }
  11:      Console.WriteLine(padding + node.Name);
  12:  
  13:      foreach (NodeBase childNode in node.Children) {
  14:          DumpNode(childNode, depth + 1);
  15:      }
  16:  }

MibService & SnmpService Integration:

Once you have a Mib module loaded the next logical step would be to fetch some of the objects in the module from a SNMP agent.  You can accomplish this using the Get method on (Node, MibTable, and Variable) nodes.  This method takes in a single parameter an SnmpSap that indicates the agent to fetch the data from.  The following example illustrats how you can fetch a MibTable from a SnmpSap:

   1: public void GetTable() {
   2:     //Create SnmpService and a Service Access Point.
   3:     SnmpService snmpService = new SnmpService();
   4:     SnmpSap snmpSap = SnmpService.CreateSap("192.168.1.203", "public");
   5:  
   6:     //Create MibService and load the RFC1213 Mib.
   7:     MibService mibService = new MibService(MibsPath);
   8:     Module module = mibService.Load("RFC1213-MIB");
   9:  
  10:     //Get the 'ifTable' from the module and fetch it from the SnmpSap
  11:     MibTable ifTable = module.GetNodeByName<MibTable>("ifTable");
  12:     MibTable fetchedTable = ifTable.Get(snmpSap);
  13:  
  14:     //Iterate each row in the table and show it's variables
  15:     foreach (MibTableRow row in fetchedTable.Rows) {
  16:         foreach (Variable var in row.Variables) {
  17:             Console.WriteLine(var.Name + “ = “ + var.ValueAsString);
  18:         }
  19:     }
  20: }

In the next installment of this series I will show you how to use the MibService to generate stronly typed objects to represent the nodes loaded into the MibService.  Using the stronly typed objects gets you out of the business of typing object identifiers.  In addition we will use these generated classes to perform Linq to Snmp.


Feedback Appreciated,
Craig R.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Putting the 'simple' back into SNMP

by Administrator 29. September 2008 14:58

While trying to find a Snmp library to use in another project, I was disappointed while evaluating the options available to .Net developers. Some of the problems I observed while testing include:

  • Complex API design. (Expected caller to create PDU objects for operations).
  • Lack of asynchronous support or asynchronous signatures did not allow the caller to pass state.
  • Implemented the component design pattern or were obvious Java ports.

It was with these limitations in mind that I set out to create a Snmp Library that would address these issues and adhere to the following architecture principals:

  • K.I.S.S (Keep It Simple Stupid)
  • Implement .Net asynchronous pattern

With these goals in mind I created the Nstrument Snmp Library which consists of three services:

  • SnmpService - provides sync and async implementations for the following operations: Get, GetNext, GetBulk, Set, TableWalk and SendTrap.
  • TrapService - Notifies when traps are received.
  • MibService - Loads mib files into its hierarchy.

Using the SnmpService and SnmpSap for basic operations:

The SnmpService is responsible for sending Snmp messages to an agent and receiving the responses to those messages. By default the SnmpService initializes a random port to send Snmp messages on, however if you prefer you can pass a port to use on the constructor. You only have to create one instance of this class in your application because it is thread safe. Currently the SnmpService only supports Snmp Version 1 and 2.

The SnmpSap (ServiceAccessPoint) encapsulates all of the parameters that define a specific endpoint (IP Address, Version, Credentials, and Timeout). You can create a SnmpSap by calling the CreateSap function on the SnmpService. Once you have an instance of a SnmpSap you can perform any of the following operations (Get, Set, GetNext, GetBulk, TableWalk, Walk). GetNext and GetBulk operations are rarely needed they are used internally by the TableWalk and Walk operations. The following diagram depicts the relationships between the SnmpService, SnmpSap and the agents they communicate with:

SnmpServiceRelationships

Time to get to some code, I’m not a fan of code snippets that are so simplistic there is nothing to observe other
than the syntax it’s self.  So with that in mind let’s create a program that will perform the following operations
for multiple hosts:

  • Get the system name and description
  • Set the contact information
  • Get all of the interface descriptions
   1: void Example() {
   2:     using (SnmpService snmpService = new SnmpService()) {
   3:         //Define some objectIdentifers that were going to use.
   4:         Variable sysDescr = new Variable("1.3.6.1.2.1.1.1.0");
   5:         Variable sysName = new Variable("1.3.6.1.2.1.1.5.0");
   6:         //To set a variable you have to know it's type.
   7:         Variable sysContact = new OctetStringVariable("1.3.6.1.2.1.1.4.0", "acme");
   8:         Variable ifDescr = new Variable("1.3.6.1.2.1.2.2.1.2");
   9:  
  10:         //Define the hosts we going to communicate with.
  11:         List<SnmpSap> hosts = new List<SnmpSap>();
  12:         hosts.Add(snmpService.CreateSap(IpAddress, "public", "private"));
  13:         hosts.Add(snmpService.CreateSap(IpAddress, "public", "private"));
  14:  
  15:         foreach (SnmpSap host in hosts) {
  16:             //Get the host name and description
  17:             Response getResponse = host.Get(new List<Variable> { sysName, sysDescr });
  18:             string hostName = getResponse[sysName].ToString();
  19:             string hostDescription = getResponse[sysDescr].ToString();
  20:  
  21:             //Set the contact information in the host.
  22:             Response setResponse = host.Set(sysContact);
  23:  
  24:             //Get all of the interface descriptions on this host.
  25:             MibTable table = host.TableWalk(ifDescr);
  26:             foreach (MibTableRow row in table.Rows) {
  27:                 string interfaceDescription = row[ifDescr].ToString();
  28:             }
  29:         }
  30:     }
  31: }

A few things to note about this example, do yourself and your fellow developers a favor by declaring the Snmp object identifiers as variables and give them descriptive names. It’s difficult to revisit code and remember what "1.3.6.1.2.1.1.1.0" represents. In addition when you access the variables in the response the code is also more readable ‘getResponse[sysDescr].ToString();’ The variables in this example could have been declared as const. This is due to the fact the SnmpService does not return the variable instances passed to it, it creates new instance for the response.  However the variables returned are clones of the ones passed, as such variable names, descriptions properties will be populated on the response.

Performing asynchronous operations:

All of the operations on the SnmpSap have synchronous and asynchronous operations. These are implemented using the IAsyncResult design pattern.

   1: Variable _sysDescr = new Variable("1.3.6.1.2.1.1.1.0");
   2:  
   3: void Async() {
   4:     using (SnmpService snmpService = new SnmpService()) {
   5:         List<SnmpSap> hosts = new List<SnmpSap>();
   6:         hosts.Add(snmpService.CreateSap(IpAddress, "public", "private"));
   7:         hosts.Add(snmpService.CreateSap(IpAddress, "public", "private"));
   8:  
   9:         foreach (SnmpSap host in hosts) {
  10:             host.BeginGet(new List<Variable> { _sysName }, EndGetCallback, host);
  11:         }
  12:     }
  13: }
  14:  
  15: void EndGetCallback(IAsyncResult ar) {
  16:     //Get the host from the AsyncState
  17:     SnmpSap host = (SnmpSap)ar.AsyncState;
  18:     //Complete the call.
  19:     Response getResponse = host.EndGet(ar);
  20:     string hostName = getResponse[_sysName].ToString();  
  21: }

If there is one place to get confused with the IAsyncResult design pattern it would be what should you pass as state.  In the example above I passed the SnmpSap, and in the callback extracted it and completed the call. If you need to pass additional state you can simply create a wrapper object that will contain the SnmpSap and any other data you may need.

In part 2 of this series I will cover using the MibService.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Powered by BlogEngine.NET 1.4.5.0

Month List