Skip Navigation

Getting started with net-snmp in C++

Hey everyone! I wanted to post my guide / experiences from the past years here - this is not strictly Zabbix, but SNMP (which Zabbix could monitor), and in C++, but I think given that lemmy isn't yet huge, I'd rather have the post in a space with more topics, than an isolated 1-user-community. See below.

End 2022, I had finally gotten around to creating a standalone SNMP agent with a lot of demo-functionality. I will post the link / instructions for that in a separate comment below.

#Getting started with net-snmp in C++

I am trying to work myself into the use of net-snmp (on a Linux system, but ideally in a way compatible to mingw/gcc on Windows). Unfortunately, the net-snmp documentation as per http://www.net-snmp.org/ seems to be very lacking. There are coding tutorials, which at some point refer you to an empty section in the wiki that you "should read before continuing" (This one here: Writing_a_MIB_Module). So I am down to a lot of web (re)searching and trial & error.

The aim of this post is to end up with a step-by-step approach that accomplishes the following:

  • Installation & configuration of all required software packages on a debian system (for starters)
  • Clarification of the relevant snmp terminology used in this step-by-step guide
  • Making the example executables (or modified versions thereof) from the net-snmp.org coding tutorials work to
    • request a specific value by existing snmp object identifiers (OIDs) with a targeted instruction
    • request a list of OIDs listed in an array, from multiple hosts
    • create & use a custom management information base (MIB)
    • create a custom snmp agent that can serve requests for OIDs defined in that custom MIB
    • create a simple monitoring application that can cyclically request certain OIDs from the custom agent

The final demo implementation will be a "hello world" of sorts for SNMP, wherein the custom snmp agent reports it's application "health" (e.g. process id, process name, a timestamp and a cyclic counter / heartbeat) via the custom MIB, and the monitoring application monitors the health of that custom agent on the console (as opposed to having to set up s.t. more complicated like zabbix).

Please bear with me, as I am stuck on the custom agent for now. I have to solve this problem, however, and I will update this post with my solution. Help is very much appreciated!

So - without further ado, here is my current status:

Package installation:

 undefined
    
# force installation of snmpd with default /etc/snmp/snmpd.conf
sudo apt install --reinstall -o Dpkg::Options::="--force-confask,confnew,confmiss" snmpd

# force installation of default /etc/snmp/snmp.conf
sudo apt install --reinstall -o Dpkg::Options::="--force-confask,confnew,confmiss" libsnmp-base

# install snmp and snmp-mibs-downloader
sudo aptitude install snmp snmp-mibs-downloader
# Note: former net-snmp-utils files are now all included in package snmp

  

Package configuration:

 undefined
    
# comment out the "mibs :" line in snmp.conf (TBC: forgot why I did this :( )
sudo sed -i 's/^mibs :$/#mibs :/' /etc/snmp/snmp.conf

# nothing else for the moment


  

Activate configuration changes (if any)

 undefined
    
sudo /etc/init.d/snmpd restart


  

#Test package configuration:

 undefined
    
snmpwalk -v 2c -c public localhost
# a bunch of output about localhost will follow


  

Explanation: snmpwalk checks the destination (localhost) for all available OIDs and requests them once.

Note: Option values can be separated by space or not at all, so -v 1 and -v1 is equivalent.

Option -v is the snmp protocol version, -v1 or -v2c work. Disclaimer: -v3 requires authentication logic that I have not yet figured out. Option -c sets the "community" string - which I have not quite figured out yet, but "public" is good for accessing OIDs that do not require a password.

Speculation: I believe the community string is simply correlated to how certain OIDs are registered in the snmp master agent (daemon) and you can only access them through the configured community.

Manual request of an OID on the command line:

 undefined
    
snmptest -v 2c -c public localhost


  

This will open a request prompt, on which you can enter the OID / variable identifier of anything you previously saw on the output of snmpwalk. Multiple variables can be requested at once. E.g.:

 undefined
    
Variable: SNMPv2-MIB::sysDescr.0
Variable: SNMPv2-MIB::sysORID.1
Variable: 


  

In order to submit (send) the request, you have to enter an empty line. When you are done, exit snmptest with CTRL-C. Now that we are done with the installation & configuration, it is time to get the demo applications running.

Getting ready for the net-snmp coding tutorial:

I have downloaded the following files from the coding tutorial subsections here:

 undefined
    
asyncapp.c
example-daemon.c # called example-demon.c there, I renamed it
Makefile
NET-SNMP-TUTORIAL-MIB.txt
nstAgentModuleObject.c
nstAgentModuleObject.h
nstAgentPluginObject.c
nstAgentPluginObject.h
nstAgentSubagentObject.c
nstAgentSubagentObject.h
snmpdemoapp
snmpdemoapp.c


  

These files were not created by me and I take no credit for them. I have, however, modified these files to fit my personal preference of C code formatting, and to add comments and better understand what is going on - and also to fix some incompatibilities with my system and change them to use SNMP_VERSION_2c so I could get a working software before I have to study the authentication logic of version 3.

I have uploaded my modified version of the tutorial files here (link valid only until 2020-05-20, I'll upload it elsewhere if someone knows a good free filehoster): 2020-05-06-net-snmp-coding-tutorial.tgz

Please review the code before compiling stuff some random stranger gave you ;)

You need to copy the tutorial MIB file to a place where your snmpd (the master agent) will find it:

 undefined
    
sudo cp NET-SNMP-TUTORIAL-MIB.txt /usr/share/snmp/mibs/

  

Testing whether the MIB file is found by the snmp applications:

 undefined
    
# snmptranslate -m +NET-SNMP-TUTORIAL-MIB -IR -On nstAgentModuleObject
# (the above has the same effect as the below)
snmptranslate -On NET-SNMP-TUTORIAL-MIB::nstAgentModuleObject

  

This should output

 undefined
    
.1.3.6.1.4.1.8072.2.4.1.1.1

  

Compiling and executing the tutorial files:

Disclaimer: As of today (2020-05-05), I have only managed to successfully use the snmpdemoapp and the asyncapp - the other apps are still untested / non-functional in this version.

When you execute "make" in the folder with your source files & the Makefile, you will get two warnings

 undefined
    
example-daemon.c:50:3: warning: implicit declaration of function ‘init_vacm_vars’; did you mean ‘init_vacm’? [-Wimplicit-function-declaration]
   init_vacm_vars();
   ^~~~~~~~~~~~~~
   init_vacm
example-daemon.c:51:3: warning: implicit declaration of function ‘init_usmUser’; did you mean ‘init_usm’? [-Wimplicit-function-declaration]
   init_usmUser();
   ^~~~~~~~~~~~
   init_usm


  

I have not yet figured out if this code is intentionally referring to unknown functions, and whether or not this is functional or a failure. Let's ignore it for now.

After building the executables, you can test:

 undefined
    
./snmpdemoapp 
SNMPv2-MIB::sysORDescr.3 = STRING: The SNMP Management Architecture MIB.
value #1 is a string: The SNMP Management Architecture MIB.


  

and

 undefined
    
./asyncapp 
---------- synchronous -----------
23:25:47.095474 localhost: SNMPv2-MIB::sysDescr.0 = STRING: Linux YOURHOSTNAME <YOUR OS VERSION AND ARCHITECTURE>
23:25:47.095637 localhost: SNMPv2-MIB::sysORDescr.1 = STRING: The MIB for Message Processing and Dispatching.
23:25:47.095758 localhost: SNMPv2-MIB::sysORDescr.2 = STRING: The management information definitions for the SNMP User-based Security Model.
23:25:47.095961 localhost: SNMPv2-MIB::sysDescr.0 = STRING: Linux YOURHOSTNAME <YOUR OS VERSION AND ARCHITECTURE>
23:25:47.096076 localhost: SNMPv2-MIB::sysORDescr.1 = STRING: The MIB for Message Processing and Dispatching.
23:25:47.096199 localhost: SNMPv2-MIB::sysORDescr.2 = STRING: The management information definitions for the SNMP User-based Security Model.
---------- asynchronous -----------
23:25:47.096538 localhost: SNMPv2-MIB::sysDescr.0 = STRING: Linux YOURHOSTNAME <YOUR OS VERSION AND ARCHITECTURE>
23:25:47.096657 localhost: SNMPv2-MIB::sysDescr.0 = STRING: Linux YOURHOSTNAME <YOUR OS VERSION AND ARCHITECTURE>
23:25:47.096769 localhost: SNMPv2-MIB::sysORDescr.1 = STRING: The MIB for Message Processing and Dispatching.
23:25:47.096877 localhost: SNMPv2-MIB::sysORDescr.1 = STRING: The MIB for Message Processing and Dispatching.
23:25:47.096986 localhost: SNMPv2-MIB::sysORDescr.2 = STRING: The management information definitions for the SNMP User-based Security Model.
23:25:47.097036 localhost: SNMPv2-MIB::sysORDescr.2 = STRING: The management information definitions for the SNMP User-based Security Model.

  

Now you can modify the snmpdemoapp.c and the asyncapp.c to include / request different hosts / OIDs. Please note that I have requested the same OIDs from localhost twice in asyncapp.c to allow this tutorial executable to function without any further host ready on the network and to demonstrate the sync vs. async behavior.

Edit 2020-05-06: Progress - I got a subagent registered (for now, only as root user :( ) and running as expected:

Configure a community "tutorial" that we can use to access our tutorial MIB

 undefined
    
# configuring a community with access rights to custom MIB(s)
# Source: https://unix.stackexchange.com/questions/463969/snmp-no-such-object-available-on-this-agent-at-this-oid

# it appears that the common path is .1.3.6.1 and then custom MIBs have the .4 branch (TBC)

# add a custom community permitting read access to our tutorial MIB
echo "" > new-communities-for-snmpd.conf
echo "# custom communities" >> new-communities-for-snmpd.conf
echo "rocommunity tutorial  127.0.0.1 .1.3.6.1.4 # TBC: branch for custom mibs" >> new-communities-for-snmpd.conf
cat new-communities-for-snmpd.conf | sudo tee -a /etc/snmp/snmpd.conf
rm new-communities-for-snmpd.conf

  

Restart the snmpd daemon to load the new config:

 undefined
    
sudo service snmpd restart

  

Build tutorial executables:

 undefined
    
make

  

Attempt to retrieve an OID handled by the example daemon

 undefined
    
snmpget -v 2c -c tutorial localhost NET-SNMP-TUTORIAL-MIB::nstAgentSubagentObject.0

  

Since the daemon is not yet running, this will fail: "NET-SNMP-TUTORIAL-MIB::nstAgentSubagentObject.0 = No Such Object available on this agent at this OID"

Start the tutorial subagent:

 undefined
    
sudo ./example-daemon -f

  

Open a second console and attempt to retrieve the OID as above:

 undefined
    
snmpget -v 2c -c tutorial localhost NET-SNMP-TUTORIAL-MIB::nstAgentSubagentObject.0

  

This time, the request should succeed: "NET-SNMP-TUTORIAL-MIB::nstAgentSubagentObject.0 = INTEGER: 2"

Check the first terminal (where the example-daemon is running), it should say

hello world!

This message was triggered by the handler function handler_nstAgentSubagentObject installed in nstAgentSubagentObject.c

 undefined
    
/* Function: handler_nstAgentSubagentObject
 * Purpose: callback handler for the request to an OID of this subagent
 * 
 */
int handler_nstAgentSubagentObject(
    netsnmp_mib_handler *mibHandler,
    netsnmp_handler_registration *handlerRegistration,
    netsnmp_agent_request_info *agentRequestInfo,
    netsnmp_request_info *requestInfo
)
{
    printf( "hello world!\n" );

    return 0;
}

  

The handler function is registered as a callback handler in the call to netsnmp_register_long_instance.

From here on, this is all work in progress. Next steps:

  • create a handler routine that will work with snmpwalk & allow to see all OIDs identified for a certain community
  • create a custom MIB with sample application telemetry
  • create a handler routine that will update the OID values as part of the snmp agent (e.g. by interacting with a separate application thread that keeps updated telemetry in a struct)
  • create a monitoring application that periodically checks the content of the custom MIB and displays it on the console
  • somewhere along the way: figure out how to run all this as non-root user!!

I stand by my original comment: The state of documentation of this de-facto standard in worldwide networking equipment has me baffled. It is embarrassingly shitty.

1 comments
  • Update 2022-11-12:

    I have finally gotten around to reducing the SNMP code in my project to an anonymized standalone SNMP master agent that runs completely in userspace, which I can now share for anyone to try out.

    What it does:

    • app-diagnostics provides an SNMP master agent in userspace
    • receives telemetry via POSIX message queues from example-app
    • example-app and generic telemetry are provided as SNMP v2c
    • a lot of utility functions are provided that are in part not related to SNMP (boost command line options, logging framework + syslog messages, C-style dual-linked list)

    What it does not do:

    • SNMP v3 / authentication
    • no snmpset functionality
    • no requests as SNMP client
    • traps

    Download link: 2022-11-12-netsnmp-agent.tgz (mediafire.com)

    SHA512 checksum:

     undefined
        
    3065f1cbab18543438a43e984c4f8011c3a84da4c19fdf37ea979694ede232864c013ccae4c45e450cf247ecda07c000625250ca6f3089c1fe69c42c527710b9  2022-11-12-netsnmp-agent.tgz
    
      

    Installation instructions:


    Install needed dependencies:

     undefined
        
     Gnu C Compiler (gcc, g++) version 9.3.0 or later
     Gnu make
     snmp
     snmp-mibs-downloader (non-free)
     libsnmp-dev
     libboost-program-options-dev
     vim
    Recommended (for editing / checking configurations & code):
     kate
     kdiff3
    
      

    E.g. on debian, the required packages are:

     undefined
        
     build-essential
     snmp
     snmp-mibs-downloader (the mibs downloader requires “non-free” repository)
     libsnmp-dev
     libboost-program-options-dev
     vim
     kate
     kdiff3
    
      

    E.g. on Arch Linux, the required packages are (tested on: Linux manjaro 5.8.6-1-MANJARO #1 SMP PREEMPT Thu Sep 3 14:19:36 UTC 2020 x86_64 GNU/Linux):

     undefined
        
     base-devel
     net-snmp
     boost
     boost-libs
     vim
     kate
     kdiff3
    
      

    Debian (and derivates):

    Once installation of the above is complete, execute this command as root to obtain the required snmp management information base (MIB) files:

     undefined
        
    $ download-mibs
    
      

    Use of rsyslog

    Enable reception of UDP syslog messages on localhost by editing /etc/rsyslog.conf and enabling the following lines (port setting may differ, take a note for app configuration):

     undefined
        
    # provides UDP syslog reception
    module(load="imudp")
    input(type="imudp" port="514")
    
    
      

    Save the changes and reload the configuration:

     undefined
        
    $ systemctl restart rsyslog
    
      

    Unpack the SW delivery & change directory into the extracted folder

     undefined
        
    $ tar -xf yyyy-mm-dd-netsnmp-agent.tgz
    $ cd netsnmp-agent
    
      

    Build the software

    When all required packages are in place, the software can be built with a single command

     undefined
        
    $ make
    
    
      

    (allowed make targets: default, all, app-diagnostics, example-app, clean, cleanObjects, cleanTargets, echo )

    -> Check that both app-diagnostics and example-app executables have been created and can be executed (e.g. with the --help option), to ensure no dependencies are missing

    Steps to establish a running configuration:

    1. create the ~/.snmp and ~/.app folders
    2. from the build folder, copy the contents of the home_.snmp folder to ~/.snmp and accordingly the contents of the home_.app folder to ~/.app
    3. edit the configuration file ~/.snmp/snmp.conf, first line, to correctly point to the absolute path of the .snmp/mibs folder (leave the other lines untouched): mibdirs +/home/appuser/.snmp/mibs
    4. edit the configuration file ~/.app/app.conf to point to the correct filesystem locations for archive-path and intermediate-storage-path (bogus values here, never mind this step - it's an example of a configuration that app could be interested in)
    5. edit the logging settings in ~/.app/app.conf to suit your use-case: - modify the syslog-port according to your system’s syslog setting - log-level is a global default for all applications - can be overridden in example-app (--log-level) - enable/disable syslog, stderr, stdout (yes/no setting) as desired - leave the other settings at defaults (you can play around with these later to test effects on central SNMP TM)

    Executing the software

    From the netsnmp-agent folder, execute the diagnostics and the example app:

    Terminal 1:

     undefined
        
    $ ./app-diagnostics
    
      

    Terminal 2:

     undefined
        
    $ ./example-app
    
      

    Monitoring SNMP telemetry

    From the netsnmp-agent folder, execute the SNMP telemetry monitoring scripts:

    Terminal 3:

     undefined
        
    $ bash scripts/overviewTM
    
      

    Terminal 4:

     undefined
        
    $ bash scripts/example-appTM
    
      

    You should now see telemetry of the example-app as well as some central telemetry coming directly from the app-diagnostics application.

    Further tests

    • Start another few instances of the example-app in new terminals & watch how the telemetry is displayed in a table
    • Play around with app parameters (check --help options) to see how they affect telemetry