Last active
August 8, 2025 13:39
-
-
Save kassane/2ca5ee0afbc7d834de26cef51eac5ebd to your computer and use it in GitHub Desktop.
C++23 asio - net-snmp client (sample)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* @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