Skip to content

Adding SNMP Traps/Informs to your daemon

Working from our previous SNMP example where we simply responded to a couple of SNMP requests, we’re going to extend our application to include the ability to emit an SNMP Trap.

The first thing we need to do is define our trap MIB. We’re going to create a new MIB file and import our previous MIB (we could just as easily add the ponyName and ponyEscape definitions into our previous MIB).

PONIES-TRAP-MIB.mib

 Text |  copy code |? 
01
PONIES-TRAP-MIB DEFINITIONS ::= BEGIN
02
 
03
IMPORTS
04
	poniesd                                   FROM PONIES-MIB
05
	OBJECT-TYPE, Integer32, MODULE-IDENTITY   FROM SNMPv2-SMI
06
        ;
07
 
08
ponyName OBJECT-TYPE
09
    SYNTAX STRING (SIZE(0..255))
10
    ACCESS read-only
11
    STATUS mandatory
12
    DESCRIPTION "Name of the pony"
13
    ::= { poniesd 100 }
14
 
15
ponyEscape NOTIFICATION-TYPE
16
    STATUS current
17
    OBJECTS { ponyName } 
18
    DESCRIPTION "Emitted when a pony escapes"
19
    ::= { poniesd 10 }
20
 
21
END

Then we’re going to use mib2c again to generate skeleton .c/.h files:

 shell |  copy code |? 
1
$ env MIBDIRS=`net-snmp-config --default-mibdirs`:`pwd` MIBS="+PONIES-TRAP-MIB" mib2c -c mib2c.notify.conf -f poniesd_traps poniesd
2
writing to poniesd_traps.h
3
writing to poniesd_traps.c
4
running indent on poniesd_traps.h
5
running indent on poniesd_traps.c
6

This time we’ll have to modify the source a bit to fill in our trap information. This is the poniesd_traps.c file that was generated for me, along with the modifications I made to make it compile and work.

 C |  copy code |? 
01
/*
02
 * Note: this file originally auto-generated by mib2c using
03
 *        $
04
 */
05
 
06
#include <net-snmp/net-snmp-config.h>
07
#include <net-snmp/net-snmp-includes.h>
08
#include <net-snmp/agent/net-snmp-agent-includes.h>
09
#include "poniesd_traps.h"
10
 
11
extern const oid snmptrap_oid[];                                 /* MODIFIED */
12
extern const size_t snmptrap_oid_len;
13
 
14
int
15
send_ponyEscape_trap( void )
16
{
17
    netsnmp_variable_list  *var_list = NULL;
18
    const oid ponyEscape_oid[] = { 1,3,6,1,4,1,32473,1,10 };
19
    const oid ponyName_oid[] = { 1,3,6,1,4,1,32473,1,100, 0 };
20
 
21
    /*
22
     * Set the snmpTrapOid.0 value
23
     */
24
    snmp_varlist_add_variable(&var_list,
25
        snmptrap_oid, snmptrap_oid_len,
26
        ASN_OBJECT_ID,
27
        ponyEscape_oid, sizeof(ponyEscape_oid));
28
 
29
    /*
30
     * Add any objects from the trap definition
31
     */
32
    snmp_varlist_add_variable(&var_list,
33
        ponyName_oid, OID_LENGTH(ponyName_oid),
34
        ASN_OCTET_STR,                                            /* MODIFIED */
35
        /* Set an appropriate value for ponyName */
36
        "Hello world", sizeof("Hello world")-1);                  /* MODIFIED */
37
        /* Note: -1 to trim off the trailing '\0', makes snmptrapd print it out nice */
38
 
39
    /*
40
     * Add any extra (optional) objects here
41
     */
42
 
43
    /*
44
     * Send the trap to the list of configured destinations
45
     *  and clean up
46
     */
47
    send_v2trap( var_list );
48
    snmp_free_varbind( var_list );
49
 
50
    return SNMP_ERR_NOERROR;
51
}

Then to emit the trap, simply include the poniesd_traps.h file and add the following line somewhere appropriate:

 C |  copy code |? 
1
send_ponyEscape_trap();

Testing things out

To test things out we’ll need to setup snmptrapd, which will print out the traps we are receiving. Presumably you’ll want to do something more useful with your traps at somepoint, but this will get you to the point of knowing your daemon is emitting traps correctly.

To setup snmptrapd, simply add the following to your /etc/snmp/snmptrapd.conf file (you can use a different username than trap_user if you want of course.)

 Text |  copy code |? 
1
authCommunity   log,execute,net    trap_user

You may also need to add the following to /etc/hosts.allow

 Text |  copy code |? 
1
snmptrapd : All : ALLOW

Now you can startup snmptrapd for debugging by running

 shell |  copy code |? 
1
snmptrapd -f -Le
This will launch snmptrapd in the foreground and log everything to stderr so it’s easy to see if our traps are hitting it or not. You can also add -DALL for a bunch of debugging output.

At this point you can perform a quick test using snmptrap to send a trap to snmptrapd and ensure everything is working:

 shell |  copy code |? 
1
$ snmptrap -v 2c -c trap_user -M+. -mALL localhost '' PONIES-TRAP-MIB::ponyEscape PONIES-TRAP-MIB::ponyName s "Test pony"

you should see the trap output on the terminal you ran snmptrapd on.

Configuring snmpd to forward your traps from your program

Since our application is a subagent of snmpd, our process will push the trap to snmpd, which will forward the trap to the desired destination. For our purposes right now, we want them to be forwarded locally to our snmptrapd process. To do this, add the following to /etc/snmp/snmpd.conf

 Text |  copy code |? 
1
trapcommunity trap_user
2
trapsink localhost
3
trap2sink localhost
4
informsink localhost

and restart snmpd.

Now when you run your poniesd it should emit the trap and you should see trap hit snmptrapd.

Download

Heres a tarball of the new files we added and modified.

snmp-trap-ponies.tar.bz2

Adding SNMP to your daemon

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

 mib |  copy code |? 
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.

 shell |  copy code |? 
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

 C |  copy code |? 
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.

 shell |  copy code |? 
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

 GNU make |  copy code |? 
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 master

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:

 Text |  copy code |? 
1
master agentx

I’ve added these read_only_user and read_write_user users as well (to the snmpd.conf file) for demonstration purposes

 Text |  copy code |? 
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:

 Text |  copy code |? 
1
snmpd : All : ALLOW

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

 shell |  copy code |? 
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:

 shell |  copy code |? 
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.)

 shell |  copy code |? 
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.

 shell |  copy code |? 
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

J2ME MIDlet programming on linux

I’ve found myself in the position of wanting to develop a portable mobile application. Now, I’m not a java programmer, nor do I use windows (I’m running 64-bit Gentoo Linux), and I prefer to use Vim + Makefiles over using some IDE, so I had a few challenges to overcome in building my first Hello World MIDlet. In the end though, it isn’t too bad to get going, so if you are in a similar situation, here are some quick notes on how I got from zero to MIDlet.

  1. First and foremost, you’ll need to have a full blown Java Development Kit (JDK) installed. I’m using Sun’s JDK 1.6.0.13, but I don’t think it is too important. (Both 32 bit and 64 bit versions seem to work fine.)
  2. If you are running a 64 bit version of Linux, you’ll need to have a 32 bit java runtime environment (JRE) installed somewhere to run the WTK device emulator. In gentoo, this means doing an ‘emerge emul-linux-x86-java’ (in gentoo, your installation will be in /opt/emul-linux-x86-java-1.X.Y.Z/).
  3. Next, you’ll need to download and install the Java Wireless Toolkit, I’m using version 2.5.2 (3.0 is out, but it is only for windows..). It is available here: http://java.sun.com/products/sjwtoolkit/download.html. You should end up with a file named something like “sun_java_wireless_toolkit-2.5.2_01-linuxi486.bin.sh”.  Run this shell script to install the WTK, and again, if you are using 64 bit linux, don’t forget to give the path to your 32 bit version of java when the installer asks for it.
  4. Now we can start working on our midlet. Create a file called HelloWorld.java containing the following code
     Java |  copy code |? 
    01
    import javax.microedition.lcdui.Alert;
    02
    import javax.microedition.lcdui.Display;
    03
    import javax.microedition.midlet.MIDlet;
    04
     
    05
    import java.util.Date;
    06
     
    07
    public class HelloWorld extends MIDlet {
    08
        Alert timeAlert;
    09
     
    10
        public HelloWorld() {
    11
            timeAlert = new Alert("Hello!");
    12
            timeAlert.setString("Hello World! The time is now " + new Date().toString());
    13
        }
    14
     
    15
        public void startApp() { 
    16
            Display.getDisplay(this).setCurrent(timeAlert);
    17
        }
    18
     
    19
        public void pauseApp() { }
    20
        public void destroyApp(boolean unconditional) { }
    21
    }
  5. Now, create a file called Manifest, containing the following lines
     shell |  copy code |? 
    1
    MIDlet-Name: HelloWorld
    
    2
    MIDlet-Vendor: YourName
    
    3
    MIDlet-Version: 1.0
    
    4
    MIDlet-Icon: /icon.png
    
    5
    MIDlet-Info-URL: http://hexist.com/?p=29
    
    6
    MicroEdition-Configuration: CLDC-1.0
    
    7
    MicroEdition-Profile: MIDP-2.0
    
    8
    MIDlet-1: HelloWorld, /icon.png, HelloWorld
  6. Save the following image icon as icon.png
  7. Create a Makefile and paste the below into it. Modify the JDK_BASE and J2ME_BASE variables appropriately depending on where you installed your JDK and your J2ME/WTK packages.
     GNU make |  copy code |? 
    01
    ### Modify these appropriately for your system!
    02
     
    03
    JDK_BASE=/opt/sun-jdk-1.6.0.13
    04
    J2ME_BASE=$(HOME)/WTK2.5.2
    05
     
    06
     
    07
    ### For the tutorial, you shouldn't have to modify anything below this line
    08
     
    09
    SRC=HelloWorld.java
    10
     
    11
    JAVAC=$(JDK_BASE)/bin/javac
    12
    JAVA_CFLAGS=-bootclasspath "$(J2ME_BASE)/lib/midpapi20.jar:$(J2ME_BASE)/lib/cldcapi10.jar"  \
    13
                -target 1.3                                                                     \
    14
                -source 1.3                                                                     \
    15
                -d compiled                                                                     \
    16
                -classpath compiled                                                             \
    17
                -sourcepath .                                                                   \
    18
                -g
    19
    PREVERIFY=$(J2ME_BASE)/bin/preverify
    20
    PREVERIFY_CLASSPATH=$(J2ME_BASE)/lib/midpapi20.jar:$(J2ME_BASE)/lib/cldcapi10.jar
    21
     
    22
     
    23
    all: HelloWorld.jar HelloWorld.jad
    24
     
    25
    run: HelloWorld.jad
    26
     $(J2ME_BASE)/bin/emulator -Xdescriptor:HelloWorld.jad
    27
     
    28
    HelloWorld.jar: verified compiled Manifest $(SRC:.java=.class)
    29
     jar cfm HelloWorld.jar Manifest icon.png -C verified $(SRC:.java=.class)
    30
     
    31
    compiled:
    32
     mkdir -p compiled
    33
     
    34
    verified:
    35
     mkdir -p verified
    36
     
    37
    %.class: %.java
    38
     $(JAVAC) $(JAVA_CFLAGS) $<
    39
     $(PREVERIFY) -classpath $(PREVERIFY_CLASSPATH):compiled -d verified HelloWorld
    40
     
    41
    %.jad: %.jar
    42
     unzip -aa -j -p $< "META-INF/MANIFEST.MF" > $@
    43
     echo "MIDlet-Jar-URL: $<" >> $@
    44
     echo "MIDlet-Jar-Size: " `stat -c%s $<` >> $@
    45
     
    46
    clean:
    47
     rm -Rf compiled verified
    48
     rm -f *.jar *.jad
    49
     
    50
    .PHONY: clean all run

Now, you should be able to run ‘make’ to build your JAR and JAD files, and ‘make run’ to run the WTK phone emulator. For those that just want to see the raw compile steps for everything that needs to be done to go from .java to a jad n jar, here they are:

mkdir -p verified
mkdir -p compiled
/opt/sun-jdk-1.6.0.13/bin/javac -bootclasspath "$HOME/WTK2.5.2/lib/midpapi20.jar:$HOME/WTK2.5.2/lib/cldcapi10.jar" -target 1.3 -source 1.3 -d compiled -classpath compiled -sourcepath . -g HelloWorld.java
$HOME/WTK2.5.2/bin/preverify -classpath $HOME/WTK2.5.2/lib/midpapi20.jar:$HOME/WTK2.5.2/lib/cldcapi10.jar:compiled -d verified HelloWorld
jar cfm HelloWorld.jar Manifest icon.png -C verified HelloWorld.class
unzip -aa -j -p HelloWorld.jar "META-INF/MANIFEST.MF" > HelloWorld.jad
echo "MIDlet-Jar-URL: HelloWorld.jar" >> HelloWorld.jad
echo "MIDlet-Jar-Size: " `stat -c%s HelloWorld.jar` >> HelloWorld.jad

Then a quick ‘make run’, which just executes

$HOME/WTK2.5.2/bin/emulator -Xdescriptor:HelloWorld.jad

HelloWorld MIDlet

HelloWorld MIDlet

and if everything went well, up will pop our device emulator ready to launch our MIDlet. Click ‘launch’, and you should see a screen that looks something like the image on the right.

To get the files installed on an actual phone, the easiest way (in my opinion) is to simply throw the .jad and .jar files up on a web page somewhere and to browse to the .jad file. Alternatively, most phones have USB hookups or some other way of installing apps without connecting them to the internet, so depending on your phone that might be easier for you.

 

Tar ball of all these files: HelloWorld.tar.bz2

Using FreeBSD MAC bsdextended Module to ensure users can’t access each others files

I had an instance where I wanted to ensure that users that have ssh access to our FreeBSD servers can’t access each others files. In reality, chmod tricks would have probably sufficed, however FreeBSD gives us the bsdextended MAC module which can absolutely ensure this, so might as well take advantage of it right?

To do this, we setup a rule which states that if someone is logged in as one of our ssh users (they all have uid/gid ≥ 2000), and they are trying to access something that is not a normal system file (ie, something owned by some uid ≤ 1000) that isn’t their own, then deny them access. And of course to set this rule up, we run:

/usr/sbin/ugidfw add \
       subject uid 2000:65530 gid 2000:65530 \
       object ! uid 0:1000 ! gid 0:1000 ! uid_of_subject ! gid_of_subject \
       mode n

And just like magic, now we have a server wide policy that restricts our ssh users accessing each others files. Pretty cool.