Querying SNMP Devices from Python

Now we know enough about SNMP to start working on our own management system, which will be querying the configured systems on regular intervals. First let's specify the configuration that we will be using in the application.

Configuring the Application

As we already know, we need the following information available for every check:

• An IP address or resolvable domain name of the system that runs the SNMP agent software

• The read-only community string that will be used to authenticate with the agent software

• The OID node's numerical representation

We are going to use the Windows INI-style configuration file, because of its simplicity. Python includes a configuration parsing module by default, so it is also convenient to use. Chapter 9 discusses the ConfigParser module in great detail; please refer to that chapter for more information about the module.

Let's go back to the configuration file for our application. There is no need to repeat the system information for every SNMP object that we're going to query, so we can define each system parameter once in a separate section and then refer to the system ID in each check section. The check section defines the OID node identifier string and a short description, as shown in Listing 1-2.

Listing 1-2. The management system configuration file [system_l]

description=My Laptop address=192.168.1.68

port=161

communityro=public [check_1]

description=WLAN incoming traffic oid=1.3.6.1.2.1.2.2.1.10.3

system=system_1 [check_2]

description=WLAN incoming traffic oid=1.3.6.1.2.1.2.2.1.16.3

system=system_1

Make sure that the system and check section IDs are unique, or you may get unpredictable results. We're going to create an SnmpManager class with two methods, one to add a system and the other to add a check. As the check contains the system ID string, it will automatically be assigned to that particular system. In Listing 1-3 you can see the class definition and also the initialization part that reads in the configuration and iterates through the sections and updates the class object accordingly.

Listing 1-3. Reading and storing the configuration import sys from ConfigParser import SafeConfigParser class SnmpManager:

def add_system(self, id, descr, addr, port, comm_ro):

self.systems[id]

{'description' 'address' 'port'

'communityro' 'checks'

def add_check(self, id, oid, descr, system):

oid_tuple = tuple([int(i) for i in oid.split('.')]) self.systems[system]['checks'][id] = {'description': descr,

def main(conf_file=""): if not conf_file: sys.exit(-1) config = SafeConfigParser() config.read(conf_file) snmp_manager = SnmpManager()

for system in [s for s in config.sections() if s.startswith('system')]: snmp_manager.add_system(system, config.get(system, 'description'), config.get(system, 'address'), config.get(system, 'port'), config.get(system, 'communityro')) for check in [c for c in config.sections() if c.startswith('check')]: snmp_manager.add_check(check, config.get(check, 'oid'), config.get(check, 'description'), config.get(check, 'system'))

As you see in the example, we first have to iterate through the system sections and update the object before proceeding with the check sections.

■Note This order is important, because if we try to add a check for a system that hasn't been inserted yet, we'll get a dictionary index error.

Also note that we are converting the OID string to a tuple of integers. You'll see why we have to do this later in this section. The configuration file is loaded and we're ready to run SNMP queries against the configured devices.

Using the PySNMP Library

In this project we are going to use the PySNMP library, which is implemented in pure Python and doesn't depend on any precompiled libraries. The pysnmp package is available for most Linux distributions and can be installed using the standard distribution package manager. In addition to pysnmp you will also need the ASN.1 library, which is used by pysnmp and is also available as part of the Linux distribution package selection. For example, on a Fedora system you can install the pysnmp module with the following commands:

$ sudo yum install pysnmp $ sudo yum install python-pyasn1

Alternatively, you can use the Python Package manager (PiP) to install this library for you:

$ sudo pip install pysnmp $ sudo pip install pyasn1

If you don't have the pip command available, you can download and install this tool from http: // pypi. python. org/pypi/pip. We will use it in later chapters as well.

The PySNMP library hides all the complexity of SNMp processing behind a single class with a simple API. All you have to do is create an instance of the CommandGenerator class. This class is available from the pysnmp. entity. rfc3413. oneliner. cmdgen module and implements most of the standard SNMP protocol commands: getCmd(), setCmd() and nextCmd(). Let's look at each of those in more detail.

The SNMP GET Command

All the commands we are going to discuss follow the same invocation pattern: import the module, create an instance of the CommandGenerator class, create three required parameters (an authentication object, a transport target object, and a list of arguments), and finally invoke the appropriate method. The method returns a tuple containing the error indicators (if there was an error) and the result object.

In Listing 1-4, we query a remote Linux machine using the standard SNMP OID (1.3.6.1.2.1.1.1.0).

Listing 1-4. An example of the SNMP GET command

>>> from pysnmp.entity.rfc3413.oneliner import cmdgen >>> cg = cmdgen.CommandGenerator()

>>> comm_data = cmdgen.CommunityData('my-manager', 'public') >>> transport = cmdgen.UdpTransportTarget(('192.168.1.68', 161)) >>> variables = (1, 3, 6, 1, 2, 1, 1, 1, 0)

>>> errlndication, errStatus, errlndex, result = cg.getCmd(comm_data, transport, variables)

>>> print errIndication None

>>> print errStatus 0

[(ObjectName('1.3.6.1.2.1.1.1.0'), OctetString('Linux fedolin.example.com^

2.6.32.11-99.fc12.i686 #1 SMP Mon Apr 5 16:32:08 EDT 2010 i686'))] >>>

Let's look at some steps more closely. When we initiate the community data object, we have provided two strings—the community string (the second argument) and the agent or manager security name string; in most cases this can be any string. An optional parameter specifies the SNMP version to be used (it defaults to SNMP v2c). If you must query version 1 devices, use the following command:

>>> comm_data = cmdgen.CommunityData('my-manager', 'public', mpModel=0)

The transport object is initiated with the tuple containing either the fully qualified domain name or the IP address string and the integer port number.

The last argument is the OID expressed as a tuple of all node IDs that make up the OID we are querying. Therefore, we had to convert the dot-separated string into a tuple earlier when we were reading the configuration items.

Finally, we call the API command getCmd (), which implements the SNMP GET command, and pass these three objects as its arguments. The command returns a tuple, each element of which is described in Table 1-3.

Table 1-3. CommandGenerator Return Objects

Tuple Element Description errIndicati0n If this string is not empty, it indicates the SNMP engine error.

errStatus If this element evaluates to True, it indicates an error in the SNMP communication; the object that generated the error is indicated by the errIndex element.

errIndex If the errStatus indicates that an error has occurred, this field can be used to find the SNMP object that caused the error. The object position in the result array is errIndex-1.

result This element contains a list of all returned SNMP object elements. Each element is a tuple that contains the name of the object and the object value.

The SNMP SET Command

The SNMP SET command is mapped in PySNMP to the setCmd () method call. All parameters are the same; the only difference is that the variables section now contains a tuple: the OID and the new value. Let's try to use this command to change a read-only object; Listing 1-5 shows the command-line sequence.

Listing 1-5. An example of the SNMP SET command

>>> from pysnmp.entity.rfc3413.oneliner import cmdgen >>> from pysnmp.proto import rfc1902 >>> cg = cmdgen.CommandGenerator()

>>> comm_data = cmdgen.CommunityData('my-manager', 'public') >>> transport = cmdgen.UdpTransportTarget(('192.168.1.68', 161)) >>> variables = ((1, 3, 6, 1, 2, 1, 1, 1, 0), rfc1902.0ctetString('new system description ' ) )

>>> errlndication, errStatus, errIndex, result = cg.setCmd(comm_data, transport,^ variables)

>>> print errIndication None

>>> print errStatus 6

>>> print errIndex

>>> print errStatus.prettyPrint()

noAccess(6)

[(ObjectName('1.3.6.1.2.1.1.1.0'), OctetString('new system description'))] >>>

What happened here is that we tried to write to a read-only object, and that resulted in an error. What's interesting in this example is how we format the parameters. You have to convert strings to SNMP object types; otherwise; they won't pass as valid arguments. Therefore the string had to be encapsulated in an instance of the OctetString class. You can use other methods of the rfc1902 module if you need to convert to other SNMP types; the methods include Bits(), Counter32(), Counter64(), Gauge32(), Integer(), Integer32(), IpAddress(), OctetString(), Opaque(), TimeTicks (), and Unsigned32(). These are all class names that you can use if you need to convert a string to an object of a specific type.

The SNMP GETNEXT Command

The SNMP GETNEXT command is implemented as the nextCmd () method. The syntax and usage are identical to getCmd (); the only difference is that the result is a list of objects that are immediate child nodes of the specified OID node.

Let's use this command to query all objects that are immediate child nodes of the SNMP system OID (1.3.6.1.2.1.1); Listing 1-6 shows the nextCmd() method in action.

Listing 1-6. An example of the SNMP GETNEXT command

>>> from pysnmp.entity.rfc3413.oneliner import cmdgen >>> cg = cmdgen.CommandGenerator()

>>> comm_data = cmdgen.CommunityData('my-manager', 'public') >>> transport = cmdgen.UdpTransportTarget(('192.168.1.68', 161)) >>> variables = (1, 3, 6, 1, 2, 1, 1)

>>> errIndication, errStatus, errIndex, result = cg.nextCmd(comm_data, transport, variables)

>>> print errIndication requestTimedOut

>>> errIndication, errStatus, errIndex, result = cg.nextCmd(comm_data, transport, variables)

>>> print errIndication None

>>> print errStatus 0

>>> for object in result: ... print object

[(ObjectName('1.3.6.1.2.1.1.1.0 2.6.32.11-99.fc12.i686 #1 SMP (ObjectName('1.3.6.1.2.1.1.2.0 (ObjectName('1.3.6.1.2.1.1.3.0 (ObjectName('1.3.6.1.2.1.1.4.0 [email protected])'))] (ObjectName('1.3.6.1.2.1.1.5.0 (ObjectName('1.3.6.1.2.1.1.6.0 MyStreet, MyCity, MyCountry'))]

'), OctetString('Linux fedolin.example.com'-' Mon Apr 5 16:32:08 EDT 2010 i686'))]

'), OctetString('Administrator

), OctetString('MyLocation, MyOrganization,'-'

(ObjectName('1.3.6.1.2.1.1.8.0 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 Architecture MIB.'))] (ObjectName('1.3.6.1.2 nd Dispatching.'))] (ObjectName('1.3.6.1.2 definitions for the SNMP User-(ObjectName('1.3.6.1.2.1.1.9.1 entities'))] (ObjectName('1.3.6.1.2 implementations'))] (ObjectName('1.3.6.1.2.1 and ICMP implementations (ObjectName('1.3.6.1.2.1 implementations'))] (ObjectName('1.3.6.1.2 for SNMP.'))] (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1 (ObjectName('1.3.6.1.2.1.1.9.1

'), TimeTicks('3'))] .2.1'), ObjectIdentif .2.2'), ObjectIdentif .2.3'), ObjectIdentif .2.4'), ObjectIdentif .2.5'), ObjectIdentif .2.6'), ObjectIdentif .2.7'), ObjectIdentif er('1.3.6.1.6.3.10.3.1.1'))]

.2.8'), ObjectIdentifier('1.3.6.1.6.3.16.2.2. .3.1'), OctetString('The SNMP Management-*

1.1.9.1.3.2'), OctetString('The MIB for Message Processing«

.3.3'), OctetString('The management information—* based Security Model.'))]

.3.4'), OctetString('The MIB module for SNMPv2-<

.3.5'), OctetString('The MIB module for managing TCP«

.3.6'), OctetString('The MIB module for managing IP—*

.3.7'), OctetString('The MIB module for managing UDP«

.3.8'), OctetString('View-based Access Control Model«

As you can see, the result is identical to that produced by the command-line tool snmpwalk, which uses the same technique to retrieve the SNMP OID subtree.

Implementing the SNMP Read Functionality

Let's implement the read functionality in our application. The workflow will be as follows: we need to iterate through all systems in the list, and for each system we iterate through all defined checks. For each check we are going to perform the SNMP GET command and store the result in the same data structure.

For debugging and testing purposes we will add some print statements to verify that the application is working as expected. Later we'll replace those print statements with the RRDTool database store commands. I'm going to call this method query_all_systems(). Listing 1-7 shows the code.

Listing 1-7. Querying all defined SNMP objects def query_all_systems(self):

cg = cmdgen.CommandGenerator() for system in self.systems.values():

comm_data = cmdgen.CommunityData('my-manager', system[ transport = cmdgen.UdpTransportTarget((system['address for check in system['checks'].values(): oid = check['oid']

errInd, errStatus, errldx, result = cg.getCmd(comm if not errInd and not errStatus:

print "%s/%s -> %s" % (system['description'], check['description'], str(result[0][l]))

If you run the tool you'll get results similar to these (assuming you correctly pointed your configuration to the working devices that respond to the SNMP queries):

My Laptop/WLAN outgoing traffic -> 1060698 My Laptop/WLAN incoming traffic -> 14305766

Now we're ready to write all this data to the RRDTool database.

The Free SEO Report

The Free SEO Report

The Internet has been growing at such a rapid pace, it is now next to impossible for people to find your websites in the Search Engines unless you really know what you are doing. You're not going to be able to do a thing with Search Engines unless you have a little information to get you started and learn a few tricks of the trade. This report will help you do just that!

Get My Free Ebook


Responses

  • olo
    How do I test if pysnmp is installed on my system?
    7 years ago
  • libby
    How to execute snmp commands in python?
    7 years ago

Post a comment