Since no good tutorials are available I put together what I learnt. My approach was step by step learning and that is what I describe here. Explorer should clone this code and checkout step by step. Or an advanced explorer could skip some steps. Your choice. To help understand what is going on, I checkin the code more often so explorer could compare what was auto-generated and what was modification to make things work.
- I used net-snmp 5.7.3 for this tutorial on Ubuntu 16.10.
- All code is in flat directory structure in start. Only during the last stage I will move things around to src and include directories to make it cleaner.
- We need a MIB first. I created a sample mib AGENTX-TUTORIAL-MIB under experimental branch (.1.3.6.1.3) so my MIB branch is .1.3.6.1.3.9999.
- I also created CMakeLists.txt for anyone wanting to use cmake for compiling.
There are multiple parts in this tutorial. As I described my approach of building it step by step, I strongly recommend to clone the code and start checking out the codes using tags that I have created. Here are the high level commands.
- Clone the repository.
git clone https://github.com/jainvishal/agentx-tutorial
- Simple MIB with two Read-Only Scalars.
- Autogenerated code.
git checkout step1_scalars_autogenerated
- Modified code with implementation.
git checkout step1_scalars_implement
- Autogenerated code.
- Simple MIB with a table using MIB-FOR-Dummies
- Autogenerated code.
git checkout step2_tables_autogenerated
- Modified code with implementation.
git checkout step2_tables_implement
- Autogenerated code.
- Simple MIB with Traps
- Autogenerated code.
git checkout step3_traps_autogenerated
- Modified code with implementation.
git checkout step3_traps_implement
- Autogenerated code.
- Final Cleaned up code
git checkout master
In step 1 I implement only two read-only scalars (one integer and one string) since they are easiest to follow. MIB structure is as below.
vishalj@dreams:~/workspace/agentx-tutorial⟫ snmptranslate -Tp -IR agentxTutorial
+--agentxTutorial(9999)
|
+--myScalars(1)
| |
| +-- -R-- String myROString(1)
| | Textual Convention: DisplayString
| | Size: 0..255
| +-- -R-- Integer32 myROInteger(2)
| Range: 0..2147483647
|
+--myConformenceGroups(99)
|
+--myScalarGroup(1)
I used smilint to ensure that mib structure is correct. You can verify it as below and it should not give any errors.
vishalj@dreams:~/workspace/agentx-tutorial⟫ smilint -l 4 -s ./AGENTX-TUTORIAL-MIB
Using mib2c
we can compile this MIB into 'C' code. Following three steps show how to
create 'C' code, makefile and subagent code respectively.
mib2c -c mib2c.scalar.conf agentxTutorial
mib2c -c mfd-makefile.m2m agentxTutorial
mib2c -c subagent.m2c agentxTutorial
Here is shell output.
vishalj@dreams:~/workspace/agentx-tutorial⟫ mib2c -c mib2c.scalar.conf agentxTutorial
writing to agentxTutorial.h
writing to agentxTutorial.c
running indent on agentxTutorial.c
running indent on agentxTutorial.h
vishalj@dreams:~/workspace/agentx-tutorial⟫ mib2c -c mfd-makefile.m2m agentxTutorial
writing to agentxTutorial_Makefile
vishalj@dreams:~/workspace/agentx-tutorial⟫ mib2c -c subagent.m2c agentxTutorial
writing to agentxTutorial_subagent.c
running indent on agentxTutorial_subagent.c
vishalj@dreams:~/workspace/agentx-tutorial⟫
I checkin the code now so explorer can see what was generated by mib2c. Nothing would compile anything yet.
- Lets introduce two variables that we are going to monitor and name them as
myROStringVar
andmyROIntVar
to match our MIB. I added them in subagent source fileagentxTutorial_subagent.c
. - Then I added an alarm handler which will update their values every 5 seconds.
myRWString
will hold the unixtime whenmyROIntVar
changed. - Finally in
agentxTutorial.c
I updated the placeholders to copy the values from application variables into MIB variables.
use make -f agentxTutorial_makefile
or use cmake
as follows.
vishalj@dreams:~/workspace/agentx-tutorial⟫ mkdir build
vishalj@dreams:~/workspace/agentx-tutorial⟫ cd build/
vishalj@dreams:~/workspace/agentx-tutorial/build⟫ cmake ../
-- The C compiler identification is GNU 6.2.0
-- The CXX compiler identification is GNU 6.2.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/vishalj/workspace/agentx-tutorial/build
vishalj@dreams:~/workspace/agentx-tutorial/build⟫ make
[ 33%] Building C object CMakeFiles/agentx-tutorial.dir/agentxTutorial.c.o
[ 66%] Building C object CMakeFiles/agentx-tutorial.dir/agentxTutorial_subagent.c.o
[100%] Linking C executable agentx-tutorial
[100%] Built target agentx-tutorial
vishalj@dreams:~/workspace/agentx-tutorial/build⟫
- On one terminal run it as below. Press
CTRL C
to kill.
vishalj@dreams:~/workspace/agentx-tutorial/build⟫ ./agentx-tutorial -f
NET-SNMP version 5.7.3 AgentX subagent connected
- On other terminal run following snmpwalk commands with some wait in between so you can see the values changing.
vishalj@dreams:~/workspace/agentx-tutorial/cmake-build-debug⟫ snmpwalk -On localhost agentxTutorial
.1.3.6.1.3.9999.1.1.0 = STRING: last changed = 1484447214
.1.3.6.1.3.9999.1.2.0 = INTEGER: 1
vishalj@dreams:~/workspace/agentx-tutorial/cmake-build-debug⟫ snmpwalk -On localhost agentxTutorial
.1.3.6.1.3.9999.1.1.0 = STRING: last changed = 1484447214
.1.3.6.1.3.9999.1.2.0 = INTEGER: 1
vishalj@dreams:~/workspace/agentx-tutorial/cmake-build-debug⟫ snmpwalk -On localhost agentxTutorial
.1.3.6.1.3.9999.1.1.0 = STRING: last changed = 1484447219
.1.3.6.1.3.9999.1.2.0 = INTEGER: 2
vishalj@dreams:~/workspace/agentx-tutorial/cmake-build-debug⟫ snmpwalk -On localhost agentxTutorial
.1.3.6.1.3.9999.1.1.0 = STRING: last changed = 1484447219
.1.3.6.1.3.9999.1.2.0 = INTEGER: 2
vishalj@dreams:~/workspace/agentx-tutorial/cmake-build-debug⟫ snmpwalk -On localhost agentxTutorial
.1.3.6.1.3.9999.1.1.0 = STRING: last changed = 1484447224
.1.3.6.1.3.9999.1.2.0 = INTEGER: 3
vishalj@dreams:~/workspace/agentx-tutorial/cmake-build-debug⟫ snmpwalk -On localhost agentxTutorial
.1.3.6.1.3.9999.1.1.0 = STRING: last changed = 1484447254
.1.3.6.1.3.9999.1.2.0 = INTEGER: 9
vishalj@dreams:~/workspace/agentx-tutorial/cmake-build-debug⟫ snmpwalk localhost agentxTutorial
AGENTX-TUTORIAL-MIB::myROString.0 = STRING: last changed = 1484447289
AGENTX-TUTORIAL-MIB::myROInteger.0 = INTEGER: 16
vishalj@dreams:~/workspace/agentx-tutorial/cmake-build-debug⟫
I checkin the code. Thats it for step 1.
I created myTestTable
as a simple table with two columns. So the new MIB structure looks like as below.
vishalj@dreams:~/workspace/agentx-tutorial⟫ snmptranslate -Tp -IR agentxTutorial
+--agentxTutorial(9999)
|
+--myScalars(1)
| |
| +-- -R-- String myROString(1)
| | Textual Convention: DisplayString
| | Size: 0..255
| +-- -R-- Integer32 myROInteger(2)
| Range: 0..2147483647
|
+--myTables(2)
| |
| +--myTestTable(1)
| |
| +--myTestTableEntry(1)
| | Index: myROStringCol1
| |
| +-- -R-- String myROStringCol1(1)
| | Textual Convention: DisplayString
| | Size: 0..255
| +-- -R-- Integer32 myROIntCol2(2)
| Range: 0..2147483647
|
+--myConformenceGroups(99)
|
+--myScalarGroup(1)
+--myTableGroup(2)
vishalj@dreams:~/workspace/agentx-tutorial⟫
Now to autogenerate the code using mib2c
. I used MIB for dummies which is quite simple to implement.
vishalj@dreams:~/workspace/agentx-tutorial⟫ mib2c -c mib2c.mfd.conf myTestTable
Defaults for myTestTable...
writing to -
There are no defaults for myTestTable. Would you like to
1) Accept hard-coded defaults
2) Set defaults now [DEFAULT]
Select your choice : 1
writing to defaults/table-myTestTable.m2d
Starting MFD code generation...
writing to myTestTable.h
| +-> Processing table myTestTable
writing to defaults/node-myROIntCol2.m2d
writing to defaults/node-myROStringCol1.m2d
writing to myTestTable.c
writing to myTestTable_data_get.h
writing to myTestTable_data_get.c
| | +-> Processing nonindex myROIntCol2
writing to myTestTable_data_set.h
writing to myTestTable_data_set.c
writing to myTestTable_oids.h
writing to myTestTable_enums.h
writing to myTestTable_interface.h
writing to myTestTable_interface.c
writing to myTestTable_data_access.h
writing to myTestTable_data_access.c
writing to myTestTable-README-FIRST.txt
writing to myTestTable-README-myTestTable.txt
running indent on myTestTable_interface.c
running indent on myTestTable.h
running indent on myTestTable_data_access.c
running indent on myTestTable_data_get.c
running indent on myTestTable_oids.h
running indent on myTestTable_enums.h
running indent on myTestTable.c
running indent on myTestTable_interface.h
running indent on myTestTable_data_set.c
running indent on myTestTable_data_access.h
running indent on myTestTable_data_set.h
running indent on myTestTable_data_get.h
vishalj@dreams:~/workspace/agentx-tutorial⟫
In case only table was to be implemented, makefile and subagent code could also be generated by following same steps as described in step 1.
I checkin the code now so it can be seen what was generated by mib2c
. This new code will not compile yet because we did
not add it to makefile yet. And there are many TODOs which need to be resolved in order to load the data.
- We need to define our datastore first. I am using
/var/tmp/dummy.conf
which is supposed to contain two column data. First column will be a string and second an integer, e.g.five 5
. Using this external data store will allow us to update it on the fly and see the changes reflect insnmpwalk
or other similar commands.
three 3
four 4
two 2
one 1
zero 0
- Now we add code to
myTestTable_data_access.c
.- Update
myTestTable_init_data
to define what columns we are implementing. This allows us to have a table with lots of columns defined in the MIB but only implement a handful. This is helpful in implementing a bigger table step by step. - Modify
myTestTable_container_load
and implement the code to read our external datastore (/var/tmp/dummy.conf
) and load the values in internal datastore/container that is provided by Net-SNMP. We first create the index followed by copying the second column. This function will be called each time cache expires which is set to 60 seconds by default.myTestTable_data_access.h
includes a macroMYTESTTABLE_CACHE_TIMEOUT
which can be tuned for lower or higher values. Value of 0 or less will cause loading of external datastore at each SNMP GET which could be initiated usingsnmpwalk
orsnmpget
orsnmpbulkwalk
etc. So be cognizent of the fact that too short interval would cause extra burden of reading a file and may not be advisable based on application's use. - Update
CMakeLists.txt
oragentxTutorial_Makefile
whichever you are using to include all the newmyTable*
files.
- Update
- Finally update
agentxTutorial_subagent.c
to initialize our Test TablemyTestTable
.
Use make -f agentxTutorial_makefile
or use cmake
as follows.
vishalj@dreams:~/workspace/agentx-tutorial/build⟫ cmake ../
-- The C compiler identification is GNU 6.2.0
-- The CXX compiler identification is GNU 6.2.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/vishalj/workspace/agentx-tutorial/build
vishalj@dreams:~/workspace/agentx-tutorial/build⟫ make
Scanning dependencies of target agentx-tutorial
[ 12%] Building C object CMakeFiles/agentx-tutorial.dir/myTestTable.c.o
[ 25%] Building C object CMakeFiles/agentx-tutorial.dir/myTestTable_data_access.c.o
[ 37%] Building C object CMakeFiles/agentx-tutorial.dir/myTestTable_data_get.c.o
[ 50%] Building C object CMakeFiles/agentx-tutorial.dir/myTestTable_data_set.c.o
[ 62%] Building C object CMakeFiles/agentx-tutorial.dir/myTestTable_interface.c.o
[ 75%] Building C object CMakeFiles/agentx-tutorial.dir/agentxTutorial.c.o
[ 87%] Building C object CMakeFiles/agentx-tutorial.dir/agentxTutorial_subagent.c.o
[100%] Linking C executable agentx-tutorial
[100%] Built target agentx-tutorial
vishalj@dreams:~/workspace/agentx-tutorial/build⟫
- Create/update
/var/tmp/dummy.conf
with following content.
three 3
four 4
two 2
one 1
zero 0
- On one terminal run the compiled code as below. Press
CTRL C
to kill when done.
vishalj@dreams:~/workspace/agentx-tutorial/build⟫ ./agentx-tutorial -f
NET-SNMP version 5.7.3 AgentX subagent connected
- On other terminal run following
snmpwalk
orsnmptable
commands.
vishalj@dreams:~/workspace/agentx-tutorial⟫ snmpwalk localhost myTestTable
AGENTX-TUTORIAL-MIB::myROStringCol1."one" = STRING: one
AGENTX-TUTORIAL-MIB::myROStringCol1."two" = STRING: two
AGENTX-TUTORIAL-MIB::myROStringCol1."four" = STRING: four
AGENTX-TUTORIAL-MIB::myROStringCol1."zero" = STRING: zero
AGENTX-TUTORIAL-MIB::myROStringCol1."three" = STRING: three
AGENTX-TUTORIAL-MIB::myROIntCol2."one" = INTEGER: 1
AGENTX-TUTORIAL-MIB::myROIntCol2."two" = INTEGER: 2
AGENTX-TUTORIAL-MIB::myROIntCol2."four" = INTEGER: 4
AGENTX-TUTORIAL-MIB::myROIntCol2."zero" = INTEGER: 0
AGENTX-TUTORIAL-MIB::myROIntCol2."three" = INTEGER: 3
vishalj@dreams:~/workspace/agentx-tutorial⟫ snmptable localhost myTestTable
SNMP table: AGENTX-TUTORIAL-MIB::myTestTable
myROStringCol1 myROIntCol2
one 1
two 2
four 4
zero 0
three 3
vishalj@dreams:~/workspace/agentx-tutorial⟫
- Now update
/var/tmp/dummy.conf
with some new content or remove old content. Wait for about a minute and run snmpwalk again to see the updates.
vishalj@dreams:~/workspace/agentx-tutorial⟫ snmpwalk localhost myTestTable
AGENTX-TUTORIAL-MIB::myROStringCol1."one" = STRING: one
AGENTX-TUTORIAL-MIB::myROStringCol1."four" = STRING: four
AGENTX-TUTORIAL-MIB::myROStringCol1."zero" = STRING: zero
AGENTX-TUTORIAL-MIB::myROStringCol1."three" = STRING: three
AGENTX-TUTORIAL-MIB::myROStringCol1."fiftysix" = STRING: fiftysix
AGENTX-TUTORIAL-MIB::myROStringCol1."twentytwo" = STRING: twentytwo
AGENTX-TUTORIAL-MIB::myROIntCol2."one" = INTEGER: 1
AGENTX-TUTORIAL-MIB::myROIntCol2."four" = INTEGER: 4
AGENTX-TUTORIAL-MIB::myROIntCol2."zero" = INTEGER: 0
AGENTX-TUTORIAL-MIB::myROIntCol2."three" = INTEGER: 3
AGENTX-TUTORIAL-MIB::myROIntCol2."fiftysix" = INTEGER: 56
AGENTX-TUTORIAL-MIB::myROIntCol2."twentytwo" = INTEGER: 22
vishalj@dreams:~/workspace/agentx-tutorial⟫ snmptable localhost myTestTable
SNMP table: AGENTX-TUTORIAL-MIB::myTestTable
myROStringCol1 myROIntCol2
one 1
four 4
zero 0
three 3
fiftysix 56
twentytwo 22
vishalj@dreams:~/workspace/agentx-tutorial⟫
I checkin the code. Thats it for step 2.
Now we add a trap definition (myROIntHit
) to our MIB. MIB structure is as below. In step 1 I introduced myROInteger
which is being incremented every 5 seconds in AlrmHandler
. I will now introduce a logic that anytime myROInteger
modulo 5 is equal to 1, a trap is generated. e.g. when myROInteger
equals 6, 11, 16, 21 and so on.
vishalj@dreams:~/workspace/agentx-tutorial⟫ snmptranslate -Tp -IR agentxTutorial
+--agentxTutorial(9999)
|
+--myNotifications(0)
| |
| +--myNotificationObjects(0)
| | |
| | +-- ---N Integer32 myTrapTime(1)
| | +-- ---N String myTrapData(2)
| | Textual Convention: DisplayString
| | Size: 0..255
| |
| +--myROIntHit(1)
|
+--myScalars(1)
| |
| +-- -R-- String myROString(1)
| | Textual Convention: DisplayString
| | Size: 0..255
| +-- -R-- Integer32 myROInteger(2)
| Range: 0..2147483647
|
+--myTables(2)
| |
| +--myTestTable(1)
| |
| +--myTestTableEntry(1)
| | Index: myROStringCol1
| |
| +-- -R-- String myROStringCol1(1)
| | Textual Convention: DisplayString
| | Size: 0..255
| +-- -R-- Integer32 myROIntCol2(2)
| Range: 0..2147483647
|
+--myConformenceGroups(99)
|
+--myScalarGroup(1)
+--myTableGroup(2)
+--myTrapObjectGroup(3)
+--myTrapsGroup(4)
vishalj@dreams:~/workspace/agentx-tutorial⟫
Now to autogenerate the code using mib2c
. I am using -f flag to tell mib2c where to store the generated code otherwise
it will end up overwriting agentxTutorial.[ch]
files which we have already updated with out code.
vishalj@dreams:~/workspace/agentx-tutorial⟫ mib2c -c mib2c.notify.conf -f agentxTutorial_traps agentxTutorial
writing to agentxTutorial_traps.h
writing to agentxTutorial_traps.c
running indent on agentxTutorial_traps.h
running indent on agentxTutorial_traps.c
vishalj@dreams:~/workspace/agentx-tutorial⟫
I checkin the code now so explorer can see what was generated by mib2c. Nothing would compile anything yet.
- So we will update
agentxTutorial_traps.h
to fill in the trap data. That is quite easy. - Next we update
agentxTutorial_subagent.c
to add logic to send trap when an interesting event happens. I define a simple event which gets triggered when modulus 5 ofmyROIntegerVar
(value being incremented every 5 seconds) equals 1. So an event goes out at the start, then at value 6, value 11 and so on.
- Configure snmpd (
/etc/snmp/snmpd.conf
) and ensure that it has a section for sending traps to localhost and restart
trap2sink localhost public
- Now setup snmptrapd to log the traps by ensuring following configuration exists in
/etc/snmp/snmptrapd.conf
authCommunity log public
- Run snmptrapd on a terminal as
snmptrapd -f -Le
. (-f
means do not fork so it remains attached to the terminal;-Le
means log messages on standard error). - Now run our updated
agentxTutorial
on a terminal asagentxTutorial -f
(Again-f
means do not fork). Following is what we will see assnmptrapd
output. UseCTRL C
to exit.
root@dreams:~# snmptrapd -f -Le
NET-SNMP version 5.7.3 AgentX subagent connected
NET-SNMP version 5.7.3
2017-01-27 20:37:50 localhost [UDP: [127.0.0.1]:42445->[127.0.0.1]:162]:
iso.3.6.1.2.1.1.3.0 = Timeticks: (453) 0:00:04.53 iso.3.6.1.6.3.1.1.4.1.0 = OID: iso.3.6.1.3.9999.0.1 iso.3.6.1.3.9999.0.0.2.0 = STRING: "This is a test string" iso.3.6.1.3.9999.0.0.1.0 = INTEGER: 1485567470 iso.3.6.1.3.9999.1.2.0 = INTEGER: 1
2017-01-27 20:38:15 localhost [UDP: [127.0.0.1]:42445->[127.0.0.1]:162]:
iso.3.6.1.2.1.1.3.0 = Timeticks: (2952) 0:00:29.52 iso.3.6.1.6.3.1.1.4.1.0 = OID: iso.3.6.1.3.9999.0.1 iso.3.6.1.3.9999.0.0.2.0 = STRING: "This is a test string" iso.3.6.1.3.9999.0.0.1.0 = INTEGER: 1485567495 iso.3.6.1.3.9999.1.2.0 = INTEGER: 6
2017-01-27 20:38:40 localhost [UDP: [127.0.0.1]:42445->[127.0.0.1]:162]:
iso.3.6.1.2.1.1.3.0 = Timeticks: (5452) 0:00:54.52 iso.3.6.1.6.3.1.1.4.1.0 = OID: iso.3.6.1.3.9999.0.1 iso.3.6.1.3.9999.0.0.2.0 = STRING: "This is a test string" iso.3.6.1.3.9999.0.0.1.0 = INTEGER: 1485567520 iso.3.6.1.3.9999.1.2.0 = INTEGER: 11
2017-01-27 20:39:05 localhost [UDP: [127.0.0.1]:42445->[127.0.0.1]:162]:
iso.3.6.1.2.1.1.3.0 = Timeticks: (7953) 0:01:19.53 iso.3.6.1.6.3.1.1.4.1.0 = OID: iso.3.6.1.3.9999.0.1 iso.3.6.1.3.9999.0.0.2.0 = STRING: "This is a test string" iso.3.6.1.3.9999.0.0.1.0 = INTEGER: 1485567545 iso.3.6.1.3.9999.1.2.0 = INTEGER: 16
2017-01-27 20:40:35 NET-SNMP version 5.7.3 Stopped.
Stopping snmptrapd
root@dreams:~#
I checkin the code. Thats it for step 3.
So far I left all the code in root directory. Lets separate it out so that it is much more clear. Following is our new directory structure.
.
├── agentxTutorial_Makefile
├── AGENTX-TUTORIAL-MIB
├── CMakeLists.txt
├── include
│ ├── agentxTutorial.h
│ ├── agentxTutorial_traps.h
│ └── myTestTable
│ ├── myTestTable_data_access.h
│ ├── myTestTable_data_get.h
│ ├── myTestTable_data_set.h
│ ├── myTestTable_enums.h
│ ├── myTestTable.h
│ ├── myTestTable_interface.h
│ └── myTestTable_oids.h
├── README.md
└── src
├── agentxTutorial.c
├── agentxTutorial_subagent.c
├── agentxTutorial_traps.c
├── CMakeLists.txt
└── myTestTable
├── myTestTable.c
├── myTestTable_data_access.c
├── myTestTable_data_get.c
├── myTestTable_data_set.c
├── myTestTable_interface.c
├── myTestTable-README-FIRST.txt
└── myTestTable-README-myTestTable.txt
We have to update our CMakeLists.txt
(or Makefile) file accordingly. I prefer CMakeLists.txt
over the usual Makefile
because cmake
would put all temporary files in a different directory. That helps me keep my codebsae clean and tidy.
So here is how we implemented a basic MIB with some scalars, a table and a trap using AgentX library provided by Net-SNMP. When I have more time I will provide an implementation of a real application in C++.