Tut:Extending snmpd using shell scripts

From Net-SNMP Wiki
Jump to: navigation, search

This tutorial is intended as a brief introduction to extending the snmpd agent using shell scripts.

Documentation

Shell scripts are configure in snmpd.conf. The man page for snmpd.conf has a section on EXTENDING AGENT FUNCTIONALITY which covers the exec, extend, pass and pass-persist methods or executing a script to implement a MIB.

There is also a FAQ on the differences between the various methods.

Examples scripts

Simplistic example

Here is a simple example using echo:

  • snmpd.conf
rocommunity testing
extend test /bin/echo hello
  • retrieving value
$ snmpwalk -v2c -c testing 127.0.0.1 nsExtendOutput1
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."test" = STRING: hello
NET-SNMP-EXTEND-MIB::nsExtendOutputFull."test" = STRING: hello
NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."test" = INTEGER: 1
NET-SNMP-EXTEND-MIB::nsExtendResult."test" = INTEGER: 0
  • finding the OID
$ snmptranslate -On NET-SNMP-EXTEND-MIB::nsExtendOutput1Line.\"test\"
.1.3.6.1.4.1.8072.1.3.2.3.1.1.4.116.101.115.116

Use the source, Luke

The GIT tree has 3 example scripts.

User contributed

Here are user contributed examples:

Pass persist

The pass_persist option is very useful if you need to implement some specific OID subtree, but it has a few traps and features which are poorly communicated, or not even mentioned in the snmpd.conf man page.

  1. the shutdown protocol: when the snmpd server wants to shutdown, it tells your script by sending a blank line on stdin.
  2. snmpd sends a PING at the start of every request (I read the docs to mean that it is sent once, on script startup).
  3. the list of types you can return is quite limited. It is actually described in the docs for pass, which says that the type must be "one of the text strings: integer, gauge, counter, timeticks, ipaddress, objectid, or string"
  4. because you cannot return DateAndTime (RFC 2579), for datetime values I've resorted to returning two values (two separate OIDs):
    1. seconds-since-epoch (type counter)
    2. datetime formatted as ISO-8601 string (type string)
  5. there doesn't seem to be a way for getnext to distinguish between an invalid input OID, and running past the end of the supported tree.


Other things I've learned:

  1. your MIB must be valid, otherwise snmpget won't even query the daemon.
  2. you will probably need to add a line to the security section of snmpd.conf, to allow access to the OID subtree e.g. something like (where nnnnn is your Private Enterprise Number):
    view all    included    .1.3.6.1.4.1.nnnnn
  3. I used python to implement my pass_persist script. You must flush stdout after sending each line, otherwise snmpd will timeout after waiting for your response. Or invoke python with -u option (unbuffered stdin/stdout).
  4. python stdin.readline() preserves the newline, so you need to strip it.


My pass_persist daemon main loop looks like this:

    def getline():
        return sys.stdin.readline().strip()


    def output(line):
        sys.stdout.write(line + "\n")
        sys.stdout.flush()


    def main():
        logger.info("starting pass_persist daemon")

        try:
            while True:
                command = getline()

                if command == "":
                    logger.info("stopping pass_persist daemon")
                    sys.exit(0)

                # snmpd 5.4.2.1 sends a PING before every snmp command
                elif command == "PING":
                    output("PONG")

                elif command == "set":
                    oid = getline()
                    type_and_value = getline()
                    logger.info("%s %s %s" % (command, oid, type_and_value))
                    #snmp_set(oid, type_and_value)
                    # set not supported yet...
                    output("not-writable")

                elif command == "get":
                    oid = getline()
                    logger.info("%s %s" % (command, oid))
                    snmp_get(oid)

                elif command == "getnext":
                    oid = getline()
                    logger.info("%s %s" % (command, oid))
                    snmp_getnext(oid)

                else:
                    logger.error("Unknown command: %s" % command)

        # If we get an exception, spit it out to the log then quit
        # (by propagating exception).
        # snmpd will restart the script on the next request.
        except Exception, e:
            logger.exception("")
            raise

Debugging script issues

If your pass script isn't working, here are a few things you can try:

check script permissions

Make sure that the script is executable.

chmod a+x /tmp/my_script

check for SELinux restrictions

On Linux systems with SELinux enabled, the script must have the right context. You can check for SELinux access violations with

grep AVC /var/log/audit/audit.log | grep snmp

check access control

Pass scripts generally fall in the enterprises branch. Some vendors default snmpd configuration do not include this branch. To see if you can see any objects in the enterprises branch, try this command:

snmpgetnext -On -v 1 -c public localhost .1.3.6.1.4.1

If you get back an object that starts with .1.3.6.1.4.1, then you should be ok.

turn on debugging

Try running snmpd in the foreground, with debugging

snmpd -f -Lo -Ducd-snmp/pass

which should result in debug output something like

 ucd-snmp/pass: pass-running:  /bin/sh /tmp/my_script -g .1.3.6.1.4.1.2021.224

touch a temporary file

At the top of your script, touch a temporary file. You can check the time stamp of the temporary file to see if the script is run. A simple example:

#!/bin/bash

echo "$0 run at `date`" >> /tmp/my_script.debug

# handle pass arguments
# ...

External examples

There are many examples on the Net-snmp_extensions page.