uSNMP ("micro-SNMP") is a small and portable 'C' library for developing SNMPv1 agent and manager. Ports to Arduino IDE, Windows and *nix are included in the source code, and have been tested on Arduino compatible (AVR ATmega328p) and Arduino Mega with Ethernet Shield, NodeMCU v0.9 (Expressif ESP8266), Windows (compiled with Embarcadero BCC32C C++ compiler) and Cygwin (with gcc).
How Small Does uSNMP Get?On an Arduino ATmega328p with an Ethernet Shield, an uSNMP agent that implements the mib-2::system
table, three minimalist tables of 2 digital inputs (with trap sent when the state toggles), 2 digital outputs and 1 analog input, is about 20kB, inclusive of the SPI, Ethernet, UDP, DNS routines. It supports Get, GetNext, Set operations and sends a Trap when the digital inputs toggle. The 2kB SRAM limits the number of MIB entries and network packet size (and thus request and response length). By forgoing the mib-2::system
table, more digital and analog I/O pins can be added to the respective tables. On an Arduino Mega or ESP8266, bigger buffers and more I/O pins can be supported as the SRAM is far bigger.
The library includes functions to store and traverse a MIB tree in lexigraphical order; support callback functions to get and set value of a MIB leaf node, make SNMPv1 Get, GetNext, Set request; construct and process the response; create and parse a varbind list, send a Trap and takes care of Endianness.
Why use SNMP?SNMP (Simple Network Management Protocol) is the de-facto standard in IT equipment and well-supported in the industrial and built environment sectors: network equipment, servers and storage, UPS, rectifiers, teleprotection or protection signalling equipment, RTU, remote I/O, etc. Its concept of a Management Information Base (MIB), defined in a text file in ASN.1 notation, is its superpower. MIB files work like a data dictionary or a device description lanaguage. They make it easy to onboard a new device into a SNMP-based management software, of which there are many including open source ones, with features like geographical and topologial map overlay, dashboard, charts, event logs, event action filters, trouble ticketing.
This setup is ideal for IoT applications or asset management, where there are many identical sites and devices with few points. Contrast this with a SCADA/HMI software which is suited for single site with many points, like a process plant or a building, and has highly visual features such as 3D and animation.
Then, why SNMPv1, and not SNMPv2 or v3?The SNMP protocol, despite its name, is really not simple to implement nor fit into small processors, even for SNMPv1. What are you losing with SNMPv1 versus v2/v3? Mainly operations for bulk data query and security features. But consider this: a device's MIB can still be traversed fully with SNMPv1 operations. And most, if not all, industrial protocols including dominant ones like Modbus, BACnet and Profinet, do not have built-in or have weak security features. This is not to trivialise security, but to urge pragmatism when circumstances permit.
How Does uSNMP Work?The uSNMP library extends the Embedded SNMP Server presented in chapter 8 of the book "TCP/IP Application Layer Protocols for Embedded Systems" by M. Tim Jones (Charles River Media, 2002. ISBN 1-58450-247-9) who very eloquently wrote
"... The problem with SNMP message generation is... forward (unknown) TLV lengths... The solution chosen for this problem is to parse the SNMP request using a predictive parser and build the response as we go... We predictively parse through the SNMP PDU, and when we reach the final TLV, we return through our function call chain and update the length values of the TLVs as needed."How Do I Use uSNMP?
There are code examples of agent and command line utilities that can be used as templates for developing a SNMPv1 agent, to make a SNMPv1 request and process the response, and to send a trap. The example uSNMP agent usnmpd.c, for Windows and *nix, reads OIDs and value pairs from a file, and can be used as a SNMPv1 gateway by having a poller program formats and writes its received data to this file. Another agent example usnmpd.ino turns an Arduino board into a SNMP-enabled controller with digital and analog I/O. MIB files are in the mibs directory. The ARDUINO.MIB file is for an Arduino Software (IDE) managed board, and the Private Enterprise Number (PEN) is 38644 of Armadino.
usnmpd.ino - a SNMP agent for ArduinoLet's take a deeper look into usnmpd.ino. For 3rd party hardware packages such as NodeMCU, it is first necessary to add the URL of their Boards Manager JSON file in the Arduino IDE. The URLs point to JSON index files that Arduino IDE uses to build the list of available installed boards. This may be done from File... Preferences and for ESP8266 boards like NodeMCU:
The first few lines of usnmpd.ino sets up the network connection (Ethernet or WiFi), IP address and agent configuration.
// Agent's IP configuration. Retain these global variable names.
IPAddress hostIpAddr( 192, 168, 1, 177 ),
dnsServer( 192, 168, 1, 1 ),
hostGateway( 192, 168, 1, 1 ),
hostNetmask( 255, 255, 255, 0 );
#ifdef ARDUINO_ETHERNET
unsigned char hostMacAddr[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
#else // assume ARDUINO_WIFI
char staSSID[] = "Wifi_SSID";
char staPSK[] = "Wifi_Password";
#endif
// SNMP agent configuration.
#define ENTERPRISE_OID "P.38644.30" // used as sysObjectID and in trap
#define RO_COMMUNITY "public"
#define RW_COMMUNITY "private"
#define TRAP_DST_ADDR "192.168.1.170"
uSNMP defines three prefixes for Object ID that every valid OID is required to start with:
B denotes Mgmt-Mib2 - 1.3.6.1.2.1
E denotes Experimental - 1.3.6.1.3
P denotes Private-Enterprises - 1.3.6.1.4.1
Thus, sysDescr.0 (1.3.6.1.2.1.1.1.0) will be coded as "B.1.1.0" and Enterprise OID of "1.3.6.1.4.1.38644.30" as "P.38644.30"
setup() initialise the board and agent 'engine', including constructing the MIB tree, and sending a coldStart trap. Pins D2 to D5 are designed as digital inputs, D6 to D8 are digital outputs, and A0 and A1 are analog inputs, depending on the amount of SRAM available on the target microcontroller. Having included the mib-2::system table, an Arduino UNO with ATmega328p could have D2, D3, D6, D7 and A0, while a Arduino Mega may go beyond D5, D8 and A1 if one so wishes. Otherwise, omitting the system table will free up space on an UNO for more pins.
initSnmpAgent(SNMP_PORT, ENTERPRISE_OID, RO_COMMUNITY, RW_COMMUNITY);
initMibTree();
trapBuild(&request, enterpriseOID, hostIpAddr, COLD_START, 0, NULL); // cold start trap
trapSend(&request, trapDstAddr, TRAP_DST_PORT, roCommunity);
The MIB tree is constructed with the miblistadd()
fucntion, that graft a MIB leave node onto it in lexigraphical order. If needed, this is followed by setting the node's value, and attaching callback functions to respond to SNMP Get and Set operations. In the extract below, sysDescr
is assigned a character string that already holds the system description. sysObjectID
is initialised with the EnterpriseOID
after it has been encoded with BER (Basic Encoding Rule). sysUpTime
is set up with get_uptime()
callback to fill in the system uptime whenever a Get operation is asked.
/* System MIB */
// sysDescr Entry
thismib = miblistadd(mibTree, "B.1.1.0", OCTET_STRING, RD_ONLY,
sysDescr, strlen(sysDescr));
// sysObjectID Entry
thismib = miblistadd(mibTree, "B.1.2.0", OBJECT_IDENTIFIER, RD_ONLY,
entOIDBer, 0); // set length to 0 first
i = str2ber(enterpriseOID, entOIDBer);
mibsetvalue(thismib, (void *) entOIDBer, (int) i); // proper length set
// sysUptime Entry
thismib = miblistadd(mibTree, "B.1.3.0", TIMETICKS, RD_ONLY, NULL, 0);
i = 0; mibsetvalue(thismib, &i, 0);
mibsetcallback(thismib, get_uptime, NULL);
The digital and analog I/O pins are presented in SNMP tables. To save memory, these tables are minimalist, comprising only an index and the pin value. Callback functions are required so that the values are retieved just-in-time when responding to a Get or Set request. Thus, for digital output D6 for instance,
// Digital output #6 index
thismib = miblistadd(mibTree, "P.38644.30.2.1.1.6", INTEGER, RD_ONLY, NULL, 0);
i = 6; mibsetvalue(thismib, &i, 0);
// The value of Digital #6
thismib = miblistadd(mibTree, "P.38644.30.2.1.2.6", INTEGER, RD_WR, NULL, 0);
i = 0; mibsetvalue(thismib, &i, 0);
mibsetcallback(thismib, get_dio, set_dio);
The agent is designed to send a trap whenever it detects a change of state in a digital input. As the uSNMP agent is not re-entrant, trap should only be built and send in the main loop.
if ( x & 0x01 ) {
vblistReset(&response); dInIndex[17]='0'+y; // use response buffer to build trap
if ( lastDIN & 0x01 ) { // input pin y was 1
i = 0; // it is thus 0 now
vblistAdd(&response, dInIndex, INTEGER, &i, 0);
trapBuild(&request, enterpriseOID, hostIpAddr, ENTERPRISE_SPECIFIC, 1, &response);
}
else {
i = 1;
vblistAdd(&response, dInIndex, INTEGER, &i, 0);
trapBuild(&request, enterpriseOID, hostIpAddr, ENTERPRISE_SPECIFIC, 2, &response);
}
trapSend(&request, trapDstAddr, TRAP_DST_PORT, rwCommunity);
}
Likewise, if the agent encounters a mismatched community string in processing a SNMP request, it sends an Authentication Failure trap.
if ( processSNMP() == COMM_STR_MISMATCH ) {
trapBuild(&request, enterpriseOID, hostIpAddr, AUTHENTICATE_FAIL, 0, NULL);
trapSend(&request, trapDstAddr, TRAP_DST_PORT, rwCommunity);
}
Test ResultsThat's just about it. The uSNMP library has functions to make SNMP request and process response; and includes command line examples such as usnmpget and usnmpset which are used to test the usnmpd.imo agent. The alternativeis to use the Net-SNMP binaries. Both set of tests are demonstrated below:
Comments