« Previous 1 2
Implement your own MIBs with Python
DIY MIBs
Tree and Leaf Care
So far, only the trunk of your own sub-tree has been defined. Some initial branches will now be added with the OBJECT IDENTIFIER
keyword. The code in Listing 5 defines a raspiMIBObjects
branch below raspiMIB
, and below that in turn a raspiMIBScalars
branch.
Listing 5
Initial Branches
-- MIB root nodes raspiMIBObjects OBJECT IDENTIFIER ::= { raspiMIB 1 } raspiMIBScalars OBJECT IDENTIFIER ::= { raspiMIBObjects 1 }
By the way, branches cannot be the subject of an IMPORTS
statement. If you want to import all the objects in a branch, you have to list them explicitly and individually.
Next up, the OBJECT-TYPE
macro in Listing 6 defines a managed object below raspiMIBScalars
with the name socTemp
and the data type Integer32
(i.e., a 32-bit integer). The object is supported in the current revision of the MIB (STATUS current
) and can only be read, but not set, through SNMP (MAX-ACCESS read-only
). By the way, in later snmpget
queries, it is important always to append a .0 to scalar managed objects.
Listing 6
Managed Object
-- Scalars socTemp OBJECT TYPE SYNTAX Integer32 MAX-ACCESS read-only STATUS current DESCRIPTION "The current temperature of Broadcom SoC multiplied by 1000." ::= { raspiMIBScalars 1 }
If you study Table 2 carefully, you will notice that it does not list a data type for floating point values. ASN.1 itself does not support a native Float
type, and a standard for encoding floating point numbers has not been established. However, the problem can usually be avoided by transmitting a value multiplied by 10 – for example, 359
instead of 35.9
. MIB authors should always describe such conventions in an object's DESCRIPTION
.
Table 2
SMIv2 Common Scalar Data Types
Type | Short Description | Defined in MIB |
---|---|---|
Counter32
|
Positive 32-bit counter value | SNMPv2-SMI
|
Counter64
|
Positive 64-bit counter value | SNMPv2-SMI
|
DisplayString
|
ASCII-String from 0 to 255 characters in length | SNMPv2-TC
|
Gauge32
|
Positive 32-bit measured value | SNMPv2-SMI
|
Integer32
|
Positive or negative 32-bit integer | SNMPv2-SMI
|
IpAddress
|
IPv4 address | SNMPv2-SMI
|
Object Identifier
|
Define a new sub-branch | SNMPv2-SMI
|
Unsigned32
|
Positive 32-bit integer | SNMPv2-SMI
|
TimeTicks
|
Positive 32-bit time value | SNMPv2-SMI
|
The example MIB is now complete; however, it makes sense to use the smilint
tool from the smitools
package first to check the MIB file passed in as a command line argument for conformity with the SMIv2 specifications from RFCs 2578 to 2580. The -l
parameter modifies the strictness of the check. At the recommended level of 4
, it is bound to find something:
$ smilint -l4 RASPI-MIB.txt RASPIMIB.TXT:36: warning: node 'socTemp' must be contained in at least one conformance group
The Conformance Statements specified in RFC 2578 take into account that the MIB designer can specify features in a MIB that not necessarily every implementation will use. For this reason, two macros allow the definition of groups of entities that can be implemented either all together or not at all: OBJECT-GROUP
for related managed objects and NOTIFICATION-GROUP
for related notifications. Several of these groups can then be grouped together with the MODULE-CAPABABILITIES
macro and define degrees of implementation that a specific implementation can claim for itself.
To satisfy smilint
, however, it is sufficient to define a suitable OBJECT-GROUP
, which is done here in separate sub-branches for the sake of clarity (Listing 7). Listing 8 shows the completed MIB, which we now yet have to implement.
Listing 7
OBJECT-GROUP Definition
raspiMIBConformance OBJECT IDENTIFIER ::= { raspiMIB 2 } -- Conformance raspiMIBGroups OBJECT IDENTIFIER ::= { raspiMIBConformance 1 } raspiMIBScalarsGroup OBJECT-GROUP OBJECTS { socTemp } STATUS current DESCRIPTION "Scalar managed objects from RASPI-MIB." ::= { raspiMIBGroups 1 }
Listing 8
RASPI-MIB.txt
RASPI-MIB DEFINITIONS ::= BEGIN ----------------------------------------------------------- -- RASPI-MIB for monitoring different values of the Rasp Pi ----------------------------------------------------------- -- Imports IMPORTS MODULE-IDENTITY, OBJECT-TYPE, Integer32 FROM SNMPv2-SMI OBJECT-GROUP FROM SNMPv2-CONF netSnmpPlaypen FROM NET-SNMP-MIB; raspiMIB MODULE IDENTITY LAST-UPDATED "202005060200Z" ORGANIZATION "None" CONTACT-INFO "Editor: Attentive reader Raspberry Pi-Way 42 10487 Berlin" DESCRIPTION "A MIB to watch over Raspberry Pis" REVISION "202005060200Z" DESCRIPTION "Version 1." ::= { netSnmpPlaypen 42 } -- MIB root nodes raspiMIBObjects OBJECT IDENTIFIER ::= { raspiMIB 1 } raspiMIBConformance OBJECT IDENTIFIER ::= { raspiMIB 2 } raspiMIBScalars OBJECT IDENTIFIER ::= { raspiMIBObjects 1 } -- Scalars socTemp OBJECT TYPE SYNTAX Integer32 MAX-ACCESS read-only STATUS current DESCRIPTION "The current temperature of Broadcom SoC multiplied by 1000." ::= { raspiMIBScalars 1 } -- Conformance raspiMIBGroups OBJECT IDENTIFIER ::= { raspiMIBConformance 1 } raspiMIBScalarsGroup OBJECT-GROUP OBJECTS { socTemp } STATUS current DESCRIPTION "Scalar managed objects from the RASPI-MIB." ::= { raspiMIBGroups 1 } END
From Agent to Agent
The interface through which snmpd
will be contacted by the yet-to-be-written implementation is the Agent Extensibility (AgentX) protocol standardized in RFC 2741 [4]. Other extension mechanisms beyond extend
exist in the form of dynamically loadable modules, such as pass_persist
and the SMUX (SNMP multiplexing) protocol; however, AgentX offers more possibilities with loose coupling, which is advantageous from a security perspective.
In AgentX jargon, snmpd
is the master agent, of which there is always exactly one, and to which multiple independently running processes, named subagents, can connect. After the connection is established, a subagent declares itself responsible for certain sub-trees of the MIB tree and their implementation. The master agent is the only agent to talk SNMP but knows nothing about custom MIBs and their implementation. With subagents, it is exactly the opposite.
To enable AgentX support in snmpd
, you just need an extra master agentx
line in the /etc/snmp/snmpd.conf
file. By default, the connection between snmpd
and the subagents uses the /var/agentx/master
Unix Domain Socket, so from this perspective, AgentX is simply more or less an Interprocess Communication (IPC) mechanism. Technically, it would also be possible to configure a TCP socket; however, because AgentX does not provide any authentication mechanisms between the master agent and subagents and accepts any subagent, such a configuration would have to be accompanied by security measures such as firewalls.
Fortunately, Net-SNMP not only comes with snmpd
, but also offers APIs and libraries, which the master agent itself uses too. For developers proficient in the C programming language, several tutorials [5] on the Net-SNMP website describe the development of a MIB module – for example, as a subagent. The mib2c
tool includes a scaffolding tool, which takes a MIB as input and, after answering a few implementation questions, generates a detailed commented basic framework of C source code, with which you can push forward your MIB implementation.
However not everyone is proficient in C. For scenarios in which you want to integrate external information sources into your MIB, and at the same time achieve fast and yet sufficiently sophisticated results, common script languages are particularly useful. Net-SNMP comes with its own Perl module, but currently one particular lingua franca is certainly Python, for which resources looked a little scarce until 2013. With the Python module included with Net-SNMP, which consists of 2,500 lines of C code, you could implement SNMP clients but not agents. SourceForge, on the other hand, had a Python AgentX module [6], but it hasn't been maintained since 2010 and has several deficits.
For this reason, I decided to develop my own open source Python module, python-netsnmpagent
[7]. Written in Python, it uses the Ctypes module provided with Python to access the C API of the Net-SNMP libnetsnmpagent.so
and libnetsnmphelpers.so
libraries and abstracts them for the Python programmer behind a netsnmpAgent
class (and other classes for common data types) that can be used with just a few lines of code.
In the meantime, another Python module, pyagentx
[8], appeared on GitHub, but it tries to implement the complete AgentX protocol on its own and has not been maintained since 2015. The python-netsnmpAgent
module, on the other hand, saw its last changes in 2019 but still works on older enterprise distributions such as SLES 11 (Python 2.7, Net-SNMP 5.4.x), as well as on more recent systems with Python 3.5 or newer and Net-SNMP 5.7.x/5.8. For some distributions, such as SUSE, there are ready-made packages, but not for Debian and Raspbian, for which Python developers will need to install the module – possibly within a virtual environment – with Python's own Pip package manager:
$ apt-get install --no-install-recommends python3-pip $ pip3 install netsnmpagent
Listing 9 shows an initial version of a subagent for this example. After importing the netsnmpagent
module, it creates an instance of the netsnmpAgent
class that serves as a central hub for connecting to snmpd
and managing objects. It expects a parameter with a descriptive name for the agent and, in this example, the path to your MIB. By explicitly specifying the MIB, you can experiment with RASPI-MIB.txt
without having to install it globally on the system in /usr/share/snmp/mibs/
.
Listing 9
raspiagent.py Structure
#!/usr/bin/python3 import os import netsnmpagent import sys try: agent = netsnmpagent.netsnmpAgent( AgentName = "RaspiAgent", MIBFiles = [ os.path.abspath(os.path.dirname(sys.argv[0]) + "/RASPI-MIB.txt" ] ) agent.start() while True: agent.check_and_process() except netsnmpagent.netsnmpAgentException as e: print(e) sys.exit(1)
Calling the start()
class method establishes the connection to snmpd
. Afterward, the check_and_process()
method is called repeatedly in an infinite loop, waiting for requests from the master agent and processing them. So far, however, the code still lacks any reference to the managed object socTemp
.
Now add the code
socTemp = agent.Integer32( oidstr = "RASPI-MIB::socTemp" writable = False )
in front of the agent.start()
line of Listing 9. The agent
object provides factory methods, named after common SMIv2 data types, that return a new instance of a class with the same name, Integer32
in this case. In terms of parameters, you will always need to specify oidstr
, which specifies the OID under which the given object instance is to be registered. The optional writable
parameter specifies whether the instance can be changed by snmpset
. The most important method provided by these classes is update()
, to update the value of the managed object.
Start raspiagent.py
in a console window with root privileges, and in a second console execute the command
$ snmpget -M+. -v2c -c rpitesting localhost RASPI-MIB::socTemp.0RASPI-MIB::socTemp.0 = INTEGER: 0
in the directory in which RASPI-MIB.txt
and raspiagent.py
reside.
However, the command always returns the value 0
, because the temperature query has not yet been implemented. The code in Listing 10 adds this query. The command is the same as in the extend
example, but further processing of the output is done with native Python tools. A successive execution of snmpget
will now return the current temperature (multiplied by 1,000), as before with extend
:
$ snmpget -M+. -v2c -c rpitesting localhost RASPI-MIB::socTemp.0 RASPI-MIB::socTemp.0 = INTEGER: 40622
Listing 10
Updating the socTemp Value
while True: line = open("/sys/class/thermal/thermal_zone0/temp").readline() socTemp.update(int(line)) agent.check_and_process()
The example shown here only implements a single managed object and, being Integer32
, a rather simple one on top. Further examples of other scalar data types and tables can be found in the netsnmpagent
archive available from the Python Package Index (PyPI) repository and the GitHub repository [9] in the form of simple_agent.py
in the examples/
subdirectory.
You will also find a solution for another common problem there: In the case of raspiagent.py
, you will notice a short wait before snmpwalk
returns a value for socTemp
. The agent performs its two main tasks of gathering and sharing information, one after the other, and calls vcgencmd
for each request, which takes a little while. The solution is to decouple the two tasks, as demonstrated by threading_agent.py
(also in the examples/
subdirectory), which uses threads.
Conclusions
Once you have familiarized yourself with the formal rules and the available data types and macros, writing a MIB is relatively easy. Now that python-netsnmpagent
is available to implement the MIB, sys admins and developers can focus on integrating additional information sources into an existing SNMP monitoring setup.
Infos
- RFC 2578: https://tools.ietf.org/html/rfc2578
- Private enterprise numbers: https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
- RFC 2863: https://tools.ietf.org/html/rfc2863
- RFC 2741: https://tools.ietf.org/html/rfc2741
- Net-SNMP tutorials: http://www.net-snmp.org/wiki/index.php/Tutorials
- Python module: https://sourceforge.net/projects/python-agentx
- netsnmpagent: https://pypi.org/project/netsnmpagent/
- Pyagentx: https://github.com/hosthvo/pyagentx
- GitHub repository: https://github.com/pief/python-netsnmpagent
« Previous 1 2
Buy this article as PDF
(incl. VAT)