Creating a Simple SNMP Class



Creating a Simple SNMP Class

The process of creating an SNMP class is different from creating the ICMP class (which was covered in Chapter 11, “ICMP”). Because there is no set byte format for SNMP packets, you must create the SNMP packet as you go, adding the appropriate values as necessary.

This makes creating a class constructor somewhat difficult, because it must be versatile enough to accommodate the changing field sizes and PDU types. I find it easier to instead implement the SNMP packet creation within a class method, which allows you to create the packet on-the-fly and directly send and receive the SNMP packets within the methods themselves. Thus you can quickly send and receive multiple SNMP packets without having to create new instances of the class for every packet.

Three pieces of information are needed to create the SNMP packet:

  • The PDU type of the packet

  • The MIB object identifier to query

  • A valid community name to gain access to the MIB database

Each of these values can be passed to the class method and built into the SNMP packet on-the-fly. After the packet is returned, the method can either return the raw SNMP packet to the application, or attempt to decode it and place it into data values in the class.

The SNMP Class Program

The SNMP.cs program in Listing 12.1 defines an SNMP class with a default constructor and one method. The get() method is used as a catch-all method to create either a GetRequest or a GetNextRequest PDU packet and send it to a specified remote host. The response packet received from the remote host is then passed directly to the calling application. The single method defined creates the required SNMP packet byte by byte, as explained earlier in "SNMP Packet Layout."

Listing 12.1: The SNMP.cs program
Start example
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class SNMP
{
  public SNMP()
  {
  }
  public byte[] get(string request, string host, string community, string_  mibstring)
  {
   byte[] packet = new byte[1024];
   byte[] mib = new byte[1024];
   int snmplen;
   int comlen = community.Length;
   string[] mibvals = mibstring.Split('.');
   int miblen = mibvals.Length;
   int cnt = 0, temp, i;
   int orgmiblen = miblen;
   int pos = 0;
   // Convert the string MIB into a byte array of integer values
   // Unfortunately, values over 128 require multiple bytes
   // which also increases the MIB length
   for (i = 0; i < orgmiblen; i++)
   {
     temp = Convert.ToInt16(mibvals[i]);
     if (temp > 127)
     {
      mib[cnt] = Convert.ToByte(128 + (temp / 128));
      mib[cnt + 1] = Convert.ToByte(temp - ((temp / 128) * 128));
      cnt += 2;
      miblen++;
     } else
     {
      mib[cnt] = Convert.ToByte(temp);
      cnt++;
     }
   }
   snmplen = 29 + comlen + miblen - 1; //Length of entire SNMP packet
   //The SNMP sequence start
   packet[pos++] = 0x30; //Sequence start
   packet[pos++] = Convert.ToByte(snmplen - 2); //sequence size
   //SNMP version
   packet[pos++] = 0x02; //Integer type
   packet[pos++] = 0x01; //length
   packet[pos++] = 0x00; //SNMP version 1
   //Community name
   packet[pos++] = 0x04; // String type
   packet[pos++] = Convert.ToByte(comlen); //length
   //Convert community name to byte array
   byte[] data = Encoding.ASCII.GetBytes(community);
   for (i = 0; i < data.Length; i++)
   {
     packet[pos++] = data[i];
   }
   //Add GetRequest or GetNextRequest value
   if (request == "get")
     packet[pos++] = 0xA0;
   else
     packet[pos++] = 0xA1;
   packet[pos++] = Convert.ToByte(20 + miblen - 1); //Size of total MIB
   //Request ID
   packet[pos++] = 0x02; //Integer type
   packet[pos++] = 0x04; //length
   packet[pos++] = 0x00; //SNMP request ID
   packet[pos++] = 0x00;
   packet[pos++] = 0x00;
   packet[pos++] = 0x01;
   //Error status
   packet[pos++] = 0x02; //Integer type
   packet[pos++] = 0x01; //length
   packet[pos++] = 0x00; //SNMP error status
   //Error index
   packet[pos++] = 0x02; //Integer type
   packet[pos++] = 0x01; //length
   packet[pos++] = 0x00; //SNMP error index
   //Start of variable bindings
   packet[pos++] = 0x30; //Start of variable bindings sequence
   packet[pos++] = Convert.ToByte(6 + miblen - 1); // Size of variable binding
   packet[pos++] = 0x30; //Start of first variable bindings sequence
   packet[pos++] = Convert.ToByte(6 + miblen - 1 - 2); // size
   packet[pos++] = 0x06; //Object type
   packet[pos++] = Convert.ToByte(miblen - 1); //length
   //Start of MIB
   packet[pos++] = 0x2b;
   //Place MIB array in packet
   for(i = 2; i < miblen; i++)
     packet[pos++] = Convert.ToByte(mib[i]);
   packet[pos++] = 0x05; //Null object value
   packet[pos++] = 0x00; //Null
   //Send packet to destination
   Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
            ProtocolType.Udp);
   sock.SetSocketOption(SocketOptionLevel.Socket,
           SocketOptionName.ReceiveTimeout, 5000);
   IPHostEntry ihe = Dns.Resolve(host);
   IPEndPoint iep = new IPEndPoint(ihe.AddressList[0], 161);
   EndPoint ep = (EndPoint)iep;
   sock.SendTo(packet, snmplen, SocketFlags.None, iep);
   //Receive response from packet
   try
   {
     int recv = sock.ReceiveFrom(packet, ref ep);
   } catch (SocketException)
   {
     packet[0] = 0xff;
   }
   return packet;
  }
}
End example

Walking through the Class

When you look at the SNMP class, you can understand why SNMP programming gets pretty complex. Manually creating an SNMP packet is not for the weak of heart.

Converting the MIB

First, the MIB object identifier is converted into a byte array:

byte[] mib = new byte[1024];
string[] mibvals = mibstring.Split('.');
int miblen = mibvals.Length;
int cnt = 0, temp, i;
int orgmiblen = miblen
for (i = 0; i < orgmiblen; i++)
{
  temp = Convert.ToInt16(mibvals[i]);
  if (temp > 127)
  {
   mib[cnt] = Convert.ToByte(128 + (temp / 128));
   mib[cnt + 1] = Convert.ToByte(temp - ((temp / 128) * 128));
   cnt += 2;
   miblen++;
  } else
  {
   mib[cnt] = Convert.ToByte(temp);
   cnt++;
  }
}

The object identifier is extracted from the MIB into a string array containing only the numerical values (still stored as strings) using the string Split() method. Next, each identifier is converted to an integer value. SNMP makes this task a little challenging.

The integer MIB value is converted into a 16-bit (2-byte) signed integer, although there are never any negative identifier values. If the value fits into a single byte, it is placed in the byte array as is. If the value requires two bytes (that is, it’s larger than 127), the high byte is placed first in the packet, followed by the low byte. When the value requires two bytes, the total MIB length value miblength is increased by one to accommodate for the extra byte. This value is important when creating the actual SNMP packet. The resulting byte array mib[] contains the bytes of the MIB object identifier for the PDU.

Creating the Packet

After the MIB is converted to a byte array, the entire SNMP packet can be created. Each piece of the SNMP packet is added individually to the packet byte array. The community name is converted from its string value to a byte array and placed piece-by-piece into the array using a for loop. The MIB byte array is also placed in the packet using the same technique.

The only difference between SNMP GetRequest and GetNextRequest packets is the PDU type value. A GetRequest packet has a value of 0xA0, while a GetNextRequest packet has a value of 0xA1. Because the class get() method can be used for both types of packets, depending on the calling program, it must provide a way for the calling application to indicate which PDU type to use. This is done using a simple string value. When the value is equal to "get", the GetRequest PDU type is used; otherwise, the GetNextRequest PDU type is used.

Sending the Packet

When the packet is created, a Socket object is created to connect to the remote device SNMP port (UDP port 161):

Socket sock = new Socket(AddressFamily.InterNetwork,
 SocketType.Dgram,
 ProtocolType.Udp);
sock.SetSocketOption(SocketOptionLevel.Socket,
 SocketOptionName.ReceiveTimeout, 5000);
IPHostEntry ihe = Dns.Resolve(host);
IPEndPoint iep = new IPEndPoint(ihe.AddressList[0], 161);
EndPoint ep = (EndPoint)iep;
sock.SendTo(packet, iep);

The SetSocketOption() method sets the ReceiveTimeout socket option to a reasonable value to wait for the response from the remote device.

Receiving a Response

After the packet is sent, a Receive() method waits for the response:

try
{
  int recv = sock.ReceiveFrom(packet, ref ep);
} catch (SocketException)
{
  packet[0] = 0xff;
}
return packet;

If no response is received from the remote device in five seconds, the first byte of the returned packet is set to 0xff. The calling program can check this value to determine if a valid SNMP packet has been received.

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows