11.12. SNMP: The Object-Oriented WayThe SNMP::Info Perl package was developed at the University of California, Santa Cruz. The official web site for this package is http://snmp-info.sourceforge.net. SNMP::Info is based on the Net-SNMP Perl module. It allows you to obtain various information from a device without having to know any OIDs, MIBs, etc., and it does so with object orientation (OO). How does it do this? It supports a well-developed list of MIBs, and it can discover the type of device you are trying to query. If it knows about the device, you can use predefined methods to get interface information and other things. Here's an example script that gathers information about interfaces on a switch: #!/usr/bin/perl use SNMP::Info; my $info = new SNMP::Info( # Auto Discover more specific Device Class AutoSpecify => 1, Debug => 0, # The rest is passed to SNMP::Session DestHost => '192.168.0.148', Community => 'public', Version => 2 ) or die "Can't connect to device.\n"; my $err = $info->error( ); die "SNMP Community or Version probably wrong connecting to device. $err\n" if defined $err; $name = $info->name( ); $class = $info->class( ); print "SNMP::Info is using this device class : $class\n"; # Find out the Duplex status for the ports my $interfaces = $info->interfaces( ); my $i_duplex = $info->i_duplex( ); # Get CDP Neighbor info my $c_if = $info->c_if( ); my $c_ip = $info->c_ip( ); my $c_port = $info->c_port( ); # Print out data per port foreach my $iid (keys %$interfaces){ my $duplex = $i_duplex->{$iid}; # Print out physical port name, not snmp iid my $port = $interfaces->{$iid}; # The CDP Table has table entries different from the interface tables. # So we use c_if to get the map from the cdp table to the interface table. my %c_map = reverse %$c_if; my $c_key = $c_map{$iid}; my $neighbor_ip = $c_ip->{$c_key}; my $neighbor_port = $c_port->{$c_key}; print "$port: $duplex duplex"; print " connected to $neighbor_ip / $neighbor_port\n" if defined $remote_ip; print "\n"; } And here's the output: $ ./getinterfaceinfo.pl SNMP::Info is using this device class : SNMP::Info::Layer2::C2900 FastEthernet0/5: half duplex FastEthernet0/10: half duplex FastEthernet0/2: full duplex FastEthernet0/6: half duplex FastEthernet0/8: half duplex FastEthernet0/1: half duplex FastEthernet0/11: half duplex Null0: duplex VLAN2: duplex FastEthernet0/7: half duplex FastEthernet0/3: half duplex VLAN1: duplex FastEthernet0/9: half duplex FastEthernet0/12: half duplex FastEthernet0/4: half duplex $ Let's talk a little bit about the script. First, we create an SNMP::Info object, with such typical parameters as the host we are querying, community string, and SNMP version. The constructor for SNMP::Info also has an AutoSpecify parameter, which instructs the session we create to try and discover the device class. This code will get the specific device class: $class = $info->class( ); The following code will get all the interfaces from the remote host: my $interfaces = $info->interfaces( ); It doesn't get any easier than that. Let's look at what each OO method does, according to the SNMP::Info documentation:
The rest of the program is just a foreach loop that runs through all the data structures. Now let's look at another script: #!/usr/bin/perl use SNMP::Info; my $bridge = new SNMP::Info ( AutoSpecify => 1, Debug => 0, DestHost => '192.168.0.148', Community => 'public', Version => 2 ); my $class = $bridge->class( ); print " Using device sub class : $class\n"; # Grab Forwarding Tables my $interfaces = $bridge->interfaces( ); my $fw_mac = $bridge->fw_mac( ); my $fw_port = $bridge->fw_port( ); my $bp_index = $bridge->bp_index( ); foreach my $fw_index (keys %$fw_mac){ my $mac = $fw_mac->{$fw_index}; my $bp_id = $fw_port->{$fw_index}; my $iid = $bp_index->{$bp_id}; my $port = $interfaces->{$iid}; print "Port:$port forwarding to $mac\n"; } And its output: $ ./bridge.pl Using device sub class : SNMP::Info::Layer2::C2900 Port:FastEthernet0/12 forwarding to 00:04:9a:da:5e:4c Port:FastEthernet0/10 forwarding to 00:04:9a:da:5e:4a Port:FastEthernet0/2 forwarding to 00:0f:1f:d3:c6:3a Port:FastEthernet0/2 forwarding to 00:11:43:04:a5:25 Port:FastEthernet0/11 forwarding to 00:04:9a:da:5e:4b Port:FastEthernet0/2 forwarding to 00:0f:20:41:b8:ed Port:FastEthernet0/2 forwarding to 00:11:43:04:c2:e6 Port:FastEthernet0/2 forwarding to 00:0b:db:d2:d6:10 Port:FastEthernet0/2 forwarding to 00:0f:1f:df:ef:f9 $ This script prints port-forwarding information for a bridge. Let's look at some of the methods used in this script:
Of course, once we've run these methods, we just use a foreach loop to access the data structures and print the forwarding details. 11.12.1. Extending SNMP::InfoSNMP::Info is great for talking to network devices. But what if you want to, say, talk to Unix systems and obtain more information than what is provided by RFC 1213 and other similar MIBs? Well, luckily the author of SNMP::Info has made it quite easy to extend. We decided to create a module called HostResources, which makes use of some of the Host Resources objects. Here's the entire Perl module: package SNMP::Info::HostResources; $VERSION = 1.0; use strict; use Exporter; use SNMP::Info; @SNMP::Info::HostResources::ISA = qw/SNMP::Info Exporter/; @SNMP::Info::HostResources::EXPORT_OK = qw//; use vars qw/$VERSION %FUNCS %GLOBALS %MIBS %MUNGE $AUTOLOAD $INIT $DEBUG/; %MIBS = (%SNMP::Info::MIBS, 'HOST-RESOURCES-MIB' => 'host', ); %GLOBALS = (%SNMP::Info::GLOBALS, 'hr_users' => 'hrSystemNumUsers', 'hr_processes' => 'hrSystemProcesses', 'hr_date' => 'hrSystemDate', ); %FUNCS = (%SNMP::Info::FUNCS, # HostResources MIB objects 'hr_sindex' => 'hrStorageIndex', 'hr_sdescr' => 'hrStorageDescr', 'hr_sused' => 'hrStorageUsed', ); %MUNGE = (%SNMP::Info::MUNGE, 'hr_date' => \&munge_hrdate, ); sub munge_hrdate { my($oct) = @_; # # hrSystemDate has a syntax of DateAndTime, which is defined in SNMPv2-TC as # #DateAndTime ::= TEXTUAL-CONVENTION # DISPLAY-HINT "2d-1d-1d,1d:1d:1d.1d,1a1d:1d" # STATUS current # DESCRIPTION # "A date-time specification. # # field octets contents range # ----- ------ -------- ----- # 1 1-2 year* 0..65536 # 2 3 month 1..12 # 3 4 day 1..31 # 4 5 hour 0..23 # 5 6 minutes 0..59 # 6 7 seconds 0..60 # (use 60 for leap-second) # 7 8 deci-seconds 0..9 # 8 9 direction from UTC '+' / '-' # 9 10 hours from UTC* 0..13 # 10 11 minutes from UTC 0..59 # # * Notes: # - the value of year is in network-byte order # - daylight savings time in New Zealand is +13 # # For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be # displayed as: # # 1992-5-26,13:30:15.0,-4:0 # # Note that if only local time is known, then timezone # information (fields 8-10) is not present." # SYNTAX OCTET STRING (SIZE (8 | 11)) # my ($year1, $year2, $month, $day, $hour, $min, $secs, $decisecs, $direction, $hoursFromUTC, $minFromUTC) = split(/ /, sprintf("%d %d %d %d %d %d %d %d %d %d %d",unpack('C*',$oct))); my $value = 0; $direction = chr($direction); $value = $value * 256 + $year1; $value = $value * 256 + $year2; my $year = $value; return "$year-$month-$day,$hour:$min:$secs:$decisecs,$direction$hoursFromUTC: $minFromUTC"; } 1; # don't forget this line Let's take a look at this module. The first line (package SNMP::Info::HostResources;) names the module and the package we are going to be a part ofin this case, SNMP::Info. The %MIBS hash configures SNMP::Info with a list of various MIBs you will use in the module. Here we loaded HOST-RESOURCES-MIB and gave it the name of the top-level OID in that MIB, host. %SNMP::Info::MIBS loads global SNMP::Info MIBs. The %GLOBALS hash lists scalar OIDs that we plan to usei.e., OIDs that are not part of a column in a table. %SNMP::Info::GLOBALS loads SNMP::Info globals. These globals include RFC 1213 objects sysUptime, sysDescr, etc. hrSystemNumUsers is: hrSystemNumUsers OBJECT-TYPE SYNTAX Gauge32 MAX-ACCESS read-only STATUS current DESCRIPTION "The number of user sessions for which this host is storing state information. A session is a collection of processes requiring a single act of user authentication and possibly subject to collective job control." ::= { hrSystem 5 } hrSystemProcesses is: hrSystemProcesses OBJECT-TYPE SYNTAX Gauge32 MAX-ACCESS read-only STATUS current DESCRIPTION "The number of process contexts currently loaded or running on this system." ::= { hrSystem 6 } hrSystemDate is: hrSystemDate OBJECT-TYPE SYNTAX DateAndTime MAX-ACCESS read-write STATUS current DESCRIPTION "The host's notion of the local date and time of day." ::= { hrSystem 2 } The %FUNCS hash lists columnar OIDs to query. In this example, we query two items from the hrStorageTable. hrStorageTable is defined as: hrStorageTable OBJECT-TYPE SYNTAX SEQUENCE OF HrStorageEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "The (conceptual) table of logical storage areas on the host. An entry shall be placed in the storage table for each logical area of storage that is allocated and has fixed resource limits. The amount of storage represented in an entity is the amount actually usable by the requesting entity, and excludes loss due to formatting or file system reference information. These entries are associated with logical storage areas, as might be seen by an application, rather than physical storage entities which are typically seen by an operating system. Storage such as tapes and floppies without file systems on them are typically not allocated in chunks by the operating system to requesting applications, and therefore shouldn't appear in this table. Examples of valid storage for this table include disk partitions, file systems, ram (for some architectures this is further segmented into regular memory, extended memory, and so on), backing store for virtual memory ('swap space'). This table is intended to be a useful diagnostic for 'out of memory' and 'out of buffers' types of failures. In addition, it can be a useful performance monitoring tool for tracking memory, disk, or buffer usage." ::= { hrStorage 3 } hrStorageIndex is: hrStorageIndex OBJECT-TYPE SYNTAX Integer32 (1..2147483647) MAX-ACCESS read-only STATUS current DESCRIPTION "A unique value for each logical storage area contained by the host." ::= { hrStorageEntry 1 } hrStorageDescr is: hrStorageDescr OBJECT-TYPE SYNTAX DisplayString MAX-ACCESS read-only STATUS current DESCRIPTION "A description of the type and instance of the storage described by this entry." ::= { hrStorageEntry 3 } hrStorageUsed is: hrStorageUsed OBJECT-TYPE SYNTAX Integer32 (0..2147483647) MAX-ACCESS read-only STATUS current DESCRIPTION "The amount of the storage represented by this entry that is allocated, in units of hrStorageAllocationUnits." ::= { hrStorageEntry 6 } We need hrStorageIndex as a way to access hrStorageDescr and hrStorageUsed, which will be indexed based on hrStorageIndex. %MUNGE lists methods, in your module, that will be called based on values in the %GLOBALS or %FUNCS hashes. For example, in the initialization for %MUNGE, we have: 'hr_date' => \&munge_hrdate, 'hr_date' is also defined in %GLOBALS. Once SNMP::Info performs the actual SNMP operation on the OID for this identifier, it will call munge_hrdate and pass it the value that was retrieved. The basic reason for having this %MUNGE data structure is to allow for routines to be defined that can perform extended processing. Take the hrSystemDate OID. Its SYNTAX is DateAndTime, which is a textual convention defined in the SNMPv2-TC MIB. The actual definition for DateAndTime is provided in the subroutine in the module, but basically we are handed a raw OctetString and we must decode the string and format the date and time as specified by this textual convention for DateAndTime. Note that the subroutine returns the converted value. This is an important step, so don't forget it. Once your module is done, you need to modify some SNMP::Info files. First, copy your new module over to where SNMP::Info is installed: $ cp HostResources.pm /usr/local/share/perl/5.8.4/SNMP/Info/ Now edit /usr/local/share/perl/5.8.4/SNMP/Info.pm and find the following line in the device_type( ) method: return undef unless (defined $layers and length($layers)); now comment it out: #return undef unless (defined $layers and length($layers)); The reason for this is that some host-based agents may not have sysServices set, which is what this is checking for. In the same method, find the lines that look like this: # These devices don't claim to have Layer1-3 but we like em anyway. } else { $objtype = 'SNMP::Info::Layer2::ZyXEL_DSLAM' if ($desc =~ /8-port .DSL Module\(Annex .\)/i); } And make it look like this: # These devices don't claim to have Layer1-3 but we like em anyway. } elsif($desc =~ /linux|unix|windows/i){ $objtype = 'SNMP::Info::HostResources'; } else { $objtype = 'SNMP::Info::Layer2::ZyXEL_DSLAM' if ($desc =~ /8-port .DSL Module\(Annex .\)/i); } This allows SNMP::Info to create and return a proper HostResources object. Here is an example script which uses this new module: #!/usr/bin/perl use SNMP::Info::HostResources; my $host = new SNMP::Info ( AutoSpecify => 1, Debug => 0, DestHost => '127.0.0.1', Community => 'public', Version => 2 ); my $class = $host->class( ); print "Using device sub class : $class\n\n"; my $users = $host->hr_users( ); my $processes = $host->hr_processes( ); my $date = $host->hr_date( ); print "(System date: $date) There are $users users running $processes processes \n\n"; my $storage_index = $host->hr_sindex( ); my $storage_descr = $host->hr_sdescr( ); my $used = $host->hr_sused( ); foreach my $index (keys %$storage_index){ print "$storage_descr->{$index} is using $used->{$index}\n"; } Note that $host->hr_users( ) and $host->hr_processes( ) are called out of %GLOBALS, and $host->hr_sindex( ) and $host->hr_sdescr( ) are called out of %FUNCS. And here is a sample run: $ ./host.pl Using device sub class : SNMP::Info::HostResources (System date: 2005-5-17,13:12:15:0,-4:0) There are 5 users running 85 processes /home is using 839925 / is using 702477 Memory Buffers is using 156044 Swap Space is using 0 /proc/bus/usb is using 0 Real Memory is using 909092 /sys is using 0 $ SNMP::Info is a well-thought-out API. It is perfect for people who may not wish to think about the gory details of OIDs, MIBS, etc. The downside is that if you are going to extend SNMP::Info, you need to know about these details. However, you may be in a situation where you want to allow others to utilize SNMP to write scripts but aren't interested in spending a week teaching people SNMP. You can instead hand someone this module with minimal instruction and they can become productive quite quickly. |