Skip to content

Instantly share code, notes, and snippets.

@kassane
Last active August 8, 2025 13:39
Show Gist options
  • Save kassane/2ca5ee0afbc7d834de26cef51eac5ebd to your computer and use it in GitHub Desktop.
Save kassane/2ca5ee0afbc7d834de26cef51eac5ebd to your computer and use it in GitHub Desktop.
C++23 asio - net-snmp client (sample)
/*
* @author: kassane <[email protected]>
* license: MIT
* build cmd: g++ asio_snmp.cc -o asnmp -std=c++23 -O2 -pthread -I$HOME/asio/asio/include $(net-snmp-config --cflags) $(net-snmp-config --libs)
* config-test doc: https://wiki.archlinux.org/title/Snmpd
*/
#include <asio.hpp>
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/library/snmpv3.h> // For SNMPv3 OIDs and constants
#include <print>
#include <string>
#include <string_view>
#include <cstring>
#include <memory>
#include <cstdlib>
#include <unordered_map>
using asio::awaitable;
using asio::co_spawn;
using asio::detached;
using asio::use_awaitable;
#ifndef USM_LENGTH_OID
#define USM_LENGTH_OID 11
#endif
/**
* Maps string names of authentication protocols to SNMPv3 OID constants.
*/
const std::unordered_map<std::string, const oid*> auth_proto_map = {
{"MD5", usmHMACMD5AuthProtocol},
{"SHA", usmHMACSHA1AuthProtocol},
{"SHA-224", usmHMAC128SHA224AuthProtocol},
{"SHA-256", usmHMAC192SHA256AuthProtocol},
{"SHA-384", usmHMAC256SHA384AuthProtocol},
{"SHA-512", usmHMAC384SHA512AuthProtocol}
};
/**
* Maps string names of privacy protocols to SNMPv3 OID constants.
*/
const std::unordered_map<std::string, const oid*> priv_proto_map = {
{"DES", usmDESPrivProtocol},
{"AES", usmAESPrivProtocol},
{"AES-192", usmAES192PrivProtocol},
{"AES-256", usmAES256PrivProtocol}
};
/**
* Holds options parsed from command line for the SNMP walk.
*/
struct Options {
std::string version; ///< SNMP version ("1", "2c", "3")
std::string host; ///< Hostname or IP address of SNMP agent
std::string community; ///< Community string for SNMPv1/v2c
std::string start_oid; ///< OID to start walking from (default "1.3.6.1" if empty)
std::string securityName; ///< SNMPv3 security name (user)
std::string authProtocol; ///< SNMPv3 authentication protocol name (e.g., "MD5", "SHA")
std::string authPassphrase; ///< Passphrase for authentication
std::string privProtocol; ///< SNMPv3 privacy protocol name (e.g., "DES", "AES")
std::string privPassphrase; ///< Passphrase for privacy
int securityLevel = SNMP_SEC_LEVEL_NOAUTH; ///< Security level (noAuthNoPriv=1, authNoPriv=2, authPriv=3)
};
/**
* Performs an SNMP walk starting at the given OID asynchronously using ASIO coroutines.
* Supports SNMPv1, SNMPv2c and SNMPv3 with authentication and privacy.
*
* @param opts Options specifying SNMP parameters and connection details.
*/
awaitable<void> snmp_walk(const Options& opts)
{
int version;
if (opts.version == "1") version = SNMP_VERSION_1;
else if (opts.version == "2c") version = SNMP_VERSION_2c;
else if (opts.version == "3") version = SNMP_VERSION_3;
else {
std::println(stderr, "Unsupported SNMP version: {}", opts.version);
co_return;
}
// Initialize the SNMP library and enable error logging to stderr
init_snmp("asnmp");
snmp_enable_stderrlog();
netsnmp_session session{};
snmp_sess_init(&session);
session.version = version;
session.peername = strdup(opts.host.c_str());
if (version == SNMP_VERSION_3) {
// Configure SNMPv3 security parameters
session.securityName = strdup(opts.securityName.c_str());
session.securityNameLen = opts.securityName.size();
session.securityLevel = opts.securityLevel;
// Set authentication protocol OID if specified
if (!opts.authProtocol.empty()) {
auto it = auth_proto_map.find(opts.authProtocol);
if (it != auth_proto_map.end()) {
session.securityAuthProto = const_cast<oid*>(it->second);
session.securityAuthProtoLen = USM_LENGTH_OID;
} else {
std::println(stderr, "Unknown auth protocol: {}", opts.authProtocol);
co_return;
}
}
// Generate authentication key from passphrase
if (!opts.authPassphrase.empty()) {
if (generate_Ku(session.securityAuthProto, USM_LENGTH_OID,
(const u_char*)opts.authPassphrase.data(), opts.authPassphrase.size(),
session.securityAuthKey, &session.securityAuthKeyLen) != SNMPERR_SUCCESS) {
std::println(stderr, "Error generating auth key");
co_return;
}
}
// Set privacy protocol OID if specified
if (!opts.privProtocol.empty()) {
auto it = priv_proto_map.find(opts.privProtocol);
if (it != priv_proto_map.end()) {
session.securityPrivProto = const_cast<oid*>(it->second);
session.securityPrivProtoLen = USM_LENGTH_OID;
} else {
std::println(stderr, "Unknown priv protocol: {}", opts.privProtocol);
co_return;
}
}
// Generate privacy key from passphrase
if (!opts.privPassphrase.empty()) {
if (generate_Ku(session.securityAuthProto, USM_LENGTH_OID,
(const u_char*)opts.privPassphrase.data(), opts.privPassphrase.size(),
session.securityPrivKey, &session.securityPrivKeyLen) != SNMPERR_SUCCESS) {
std::println(stderr, "Error generating priv key");
co_return;
}
}
}
else {
// For SNMPv1/v2c, set community string
session.community = reinterpret_cast<u_char*>(strdup(opts.community.c_str()));
session.community_len = opts.community.size();
}
// Open SNMP session
void* sessp = snmp_sess_open(&session);
if (!sessp) {
snmp_perror("snmp_sess_open");
co_return;
}
oid anOID[MAX_OID_LEN];
size_t anOID_len = MAX_OID_LEN;
// Parse the start OID or default to "1.3.6.1" (iso)
if (!opts.start_oid.empty()) {
if (!read_objid(opts.start_oid.c_str(), anOID, &anOID_len)) {
snmp_perror("read_objid");
snmp_sess_close(sessp);
co_return;
}
} else {
read_objid("1.3.6.1", anOID, &anOID_len);
}
oid baseOID[MAX_OID_LEN];
memcpy(baseOID, anOID, anOID_len * sizeof(oid));
size_t baseOID_len = anOID_len;
netsnmp_pdu* response = nullptr;
while (true) {
netsnmp_pdu* pdu;
if (version == SNMP_VERSION_2c || version == SNMP_VERSION_3) {
// Use GETBULK for SNMPv2c and SNMPv3
pdu = snmp_pdu_create(SNMP_MSG_GETBULK);
pdu->non_repeaters = 0;
pdu->max_repetitions = 10;
} else {
// Use GETNEXT for SNMPv1
pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
}
snmp_add_null_var(pdu, anOID, anOID_len);
// Send synchronous request and receive response
int status = snmp_sess_synch_response(sessp, pdu, &response);
if (status != STAT_SUCCESS || !response) {
std::println(stderr, "SNMP request failed: {}", snmp_api_errstring(snmp_errno));
break;
}
// Check for errors in the response
if (response->errstat != SNMP_ERR_NOERROR) {
if (version == SNMP_VERSION_1 && response->errstat == SNMP_ERR_NOSUCHNAME) {
// End of MIB for SNMPv1
break;
}
std::println(stderr, "Error in packet: {}", snmp_errstring(response->errstat));
break;
}
bool done = false;
// Iterate through returned variables
for (netsnmp_variable_list* vars = response->variables; vars; vars = vars->next_variable) {
// Detect end of MIB or no such object/instance
if ((vars->type == SNMP_ENDOFMIBVIEW) ||
(vars->type == SNMP_NOSUCHOBJECT) ||
(vars->type == SNMP_NOSUCHINSTANCE)) {
done = true;
break;
}
// If the OID is outside the subtree, stop
if (snmp_oid_compare(baseOID, baseOID_len, vars->name, baseOID_len) != 0) {
done = true;
break;
}
// Print OID and value
char oid_buf[1024];
snprint_objid(oid_buf, sizeof(oid_buf), vars->name, vars->name_length);
char val_buf[1024];
snprint_value(val_buf, sizeof(val_buf), vars->name, vars->name_length, vars);
std::println("{} = {}", oid_buf, val_buf);
// Prepare next OID for GETNEXT/GETBULK
memcpy(anOID, vars->name, vars->name_length * sizeof(oid));
anOID_len = vars->name_length;
}
snmp_free_pdu(response);
if (done) break;
}
snmp_sess_close(sessp);
co_return;
}
/**
* Entry point.
*
* Usage:
* ./asnmp <version> <host> <community|securityName> [start_oid]
* [authProtocol authPassphrase privProtocol privPassphrase securityLevel]
*
* Examples:
* SNMPv2c: ./asnmp 2c 127.0.0.1 public
* SNMPv3: ./asnmp 3 127.0.0.1 usr "" MD5 myauthpass AES myprivpass 3
*/
int main(int argc, char* argv[])
{
if (argc < 4) {
std::println(stderr,
"Usage: {} <version> <host> <community|securityName> [start_oid] "
"[authProtocol authPassphrase privProtocol privPassphrase securityLevel]",
argv[0]);
return EXIT_FAILURE;
}
Options opts;
opts.version = argv[1];
opts.host = argv[2];
opts.community = argv[3];
opts.start_oid = (argc > 4) ? argv[4] : "";
if (opts.version == "3") {
if (argc > 5) opts.authProtocol = argv[5];
if (argc > 6) opts.authPassphrase = argv[6];
if (argc > 7) opts.privProtocol = argv[7];
if (argc > 8) opts.privPassphrase = argv[8];
if (argc > 9) opts.securityLevel = std::atoi(argv[9]);
}
asio::io_context io;
co_spawn(io,
snmp_walk(opts),
detached);
io.run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment