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.
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.
#!/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.
Your mileage will vary both among different MIB objects and different agents you query. I have seen different routers respond at least three different ways to queries for the same MIB object. (Each router answers the same all the time, but the different routers' answers differ from each other.) In theory, this shouldn't happen, since the same MIB object should be defined the same way in every case, so the routers should all respond in the same way. I have learned to be paranoid about this anyway, and make no assumptions about the returned data.
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.
&SNMP::addMibFiles("/path/to/some/file.mib"); &SNMP::initMib();
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).
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.
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:# 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();
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.Cannot find module (THIS-AND-THAT): At line 1 in (none)
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} = 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"; }
Note that it's almost entirely the same except that UseSprintValue is set to zero when the SNMP session object is created.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"; }
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.
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 = ('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);
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.@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"; }
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.
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.
|
---|
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 | |
by Anonymous Monk on Feb 27, 2008 at 13:45 UTC |