The problem: You have some server process you’ve written in C/C++ and you want to use SNMP to read out interesting information.
This solution: Using Net-SNMP we’ll turn the server process into an SNMP agent, which will communicate with the Net SNMP daemon (snmpd), which will communicate with whatever SNMP monitoring services you may have.
Writing a MIB file
Loosely, MIBS are used to identify the bits of information you’re interested in, as well as categorize and organize that information. At the protocol level, MIBs are accessed through an arbitrary sequence of integers (like 1.3.6.1.4.1.32473.1.1), which are of course only intended for machines to use. MIB files are used to organize your mibs, provide names to make it easy for humans to specify a mib without addressing it by their MIB number, provide type information , and provide description text for human users.
We’re going to write a silly MIB that has two counters, one a read-write counter and one a read only counter. For mnemonic reasons, we’re going to call one counter livePonies and the other deadPonies, and our daemon is going to randomly increment these two counters.
PONIES-MIB.mib
| 01 | PONIES-MIB DEFINITIONS ::= BEGIN
|
| 02 |
|
| 03 | IMPORTS
|
| 04 | enterprises FROM SNMPv2-SMI
|
| 05 | OBJECT-TYPE, Integer32, MODULE-IDENTITY FROM SNMPv2-SMI
|
| 06 | ;
|
| 07 |
|
| 08 |
|
| 09 | -- Specify our organizations module. (Note: for big organizations, you'd
|
| 10 | -- usually break this off into it's own MIB, then include it from other
|
| 11 | -- MIBs (such as department mibs, or project mibs, or whatever..))
|
| 12 | --
|
| 13 | -- NOTE: You will need to replace 32473 with YOUR enterprises Private
|
| 14 | -- Enterprise Number, which is obtainable for free from
|
| 15 | -- http://pen.iana.org/pen/PenApplication.page
|
| 16 | --
|
| 17 | poniesIncorporated MODULE-IDENTITY
|
| 18 | LAST-UPDATED "201111280000Z" -- 28 November 2011, midnight UTC
|
| 19 | ORGANIZATION "ponies"
|
| 20 | CONTACT-INFO "postal: Pony
|
| 21 | 123 Pony Ln
|
| 22 | Palo Alto, CA 94301
|
| 23 |
|
| 24 | email: hexist@hexist.com
|
| 25 | "
|
| 26 | DESCRIPTION "Example enterprise MIB base.
|
| 27 | "
|
| 28 | ::= { enterprises 32473 }
|
| 29 |
|
| 30 |
|
| 31 | -- Create a node for our poniesd daemon to keep everything organized"
|
| 32 | poniesd OBJECT IDENTIFIER ::= { poniesIncorporated 1 }
|
| 33 |
|
| 34 | -- Create a few attributes we're interested in (live and dead ponies)
|
| 35 | livePonies OBJECT-TYPE
|
| 36 | SYNTAX Integer32
|
| 37 | MAX-ACCESS read-write
|
| 38 | STATUS current
|
| 39 | DESCRIPTION
|
| 40 | "Create a read/write variable controlling the number of live ponies"
|
| 41 | DEFVAL { 1 }
|
| 42 | ::= { poniesd 1 }
|
| 43 |
|
| 44 | deadPonies OBJECT-TYPE
|
| 45 | SYNTAX Integer32
|
| 46 | MAX-ACCESS read-only
|
| 47 | STATUS current
|
| 48 | DESCRIPTION
|
| 49 | "Create a read only variable noting how many ponies have died."
|
| 50 | DEFVAL { 0 }
|
| 51 | ::= { poniesd 2 }
|
| 52 |
|
| 53 | END |
Using mib2c to turn your MIB file into a compileable C source file
Now that we have a MIB file we’ll want to use mib2c (a tool that comes standard with net-snmp) to generate ourselves a C file that handles the SNMP get/setter functionality for our livePonies and deadPonies values.
| 1 | $ env MIBDIRS=`net-snmp-config --default-mibdirs`:`pwd` MIBS="+PONIES-MIB" mib2c -c mib2c.int_watch.conf -f poniesd_mibs poniesd
|
| 2 | *** Warning: only generating code for nodes of MIB type INTEGER
|
| 3 | writing to poniesd_mibs.h
|
| 4 | writing to poniesd_mibs.c
|
| 5 | running indent on poniesd_mibs.h
|
| 6 | running indent on poniesd_mibs.c
|
| 7 | |
This will generate two files, poniesd_mibs.c and poniesd_mibs.h which we’ll compile into our project. There are a lot of mib2c configuration files for generating different things, some of which you’ll be required to edit the generated code to provide your own logic and such, however for simple integer binding you don’t need to modify anything.
Making your daemon into an SNMP Agent
Next we’ll write our main() for our poniesd process.
poniesd.c
| 01 | /** Daemon that counts ponies at a random interval */ |
| 02 | |
| 03 | |
| 04 | #include <net-snmp/net-snmp-config.h> |
| 05 | #include <net-snmp/net-snmp-includes.h> |
| 06 | #include <net-snmp/agent/net-snmp-agent-includes.h> |
| 07 | #include <signal.h> |
| 08 | #include <unistd.h> |
| 09 | |
| 10 | #include "poniesd_mibs.h" /* defines init_poniesd_mibs() */ |
| 11 | |
| 12 | |
| 13 | extern long livePonies; /* defined in ponies_mibs.c */ |
| 14 | extern long deadPonies; /* defined in ponies_mibs.c */ |
| 15 | static int keep_running; |
| 16 | |
| 17 | void stop_server(int a) { |
| 18 | keep_running = 0; |
| 19 | } |
| 20 | |
| 21 | int main (int argc, char **argv) { |
| 22 | int agentx_subagent=1; /* change this if you want to be a SNMP master agent */ |
| 23 | int syslog = 0; /* change this if you want to use syslog */ |
| 24 | |
| 25 | /* print log errors to syslog or stderr */ |
| 26 | if (syslog) |
| 27 | snmp_enable_calllog(); |
| 28 | else |
| 29 | snmp_enable_stderrlog(); |
| 30 | |
| 31 | /* Set that we're an SNMP Agent */ |
| 32 | netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1); |
| 33 | |
| 34 | /* initialize tcpip, if necessary */ |
| 35 | SOCK_STARTUP; |
| 36 | |
| 37 | init_agent("poniesd"); |
| 38 | init_poniesd_mibs(); |
| 39 | init_snmp("poniesd"); |
| 40 | |
| 41 | /* In case we recevie a request to stop (kill -TERM or kill -INT) */ |
| 42 | keep_running = 1; |
| 43 | signal(SIGTERM, stop_server); |
| 44 | signal(SIGINT, stop_server); |
| 45 | |
| 46 | snmp_log(LOG_INFO,"poniesd is up and running.\n"); |
| 47 | |
| 48 | /* your main loop here... */ |
| 49 | while(keep_running) { |
| 50 | agent_check_and_process(0); /* 0 == don't block, 1 == block. See snmp_select_info for select based polling. */ |
| 51 | usleep(1000); /* sleep for a ms so we don't spin too fast */ |
| 52 | |
| 53 | if (rand() % 100 == 0) { |
| 54 | long birthed = rand() % 5; |
| 55 | long died = rand() % 3; |
| 56 | |
| 57 | livePonies += birthed; |
| 58 | livePonies -= died; |
| 59 | deadPonies += died; |
| 60 | |
| 61 | printf("Live ponies: %8d Dead ponies: %8d \r", livePonies, deadPonies); |
| 62 | fflush(stdout); |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | /* at shutdown time */ |
| 67 | snmp_shutdown("poniesd"); |
| 68 | SOCK_CLEANUP; |
| 69 | |
| 70 | return 0; |
| 71 | } |
To compile our final executable we’ll compile our poniesd.c file along with our generated poniesd_mibs.c file. We’ll also need to use net-snmp-config to obtain some necessary C compilation flags and libraries to link against.
| 1 | $ gcc `net-snmp-config --cflags` -o poniesd poniesd.c poniesd_mibs.c `net-snmp-config --agent-libs` |
A Makefile to tie it all together
Because no project is complete without a Makefile, here’s a quick one to build all the files in this post, as well as provide some targets to do snmpwalking and snmpsetting and such for reference.
Makefile
| 01 | CFLAGS=`net-snmp-config --cflags` |
| 02 | LIBS=`net-snmp-config --agent-libs` |
| 03 | MIBDIRS=`net-snmp-config --default-mibdirs`:`pwd` |
| 04 | |
| 05 | SRC=poniesd.c poniesd_mibs.c |
| 06 | |
| 07 | all: poniesd |
| 08 | |
| 09 | poniesd: $(SRC) |
| 10 | $(CC) $(CFLAGS) -o poniesd $(SRC) $(LIBS) |
| 11 | |
| 12 | poniesd_mibs.c: PONIES-MIB.mib |
| 13 | env MIBDIRS=$(MIBDIRS) MIBS="+PONIES-MIB" mib2c -c mib2c.int_watch.conf -f poniesd_mibs poniesd |
| 14 | |
| 15 | |
| 16 | # Everything below is purely informational |
| 17 | |
| 18 | run: |
| 19 | sudo ./poniesd |
| 20 | |
| 21 | snmpwalk: |
| 22 | snmpwalk -v 2c -c read_only_user -M+. -mPONIES-MIB localhost poniesd |
| 23 | |
| 24 | snmpset: |
| 25 | snmpset -v 2c -c read_write_user -M+. -mPONIES-MIB localhost poniesd.livePonies.0 i 10 |
| 26 | |
| 27 | snmptranslate: |
| 28 | snmptranslate -M+. -mPONIES-MIB -Tp -IR poniesd |
| 29 | |
Configuring and running net-snmp to act as your AgentX master2>
In order for net-snmps snmpd to accept our agentx connections, we’ll need to add the following line to your /etc/snmp/snmpd.conf file:
I’ve added these read_only_user and read_write_user users as well (to the snmpd.conf file) for demonstration purposes
| 1 | rocommunity read_only_user |
| 2 | rwcommunity read_write_user |
On some systems you’ll also need to add the following to your /etc/hosts.allow file:
Once these changes are done, (re)start your snmpd process.
Running your agent and using snmpwalk/snmpset
To run your process, you’ll need to launch it as root
| 1 | $ sudo ./poniesd
|
| 2 | NET-SNMP version 5.7.1 AgentX subagent connected
|
| 3 | poniesd is up and running.
|
| 4 | Live ponies: 3 Dead ponies: 0 |
Once running, the Live ponies and Dead ponies count will continue to increment. From another terminal window you can now use snmpwalk to poll the current value of livePonies and deadPonies like so:
| 1 | $ snmpwalk -v 2c -c read_only_user -M+. -mPONIES-MIB localhost poniesd
|
| 2 | PONIES-MIB::livePonies.0 = INTEGER: 49
|
| 3 | PONIES-MIB::deadPonies.0 = INTEGER: 20 |
(Note: You’ll have to be in the same directory as your PONIES-MIB.mib file, or have installed it wherever your MIB files live on your system.)
Next, you can use snmpset to modify the livePonies value (since we set it to be a read-write variable in our MIB file.)
| 1 | $ snmpset -v 2c -c read_write_user -M+. -mPONIES-MIB localhost poniesd.livePonies.0 i 10
|
| 2 | PONIES-MIB::livePonies.0 = INTEGER: 10 |
Then if you were to run snmpwalk again, you’ll notice your livePonies count was reset. You’ll also notice the running number in your running poniesd process will have jumped to 10 when you ran the snmpset.
| 1 | $ snmpwalk -v 2c -c read_only_user -M+. -mPONIES-MIB localhost poniesd
|
| 2 | PONIES-MIB::livePonies.0 = INTEGER: 12
|
| 3 | PONIES-MIB::deadPonies.0 = INTEGER: 34
|
| 4 | |
Download
Heres a tarball of the three files we wrote: Makefile, ponies.d, and PONIES-MIB.mib
snmp-ponies.tar.bz2