Softpanorama

May the source be with you, but remember the KISS principle ;-)
Home Switchboard Unix Administration Red Hat TCP/IP Networks Neoliberalism Toxic Managers
(slightly skeptical) Educational society promoting "Back to basics" movement against IT overcomplexity and  bastardization of classic Unix

Perl SNMP

News SNMP Recommended Links References Security Perl Tools
Net-SNMP     Tivoli SNMP adapter Humor Etc

Perl and SNMP both have simple data types. Perl uses a scalar as its base type. Lists and hashes are just collections of scalars in Perl. In SNMP, you also work with scalar variables. SNMP variables can hold one of four primitive types: integers, strings, object identifiers (more on this in a moment), or null values. And just like Perl, in SNMP a set of related variables can be grouped together to form larger structures (most often tables). This is where their similarity ends.

Perl and SNMP diverge radically when we come to the subject of variable names. In Perl, you can, given a few restrictions, name your variables anything you'd like. SNMP variable names are considerably more restrictive. All SNMP variables exist within a virtual hierarchical storage structure known as the Management Information Base (MIB). All valid variable names are defined within this framework. The MIB, now at version MIB-II, defines a tree structure for all of the objects (and their names) that can be managed via SNMP.

MIB is similar to a filesystem. Instead of organizing files, the MIB logically organizes management information in a hierarchical tree-like structure with a single root like in Unix.  Each node in this tree has a short text string, called a label, and an accompanying number that represents its position at that level in the tree. To give you a sense of how this works, let's go find the SNMP variable in the MIB used to hold a system's description of itself. Bear with me; we have a bit of a tree walking (eight levels' worth) to get there.

It might be worth defining a few terms:
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).

There are also a few other things you' ll need to recognize in order to use SNMP correctly:

The simplest way to use SNMP from Perl is to call command-line programs like Net-SNMP.  This is very straightforward, no different then calling other external programs.

Note: if you use the community name as one of command line parameters  anyone who can list the process table may be able to see this community name. 

Another choice is to use a Perl SNMP module. There are at least three SNMP-related modules:


Top Visited
Switchboard
Latest
Past week
Past month

NEWS CONTENTS

Old News

[Jan 21, 2011] Using the SNMP module from the Net-SNMP library by Rhys (Pilgrim)

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. To include it use
use SNMP;

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.

Now, We' ll Add Enterprise MIB Files

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();

[download]

 

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 {} \\; " ;

[download]

 

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();

[download]

 
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)

[download]

 
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.

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" ; }

[download]

 
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" ; }

[download]

 
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);

[download]

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);

[download]

 
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" ; }

[download]

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

[download]

 

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"
;
 } 
		
	
    

[download]

 

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.

[Jan 21, 2011] Homebrew NMS Put It Together with Perl and NetSNMP by Charlie Schluting

August 29, 2007 | www.enterprisenetworkingplanet.com

Taking SNMP for a Walk

On to the useful stuff. Let's try out an SNMP query, using out old friend snmpwalk, which comes with the net-snmp software on most *nix hosts. The snmpget command is useful to get a single object, but if you want all information available down a certain branch of the tree, snmpwalk will do just that.

% snmpwalk -v2c -c public nermal 1.3.6.1.2.1.1.6
 SNMPv2-MIB::sysLocation.0 = STRING: "System administrators office"

Running snmpwalk on a specific OID, where nothing follows in the tree, is akin to using snmpget. However, if we bump it up a notch, we can get the entire system MIB:

% snmpwalk -v2c -c public nermal 1.3.6.1.2.1.1
 SNMPv2-MIB::sysDescr.0 = STRING: SunOS nermal.domain.com 5.10 Generic_118833-24 sun4u
 SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.3
 DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (338266757) 39 days, 3:37:47.57
 SNMPv2-MIB::sysContact.0 = STRING: "System administrator"
 …

Experiment with your network gear, and the private, instead of mgmt branch, under Internet to see what's available. In the Cisco MIB, we can get everything we'd ever want to know, including the ARP table, the Bridge table, and MAC address with port locations. Those are all standards-based OIDs, and the interesting Cisco-specific goodies are in the private tree, under 1.3.6.1.4.1.9.

Port numbers are difficult, since Cisco uses an interface identifier and a few layers of indirection to identify a specific port, but it is possible. Let's start off easier. We'll fetch the port descriptions (string assigned to a port) from our Cisco switch, so that we can ensure they are accurate based on our formatting standards and MAC address discovery information.

Put It All Together With Perl

Hopefully we know Perl already. We'll start by finding the OID we want. This is done by examining the MIBs from your switch's vendor, and painstakingly figuring out which object provides the information. Luckily I already know that IF-MIB::ifAlias.22 will give me the description for port alias number 22. Also, this isn't a vendor-specific item.This isn't the normal "Gi2/37" form we're used to, but it can be converted to the friendly string using other objects to dereference the ifAlias number 22.

% snmpwalk -v2c -c public switch_hostname 1.3.6.1.2.1.31.1.1.1.18.22
 IF-MIB::ifAlias.22 = STRING: nermal.domain.com [F324 60-18]

My computer's name is nermal, and ourport descriptions at work include two other pieces of information: the wall jack number, and room number. Now that we know what OID is required, it's time to pull all port descriptions from the entire switch, just: 'snmpwalk 1.3.6.1.2.1.31.1.1.1.18'.

In Perl, we simply need to define our OIDs, and connect to the agent on our Cisco to grab the information. Using the Net::SNMP module is the best way:

#!/usr/bin/perl
use Net::SNMP;
my $desc = '1.3.6.1.2.1.31.1.1.1.18;
($session, $error) = Net::SNMP->session(
                   -hostname => "switch",
                   -community => "public",
                   -timeout  => "30",
                   -port => "161");

if (!defined($session)) {
      printf("ERROR: %s.\n",  $error);
      exit 1;
}
my $response = $session->get_request($desc);
my %pdesc = %{$response};

my $err = $session->error;
if ($err){
   return 1;
}
print %pdesc;
exit 0;

That's all there is to it. Net::SNMP->session() creates an object, which can be used to call the get_table() method. If you want multiple OIDs in the same session, just define them as comma-seperated values, like so: get_request($desc,$uptime,$etc). The get_table() method returns a reference to a hash, by the way.

[Jan 21, 2011] Cacti • View topic - Perl SNMP polling script

Thanks for the comments, I'm posting a version 0.2 with more instructions and a couple of modifications.

Instructions:
* Please check/follow pre-requisites explained in my original post.
* For this example I'm assuming your putting this script in a file called: spine.pl which will be located at /usr/local/bin... remember to chmod 0755 spine.pl
* At the top of the script, please modify the configuration described as follows:

... ... ...

This perl script is great! Only problem I had with it is the fact that it breaks snmpv2 counters, which I depend on for some links. I put together a quick hack of the original (with all the changes listed in the posts above) that allows hcin and hcout to work correctly. (They were just using the snmpv1 OID's rather than the 64bit v2 OID's)

Change is minimal, and works for me.

Available as either the complete spine.pl or a diff at:

http://foo.oldbrownjeep.net/spine/

If you patch the original using the .diff file, it will leave your current settings in spine.pl, otherwise you will need to follow the directions posted above.

If anyone notices any errors with this - let me know. :)

-dallas

(Edited - wrong URL. Doh.)

[Jan 21, 2011] SNMP support for Perl 5

This package contains Perl 5 modules SNMP_Session.pm, BER.pm, and SNMP_util.pm which, when used together, provide rudimentary access to remote SNMP (v1/v2) agents.

This package is moving to Google Code hosting: http://code.google.com/p/snmp-session/

The library is featured in the book Essential SNMP by Douglas R. Mauro and Kevin J. Schmidt, July 2001, O'Reilly & Associates, ISBN: 0-59600020-0. You can buy it on-line at Amazon.

Features

This module differs from existing SNMP packages in that it is completely stand-alone, i.e. you don't need to have another SNMP package such as Net-SNMP. It is also written entirely in Perl, so you don't have to compile any C modules. It uses the Perl 5 Socket.pm module and should therefore be very portable, even to non-Unix systems.

Note: For the development of new scripts, I strongly recommend to use the higher-level programming interface provided by SNMP_util.pm. Its use is described in README.SNMP_util. The remainder of this page desribes the low-level API in SNMP_Session.pm, which you normally shouldn't use.

The SNMP operations currently supported are "get", "get-next", "get-bulk" and "set", as well as trap generation and reception.

For an excellent example of the type of application this is useful for, see Tobias Oetiker's ``mrtg'' (Multi Router Traffic Grapher) tool. Another application that uses this library is IOG (Input/Output Grapher).

Recent Changes:

For a list of changes, see the changes.html file packaged with this system.

Usage

The basic usage of these routines works like this:

use BER;
require 'SNMP_Session.pm';

# Set $host to the name of the host whose SNMP agent you want
# to talk to.  Set $community to the community name under
# which you want to talk to the agent.	Set port to the UDP
# port on which the agent listens (usually 161).

$session = SNMP_Session->open ($host, $community, $port)
    or die "couldn't open SNMP session to $host";

# Set $oid1, $oid2... to the BER-encoded OIDs of the MIB
# variables you want to get.

if ($session->get_request_response ($oid1, $oid2, ...)) {
    ($bindings) = $session->decode_get_response ($session->{pdu_buffer});

    while ($bindings ne '') {
	($binding,$bindings) = &decode_sequence ($bindings);
	($oid,$value) = &decode_by_template ($binding, "%O%@");
	print &pretty_print ($oid)," => ", &pretty_print ($value), "\n";
    }
} else {
    die "No response from agent on $host";
}

Encoding OIDs

In order to BER-encode OIDs, you can use the function BER::encode_oid. It takes (a vector of) numeric subids as an argument. For example,

use BER;
encode_oid (1, 3, 6, 1, 2, 1, 1, 1, 0)

will return the BER-encoded OID for the sysDescr.0 (1.3.6.1.2.1.1.1.0) instance of MIB-2.

Decoding the results

When get_request_response returns success, you must decode the response PDU from the remote agent. The function decode_get_response can be used to do this. It takes a get-response PDU, checks its syntax and returns the bindings part of the PDU. This is where the remote agent actually returns the values of the variables in your query.

You should iterate over the individual bindings in this bindings part and extract the value for each variable. In the example above, the returned bindings are simply printed using the BER::pretty_print function.

For better readability of the OIDs, you can also use the following idiom, where the %pretty_oids hash maps BER-encoded numerical OIDs to symbolic OIDs. Note that this simple-minded mapping only works for response OIDs that exactly match known OIDs, so it's unsuitable for table walking (where the response OIDs include an additional row index).

%ugly_oids = qw(sysDescr.0	1.3.6.1.2.1.1.1.0
		sysContact.0	1.3.6.1.2.1.1.4.0);
foreach (keys %ugly_oids) {
    $ugly_oids{$_} = encode_oid (split (/\./, $ugly_oids{$_}));
    $pretty_oids{$ugly_oids{$_}} = $_;
}
...
if ($session->get_request_response ($ugly_oids{'sysDescr.0'},
				    $ugly_oids{'sysContact.0'})) {
    ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
    while ($bindings ne '') {
	($binding,$bindings) = &decode_sequence ($bindings);
	($oid,$value) = &decode_by_template ($binding, "%O%@");
	print $pretty_oids{$oid}," => ",
	      &pretty_print ($value), "\n";
    }
} ...

Set Requests

Set requests are generated much like get or getNext requests are, with the exception that you have to specify not just OIDs, but also the values the variables should be set to. Every binding is passed as a reference to a two-element array, the first element being the encoded OID and the second one the encoded value. See the test/set-test.pl script for an example, in particular the subroutine snmpset.

Walking Tables

Beginning with version 0.57 of SNMP_Session.pm, there is API support for walking tables. The map_table method can be used for this as follows:

sub walk_function ($$$) {
  my ($index, $val1, $val3) = @_;
  ...
}

...
$columns = [$base_oid1, $base_oid3];
$n_rows = $session->map_table ($columns, \&walk_function);

The columns argument must be a reference to a list of OIDs for table columns sharing the same index. The method will traverse the table and call the walk_function for each row. The arguments for these calls will be:

  1. the row index as a partial OID in dotted notation, e.g. "1.3", or "10.0.1.34".
  2. the values of the requested table columns in that row, in BER-encoded form. If you want to use the standard pretty_print subroutine to decode the values, you can use the following idiom:
      grep (defined $_ && ($_=pretty_print $_), ($val1, $val3));
    

Walking Tables With get-bulk

Since version 0.67, SNMP_Session uses a different get_table implementation for SNMPv2c_Sessions. This version uses the ``powerful get-bulk operator'' to retrieve many table rows with each request. In general, this will make table walking much faster under SNMPv2c, especially when round-trip times to the agent are long.

There is one difficulty, however: With get-bulk, a management application can specify the maximum number of rows to return in a single response. SNMP_Session.pm provides a new function, map_table_4, in which this maxRepetitions value can be specified explicitly.

For maximum efficiency, it should be set to a value that is one greater than the number of rows in the table. If it is smaller, then map_table will use more request/response cycles than necessary; if it is bigger, the agent will have to compute variable bindings beyond the end of the table (which map_table will throw away).

Of course it is usually impossible to know the size of the table in advance. If you don't specify maxRepetitions when walking a table, then map_table will use a per-session default ($session->default_max_repetitions). The default value for this default is 12.

If you walk a table multiple times, and the size of the table is relatively stable, you should use the return value of map_table (which is the number of rows it has encountered) to compute the next value of maxRepetitions. Remember to add one so that map_table notices when the table is finished!

Note that for really big tables, this doesn't make a big difference, since the table won't fit in a single response packet anyway.

Sending Traps

To send a trap, you have to open an SNMP session to the trap receiver. Usually this is a process listening to UDP port 162 on a network management station. Then you can use the trap_request_send method to encode and send SNMPv1 traps. There is no way to find out whether the trap was actually received at the management station - SNMP traps are fundamentally unreliable.

When constructing an SNMPv1 trap, you must provide

For SNMPv2 traps, you need:

For SNMPv2 traps, the uptime and trap OID are encoded as bindings which are added to the front of the other bindings you provide.

Here is a short example:

my $trap_receiver = "netman.noc";
my $trap_community = "SNMP_Traps";
my $trap_session = $version eq '1'
    ? SNMP_Session->open ($trap_receiver, $trap_community, 162)
    : SNMPv2c_Session->open ($trap_receiver, $trap_community, 162);
my $myIpAddress = ...;
my $start_time = time;

...

sub link_down_trap ($$) {
  my ($if_index, $version) = @_;
  my $genericTrap = 2;		# linkDown
  my $specificTrap = 0;
  my @ifIndexOID = ( 1,3,6,1,2,1,2,2,1,1 );
  my $upTime = int ((time - $start_time) * 100.0);
  my @myOID = ( 1,3,6,1,4,1,2946,0,8,15 );

  warn "Sending trap failed"
    unless ($version eq '1')
	? $trap_session->trap_request_send (encode_oid (@myOID),
					    encode_ip_address ($myIpAddress),
					    encode_int ($genericTrap),
					    encode_int ($specificTrap),
					    encode_timeticks ($upTime),
					    [encode_oid (@ifIndex_OID,$if_index),
					     encode_int ($if_index)],
					    [encode_oid (@ifDescr_OID,$if_index),
					     encode_string ("foo")])
	    : $trap_session->v2_trap_request_send (\@linkDown_OID, $upTime,
						   [encode_oid (@ifIndex_OID,$if_index),
						    encode_int ($if_index)],
						   [encode_oid (@ifDescr_OID,$if_index),
						    encode_string ("foo")]);
}

Receiving Traps

Since version 0.60, SNMP_Session.pm supports the receipt and decoding of SNMPv1 trap requests. Since version 0.75, SNMPv2 Trap PDUs are also recognized.

To receive traps, you have to create a special SNMP session that passively listens on the SNMP trap transport address (usually UDP port 162). Then you can receive traps (actually, SNMPv1 traps, SNMPv2 traps, and SNMPv2 informs) using the receive_trap method and decode them using decode_trap_request. The enterprise, agent, generic, specific and sysUptime return values are only defined for SNMPv1 traps. In SNMPv2 traps and informs, the equivalent information is contained in the bindings.

my $trap_session = SNMPv1_Session->open_trap_session ()
  or die "cannot open trap session";
my ($trap, $sender_addr, $sender_port) = $trap_session->receive_trap ()
  or die "cannot receive trap";
my ($community, $enterprise, $agent,
    $generic, $specific, $sysUptime, $bindings)
  = $trap_session->decode_trap_request ($trap)
    or die "cannot decode trap received"
...
my ($binding, $oid, $value);
while ($bindings ne '') {
    ($binding,$bindings) = &decode_sequence ($bindings);
    ($oid, $value) = decode_by_template ($binding, "%O%@");
    print BER::pretty_oid ($oid)," => ",pretty_print ($value),"\n";
}

Future Plans

SNMPv3 Support

The code could first be restructured to follow the modularization proposed in RFC 2271 (An Architecture for Describing SNMP Management Frameworks). The existing SNMPv1 and SNMPv2c support must somehow be retrofitted to this framework. Later, one could add support for SNMPv3 PDU formats and for user-based security.

Higher-Level APIs

The current programming interface is very close to the level of SNMP operations and PDUs. For actual management applications, there are probably more convenient interfaces that could be defined.

The 20-Minute SNMP Tutorial - Automating System Administration with Perl - O'Reilly Media

(Perl for System Administration, first edition 2001)

Let's start with a small Perl example. If we need to know the number of interfaces a particular device has, we could query the interfaces.ifNumber variable. Using Net::SNMP, it is this easy:

use Net::SNMP;

# requires a hostname and a community string as its arguments
($session,$error) = Net::SNMP->session(Hostname => $ARGV[0],
                                       Community => $ARGV[1]);

die "session error: $error" unless ($session);

# iso.org.dod.internet.mgmt.mib-2.interfaces.ifNumber.0 = 
#   1.3.6.1.2.1.2.1.0
$result = $session->get_request("1.3.6.1.2.1.2.1.0");

die "request error: ".$session->error unless (defined $result);

$session->close;

print "Number of interfaces: ".$result->{"1.3.6.1.2.1.2.1.0"}."\n";

When pointed at a workstation with an Ethernet and a loopback interface, it will print Numberofinterfaces:2; a laptop with Ethernet, loopback, and PPP interfaces returns Number of interfaces: 3; and a small router returns Number of interfaces: 7.

One key thing to notice is the use of Object Identifiers (OIDs) instead of variable names. Both Net::SNMP and SNMP_Session.pm handle SNMP protocol interactions only. They make no pretense of handling the peripheral SNMP-related tasks like parsing SNMP MIB descriptions. For this functionality you will have to look to other modules such as SNMP::MIB::Compiler or SNMP_util.pm by Mike Mitchell for use with SNMP_Session.pm (not to be confused with SNMP::Util by Wayne Marquette, for use with the SNMP module).

If you want to use textual identifiers instead of numeric OIDs without coding in the mapping yourself or using an additional module, your only choice is to use the SNMP module, which has a built-in MIB parser. Let's do a table walk of the Address Resolution Protocol (ARP) table of a machine using this module:

use SNMP;

# requires a hostname and a community string as its arguments
$session = new SNMP::Session(DestHost => $ARGV[0], Community => $ARGV[1],
                             UseSprintValue => 1);

die "session creation error: $SNMP::Session::ErrorStr" unless 
  (defined $session);

# set up the data structure for the getnext command
$vars = new SNMP::VarList(['ipNetToMediaNetAddress'],
                          ['ipNetToMediaPhysAddress']);

# get first row
($ip,$mac) = $session->getnext($vars);
die $session->{ErrorStr} if ($session->{ErrorStr});

# and all subsequent rows
while (!$session->{ErrorStr} and 
       $$vars[0]->tag eq "ipNetToMediaNetAddress"){
    print "$ip -> $mac\n";
    ($ip,$mac) = $session->getnext($vars);
};

Here's an example of the output this produces:

192.168.1.70 -> 8:0:20:21:40:51
192.168.1.74 -> 8:0:20:76:7c:85
192.168.1.98 -> 0:c0:95:e0:5c:1c

This code looks similar to the previous Net::SNMP example. We'll walk through it to highlight the differences:

use SNMP;

$session = new SNMP::Session(DestHost => $ARGV[0], Community => $ARGV[1],
                             UseSprintValue => 1);

After loading the SNMP module, we create a session object just like we did in the Net::SNMP example. The additional UseSprintValue => 1 argument just tells the SNMP module to pretty-print the return values. If we didn't do this, the Ethernet addresses listed above would be printed in an encoded form.

# set up the data structure for the getnext command
$vars = new SNMP::VarList(['ipNetToMediaNetAddress'],
                          ['ipNetToMediaPhysAddress']);

SNMP is willing to use simple strings like sysDescr.0 with its commands, but it prefers to use a special object it calls a "Varbind." It uses these objects to store return values from queries. For example, the code we're looking at calls the getnext( ) method to send a get-next-request, just like in the IP route table example in Appendix E, "The Twenty-Minute SNMP Tutorial". Except this time, SNMP will store the returned indices in a Varbind for us so we don't have to keep track of them by hand. With this module, you can just hand the Varbind back to the getnext method each time you want the next value.

Varbinds are simply anonymous Perl arrays with four elements: obj, iid , val, and type. For our purposes, we only need to worry about obj and iid. The first element, obj, is the object you are querying. obj can be specified in one of several formats. In this case, we are using a leaf identifier format, i.e., specifying the leaf of the tree we are concerned with. IpNetToMediaNetAddress is the leaf of the tree:

.iso.org.dod.internet.mgmt.mib-2.ip.ipNetToMediaTable.ipNetToMediaEntry.ipNetToMediaNetAddress

The second element in a Varbind is the iid, or instance identifier. In our previous discussions, we've always used a 0 here (e.g., system.sysDescr.0), because we've only seen objects that have a single instance. Shortly we'll see examples where the iid can be something other than 0. For instance, later on we'll want to refer to a particular network interface on a multi-interface Ethernet switch. obj and iid are the only two parts of a Varbind you need to specify for a get. getnext does not need an iid since it will return the next instance by default.

The line of code above uses VarList( ), which creates a list of two Varbinds, each with just the obj element filled in. We feed this list to the getnext( ) method:

# get first row
($ip,$mac) = $session->getnext($vars);
die $session->{ErrorStr} if ($session->{ErrorStr});

getnext( ) returns the values it received back from our request and updates the Varbind data structures accordingly. Now it is just a matter of calling getnext( ) until we fall off the end of the table:

while (!$session->{ErrorStr} and 
       $$vars[0]->tag eq "ipNetToMediaNetAddress"){
        print "$ip -> $mac\n";
       ($ip,$mac) = $session->getnext($vars);
};

For our final SNMP example, let's return to the world of security. We'll pick a task that would be tricky, or at least annoying, to do well with the command-line SNMP utilities.

Here's the scenario: you've been asked to track down a misbehaving user on your switched Ethernet network. The only info you have is the Ethernet address of the machine that user is on. It's not an Ethernet address you have on file (which could be kept in our host database from Chapter 5, "TCP/IP Name Services" if we extended it), and you can't sniff your switched net, so you are going to have to be a little bit clever about tracking this machine down. Your best bet in this case may be to ask one or all of your Ethernet switches if they've seen that address on one of their ports.

Just to make this example more concrete so we can point at specific MIB variables, we'll say that your network consists of several Cisco Catalyst 5500 switches. The basic methodology we're going to use to solve this problem will apply to other products and other vendors as well. Any switch or vendor-specific information will be noted as we go along. Let's walk through this problem step by step.

As before, first we have to go search through the correct MIB module files. With a little jumpstart from Cisco's tech support, we realize we'll need to access four separate objects:

Why four different tables? Each table has a piece to contribute to the answer, but no one table has the specific information we seek. The first table provides us with a list of the VLANS (Virtual Local Area Networks), or virtual "network segments," on the switch. Cisco has chosen to keep separate tables for each VLAN on a switch, so we will need to query for information one VLAN at a time. More on this in a moment.

The second table provides us with a list of Ethernet addresses and the number of the switch's bridge port on which each address was last seen. Unfortunately, a bridge port number is an internal reckoning for the switch; it does not correspond to the name of a physical port on that switch. We need to know the physical port name, i.e., from which card and port the machine with that Ethernet address last spoke, so we have to dig further.

There is no table that maps bridge port to physical port name (that would be too easy), but the dot1dBasePortTable can provide a bridge port to interface number mapping. Once we have the interface number, we can look it up in ifXTable and retrieve the port name.

Figure 10-1 shows a picture of the four-layer deference necessary to perform our desired task.

figure

Figure 10.1. The set of SNMP queries needed to find the port name on a Cisco 5000

Here's the code to put these four tables together to dump the information we need:

use SNMP;

# These are the extra MIB module files we need, found in the same 
# directory as this script
$ENV{'MIBFILES'}=
  "CISCO-SMI.my:FDDI-SMT73-MIB.my:CISCO-STACK-MIB.my:BRIDGE-MIB.my";

# Connect and get the list of VLANs on this switch
$session = new SNMP::Session(DestHost => $ARGV[0], 
                             Community => $ARGV[1]);
die "session creation error: $SNMP::Session::ErrorStr" unless 
  (defined $session);

# enterprises.cisco.workgroup.ciscoStackMIB.vlanGrp.vlanTable.vlanEntry 
# in CISCO-STACK-MIB
$vars = new SNMP::VarList(['vlanIndex']);
                          
$vlan = $session->getnext($vars);
die $session->{ErrorStr} if ($session->{ErrorStr});

while (!$session->{ErrorStr} and $$vars[0]->tag eq "vlanIndex"){

    # VLANS 1000 and over are not "real" ON A CISCO CATALYST 5XXX
    # (this limit is likely to be different on different switches)
    push(@vlans,$vlan) if $vlan < 1000;

    $vlan = $session->getnext($vars);
};

undef $session,$vars;

# for each VLAN, query for the bridge port, the interface number 
# associated with that port, and then the interface name for that 
# port number
foreach $vlan (@vlans){
    # note our use of "community string indexing" as part 
    # of the session setup
    $session = new SNMP::Session(DestHost => $ARGV[0], 
                                 Community => $ARGV[1]."@".$vlan,
                                 UseSprintValue => 1);

    die "session creation error: $SNMP::Session::ErrorStr" 
      unless (defined $session);
  
    # from transparent forwarding port table at 
    # dot1dBridge.dot1dTp.dot1dTpFdbTable.dot1dTpFdbEntry 
    # in RFC1493 BRIDGE-MIB
    $vars = new SNMP::VarList(['dot1dTpFdbAddress'],['dot1dTpFdbPort']);

    ($macaddr,$portnum) = $session->getnext($vars);
    die $session->{ErrorStr} if ($session->{ErrorStr});

    while (!$session->{ErrorStr} and 
           $$vars[0]->tag eq "dot1dTpFdbAddress"){

        # dot1dBridge.dot1dBase.dot1dBasePortTable.dot1dBasePortEntry
        # in RFC1493 BRIDGE-MIB
        $ifnum = 
          (exists $ifnum{$portnum}) ? $ifnum{$portnum} :
            ($ifnum{$portnum} = 
               $session->get("dot1dBasePortIfIndex\.$portnum"));

        # from ifMIB.ifMIBObjects.ifXTable.ifXEntry in RFC1573 IF-MIB
        $portname = 
          (exists $portname{$ifnum}) ? $portname{$ifnum} :
            ($portname{$ifnum}=$session->get("ifName\.$ifnum"));            

        print "$macaddr on VLAN $vlan at $portname\n";

        ($macaddr,$portnum) = $session->getnext($vars);
    };

    undef $session, $vars, %ifnum, %portname;
}

If you've read Appendix E, "The Twenty-Minute SNMP Tutorial", most of this code will look familiar. Here are some comments on the new stuff:

$ENV{'MIBFILES'}=
  "CISCO-SMI.my:FDDI-SMT73-MIB.my:CISCO-STACK-MIB.my:BRIDGE-MIB.my";

This code sets the MIBFILES environment variable for the UCD-SNMP package library. When set, this variable instructs the library to parse the listed set of additional files for MIB object definitions. The only strange MIB module file in that list is FDDI-SMT73-MIB.my. This is included because CISCO-STACK-MIB.my has the following statement at the top to include certain definitions from other MIB entries:

IMPORTS
        MODULE-IDENTITY, OBJECT-TYPE, Integer32, IpAddress, TimeTicks,
        Counter32, Counter64, NOTIFICATION-TYPE
                FROM SNMPv2-SMI
        DisplayString, RowStatus
                FROM SNMPv2-TC
        fddimibPORTSMTIndex, fddimibPORTIndex
                FROM FDDI-SMT73-MIB
        OwnerString
                FROM IF-MIB
        MODULE-COMPLIANCE, OBJECT-GROUP
                FROM SNMPv2-CONF
        workgroup
                FROM CISCO-SMI;

Even though we don't reference any objects that use fddimibPORTSMTIndex or fddimibPORTIndex, we still (by choice) include that file in the file list to keep the MIB parser from complaining. All of the other MIB definitions in this IMPORTS statement are included either in our parse list or the library's default list. You often need to look for the IMPORTS section of a MIB module to see that module's dependencies when going MIB groveling.

Moving on in our code, here's another strange statement:

$session = new SNMP::Session(DestHost => $ARGV[0], 
                             Community => $ARGV[1]."@".$vlan,
                             UseSprintValue => 1);

Instead of just passing on the community name as provided by the user, we're appending something of the form @VLAN-NUMBER. In Cisco parlance, this is "community string indexing." When dealing with VLANs and bridging, Cisco devices keep track of several "instances" of the MIB, one for each VLAN. Our code makes the same queries once per each VLAN found on the switch:

$ifnum =  
         (exists $ifnum{$portnum}) ? $ifnum{$portnum} :
            ($ifnum{$portnum} = 
                $session->get("dot1dBasePortIfIndex\.$portnum"));

Two comments on this piece of code. First, for variety's sake, we're using a simple string argument to get( ). We could easily have used something more Varbind-ish:

($ifnum{$portnum}=$session->get(['dot1dBasePortIfIndex',$portnum]));

Second, note that we're doing some very simple caching here. Before we actually perform a get( ), we look in a simple hash table (%ifnum) to see if we've already made this query. If we haven't, we make the query and populate the hash table with the result. At the end of each VLAN pass, we delete the cache hash (undef%ifnum) to prevent previous VLAN information from providing false information.

This is a good technique to remember when programming SNMP code. It is important to query as little and as seldom as possible if you want to be kind to your network and network devices. A device may have to take horsepower away from its usual tasks to respond to your slew of queries if you are not prudent.

Here's an excerpt from our code in action:

"00 10 1F 2D F8 FB " on VLAN 1 at 1/1
"00 10 1F 2D F8 FD " on VLAN 1 at 1/1
"08 00 36 8B A9 03 " on VLAN 115 at 2/18
"08 00 36 BA 16 03 " on VLAN 115 at 2/3
"08 00 36 D1 CB 03 " on VLAN 115 at 2/15

It's not hard to see how this program could be enhanced. Besides prettier or more orderly output, it could save state between runs. Each time it ran, the program could let you know how things have changed: new addresses appearing, ports being changed, etc. One quick caveat: most switches are of the "learning" variety, so they will age out entries for addresses that they haven't heard from in a while. This just means that your program will need to run at least as often as the standard port aging time.


Recommended Links



Etc

Society

Groupthink : Two Party System as Polyarchy : Corruption of Regulators : Bureaucracies : Understanding Micromanagers and Control Freaks : Toxic Managers :   Harvard Mafia : Diplomatic Communication : Surviving a Bad Performance Review : Insufficient Retirement Funds as Immanent Problem of Neoliberal Regime : PseudoScience : Who Rules America : Neoliberalism  : The Iron Law of Oligarchy : Libertarian Philosophy

Quotes

War and Peace : Skeptical Finance : John Kenneth Galbraith :Talleyrand : Oscar Wilde : Otto Von Bismarck : Keynes : George Carlin : Skeptics : Propaganda  : SE quotes : Language Design and Programming Quotes : Random IT-related quotesSomerset Maugham : Marcus Aurelius : Kurt Vonnegut : Eric Hoffer : Winston Churchill : Napoleon Bonaparte : Ambrose BierceBernard Shaw : Mark Twain Quotes

Bulletin:

Vol 25, No.12 (December, 2013) Rational Fools vs. Efficient Crooks The efficient markets hypothesis : Political Skeptic Bulletin, 2013 : Unemployment Bulletin, 2010 :  Vol 23, No.10 (October, 2011) An observation about corporate security departments : Slightly Skeptical Euromaydan Chronicles, June 2014 : Greenspan legacy bulletin, 2008 : Vol 25, No.10 (October, 2013) Cryptolocker Trojan (Win32/Crilock.A) : Vol 25, No.08 (August, 2013) Cloud providers as intelligence collection hubs : Financial Humor Bulletin, 2010 : Inequality Bulletin, 2009 : Financial Humor Bulletin, 2008 : Copyleft Problems Bulletin, 2004 : Financial Humor Bulletin, 2011 : Energy Bulletin, 2010 : Malware Protection Bulletin, 2010 : Vol 26, No.1 (January, 2013) Object-Oriented Cult : Political Skeptic Bulletin, 2011 : Vol 23, No.11 (November, 2011) Softpanorama classification of sysadmin horror stories : Vol 25, No.05 (May, 2013) Corporate bullshit as a communication method  : Vol 25, No.06 (June, 2013) A Note on the Relationship of Brooks Law and Conway Law

History:

Fifty glorious years (1950-2000): the triumph of the US computer engineering : Donald Knuth : TAoCP and its Influence of Computer Science : Richard Stallman : Linus Torvalds  : Larry Wall  : John K. Ousterhout : CTSS : Multix OS Unix History : Unix shell history : VI editor : History of pipes concept : Solaris : MS DOSProgramming Languages History : PL/1 : Simula 67 : C : History of GCC developmentScripting Languages : Perl history   : OS History : Mail : DNS : SSH : CPU Instruction Sets : SPARC systems 1987-2006 : Norton Commander : Norton Utilities : Norton Ghost : Frontpage history : Malware Defense History : GNU Screen : OSS early history

Classic books:

The Peter Principle : Parkinson Law : 1984 : The Mythical Man-MonthHow to Solve It by George Polya : The Art of Computer Programming : The Elements of Programming Style : The Unix Hater’s Handbook : The Jargon file : The True Believer : Programming Pearls : The Good Soldier Svejk : The Power Elite

Most popular humor pages:

Manifest of the Softpanorama IT Slacker Society : Ten Commandments of the IT Slackers Society : Computer Humor Collection : BSD Logo Story : The Cuckoo's Egg : IT Slang : C++ Humor : ARE YOU A BBS ADDICT? : The Perl Purity Test : Object oriented programmers of all nations : Financial Humor : Financial Humor Bulletin, 2008 : Financial Humor Bulletin, 2010 : The Most Comprehensive Collection of Editor-related Humor : Programming Language Humor : Goldman Sachs related humor : Greenspan humor : C Humor : Scripting Humor : Real Programmers Humor : Web Humor : GPL-related Humor : OFM Humor : Politically Incorrect Humor : IDS Humor : "Linux Sucks" Humor : Russian Musical Humor : Best Russian Programmer Humor : Microsoft plans to buy Catholic Church : Richard Stallman Related Humor : Admin Humor : Perl-related Humor : Linus Torvalds Related humor : PseudoScience Related Humor : Networking Humor : Shell Humor : Financial Humor Bulletin, 2011 : Financial Humor Bulletin, 2012 : Financial Humor Bulletin, 2013 : Java Humor : Software Engineering Humor : Sun Solaris Related Humor : Education Humor : IBM Humor : Assembler-related Humor : VIM Humor : Computer Viruses Humor : Bright tomorrow is rescheduled to a day after tomorrow : Classic Computer Humor

The Last but not Least Technology is dominated by two types of people: those who understand what they do not manage and those who manage what they do not understand ~Archibald Putt. Ph.D


Copyright © 1996-2021 by Softpanorama Society. www.softpanorama.org was initially created as a service to the (now defunct) UN Sustainable Development Networking Programme (SDNP) without any remuneration. This document is an industrial compilation designed and created exclusively for educational use and is distributed under the Softpanorama Content License. Original materials copyright belong to respective owners. Quotes are made for educational purposes only in compliance with the fair use doctrine.

FAIR USE NOTICE This site contains copyrighted material the use of which has not always been specifically authorized by the copyright owner. We are making such material available to advance understanding of computer science, IT technology, economic, scientific, and social issues. We believe this constitutes a 'fair use' of any such copyrighted material as provided by section 107 of the US Copyright Law according to which such material can be distributed without profit exclusively for research and educational purposes.

This is a Spartan WHYFF (We Help You For Free) site written by people for whom English is not a native language. Grammar and spelling errors should be expected. The site contain some broken links as it develops like a living tree...

You can use PayPal to to buy a cup of coffee for authors of this site

Disclaimer:

The statements, views and opinions presented on this web page are those of the author (or referenced source) and are not endorsed by, nor do they necessarily reflect, the opinions of the Softpanorama society. We do not warrant the correctness of the information provided or its fitness for any purpose. The site uses AdSense so you need to be aware of Google privacy policy. You you do not want to be tracked by Google please disable Javascript for this site. This site is perfectly usable without Javascript.

Last modified: March 12, 2019