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:
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.