http://qs1969.pair.com?node_id=396223

Where to get Net-SNMP

You 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:

use SNMP; # This is Net-SNMP. use Net::SNMP; # This is NOT. DO NOT USE WITH THIS TUTORIAL!

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 Queries

Before 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.

#!/usr/bin/perl use warnings; use strict; use SNMP; use Socket; # VARIABLES YOU SHOULD EDIT. my $comm = 'public'; # EDIT ME! my $dest = 'localhost'; # EDIT ME! my $mib = 'sysDescr'; # Toy with this to get different # results. my $sver = '2'; # EDIT ME! # VARIABLES YOU SHOULD LEAVE ALONE. my $sess; # The SNMP::Session object that does the work. my $var; # Used to hold the individual responses. my $vb; # The Varbind object used for the 'real' query. # Initialize the MIB (else you can't do queries). &SNMP::initMib(); my %snmpparms; $snmpparms{Community} = $comm; $snmpparms{DestHost} = inet_ntoa(inet_aton($dest)); $snmpparms{Version} = $sver; $snmpparms{UseSprintValue} = '1'; $sess = new SNMP::Session(%snmpparms); # Turn the MIB object into something we can actually use. $vb = new SNMP::Varbind([$mib,'0']); # '0' is the instance. $var = $sess->get($vb); # Get exactly what we asked for. if ($sess->{ErrorNum}) { die "Got $sess->{ErrorStr} querying $dest for $mib.\n"; # Done as a block since you may not always want to die # in here. You could set up another query, just go on, # or whatever... } print $vb->tag, ".", $vb->iid, " : $var\n"; # Now let's show a MIB that might return multiple instances. $mib = 'ipNetToMediaPhysAddress'; # The ARP table! $vb = new SNMP::Varbind([$mib]); # No instance this time. # I prefer this loop method. YMMV. for ( $var = $sess->getnext($vb); ($vb->tag eq $mib) and not ($sess->{ErrorNum}); $var = $sess->getnext($vb) ) { print $vb->tag, ".", $vb->iid, " : ", $var, "\n"; } if ($sess->{ErrorNum}) { die "Got $sess->{ErrorStr} querying $dest for $mib.\n"; } exit;

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 MIBs

It might be worth defining a few terms before we go on:
MIB
Strictly speaking, the Management Information Base or "MIB" is a database of information that you can query from an SNMP agent. In practice, the term 'MIB' by itself is confusing. It is much better to be more specific and use one of the other terms here.
MIB module
The actual specifications and definitions for a MIB, found in a file somewhere on your system. It begins with 'YOUR-MIB-NAME DEFINITIONS ::= BEGIN' and ends with 'END'. It is best to load MIB modules by name, since this allows Net-SNMP to do all of the file-searching for you, rather than hard-coding in file names. This puts the onus on you to make sure there's only one file in your search path with definitions for a given MIB.
MIB file
A MIB file is just a text file that contains one or more MIB modules. NOTE: Net-SNMP has problems loading files with more than one MIB module in them. In my experience, it only parses the first module properly. This is a bug in Net-SNMP. Please let me know if this has been solved so I can upgrade both my libraries and this file.
MIB object
A single item in a MIB specification such as 'sysDescr'. This is what people usually mean when they say they are 'looking for a MIB' or have been 'querying a bunch of MIBs'. Each MIB object has a type (integer, string, enumerated, etc.). MIB objects are queried by asking the agent to return one or more instances of that object (more on this later). Each instance of a MIB object has a value (which can be NULL).
MIB tree
Another, clearer term for describing the entire MIB. The MIB itself is laid out in a tree structure, with MIB objects as the leaf nodes. Each node is identified both by number (positive integers or zero) and by name (which is NOT guaranteed to be unique). The tree layout is very flexible and provides an absolute unique path to every MIB object. Each MIB module loaded adds new branches to the MIB tree and allows you to query the objects defined in the new module(s).

And Now, a Few Other Terms

I 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 Files

Net-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:
&SNMP::addMibFiles("/path/to/some/file.mib"); &SNMP::initMib();

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 Files

If 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:

# Where $destdir is /usr/share/snmp/mibs in most cases... system "mkdir -p $destdir/JScan/everything ; cd $destdir/JScan/everything ; find .. -type f -exec ln -sf {} \\;";

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 Files

There 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:
# Add the search directory. &SNMP::addMibDirs("/usr/share/snmp/mibs/JScan/everything"); # Load the modules AND their pre-reqs. &SNMP::loadModules('S5-CHASSIS-MIB', 'RAPID-CITY'); # Wonder-Twin powers, ACTIVATE! &SNMP::initMib();
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:
Cannot find module (THIS-AND-THAT): At line 1 in (none)
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 Bits

There 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.

Review

At 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 Values

Okay, 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:
my $comm = 'ihavepower'; # Use read/write community. my $dest = '10.1.1.1'; # IP or DNS will work. my $sver = '2'; # Use 1 for simple devices, and 3 if you # really know your SNMP security. my %snmpparms; $snmpparms{Community} = $comm; $snmpparms{DestHost} = inet_ntoa(inet_aton($dest)); $snmpparms{Version} = $sver; $snmpparms{UseSprintValue} = 1; my $sess = new SNMP::Session(%snmpparms); my $mib = 'sysName'; my $instance = '0'; # There's only one instance of sysName. my $value = "New system name."; my $vb = new SNMP::Varbind([$mib,$instance,$value]); # This does it! $sess->set($vb); if ( $sess->{ErrorNum} ) { print "Got $sess->{ErrorStr} setting $mib on $host.\n"; }
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:
my $comm = 'ihavepower'; # Use read/write community. my $dest = '10.1.1.1'; # IP or DNS will work. my $sver = '2'; # Use 1 for simple devices, and 3 if you # really know your SNMP security. my %snmpparms; $snmpparms{Community} = $comm; $snmpparms{DestHost} = inet_ntoa(inet_aton($dest)); $snmpparms{Version} = $sver; $snmpparms{UseSprintValue} = 0; ### NEW SESSION REQUIRED! my $sess2 = new SNMP::Session(%snmpparms); my $mib = 'some32BitMib'; # Suppose it takes a packed IP. my $instance = '0'; # Will vary with the MIB object. my $value = inet_aton($ipaddr); my $vb = new SNMP::Varbind([$mib,$instance,$value]); $sess2->set($vb); if ( $sess2->{ErrorNum} ) { print "Got $sess2->{ErrorStr} setting $mib on $host.\n"; }
Note that it's almost entirely the same except that UseSprintValue is set to zero when the SNMP session object is created.

Using VarLists

Sometimes 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.

$MIB[0] = 'sysName'; $MIB[1] = 'sysDescr'; $MIB[2] = 'sysLocation'; $vl = new SNMP::VarList([$MIB[0], 0], [$MIB[1], 0], [$MIB[2], 0]); @ANSWERS = $sess->get($vl);

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.

@MIBS = ('sysName', 'sysDescr', 'sysLocation'); foreach $mib ( @MIBS ) { push @bunchovbs, new SNMP::Varbind([$mib,0]); } # Now the magic! $vl = new SNMP::VarList(@bunchovbs); @ANSWERS = $sess->get($vl);
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:
@MIBS = ('ifAdminStatus', 'ifOperStatus', 'ifSpeed'); $vl = new SNMP::VarList([$MIBS[0]], [$MIBS[1]], [$MIBS[2]]); for ( $ifindex = 1 ; $ifindex < 25 ; $ifindex++ ) { @STATUS = $sess->getnext($vl); if ( $STATUS[0] eq 'up' ) { print "Port $ifindex enabled.\n"; } else next; print " Link status is $STATUS[1]. Speed is $STATUS[2].\n"; }
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[0] 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 Query

So 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:

$sess->getbulk(<non-repeaters>, <max-repeaters>, <vars>) do an SNMP GETBULK, from the list of Varbinds, the single next lex- ico instance is fetched for the first n Varbinds as defined by <non-repeaters>. For remaining Varbinds, the m lexico instances are retrieved each of the remaining Varbinds, where m is <max-repeaters>.

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:

use SNMP; use Socket; # Set up the SNMP session. my %snmpparms = (); $snmpparms{DestHost} = inet_ntoa(inet_aton($host)); $snmpparms{Community} = 'public'; $snmpparms{UseSprintValue} = '1'; # readable! $snmpparms{Version} = '2'; # MUST USE 2 for GETBULK! my $sess = new SNMP::Session(%snmpparms); # I prefer to use VarLists for this sort of thing, since # we have enough confusion without making the actual # getbulk() call complicated. my @vbs = (); foreach my $mib ( 'sysName', 'sysDescr', 'sysLocation', 'sysUpTime', 'ifIndex', 'ifAdminStatus', 'ifOperStatus' ) { push @vbs, new SNMP::Varbind([$mib]); } my $vl = new SNMP::VarList(@vbs); # We'll keep our answers in these. my %sysStuff; my @ANSWERS; # Query the first four objects ONCE EACH, and store the # answers in the appropriate places in %sysStuff. # Then get ifIndex and ifAdminStatus 24 times and store # all of those reponses in @ANSWERS. ($sysStuff{Name}, $sysStuff{Descr}, $sysStuff{Location}, $sysStuff{UpTime}, @ANSWERS) = $sess->getbulk(4, 24, $vl); # AT LAST! # Always, always, always... if ( $sess->{ErrorNum} ) { die "Got ", $sess->{ErrStr}, " during getbulk.\n"; } # So $ANSWERS[0] now contains the first value of ifIndex. # $ANSWERS[1] contains the FIRST VALUE OF ifAdminStatus, # NOT the second value of ifIndex. # The remaining code could be MUCH simpler, but it's done # this way to illustrate how the answers are returned. my @INDEXES; my @STATUS; for ( my $x = 0 ; @ANSWERS ; $x++ ) { # Smart people would probably use map() for this. # I'm not that smart... $INDEXES[@INDEXES] = shift @ANSWERS; $STATUS[@STATUS] = shift @ANSWERS; # So we round-robin between @INDEXES and @STATUS, # thus "unstriping" the responses stored in @ANSWERS. } print "Name: $sysStuff{Name}\n"; print "Description: $sysStuff{Descr}\n"; print "Location: $sysStuff{Location}\n"; print "Uptime: $sysStuff{UpTime}\n"; print "\n"; # This now prints out clearly. for ( my $x = 0 ; $x <= $#INDEXES ; $x++ ) { print " INDEX: $INDEXES[$x] STATUS: $STATUS[$x]\n"; }

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.


Planned:

  1. Using the callback (SNMP in the background!)
  2. Using SNMPv3 (NO MORE CLEARTEXT 'PASSWORDS'!)

Quick and Dirty Synopses of Planned Stuff for the Impatient

Heh. 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.
  1. Using the callback - The short version of this is you pass get(), getnext(), or getbulk() a reference to a subroutine that should be executed when the response arrives (works with the SET commands as well). Then you run a waiting function to sit and allow them all to be processed. Can seriously speed up things like querying many different devices.
  2. SNMPv3 - User names, passwords, authentication, and privacy. Using the authentication feature means you may or may not have to supply a user name and password (as opposed to a plain ol' community) when you query the device. Using the privacy feature means you can set up an encrypted session (typically TLS) for your queries, so people can't see your user/pass or communities or the data you asked for, any of which could be considered sensitive data.
Sorry I don't have time (well, patience) to write more on these topics at the moment. I promise to get back to it, though. They say the best way to learn is to teach. Well, I've learned a bit myself while writing this. Suggestions on re-organizing and/or reformatting this document are welcomed. Flames and pointless criticisms can be sent to 127.0.0.1:25.

Replies are listed 'Best First'.
Re: Using the SNMP module from the Net-SNMP library
by jonnybe (Scribe) on Oct 04, 2004 at 18:23 UTC
    Kudos. Your text and code are easy to understand. I'll be looking forward to the final product. I'd love to be of help, but being a newb to SNMP, I will more likely benefit from it rather then contribute to it. That being said, I'll surely have plenty of questions. lol

    One request would be a terse description of SNMP in context of the tutorial.

    Jon

      hello, I am try to use asynchronous mode with the callback function. I don't know why but I can't access the returned values within the callback function. Do you know why ? Here's the code:
      my $res = $session->get("sysDescr.0",[\&call]); die $session->{ErrorStr} if ($session->{ErrorStr}); SNMP::MainLoop(); sub call { my $vlist = shift; print "ok\t0:'$vlist->[0][0]'\t1:'$val->[0][1]'\t2:'$val->[0][2]'\ +t3:'$val->[0][3]'\n"; print Dumper($vlist); SNMP::finish(); }
      It prints this:
      ok 0:'sysDescr' 1:'' 2:'' 3:'' $VAR1 = bless( [ bless( [ 'sysDescr', '0', 'Cisco Systems WS-C6513 Cisco Catalyst Operating System Software, Version 8.4(6) Copyright (c) 1995-2006 by Cisco Systems ', 'OCTETSTR' ], 'SNMP::Varbind' ) ], 'SNMP::VarList' );
      why only "sysDescr" is printed ? Thank you, I'm really stuck and I can't find any examples aon that anywhere ! kenwaz