Unleashing the Power of SNMP: Exposing Your Embedded Elixir/Erlang (Nerves, GRiSP) Apps to the World
- Matt Galvin
- 22nd Jun 2023
- 16 min of reading time
Did you know that Erlang/OTP ships with built-in SNMP (Simple Network Management Protocol) support? Using SNMP is a great way to integrate your Elixir or Erlang application into an industrial environment. This will be of particular interest for those working with embedded systems.
For those unfamiliar with SNMP, it facilitates information sharing among diverse devices on a network, enabling communication between devices, regardless of their hardware or software variations.
In this blog post, we’ll highlight the benefits of using SNMP in your Elixir/Erlang apps and we’ll briefly touch upon the implementation details, with the main focus on the significance and rationale of using SNMP. For detailed instructions on how to incorporate SNMP into your Elixir/Erlang application, relevant links will be provided at the end of the post.
SNMP v1, developed in 1988 as a temporary solution, was intended to be replaced by another protocol from the OSI protocol suite. However, this transition never occurred. Despite its comical history, SNMP evolved, gained widespread adoption, and remains extensively utilised. The latest version, SNMP v3, developed in 1998, continues to undergo improvements based on years of practical experience. While the posts focus isn’t on the intricacies of SNMP, understanding its basics can be beneficial.
Despite its misleading name, the Simple Network Management Protocol (SNMP) is not so “simple”. It has a history filled with peculiarities and absurdities. For instance, early versions lacked robust (“trivial” as RFC 3410 puts it) security measures, version numbers were occasionally inconsistent (v1.5 followed 2.0), and there even exists a version called SNMPv2* with an asterisk as part of its name. Competing variations of SNMPv2 were created, and the competition gave rise to incompatibility and the subsequent breakdown of the typical standardisation process (TCP/IP Guide, 1077). Despite its flaws, SNMP has achieved ubiquity. Moreover, it has been battle-tested since the 1980s- sound familiar? Looking at you, Erlang…
SNMP’s fundamental concept revolves around facilitating the exchange of network management information using TCP/IP, in something like a client server pattern. Erlang’s SNMP User Guide explains that a manager generates commands and receives notifications from agents, while an agent responds to manager commands and sends notifications. Typically, there are only a few managers but potentially numerous agents within a system. Rather than duplicate examples, I refer you to the Erlang docs (agent, manager). But to get a flavour, here’s a code snippet taken from the Nerves Examples (Hello SNMP Manager) that shows a manager asking an agent for information:
There are essentially 3 layers of abstraction in SNMP as described by Kozierok in TCP/IP Guide:
Primarily, in order to use SNMP in your app, you will (optionally) load a MIB (Erlang/OTP MIB Compiler) and then call agent and/or manager functions. We will return to this topic a little later, but you will need to know little to nothing about MIBs in order to use SNMP. MIB files have a peculiar and unfamiliar appearance initially but become straightforward with a little experience.
Page 7 of the Erlang SNMP User’s Guide depicts the general SNMP architecture, but it includes additional details beyond a basic example. Also, by taking advantage of built-in SNMP support, you can harness SNMP functionality without being burdened by intricate details:
That’s probably enough background and trivia.
The embedded Elixir/Erlang journey often starts with the iconic “Hello World” app of Nerves/GRiSP… blinking LEDs. As the freshly minted embedded developer starts producing devices to perform more practical functionality, network interaction becomes crucial.
In the realm of networked devices, administrators typically perform two actions: gathering data to monitor device functionality and issuing commands to alter device behavior (TCP/IP Guide, 1159).
Much software development today revolves around web and browser-centric applications, so developers experimenting with embedded Elixir/Erlang often create devices controlled and monitored through web-based user interfaces. While this approach has its merits, it becomes limiting when browser dependency is undesirable, especially in environments where other devices aren’t browser-based—a common scenario in real-world use cases. Long before browsers were used to violate your privacy and sell your life’s history to advertisers, RFCs describing different industrial protocols were created for the specific purpose of controlling and monitoring devices.
The resemblance between hardware datasheets and various TCP/IP message specifications is obvious. Here’s an example of reading data from an ADC (datasheet snippet included for comparison) via I2C (see Ries, Embedded-Elixir):
You’ll notice that there’s essentially a direct mapping between that ADC’s datasheet and the Elixir code. Look at how the 16-bits are pattern-matched to get the `status` and `mux` values. Such beautiful, elegant expression of a datasheet in code is afforded to us by the BEAM.
With SNMP support built into Erlang/OTP, there’s no need to manually parse message bits/bytes as required for unsupported protocols, but you can see how this Modbus protocol library does it. Here’s a general SNMP message structure (TCP/IP Guide 1118):
Not all SNMP messages have the same format. They differ based on the SNMP version and message type (like traps). As an Elixir/Erlang developer, you don’t need to worry about these details. Understanding this at a conceptual level gives you a framework for understanding how protocols (HTTP too for that matter) work with your software and that is enough for now.
As a BEAM enthusiast, I’m interested in using Elixir/Erlang to work with hardware. I have experience working with embedded Elixir/Erlang in data center environments, and have worked with other embedded software tools in large-scale commercial projects. When deploying devices in diverse settings, seamless integration with other devices is crucial. To achieve this, exposing your device to be managed or to manage others like standard network devices becomes invaluable.
We’re going to breeze through this because the Erlang docs and Nerves Examples explain it better than I would in this post. The Erlang docs show how to use SNMP (agent, manager) in your app, and there are also embedded Elixir/Erlang examples in the Nerves Project repo (agent, manager).
You can issue SNMP commands to your Erlang/Elixir app via the command line like:
$ snmpset -c public nerves.local .1.3.6.1.3.17.1.0 i 1
The numbers, .1.3.6.1.3.17.1.0, represent an “OID” in SNMP parlance. This OID ultimately corresponds to a specific functionality on your device defined in the MIB (there is also other information encoded in the OID, but that’s not important now). If your device is reachable at nerves.local (or whatever ip) and if the MIB for your application’s SNMP agent states that .1.3.6.1.3.17.1.0 is responsible for turning on a pin on your Nerves device (setting it to 1), you can execute this command via the command line or write Elixir/Erlang code to do so. Your terminal would then print a response like the following to indicate a success
SNMPv2-SMI::experimental.17.1.0 = INTEGER: 1
To read a temperature sensor, retrieve data from a gen_server, a PSQL database, or any other desired source, you can define an OID like .1.3.6.1.3.17.2.0. Then, you can use a command like:
$ snmpget -c public nerves.local .1.3.6.1.3.17.2.0
Remember, interacting with SNMP devices can be done through various methods, not limited to the command line. Your application code will have SNMP Manager(s)/Agent(s) issuing/responding-to commands, there are also GUI tools known as “MIB browsers” available for SNMP interaction for those who prefer graphical interfaces (no web browser required), etc..
Imagine your successful Nerves prototype running on some arbitrary supported target (RPi, GRiSP 2, BeagleBone Black, etc.) is receiving numerous orders. To effectively deploy it, your device should seamlessly integrate with other managed devices (e.g. power meters, smart relays, BMCs, etc.) that users expect. By enabling SNMP communication, your device becomes part of the network alongside other industrial devices.
You can run GRiSP’s Erlang-based OS or Nerves on a GRiSP 2 board. The GRiSP 2 boards enhance security with the ATECC608b secure element for best of breed secret storage, supported by Nerves and GRiSP. With SNMP, your Raspberry Pi running Nerves and your GRiSP 2 running the GRiSP stack can communicate with each other, a router, a BMC, smart breakers, sensors, power meters, switches- anything that speaks SNMP! They can manage or be managed by a Linux Server running other applications, whether those other applications are written in Elixir/Erlang or some other language, as long as the application can speak SNMP. Even a network administrator, handling numerous devices in different languages across different hardware, can access and manage your device using a MIB browser or commands from their {Linux,Windows,Mac etc} terminal. All these components collaborate seamlessly, oblivious to the underlying technologies involved.
There are significant advantages to designing systems that align with industry standards.
To have your device incorporated into an industrial setting, you need to convince the administrators responsible for the environment to allow it. By ensuring your device supports SNMP, which is widely used in settings like data centers, you eliminate the need for workflow changes for these administrators. Your device can seamlessly manage or be managed like any other SNMP device on their network and will thus be compatible with the environment. The MIB for your device acts like a contract or API for it. Here’s an example of a MIB for a power meter. The MIB contains all the OIDs and what reading/writing to each will do. So anyone interested in using this power meter essentially just needs to get it on their network, and start issuing the desired SNMP commands. Your Elixir/Erlang applications can be this way too. Another benefit to using SNMP is that your application code can be simpler because you can, depending on SNMP version, rely on the security that SNMP offers for monitoring and controlling devices, eliminating the need to develop custom security mechanisms. Depending on SNMP version, the security that SNMP offers for monitoring and controlling devices, eliminating the need to develop custom authentication mechanisms. Further, you don’t need to figure out how to orchestrate communication between applications, SNMP has standardised communication methods for these purposes.
Another important business aspect is guarantees to customers. Suppose your application is guaranteed to work with specific devices (like the previously mentioned power meter), and you want automated tests to validate this functionality. By loading the device’s MIB into your application and defining an SNMP agent during testing, you can mimic communication with the actual device. Your application interacts with an Elixir process that speaks SNMP and responds to the same OIDs as the real device. This approach allows your application, or any SNMP-compatible entity, to seamlessly communicate with the test process exactly as if it were the actual device. Elixir-SNMP simplifies using SNMP in general, and also aids in writing tests like this. Although it may seem like a contrived example, the ability to test different SNMP devices becomes invaluable when numerous guarantees about numerous devices are made about your application’s compatibility.
When you make the decision to use SNMP in your app, you are taking advantage of decades of experience, research, and field testing on how networked devices can manage and be managed. Your app becomes compatible with other devices on a network, it is more generic, and is more likely to be accepted by administrators working in industrial environments. SNMP communication between your app and others is handled in a standardised, well thought out way.
People working with hardware often have familiarity with protocols like SNMP, while typical software developers may not. Understanding protocols like SNMP helps bridge the conceptual gap between software and hardware in a similar way as tools like Nerves and GRiSP. When working with embedded systems, it’s crucial to consider the device’s ultimate goal and its compatibility with the target environment.