SNMP v3 Agent framework
This is work towards a framework for developing SNMP v3 agents, using the rasn ASN-1 library for decoding the on the wire data. While a manager (effectively a client) has to support a range of legacy agents, an agent (e.g. server) can offer a subset of features and still be useful.
This agent only supports SNMP v3, older variants have no on the wire security.
This project follows semver - but that means no guarantees at all pre v1! There are changes in the internal APIs and traits in this release, so this is a minor version (0.2.0) rather than a patch.
This release implements (more or less) the transaction model for Set PDUs. This required a change to the OidKeeper trait, defined in src/keeper.rs. The sample handlers, and the Rust MIB compiler have been updated to follow this. There is support in the agent and stub generator for Module Compliance statements. The documentation is improved.
The agent server loop is a single threaded blocking design. I would argue that this is appropriate for almost all agents, as typically a single manager will interact with multiple agents. Managers may well want to support high levels of concurrency. The single threaded design avoids many issues with concurrency and locking. Of course, there is nothing to stop handlers for specific long running operations using a thread. Good luck with that!
At present, there is a simplistic permissions model, and some real world applications will need more than that. Coming real soon. It may not be RFC view action model, as that seems complex, and the facility to dynamically change the permissions model remotely is not often implemented. Instead, some sort of compile time model seems more appropriate to the sort of boxes that run agents.
In this release the Engine ID is loaded from a configuration file. A sample configuration file is included at .snmp-agent.conf. Two example MIB module handlers are included under src/handlers for SNMPv3-MIB and SNMP-USER-BASED-SM-MIB. They were written based on stubs generated by the Rust tool. The USM handler does not yet include remote password changes and user creation.
Commented out Module Compliance statements are generated by the stub generator. Instructions are provided for how to make use of these once modules implement the required Object groups.
There is no usable support for notifications so far, although I am thinking about it, and the start of some code in src/notifier.rs. Don't take that seriously, I will probably do something completely different when I understand the problem better. Please raise an issue or start a discussion if you have a specific use case in mind, as that would really help me decide how to do it.
There are known limitations for tables that use either the AUGMENTS or use index columns drawn from foreign tables. The generated stubs have a single row of junk data in all tables. For most tables, that row is indexed correctly. For tables that foreign index columns, an arbitrary single integer index is used, with the row indexed at value 1. This allows operations like get and get_next to work. If you take that stub and implement it as real handler, you will need to do your own implementation of foreign indexing, almost certainly involving referencing the other table structs.
Users and passwords
There is a small python tool for generating a username and password file under tools/usekey.py. It picks the EngineID up from the configuration file.
Changing passwords on the wire is not yet implemented, and would be a really good project.
Tools for stub generation
At present, there is only rough tooling to help implement an useful agent, but it is possible with some patience. There is a stub generator, written in Rust. There used to be a Python one too, but it is now obsolete and has been removed in this release.
The source files for the Rust stub generator are under src/bin/stub-gen. It uses the nom parser combinator library for parsing. It has a reasonably complete parser implementation, which can parse almost all the MIBs on my machine except for legacy MIBS in Smi v1 and a few bootstrap definition files. The code generator in this version ignores everything to do with notifications (but does detailed parsing). It also does the wrong thing with AUGMENTS and tables that use foreign index columns (AUGMENTS is actually just a special case of foreign indices).
The stub generator now sorts items by Oid order, so as to give stable output.
Workflow
First build the stub generator with:
cargo build --bin stub-gen
then generate the stubs for the mibs you want with:
target/debug/stub-gen -o src/stubs/ MIB1 MIB2 ...
where MIB1 and MIB2 and so on are the names of the MIB files to generate stubs from. The generator searches /var/lib/mibs/ietf, /var/lib/mibs/iana and /usr/share/snmp/mibs to find the files, and tries adding .txt extension as well. If your system has the files somewhere different, or you wish to include vendor mibs, edit src/bin/stub-gen/importer.rs and change the SEARCH_PATH constant. Arguably, this should be settable by the command line and / or an environment variable.
By default, the stub generator ignores objects that are marked as "deprecated" or "obsolete". You can include deprecated objects with the -d
or --deprecated
flags. If you really need to implement something obsolete,
there is a --obsolete
flag, if you are stuck with a manager that can't be updated and depends on some old stuff.
The generated stubs will be placed under src/stubs/.
If you want the agent to do something useful, you need to write your own back-end implementations. The generated stubs are placed in the src/stubs/ directory. The basic idea is to associate instances that support the OidKeeper trait with the OID value or values that they support in the OidMap. This is populated and then the agent loop_forever() runs.
If you implement a suitable back end, by editing a stub or from scratch, move it to src/handlers/, and update src/stubs.rs and src/handlers.rs to reflect the new location. If it is a public MIB that you would like to contribute, make a PR, and I would be delighted to start shipping some more handlers.
Two toy implementations of the OidKeeper trait are provided by way of example, both purely memory based. One is for scalars, and the other is a limited table mode. Set can change cell values in existing rows. New rows can be created by the CreateAndWait mechanism if there is a RowStatus column in the table, and destroyed by Destroy. If you change the value of index cells, the results may be puzzling. If the MIB is correctly structured, the permissions checks should stop you making that mistake. The generated stub implementations just wrap the toy struct types, and need to be replaced by real actions.
The handlers directory contains a couple of examples of stubs that have been edited to give at least partial implementations of a couple of the core MIBs.
Trying it out
Edit the configuration file .snmp-agent.conf, and insert your Enterprise number and other details.
Create a password file called users.txt, using python3 tools/usekey.py. For example,
python3 tools/usekey.py admin myv3user password password1 > users.txt
Optionally, edit groups.txt.
Set a suitable log level with, for example, export RUST_LOG=info
.
Run the agent with cargo run.
Install netsnmp for testing purposes, and run in another terminal:
'''shell snmpwalk -v 3 -l authPriv -a SHA -A password -x AES -X password1 -u myv3user 127.0.0.1:2161 1.3.6.1 1.3.6.1.6.3 '''