Python SNMP Tutorial: Get, Set and GetBulk in minutes

Learn how to use SNMP in Python with this Python SNMP Tutorial.

Share This Post

SNMP is a must-have for SDN. In fact, Simple Network Management Protocol is the best option for controlling devices in software. Even more than that, in-software access is the actual purpose of SNMP. With no surprise, all monitoring systems use SNMP to monitor and control servers and network devices. Having this enormous power that comes from SNMP in a script would be awesome. Thus, this Python SNMP Tutorial explains how to use SNMP in minutes for your script.

Before we start

A couple of words on SNMP

SNMP stands for Simple Network Management Protocol. It is a standard way of communication between a management server and a remote device, the agent. The goal of SNMP is to have the manager understand (or even change) information on the agent. For example, the manager can check which interfaces are up and which are down, or change the hostname of the remote device.

Python SNMP Tutorial allows you to connect to a remote device with SNMPv2 and SNMPv3
Our script allows a Python program on a management station to control a remote device running an SNMP agent.

The SNMP agent prepare all the information the manager can read or change in a special table, the MIB. The MIB is a tree-like structure, where each node in the tree will be represented by a number. For example, 1.3.6.1.2.1.1.1 represents the system description. If you are wondering where this chain of numbers came from, this is the whole tree structure! In fact, each number relates to a name. Consequently, we can translate that into a more explicative iso.org.dod.internet.mgmt.mib-2.system.sysDescr.

We won’t be diving much more into SNMP today. In fact, this Python SNMP tutorial assumes you have an understanding of SNMP. In case you have doubts about that, we got you covered. Just check this SNMP guide.

PySNMP

PySNMP is an open-source module for Python. Unlike telnet or HTTP, Python does not natively implement SNMP. After all, just network and system engineers will need that, not any Python developer on the plant. PySNMP does a great job of covering this lack of native Python. In fact, it allows you to use any version of SNMP, both as an agent or as a manager. Creating an agent means you are creating an application or an appliance, so we won’t check that out today. However, we will check how to use PySNMP to manage a remote device.

As we will see, PySNMP adds a lot of cool stuff to python. With them, it adds some complexity as well. Our goal today is to create a quick python file that can make things simple for you. In that file, you will have all the SNMP operations you need.

Starting our Python SNMP Tutorial

Preparing the environment

First, we need to install PySNMP. We created this tutorial on Python 3.6, but it should work even on old 2.7. If you need to install Python, go with Python 3.x, as 2.7 is now legacy. More on that here.

To start, just install PySNMP with pip. This is the only module we will need for this tutorial.

pip install pysnmp

Now we are ready to start!

Testing our SNMP scripts

There is no joy in preparing our script if we cannot test it. Luckily, we can! There is a cool free online resource that offers a “fake” SNMP agent on the Internet. I am talking about snmplabs.com, and you can point to the fake host demo.snmplabs.com. However, if you are more into networking you may need something more flexible. If that is the case, you can set up a GNS3 environment as we explained in this article. Then, configure the router with SNMP. For example, a quick way to configure SNMPv2c for read-write is with the following command.

snmp-server community ICTSHORE RW

This allows anyone that knows the password (which is “ICTSHORE”) to access the device in SNMP, Read and Write. Of course, SNMPv2c is not secure, so never use read-write in production. Even more, never use SNMPv2c in production at all! Always opt for SNMPv3.

For this tutorial, we will be testing against a GNS3 router. However, you can do the same tests against snmplabs.com, you may only need to change MIB Object IDs.

Python SNMP Get

The SNMP Get operation allows you to retrieve the value of an individual object in the MIB. We can also use it to get a list of individual objects, so we can start writing out our get() function like this:

from pysnmp import hlapi


def get(target, oids, credentials, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):
    handler = hlapi.getCmd(
        engine,
        credentials,
        hlapi.UdpTransportTarget((target, port)),
        context,
        *construct_object_types(oids)
    )
    return fetch(handler, 1)[0]

As you can see, we are leveraging the High-Level API of PySNMP. This is the only part of PySNMP we will use today, as it contains everything we need. Our function is simple: first, it requires a target (IP or name of the remote device). Then, it requires the list of Object IDs (oids) we want to get and after that a set of credentials to authenticate our session. We can also specify a different UDP port if we want, and use an existing SNMP engine or custom context. You may want to use the same engine for all the operations on the same device, as this saves resources. However, this is not required for a simple script, so you can ignore both engine and context.

The function creates a handler for the SNMP session and fetches the information from it. To do that, it relies on two functions we need to create: construct_object_types and fetch.

Constructing Object Types

As we said earlier, having more power means having more complexity. This is why the hlapi.getCmd() function wants some special hlapi.ObjectType objects, and not a simple list of string OIDs. Thus, our construct_object_type function creates what PySNMP wants to hear. You can simply copy-and-paste it in your code if you don’t have the time for that. However, this should be a very simple function, take a look:

def construct_object_types(list_of_oids):
    object_types = []
    for oid in list_of_oids:
        object_types.append(hlapi.ObjectType(hlapi.ObjectIdentity(oid)))
    return object_types

This returns a list, that we can expand by prepending a *, as we did in the get() function. That’s what PySNMP needs.

Fetching data

The fetch() function is a masterpiece of our Python SMP Tutorial. In fact, we wrote it so that we can re-use it for other Python SNMP functions, like get-bulk. It simply loops  on the handler for as many times as we tell it (the count variable). If it encounter any kind of error, it stops and raises a RuntimeError. In any other case, it stores the data in a list of dictionaries.

def fetch(handler, count):
    result = []
    for i in range(count):
        try:
            error_indication, error_status, error_index, var_binds = next(handler)
            if not error_indication and not error_status:
                items = {}
                for var_bind in var_binds:
                    items[str(var_bind[0])] = cast(var_bind[1])
                result.append(items)
            else:
                raise RuntimeError('Got SNMP error: {0}'.format(error_indication))
        except StopIteration:
            break
    return result

We have a try ... except StopIteration construct for a reason. In case the count the user specifies is higher than the number of objects we actually have, we simply stop and return what we got so far. This is the purpose of the construct.

Why do we return a list of dictionaries? In each get operation, we can get multiple object IDs. Thus, each dictionary will have as key the object ID, and as value the value of that OID in the MIB. In case we require multiple OIDs in a single get, we will return a dictionary with multiple keys. Why a list then? With get, you only retrieve the information once. However, as we will see in get bulk, we might want to get the same information many times on different instances. For example, imagine you want to check the errors on all the interfaces: the information is always the errors, but you have multiple instances of it (one per interface). We can visualize it with a list, where each item is a dictionary representing an instance.

Note that the fetch() function relies on another function we need to create: cast(). This simply converts the data as received from PySNMP to int, float, or string.

def cast(value):
    try:
        return int(value)
    except (ValueError, TypeError):
        try:
            return float(value)
        except (ValueError, TypeError):
            try:
                return str(value)
            except (ValueError, TypeError):
                pass
    return value

Providing credentials

The pySNMP authentication system is strong and simple enough. There is no reason to write an additional layer above it, so we can directly use it. Our get() function, as well as the others we need, requires a special authentication object in the credentials variable. This object is different if we use SNMPv2c or SNMPv3.

In the case of SNMPv2c (or lower), we just need to specify the community. We can do that with the CommunityData object, as below.

hlapi.CommunityData('ICTSHORE'))

Instead, SNMPv3 is trickier. This is because it uses a user with two passwords, and two protocols: for authentication and encryption. Thus, you need to specify the username, authentication password, authentication protocol, encryption password, and encryption protocol. To do that, we need to use the UsmUserData class. An example would be the one below.

hlapi.UsmUserData('testuser', authKey='authenticationkey', privKey='encryptionkey', authProtocol=hlapi.usmHMACSHAAuthProtocol, privProtocol=hlapi.usmAesCfb128Protocol)

It is simpler than it seems, you just need to know what protocols the remote device is using. However, you may want to check the full UsmUserData documentation.

Getting the hostname!

It is time to test our get() function. We are going to use it to get the hostname of the device, which is object 1.3.6.1.2.1.1.5.0. We can simply write this:

print(get('10.0.0.1', ['1.3.6.1.2.1.1.5.0'], hlapi.CommunityData('ICTSHORE')))

This will print something like that:

{'1.3.6.1.2.1.1.5.0': 'R1.sdn.local'}

Of course, R1.sdn.local is the name of our router. You will get the name of your own router, or what snmplabs provides. You can require more objects if you want, they will all appear in this dictionary.

Note: here we are not getting a list of dictionaries, but just a dictionary. This is intentional, and the get() function will always behave like that. In fact, since we know that the get function runs only once, it cannot create multiple instances. Therefore, we simply return the first element we got from fetch().

Python SNMP Get Bulk

The get_bulk() function retrieves multiple instances of the same Object ID, for example, one for each interface. It is useful when it comes to working with tables, like the routing table or the interface table. The function is simple and similar to get(). However, it needs some additional information: which object to start with, and how many instances we want to get. We provide them in start_from and count.

def get_bulk(target, oids, credentials, count, start_from=0, port=161,
             engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):
    handler = hlapi.bulkCmd(
        engine,
        credentials,
        hlapi.UdpTransportTarget((target, port)),
        context,
        start_from, count,
        *construct_object_types(oids)
    )
    return fetch(handler, count)

Here we return the whole output from fetch(). We expecting a list of dictionaries, so we don’t want to extract just the first dictionary as we did in get().

Python SNMP Get Bulk Auto

This is a cool improvement to get_bulk() you will appreciate. Imagine you are looping through the interfaces of a device using get_bulk(). How can you know how many interfaces are there? You need to know that because SNMP wants to know how many times to iterate. You simply can’t know that in advance, but chances are you can find this information using SNMP.

With get_bulk_auto(), we tell our script to retrieve the count variable from another OID. Thus, instead of specifying a number, we specify an object ID. The function will make a get for that object, and use it as count.

def get_bulk_auto(target, oids, credentials, count_oid, start_from=0, port=161,
                  engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):
    count = get(target, [count_oid], credentials, port, engine, context)[count_oid]
    return get_bulk(target, oids, credentials, count, start_from, port, engine, context)

Using Python SNMP Get Bulk & Get Bulk Auto

Now you can run this code on a device.

its = get_bulk_auto('10.0.0.1', ['1.3.6.1.2.1.2.2.1.2 ', '1.3.6.1.2.1.31.1.1.1.18'], hlapi.CommunityData('ICTSHORE'), '1.3.6.1.2.1.2.1.0')
for it in its:
    for k, v in it.items():
        print("{0}={1}".format(k, v))
    print('')

Our router had 8 interfaces, so this is what we got.

1.3.6.1.2.1.2.2.1.2.1=FastEthernet1/0
1.3.6.1.2.1.31.1.1.1.18.1=

1.3.6.1.2.1.2.2.1.2.2=FastEthernet0/0
1.3.6.1.2.1.31.1.1.1.18.2=

1.3.6.1.2.1.2.2.1.2.3=FastEthernet0/1
1.3.6.1.2.1.31.1.1.1.18.3=Test Desc

1.3.6.1.2.1.2.2.1.2.4=Serial2/0
1.3.6.1.2.1.31.1.1.1.18.4=

1.3.6.1.2.1.2.2.1.2.5=Serial2/1
1.3.6.1.2.1.31.1.1.1.18.5=

1.3.6.1.2.1.2.2.1.2.6=Serial2/2
1.3.6.1.2.1.31.1.1.1.18.6=

1.3.6.1.2.1.2.2.1.2.7=Serial2/3
1.3.6.1.2.1.31.1.1.1.18.7=

1.3.6.1.2.1.2.2.1.2.9=Null0
1.3.6.1.2.1.31.1.1.1.18.9=

Python SNMP Set

Our last function is the set() function. This allows you to change the value of an OID or a list of OIDs. We implemented it in a very intuitive way: you provide a dictionary of items to change. The key is the object ID, while the value is the new value to set. It works exactly like get(), but it needs to create a different list of objects.

def set(target, value_pairs, credentials, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()):
    handler = hlapi.setCmd(
        engine,
        credentials,
        hlapi.UdpTransportTarget((target, port)),
        context,
        *construct_value_pairs(value_pairs)
    )
    return fetch(handler, 1)[0]

Our construct_value_pairs function simply converts our input dictionary in a format PySNMP will like.

def construct_value_pairs(list_of_pairs):
    pairs = []
    for key, value in list_of_pairs.items():
        pairs.append(hlapi.ObjectType(hlapi.ObjectIdentity(key), value))
    return pairs

Now you can use it, for example to set the hostname:

set('10.0.0.1', {'1.3.6.1.2.1.1.5.0': 'SNMPHost'}, hlapi.CommunityData('ICTSHORE'))

TL;DR Python SNMP made easy

In this tutorial, we saw how to use PySNMP to integrate SNMP in our Python Scripts. While doing so, we populated a file with all the quick functions you need. You can get this file from GitHub and paste it into your project. We suggest you name it quicksnmp.py.

Then, you can use it like below.

from pysnmp import hlapi
from . import quicksnmp

# Using SNMPv2c, we set the hostname of the remote device to 'SNMPHost'
quicksnmp.set('10.0.0.1', {'1.3.6.1.2.1.1.5.0': 'SNMPHost'}, hlapi.CommunityData('ICTSHORE'))

# Using SNMPv2c, we retrieve the hostname of the remote device
print(get('10.0.0.1', ['1.3.6.1.2.1.1.5.0'], hlapi.CommunityData('ICTSHORE')))

# We get interface name and Cisco interface description for all interfaces
# The last parameter is the OID containing the number of interfaces, so we can loop 'em all!
its = get_bulk_auto('10.0.0.1', [
    '1.3.6.1.2.1.2.2.1.2 ',
    '1.3.6.1.2.1.31.1.1.1.18'
    ], hlapi.CommunityData('ICTSHORE'), '1.3.6.1.2.1.2.1.0')
# We print the results in format OID=value
for it in its:
    for k, v in it.items():
        print("{0}={1}".format(k, v))
    # We leave a blank line between the output of each interface
    print('')

Hopefully, this will allow you to make some great Python scripts that use SNMP without the pain that used to come with them. What are you going to use this script for? What would you like to see implemented? As always, let me know in the comments!

Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.
Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.

Alessandro Maggio

2018-10-25T16:30:43+00:00

Unspecified

Networking

Unspecified