|laziness, impatience, and hubris|
Using the SNMP module from the Net-SNMP libraryby Rhys (Pilgrim)
|on Oct 04, 2004 at 13:26 UTC||Need Help??|
Where to get Net-SNMPYou can get the Net-SNMP library package from http://net-snmp.sourceforge.net/. If you have RedHat Linux 9 or RedHat Enterprise Linux 3, you can install the net-snmp-5.0.9 packages from you installation CDs (or from RedHat Network, if you are fortunate enough to have access).
If you install from source, you will need to compile and install the libraries, then cd into the 'perl' directory and install the Perl module separately. If you install using RPMs from RedHat, you need the net-snmp-perl package and all of its dependencies. If you build from the src.rpm package, make sure you use --with perl_modules.
NOTE: If you use RPM, I highly recommend uninstalling the 5.0.9 packages provided by RedHat, getting the 5.1.x src.rpm, and rebuilding from there. Version 5.1 resolves a problem that causes 'make test' to fail any time you use MakeMaker. Everything actually works, but you can't test anything.
Update:As of January, 2005, version 5.2.1 of Net-SNMP is available. I have had no trouble with it so far.
Do Not Confuse Net-SNMP with Net::SNMP!The naming is unfortunate, but the SNMP module that comes with the Net-SNMP package is just 'SNMP'. The Net::SNMP module is a completely different beast. Confusing the two will get you in big trouble. Here's the test:
Also, remember that the Net-SNMP package began life as UCD-SNMP, since it was originally sponsored and maintained by the University of California - Davis. It was renamed in version 5.0 and has moved to SourceForge. In fact, if you do install SNMP from CPAN, you get the 4.2.0 module from the UCD-SNMP package. It will probably work with most of what you see here, but I recommend getting the 5.1 package if you can.
Try Some Trivial QueriesBefore we get to the advanced stuff, let's make sure it's working. You will need to have a device attached to the network that will respond to SNMP queries (you can use 'localhost' if snmpd is running). Edit the script below to suit your environment and try it.
Whew! That's a lot of code just for a couple of simple queries! There are a lot of comments, and the code demonstrates the two most common ways of getting SNMP data from an agent (single query or loop through some unknown number of instances). Your coding style may be more succint, and you may not need some of the error checking. Proceed without it at your own peril, though.
And Now, A Few Words About MIBsIt might be worth defining a few terms before we go on:
And Now, a Few Other TermsI addressed 'MIBs' first because there is so much confusion about that term. There are also a few other things you'll need to recognize in order to use SNMP correctly:
And Now, We'll Add Enterprise MIB FilesNet-SNMP comes with plenty of RFC-defined MIB modules, usually stored in /usr/share/snmp/mibs. However, most of the really good info you want from an SNMP agent is stored in the proprietary Enterprise MIBs (dum dum DUM!). These MIBs are stored in the 'enterprises' MIB subtree. Each vendor has its own identifier (e.g. 'cisco', 'nortel', 'rapidCity'), and under that, the vendor is free to create all the subtrees and objects they want. But for you to be able to get to them, you need to get Net-SNMP to parse the definitions for all this great stuff. You need to get it to load the MIB modules for your enterprise MIBs.
This is really a two-part problem. First, where are you going to keep the MIB files themselves? Second, how are you going to get the files imported when you go to do SNMP queries? I'll tell you how I did it, and hopefully you'll e-mail me if you find some horrible deficiency in this method.
Look, I Just Want to Load One File!Fine. Here's the fastest - and most unwise - way to do it. It works, of course, but you'll soon find that this method is rife with practical problems:
So What's So Bad About That?Since you asked, I'll tell you.
First, that's a hard-coded path to a particular file. The path may change. The file name may change, which happens a lot when vendors mark the version of a MIB module in the file name. This method is extremely resistant to change.
Second, what if this MIB module requires that you load another module first? Done this way, you'll probably have to load all of the pre-requisite files by hand before this one will work, especially if the prereqs are other enterprise MIB modules. There's a better way. Let's go back to the beginning and pretend we want to do things the Right Way(tm).
Where to Put the FilesIf you installed from RPM, the MIB files the come with Net-SNMP are put in /usr/share/snmp/mibs (or /usr/local/share/snmp/mibs if you installed from source). Every time I added a group of related MIBs files, I put them in a subdirectory under this one. For example, I wrote a program called JScan, and put several subdirectories named JScan/APC, JScan/BayStack, etc. for a full path of /usr/share/snmp/mibs/JScan/APC. This kept all of the files separate (so 'rm -r ..../JScan' would get rid of them all in a pinch), but in a predictable and sensible place.
Then a second problem presented itself. I decided it was a pain to add that many search directories all of the time (discussed below), so I created a single directory /usr/share/snmp/mibs/JScan/everything that contains symbolic links to all of the actual files in all of the other directories. This is done at install time by this excerpt of the post-install script:
I then installed the MIB files for other packages in other subdirs under /usr/share/snmp/mibs. Again, keeps things separate, but still in more or less the Right Place.
Known Problem: While this has not happened to me yet in practice, it is possible that two vendors may name their MIB files with the same name, in which case 'ln' above will return an error when it tries to create the second link. This has never happened to me yet, though I have the practice of pulling the RFC-based MIB files out of the vendor-supplied list before I split them up, which resolves a LOT of conflicts.
Get Perl to Read Your MIB FilesThere are several ways to do this, but in my opinion the best way is as follows. Suppose I wanted to load the MIB modules 'S5-CHASSIS-MIB', 'RAPID-CITY', and all of their pre-requisites. I would do this:
In the example shown, the directories /usr/share/snmp/mibs AND /usr/share/snmp/mibs/JScan/everything will be searched for a file with the S5-CHASSIS-MIB module. It will then load that file. Since the S5-CHASSIS-MIB also requires the S5-ROOT-MIB, those same directories will be searched again for the file containing THAT MIB module, and so on until all the necessary modules have been loaded, or until something fails. If I load a non-existent module THIS-AND-THAT on my system, I get:
You can, of course, turn this off and make it fail silently, but by default it won't cause your script to fail. The MIB parsing errors can be turned on or off in your snmp.conf file.
Other Useful BitsThere are several other ways you can get Net-SNMP to add directories to your search path and/or automatically load MIB modules. See the snmp.conf(5) man page for more details, paying particular attention to the parts about the MIBS and MIBDIRS environment variables and the ~/.snmp/snmp.conf file, which might be more appropriate than making system-wide changes.
ReviewAt this time, I would like to suggest that you go back to Try Some Trivial Queries and play for a bit. Re-write this code in your own style, taking the time to understand as much of it as possible. Try loading some MIB modules and querying new stuff from those modules. Get used to OIDs, IIDs, values, and MIB object definitions before you proceed to the next section, in which we start doing the dangerous stuff (changing things on a remote agent).
Setting MIB Object ValuesOkay, so we've covered how to get Net-SNMP, load new MIB modules, and query some MIB objects. Suppose now that you want to use Net-SNMP to set the value of a read/write MIB object, such as sysName or sysLocation. This is actually pretty simple... most of the time. Take these two objects for instance. Here's the code to set the value of sysName on a host:
This works for most things. However, I have run into a lot of trouble when trying to set values that aren't strings or integers when I have set UseSprintValue to true, which I usually do, since subsequent queries using the same session make FAR more sense with UseSprintValue (which translates enums and some packed binary values into readable strings for the user). Setting packed binary values, in particular, is a pain. Fortunately, all you need is a second session to cure this:
Note that it's almost entirely the same except that UseSprintValue is set to zero when the SNMP session object is created.
Using VarListsSometimes you want to query a bunch of different SNMP values on the same device. You could query each of them one at a time, but if you want to conserve bandwidth or if you're trying to keep several variables in sync it's a lot easier to use a VarList.
So what's a VarList? A VarList is a blessed reference to an array of Varbinds (so you can't just do push @varbinds, $vb; in a loop and expect that to work). So basically, a VarList is a bunch of Varbinds stuck together so you can make many queries at once. Let's walk through two examples of using VarLists and see how they work.
Here we create a VarList and query a device for the variables 'sysName', 'sysDescr', and 'sysLocation'. We're assuming that the session is already created and we're going to just query instance 0, since we know from experience that this is the only instance that exists for these MIB objects.
And that's it! Instead of using one array constructor with "MIB, instance, value" inside of it like with a Varbind, you just create a list of them. Now let's do that same query above, but this time we'll use a loop to create an array of Varbinds. For this example, it's trivial, but this can make for an elegant solution in some cases and save you a lot of code and a lot of bandwidth.
This method is most useful when the instance number or the value you need in the Varbind is only known after some calculation AND you want to put several MIB objects in the same get or set action. Here's a more useful example of using a VarList:
Now we skipped all the setup and there's absolutely no error checking in the loop shown, but you can see how using VarLists can drastically shorten the process of getting (or setting) several MIB objects at once.
TIP: One problem I've come across when using VarLists in this way is that in some cases the getnext causes one of the MIB objects in the list to get a different instance number than the others. Suppose in the example above that the switch does not keep a value for 'ifOperStatus' at all if 'ifAdminStatus' is 'down'. If port 2 is disabled, then on the second query, the instance ID for $$vl will be 2, but for the other two objects the instance will be 3 (assuming port 3 is enabled). The getnext is performed on all of the MIBs regardless of the success or failure or values of the other MIBs in the same query SNMP doesn't magically 'know' you want the objects in your VarList to be in sync.
SO: When using VarLists, even though you're querying them all at once and they share a packet, they are separate queries, so if you want them to stay in sync, you may need to add code to make sure they actually do. Check that all of the instance IDs are the same after each query and then do whatever is appropriate to resolve it when they turn up unequal. Sometimes a getnext will skip an instance, but going back and doing a get on that same instance will return the information you want.
Getting Many Values in ONE QuerySo far, we have been using SNMP commands that were defined in version 1 of the SNMP specification: GET, GETNEXT, and SET. In version 2, the SNMP designers decided that they wanted a way to send a single query that would return N values of a single object, like doing 20 "GETNEXTs" at once. To do this, they created the GETBULK command.
The syntax for the getbulk method in the SNMP module is a little complicated. Here's what perldoc has to say about it:
So the first two arguments, non-repeaters and max-repeaters are integers that define how getbulk is going to behave, and the remaining arguments are Varbinds (or better, a VarList).
Now getbulk is a little tricky. It's behaves like a bunch of getnext commands strung together. If non-repeaters is set to '4', it will take the first four Varbinds in the list given and get their next values exactly once (as if you had used getnext on each of them). Then if we suppose that max-repeaters is set to '10', getbulk will behave like a getnext on the remaining Varbinds 10 times as a list. So it doesn't get the next 10 of the first Varbind, then get 10 of the next Varbind, and so on, but rather it does 10 getnext's on the list (just as if you had used a for loop on a VarList).
So let's take an example that I threw together that queries some basic stuff about a 24-port switch, and hopefully things will become clearer:
So we asked for four non-repeaters, sysName, sysDescr, sysLocation, and sysUpTime. We also ask for 24 instances of ifIndex and ifAdminStatus. The first four objects are queried once each, then the 24 answers for ifIndex and ifAdminStatus are "striped" together, and we use a simple for loop to break them apart again.
Now, if this were a real program, we would have just used @ANSWERS directly in the outputfor loop at the bottom and done $x+=2 for each iteration and saved a lot of code and extra variables, but I digress...
Using the getbulk command with max-repeaters set to less than '2' is silly, though it can be used with non-repeaters set to '0'. There is no requirement to have any non-repeaters in your query.
WARNING: The getbulk command is not the Hold Grail of Supah-Queries. You can't just ask it to return 1000 instances, because SNMP will only send one packet in reply. SNMP can't fragment its responses into multiple UDP messages, and won't send a single UDP message that would cause the IP packet to be fragmented either, so you'll have to use getbulk with a little circumspection. I've found that I can safely pack about 75 small queries (for 'ifAdminStatus', say) into a single getbulk, but that's about it. CHECK YOUR RESPONSES!
NOTE: It should be noted that I haven't tried to see if this limitation still exists in SNMP version 3 when using TCP instead of UDP. If anyone gets large queries to work using this - or any other - method, PLEASE let me know. You can imagine how much simpler it would make large queries.
Quick and Dirty Synopses of Planned Stuff for the ImpatientHeh. This is also to help motivate me to get this writeup finished. Perhaps it will help you make sense of the documentation you hopefully referred to already.