Progress Update – 2010-10-10

A number of new ideas and improvements have been suggested over the past couple of months and I have been slowly catching up on them.

First was the introduction of IPv6 support which was interesting, not only because of the extended feature support within SnmpSharpNet but also from my personal interest in the protocol. I have integrated the code required in UdpTarget and UdpTransport classes and it seams to work in the limited testing I have been able to do in my home lab. Goal of the implementation was not to impact existing functionality and to make IPv6 usable within the same class, method, parameter structure as with IPv4 support alone.

I had it pointed out to me by Pavel Tatarinov that SNMPv3 operation is very slow for the reason of continuous need to generate authentication and privacy keys for each call. This is not very efficient since when the engine id has been obtained through the discovery process, none of the information needed for authentication and privacy key generation changes in subsequent calls. Caching of some sort seams to be in order.

During the first few tests I performed, a GetNext walk operation on a subtree with 39 items resulted in 156 calls to the PasswordToKey method of the selected authentication protocol class. Considering how key is generated, each call resulted in a significant delay between GetNext calls. Significant enough that walk operation result on the console was visibly slow.

To work around this, I have added PrivacyKey and AuthenticationKey properties to the SecureAgentParameters class that allows for caching of required keys. I have also created BuildCachedSecurityKeys() method to the same class that will generate the keys for both authentication and privacy (if they are appropriate for the selected security model) and cached keys will then be used by every request made using UdpTarget class.

After required changes were made, number of calls to PasswordToKey method have dropped to just 2 and execution speed is visibly better. Looking in a profiler, execution of the above mentioned walk with described changes has reduced running time from 2223ms to 478ms. Anything that results in order of magnitude improvement in speed is good so I’m putting this in a plus column.

One of the bug fixes I introduced in the last revision was related to the source host IP address and port number check. The way it is done in the code is that check was not performed when requested. It is only performed when not requested. Since this is opposite of the required behavior, fix is necessary and will be included in the next version.

A bug that took up most of my time and resulted in no fix is the issue with the Socket class on Windows XP when network cable is disconnected. It appears that once network cable is disconnected, Socket becomes unusable and needs to be recreated. Big problem is that when you first disconnect the network cable, any call to UdpTarget class or network event generated within the class results in a NullArgumentException being thrown with no way to catch it. I have gone through all the calls, wrapped them all into try/catch blocks and still didn’t manage to figure out how or why this is happening. What makes it even more interesting is that it only happens on Windows XP. I didn’t manage to replicate the same issue on Vista or Win7.

The only way to deal with this that I could see was to add a whole new layer of code to monitor network interface status and when connection is disabled put UdpTarget and UdpTransport classes into special state which releases the internal Socket reference until network connectivity is restored. Unfortunately, there is no easy way to do this. It would require a lot of additional code to implement this fix and, more of a concern for me, a lot more effort to support it.

For now, status of this issue is that it can be replicated but there doesn’t not appear to be a way to fix the issue with managed code so application writers will have to build their own code to check for events that could cause uncatchable exceptions. If anybody out there has any information on what could cause this kind of a problem, I would appreciate a pointer.

Now a little about future plans. I’ve spent some time wrapping my mind around MIB parsing and it doesn’t look like I’ll be able to do a quick hack to get this functionality of the ground. My “quick and dirty” parser that I whipped out in a week works ok up to a point but there are MIB file formats that it can’t deal with and it provides very limited subset of information that is available in MIB files that I don’t consider it acceptable. So, instead of looking at just throwing something out there and calling it quits, I decided to devote this winter (or non-golfing season if you prefer) to developing a proper ASN.1 parser. Plan for the library is to advance to version 0.9 with a usable parser.

Final step in the libraries path to version 1 is to include AgentX protocol support. Development plan for the library was never to make it capable of building Master SNMP agents because there is enough of them available for free and another one would not add any value to the potential users. What could be very useful to a number of people is to allow their managed code applications to register their MIB sub-tree with the master SNMP agent that already exists on the machine (server or workstation) and provide status information (or receive commands) from remote management stations through the master agent using SNMP protocol. AgentX protocol is a pain, it has way too many message types and is created for maximum flexibility of registering applications. While very handy for users, this makes it difficult to provide a simple interface in a library so I will be spending some time trying to identify most common use cases and required interfaces to implement it before getting down to the coding part.

That’s the story so far. If anybody has any suggestions on improvements, additional features or any other constructive comments, don’t hesitate to send them to me on dev [at] snmpsharpnet [dot] com.

Release of bug fix version 0.7.5

I finally got around to releasing this bug fix version. What pushed me over the line were two bugs in the previous version, error in encoding of unsigned integer values (some of them above the 255 value) and invalid Pdu type being set in the Pdu.GetBulkPdu() static method.

You can download it from the SourceForge project page.

As always, let me know if you run into any problems or would like to see new functionality in the library.

Major bug discovered in version 0.7.4 and earlier

I would like to thank Jozsef Horvath for discovering, reporting and providing a fix for an issue related to encoding of specific unsigned integer values used by Gauge32, Counter32, etc. variable types derived from UInteger32 base type.

Fix for the issue will be included in the version 0.7.5 scheduled for release tomorrow.

Changes in preparation for version 0.7.5 release

Based on the feedback so far from users and the fixes needed in the released version of the library, following changes have so far been planned for the new release:

  • fixed: socket error 10054 now throws SnmpNetworkException in UdpTransport.Request method
  • added ICloneable to all AsnType child classes that didn’t have it
  • added IComparable interface implementation to OctetString class
  • implemented error handling in UdpTransport async methods. Now legitimate errors will result in request being aborted (same as blocking requests) with
    appropriate error code returned in the callback
  • added VbCollection.ContainsOid(Oid) method
  • added VbCollection index access to variable bindings using Oid class and string representation of oid (this(string oid))
  • removed unnecessary byte array allocation in MutableByte.Append(byte) method
  • removed unused MutableByte class allocation from Pdu.encode() method
  • added a check for a correct sequence type at the start of an SNMP packet in SnmpPacket.GetProtocolVersion() and SnmpPacket.decode() methods (SnmpDecodingException thrown)
  • added sequence type check when parsing global message data sequence in SnmpV3Packet.decode() method
  • removed unused versionHeaderLength variable in SnmpV3Packet.encode method
  • removed unused variable byteCount from Privacy3DES.Encrypt method
  • removed unused asnBuf variable from Counter64.decode method
  • removed unused oldOffset variable from Oid.decode method
  • removed unused oldOffset variable from Sequence.decode method
  • removed unused AgentParameter assignments from UdpTarget.AsyncResponse method
  • added sequence type check in SnmpV3Packet.GetUSM method
  • added exception XML documentation to SnmpV3Packet.GetUSM method
  • removed unused preVersionLength variable in SnmpV3Packet.encode method
  • removed unused asnBuf variable from Counter64.decode() method
  • implemented IDisposeable interface in UdpTransport class and changed UdpTransport.Close() into UdpTransport.Dispose() method
  • added IDisposeable interface to UdpTarget class. Implementation Dispose() method is inherited from the UdpTransport class
  • fixed Pdu.Reset() method request id value increment to handle value rollover to avoid integer overflow

More updates will be made based on any new feedback received.

To keep the library evolving to provide as complete as possible implementation of SNMP, send me feedback or comments you have through the SourceForge project page and I’ll do my best to implement it.

SnmpSharpNet in IronPython

I had a little bit of time today and figured I’ll try to use the library in IronPython.

I have used Python a lot a few years back for an SNMP based project and did like it a lot. It has a clean syntax and is very easy to learn.

It has taken about an hour of googling to find what I needed and in the end I had a fully working Get example:

import clr
from SnmpSharpNet import *
from System.Net import *
param = AgentParameters()
agentIP = IpAddress("")
agent = UdpTarget(IpAddress.op_Explicit(agentIP), 161, 2000, 1)
pdu = Pdu(PduType.Get)
pdu.VbList.Add("") # sysDescr
pdu.VbList.Add("") # sysObjectID
pdu.VbList.Add("") # sysUpTime
pdu.VbList.Add("") # sysContact
pdu.VbList.Add("") # sysName
result = None
  result = agent.Request(pdu, param)
# If result is null then agent didn't reply or we couldn't parse the reply.
if (result != None):
  # ErrorStatus other then 0 is an error returned by
  # the Agent - see SnmpConstants for error definitions
  if (result.Pdu.ErrorStatus != 0):
    # agent reported an error with the request
    print "Error in SNMP reply. Error ", result.Pdu.ErrorStatus, \
      "index ", result.Pdu.ErrorIndex
    # Reply variables are returned in the same order as they were added
    #  to the VbList
    print "sysDescr(", result.Pdu.VbList[0].Oid.ToString(), ") (", \
      SnmpConstants.GetTypeName(result.Pdu.VbList[0].Value.Type), "): ", \
    print "sysObjectID(", result.Pdu.VbList[1].Oid.ToString(), ") (", \
      SnmpConstants.GetTypeName(result.Pdu.VbList[1].Value.Type), "): ", \
    print "sysUpTime(", result.Pdu.VbList[2].Oid.ToString(), ") (", \
      SnmpConstants.GetTypeName(result.Pdu.VbList[2].Value.Type) ,"): ", \
    print "sysContact(", result.Pdu.VbList[3].Oid.ToString(), ") (", \
      SnmpConstants.GetTypeName(result.Pdu.VbList[3].Value.Type) ,"): ", \
    print "sysName(", result.Pdu.VbList[4].Oid.ToString(), ") (", \
      SnmpConstants.GetTypeName(result.Pdu.VbList[4].Value.Type) ,"): ", \
  print("No response received from SNMP agent.")

Output is as expected:

sysDescr( ) ( OctetString ):  "Dual core Intel notebook"
sysObjectID( ) ( ObjectId ):
sysUpTime( ) ( TimeTicks ):  0d 7h 54m 1s 460ms
sysContact( ) ( OctetString ):  ""
sysName( ) ( OctetString ):  "milans-nbook"

Progress report

Today I have posted version 0.5.1 to the SourceForge site with 3 critical bug fixes included.

I’m not too happy about releasing too many versions but if I find a bug with a serious impact on library usability, I’ll release a new versions in rapid succession.

I’m already working on the next version. One of the changes that I have already put in place for the next version is to change Pdu.Type (and associated ScopedPdu.Type) data type from byte and reliance on the SnmpConstants byte constants, that can cause mistakes, to a new enumeration PduType.

I’m looking through the code looking for other places where enumerations make more sense then using constants.

I have decided that enumerations are a better way to go about this. Constants mean that you can set a wrong value in a wrong place. It also means that a lot of code needs to be put in place to verify input. Extra code means more potential bugs, not checking every parameter means wrong values can be set by the library users and result in unexpected consequences. So, enumerations are the way forward…

Current project status

Just a quick note.

Version 0.5.0 is out the door … finally.

There is plenty to do about the project but I have decided to start working on actually delivering some applications instead of just plugging away at SNMP.

Before shifting focus, I’ll work on a series of documentation pages to be posted on the project web site. Usually people get the coding to a certain level and take vacation forgetting the documentation. I am trying not to do that.

My intention from the beginning was to depend on the people using the library to tell me what bugs or issues they had using it.

I would like to ask you all that if you are using the library and find an issue, let me know.

You don’t have to have a solution for the problem, just a description of what you were trying to do, what happened and possibly dump of information you were working on at the time so I can dig into it deeper.

By letting me know what issues or limitation you’ve found, you will help improve this project for everybody (yes, yourself included).

SNMPv3 INFORM and TRAP handling

Just had an interesting time testing TRAP and INFORM handling with SNMP version 3 using net-snmp snmptrap utility.

Here is the summary of what I have learned over the last week and what the testing has confirmed.

SNMP Traps are handled like a response to a query. You get a packet, parse it using USM information for the specific agent and presto, you’ve got the notification information.

In the Trap packet, engineId, engineBoots, engineTime and securityName are set by the agent sending the trap so handling is relatively easy with the only difference being that you have to locally store engineId and securityName mappings to the correct authentication digest, privacy protocol and related secrets. Then, when the packet arrives, parse the USM header information, find the appropriate information for the authoritative engine that sent you the trap, apply the authentication and privacy parameters to the SnmpV3Packet class and call SnmpV3Packet.decode method.

While this is not as easy as request/response operation, it is not too hard.

SNMP version 3 Inform is a whole different game. In the Inform packet, engineId, engineBoots and engineTime are set to the manager values. This means that your application has to be able to respond to discovery packets.

This took a little while to figure out. With the use of SnmpV3Packet.GetUSM (and the unintended consequence of how I implemented this method) you can get all the values needed to generate a Discovery response. New SnmpV3Packet methods like IsDiscoveryPacket and DiscoveryResponse should help with implementing this functionality.

Only thing remaining to finish before version 0.5 is released is SNMP version 2 inform handling and testing. I’m planning to have it done and tested enough for release by the end of next week.

Keep checking the project page for news…

SNMP v2c and v3 EndOfMibView, NoSuchInstance and NoSuchObject types

SNMP v2 introduced a concept of MIB status notification encoded in the VbList as a value that notifies the caller when there is an issue with the request or if request has reached the end of available data.

This is where EndOfMibView, NoSuchInstance and NoSuchObject come in. EndOfMibView tells caller that there is no more data to “walk” (using either GET-NEXT or GET-BULK packets), NoSuchInstance tells the caller that requested instance of the object does not exist (for example, asking for interface speed – for interface instance 200 on an agent that only has interfaces up to instance 100 will result in this error), and EndOfMibView signifying the end of the data in the requested view.

To check for these notifications, you should evaluate each value returned by the agent, first for the notification and second for the value it should contain.

For example, after a request completed using SNMP v2c or 3, you could do the following:

SnmpV2Packet pkt = GetData();
foreach( Vb v in pkt.Pdu.VbList ) {
  if( v.Value.Type == SnmpConstants.SMI_ENDOFMIBVIEW ) {
    // end of mib reached
  } else {
   // process Vb value

OctetString.ToString() method

Unlike the name of the class, OctetString doesn’t really store text strings as would be expected, but any type of byte based data.

Because data in the OctetString is just a series of bytes, there is no guarantee that it is printable so some fancy footwork is required to perform the ToString() method operation.

In the current implementation, OctetString.ToString() tries to determine if the value contained within the class is binary and therefore unreadable, or ASCII and therefore printable. This is done by scanning the class data for values that are less then 32 (excluding the new line characters which are considered printable) or greater then 127. Values outside of this range are not considered ASCII text so assumption is that class value is binary and special output format is required.

Now, if the value is determined to be all text then there is no problem, convert the byte array to a System.String class and return the result.

Problem is that if the value is binary, then converting the class data to a System.String would result in a bunch of junk that is not printable. When this happens, OctetString.ToString() method calls OctetString.ToHexString() which will print out each byte value as a 2 character hex value representing the byte value.

As you can imagine, this method of trying to “guess” what type of value is contained within the class is not very exact. One example is that you have binary data that just happens to fall within the ASCII character range and shouldn’t be represented as a printable string.

In cases where you know you will be receiving binary data that is not in the text format, you should use OctetString.ToHexString() to get predictable results.

There is an additional ToString??? method provided for MAC addresses. I have a need for specific vendor format MAC address that groups MAC address bytes into groups of 2 delimited by “.”. For example: 0008.0102.0304. To get this kind of output, you can use OctetString.ToMACAddressString(). Please note that this method will only work if the OctetString class data is exactly 6 bytes long. Any other data length will result in a zero length string being returned.