Asynchronous SNMP via gevent.
Main characteristics as bullet point list:
- Gives you an 'AsyncSession' object, which works inside a gevent greenlet.
- Very speedy, ~8000 GET requests per second.
- It uses the libnetsnmp.so directly via cython (Cython>=0.21 is required).
- It requires a patched libnetsnmp.so. See implementation notes.
- Uses the "high level" API of libnetsnmp => Easily use the features provided by libnetsnmp.
- Tested with gevent 1.1
- Tested with libnetsnmp 5.7.X
The API does no oid translation for you.
An oid must be given as a tuple of integers.
For example system.sysDescr.0
must be convertet to (1, 3, 6, 1, 2, 1, 1, 1, 0)
The output is always a dictionary containing the varbind list from the response pdu. This varbind list is converted to a dictionary where the keys are the oids (as tuples) and the values the corresponding value from the response pdu.
from async_session import AsyncSession
config = {
'peername': '127.0.0.1',
'version': '2c',
'community': 'public',
'retries': 5,
'timeout': 3
}
session = AsyncSession(config)
session.open_session()
Converts a oid string of digits into a tuple of integers.
Example:
ret = async_session.oid_str_to_tuple("1.3.6.1.2.1.1.1.0")
assert ret == (1, 3, 6, 1, 2, 1, 1, 1, 0)
Converts a tuple of integers to a string with digits and dots.
Example:
ret = async_session.oid_tuple_to_str((1, 3, 6, 1, 2, 1, 1, 1, 0))
assert ret == "1.3.6.1.2.1.1.1.0"
Checks if an oid is within another one.
First parameter is the root
. Second one is checked if its a subtree of the root.
Both parameters must be tuple of integers.
ret = async_session.is_in_subtree(
(1, 3, 6, 1, 4, 1, 4491, 2, 1, 20, 1, 4, 1, 4),
(1, 3, 6, 1, 4, 1, 4491, 2, 1, 20, 1, 4, 1, 4, 3165, 50798601)
)
assert ret == True
ret = async_session.is_in_subtree(
(1, 3, 6, 1, 4, 1, 4491, 2, 1, 20, 1, 4, 1, 4),
(1, 3, 6, 1, 4, 1, 4491, 2, 1, 20, 1, 4, 1, 5, 1)
)
assert ret == False
- Input: List of oid tuples.
- Output: Dictionary
Example:
oids = [
(1, 3, 6, 1, 2, 1, 1, 1, 0),
(1, 3, 6, 1, 2, 1, 1, 3, 0),
]
result = session.get(oids)
print 'system description', result[(1, 3, 6, 1, 2, 1, 1, 1, 0)]
print 'system uptime', result[(1, 3, 6, 1, 2, 1, 1, 3, 0)]
- Input: A single oid tuple
- Output Dictionary with the next oid
Example:
oid = (1, 3, 6, 1, 2, 1, 1, 1, 0)
result = session.get_next(oid)
if result:
next_oid, next_value = result.items()[0]
print 'next oid is', next_oid
print 'value for this id', next_value
- Input: List of oid tuples, non repeaters, max repetitions
- Output: Dictionary
Example:
oids = [
(1, 3, 6, 1, 2, 1, 1, 1),
(1, 3, 6, 1, 2, 1, 1, 3),
(1, 3, 6, 1, 2, 1, 2, 2, 1, 2)
]
# Get
# - system description
# - system uptime
# - The first 5 interface names
result = session.get_bulk(oids, nonrepeaters=2, maxrepetitions=5)
print 'system description', result.pop((1, 3, 6, 1, 2, 1, 1, 1, 0))
print 'system uptime', result.pop((1, 3, 6, 1, 2, 1, 1, 3, 0))
for oid, ifname in result.items():
print 'oid', oid
print 'interface name', ifname
- Input: A single oid tuple
- Output Dictionary having all oids which are childs of the input oid
This walk uses only snmp-getnext
to traverse the tree.
Example:
root_id = (1, 3, 6, 1, 2, 1, 1)
result = session.walk(root_id)
for oid in sorted(result):
print 'oid', oid, result[oid]
- Input: A single oid tuple, how much oids to retrieve in a single
getbulk
- Output Dictionary having all (full) oids which are childs of the input oid
The main difference to walk
is the usage of the snmp-getbulk
operation to traverse the tree.
Example:
root_id = (1, 3, 6, 1, 2, 1, 1)
result = session.walk_with_get_bulk(root_id, maxrepetitions=10)
for oid in sorted(result):
print 'oid', oid, result[oid]
- Input: Dictionary with oid tuples as keys. The values are tuples again, where the first element is the value to set as a string and the second parameter a type specifiction. The type specification is used to encode the value correctly inside the pdu. Have a look here for the possible type specifications. https://linux.die.net/man/1/snmpset The library takes care to encode the value string according to the type specification.
Example:
to_set = {
# Set a new location.
(1, 3, 6, 1, 2, 1, 1, 1, 0): ('new location', 's'),
# Also integers have to be given as strings.
# The following disable IP forwarding on the first interface.
(1, 3, 6, 1, 2, 1, 4, 1, 0): ("2", "i")
}
result = session.set_oids(to_set)
The APIs mentioned above may raise the following exceptions.
- SNMPTimeoutError
- SNMPResponseError
This exception is raised if there is no SNMP-Response within the configured time constraints.
This exception is raised if something with the response is wrong. It has the following attributes:
code
: Which is the error status from the response PDUindex
: Which is the error index from the response PDUmessage
: String representation of the error
try:
session.set_oids(to_set)
except SNMPResponseError as error:
print error.code
print error.index
print error.message
The following methods have an optional py_flags
parameter to control how
the response is parsed.
- walk
- walk_with_get_bulk
- get
- get_next
- get_bulk
- set
This parameter is a python dictionary which may contains the following flags:
Each entry in the varbind list of a SNMP response contains type
and value
.
Per default the API takes automatically care to convert the value into the
corresponding python object. However it is also possible to get the type
for each entry. Is get_var_type
given and set to True
the value for the returned
dictionary will be a tuple. Where the first element is the type
and the second
element the value
as a python object.
For example:
for oid, (asn_type, asn_value) for session.walk(oid, py_flags={'get_var_type': True}).items():
print oid, asn_type, asn_value
If given and set to True
each single varbind of type
SNMP_ENDOFMIBVIEW
will be encode to the special object
async_session.END_OF_MIB
. If this flag is not set 'end of mib view'
is converted to None
.
If given and set to True
each single varbind of type
SNMP_NOSUCHOBJECT:
will be encode to the special object
async_session.NO_SUCH_OBJECT
. If this flag is not set 'no such object'
is converted to None
.
If given and set to True
each single varbind of type
SNMP_NOSUCHINSTANCE
will be encode to the special object
async_session.NO_SUCH_INSTANCE
. If this flag is not set 'no such instance'
is converted to None
.
If given and set to True
a collections.OrderedDict
is returned.
It preserves the order of the incoming varbinds.
If given and set to True
the result is return as a list of (oid, value)
tuples.
If given and set to True
each result value is converted to a string.
This string is generated by the NET-SNMP function family print_value
.
These functions take the definition of an OID from its corresponding MIB to
format the value accordingly. NET-SNMP offers some options to control the
format behaviour. See toggle_netsnmp_format_options
.
The result of the SNMP-API changes then to a tuple where the raw value is at
position one and the generated string at position two.
Note: The MIB's must be loaded, otherwise the format does not work. To do so call the following at process startup:
async_session.init_snmplib()
Use this call to clone an existing session.
- Input: Config options for the new session. Options not mentioned here are cloned from the existing session. The overriding options must be given as keyword arguments.
- Output: Contextmanager which delivers the cloned session.
For this clone
open_session
has been already called.
Example:
with sess.clone_session(community='private') as priv_sess:
print priv_sess.set_oids(oids)
Control the format of the values if the flag get_netsnmp_string=True
is
used. Since the NET-SNMP functions for formatting are used, the corresponding
format options are also supported.
To get a list of all supported options see:
- https://linux.die.net/man/1/snmpcmd (section 'Output Options')
- Output of
snmpget -h
For example to enable 'quick view' and 'numeric oids use:
async_session.toggle_netsnmp_format_options('nq')
Keep in mind:
This function toggles the option. For example:
# Enables 'quick view'
async_session.toggle_netsnmp_format_options('q')
# Disables 'quick view'
async_session.toggle_netsnmp_format_options('q')
Furthermore these options are global for the entire process. So they affect all AsyncSession() at the same time. Hence the recommendation is to set them once at process startup.
Why the patch ? gevent_snmp works by replacing the calls to 'select()' inside libnetsnmp. So if libnetsnmp would call 'select()', gevent.socket.wait_read is called insted. Per default libnetsnmp does not allow to replace/override the calls to select. The patch to libnetsnmp just adds another function where the select could be replaced via a callback. The advantage of this patch is, that we can still use the synchronous 'high level' API of libnetsnmp. This synchronous 'high levell' is way more easier to use than the asynchronous 'low level' API. For example we get retry and timeout handling for free, because the synchronous 'high level' API implements them. Whereas the asynchronous 'low level' API does not support retry/timeout.
Have a look at netsnmp_patch.diff, its fairly easy and not intrusive at all.