# UD::perl.pm =head1 UD::SNMP.pm UDel common snmp routines Perl module to ease writing snmp for University of Delaware NSS. =cut # # $Header: /home/metal1.nss/usra/doke/work/perl/UD/RCS/SNMP.pm,v 1.9 2016/06/24 17:09:45 doke Exp $ # # # $Log: SNMP.pm,v $ # Revision 1.9 2016/06/24 17:09:45 doke # *** empty log message *** # # Revision 1.8 2015/06/09 17:04:41 doke # *** empty log message *** # # Revision 1.7 2015/06/04 23:41:48 doke # *** empty log message *** # # Revision 1.6 2015/03/13 19:40:30 doke # *** empty log message *** # # Revision 1.5 2013/10/24 20:28:53 doke # *** empty log message *** # # Revision 1.4 2013/09/05 21:43:49 doke # *** empty log message *** # # Revision 1.3 2013/09/05 21:24:56 doke # *** empty log message *** # # Revision 1.2 2013/08/31 00:13:15 doke # *** empty log message *** # # # by Doke Scott, doke at udel dot edu, 2013-8-30 package UD::SNMP; require 5.000; use strict; use warnings; use Net::SNMP; use Carp; use Exporter 'import'; use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK $mib2 $upsmib $enterprises $aruba $cisco $juniper $liebert ); @ISA = qw(Exporter); @EXPORT = (); @EXPORT_OK = qw( $mib2 $upsmib $enterprises $aruba $cisco $juniper $liebert ); my $VERSION = '$Revision: 1.9 $ $Date: 2016/06/24 17:09:45 $'; # RCS version $VERSION =~ s/^ \$\w*: \s* ([\d\.]+) \s* \$ \s* \$\w+: \s* ([\d\/\:\s]+) \s* \$ $/$1, $2 UTC/ix; $mib2 = '1.3.6.1.2.1'; $upsmib = "$mib2.33"; $enterprises = '1.3.6.1.4.1'; $aruba = "$enterprises.14823"; $cisco = "$enterprises.9"; $juniper = "$enterprises.2636"; $liebert = "$enterprises.476"; ############################ =head1 Example use UD::SNMP; my $udsnmp = new UD::SNMP; $sysDescr = $udsnmp->snmp_get_one( $dev, $community, "$mib2.1.1.0" ); if ( ! $sysDescr ) { print "couldn't get sysDescr: ", $udsnmp->{error}, "\n"; =head1 Configuration Variables $udsnmp->{verbose} = 0; # increment for more $udsnmp->{timeout} = 20; # seconds $udsnmp->{retries} = 3; $udsnmp->{use_snmpv2c} = 1; # use snmp version 2c instead of 1 $udsnmp->{prefered_maxmsgsize} = 1472; $udsnmp->{maxrepetitions} = 10; # for get_next_bulk $udsnmp->{snmp_port} = 161; # for session =head1 Status Variables $udsnmp->{error} -- The last error string. $udsnmp->{result} -- The last result hashref. =head1 MIB Convenience Variables $mib2 = '1.3.6.1.2.1'; $upsmib = "$mib2.33"; $enterprises = '1.3.6.1.4.1'; $aruba = "$enterprises.14823"; $cisco = "$enterprises.9"; $juniper = "$enterprises.2636"; $liebert = "$enterprises.476"; =head1 Routines $udsnmp = UD::SNMP->new( [options] ); Create and initialize a new instance. Options are key-value pairs, ie 'snmp_port => 161, verbose => 1' =cut sub new { my $class = shift; my $self = {}; bless $self, $class; $self->{verbose} = 0; $self->{error} = ''; # last error $self->{result} = undef; # last result $self->{use_snmpv2c} = 1; $self->{timeout} = 20; $self->{retries} = 3; $self->{prefered_maxmsgsize} = 1472; # default $self->{snmp_port} = 161; # default # try setting maxrepetitions to 1 to force get-next queries, instead of get-bulk $self->{maxrepetitions} = 10; # internal caches $self->{snmp_sessions} = {}; $self->{name2ip} = {}; # read optional parameters while ( $#_ > 0 ) { my $key = shift; my $val = shift; $key =~ s/^-//; $self->{ $key } = $val; } return $self; } =pod $result = $udsnmp->snmp_get( $dev, $community, $name, ( $oid1, $oid2, ... ) ); get snmp object ids from device. Returns a pointer to a hash of results. =cut sub snmp_get { my( $self, $dev, $community, $name, $oids ) = @_; my( $session, $oid ); $self->{verbose} && print "snmp_get( $dev, $community, $name, $oids )\n"; $session = $self->snmp_session( $dev, $community ) || return undef; $self->{result} = $session->get_request( -varbindlist => $oids ); if ( ! defined( $self->{result} ) ) { $self->{error} = $session->error(); $self->{verbose} && print "get_request returned undefined result, ", $self->{error}, "\n"; return undef; } # clean the noSuchObject and noSuchInstance errors out of the self->{result} foreach $oid ( keys %{$self->{result}} ) { $self->{verbose} > 1 && print "snmp_get $oid = $self->{result}->{ $oid }\n"; if ( $self->{result}->{ $oid } eq 'noSuchObject' || $self->{result}->{ $oid } eq 'noSuchInstance' ) { delete $self->{result}->{ $oid }; } elsif ( $self->{verbose} ) { my( $val ); $val = $self->{result}->{ $oid }; $val =~ tr/\040-\176//cd; print " $oid: $val\n"; } } return $self->{result}; } =pod $value = $udsnmp->snmp_get_one( $dev, $community, $name, $oid ); get a single snmp value from the device. =cut sub snmp_get_one { my( $self, $dev, $community, $name, $oid ) = @_; $self->{verbose} && print "snmp_get_one( $dev, $community, $name, $oid )\n"; $self->{result} = $self->snmp_get( $dev, $community, $name, [ $oid ] ); return undef unless ( defined $self->{result} ); return $self->{result}->{ $oid }; } =pod $result = $udsnmp->snmp_walk( $dev, $community, $name, $baseoid ); Walk a tree of the snmp mib on the device. Returns a pointer to a hash of results. =cut sub snmp_walk { my( $self, $dev, $community, $name, $baseoid ) = @_; my( $session, $maxmsgsize ); $self->{verbose} && print "snmp_walk( $dev, $community, $name, $baseoid )\n"; $session = $self->snmp_session( $dev, $community ) || return undef; foreach $maxmsgsize ( $self->{prefered_maxmsgsize}, 1472, 1600, 1800, 2048, 4096, 9000 ) { $session->max_msg_size( $maxmsgsize ); if ( $self->{use_snmpv2c} ) { $self->{result} = $session->get_table( -baseoid => $baseoid, -maxrepetitions => $self->{maxrepetitions} ); } else { $self->{result} = $session->get_table( -baseoid => $baseoid ); } $self->{error} = $session->error(); if ( defined( $self->{result} ) ) { # got data last; } elsif ( $self->{error} =~ m/Requested table is empty|Requested entries are empty or do not exist/i ) { # ok, return empty set return {}; } elsif ( $self->{error} =~ m/Message size exceeded buffer maxMsgSize/ ) { # retry with different size $self->{verbose} && print "snmp get_entries failed with maxmsgsize $maxmsgsize\n"; } else { # some other error $self->{verbose} && print "snmp get_entries failed with error $self->{error}\n"; return undef } # else try a bigger maxmessage size } if ( ! defined $self->{result} ) { # ran out of max_msg_size attempts return undef; } if ( $self->{verbose} > 1 ) { my( $oid, $val ); foreach $oid ( sort keys %{$self->{result}} ) { $val = $self->{result}->{ $oid }; print "$oid = $val\n"; } } return $self->{result}; } =pod $data = $udsnmp->snmp_table( $dev, $community, $name, $baseoid, \@columns ); Walk selected columns of a table. This version is for tables with simple 1 integer row indecies, ie most normal ones. Returns a pointer to an array of arrays: $data->[ row ][ column ] =cut sub snmp_table { my( $self, $dev, $community, $name, $baseoid, $columns ) = @_; my( $session, $rows, $oid, $val, $col, $row, $data, @oids, $maxmsgsize ); $self->{verbose} && print "snmp_table( $dev, $community, $name, $baseoid, $columns )\n"; if ( defined $columns ) { foreach $col ( @$columns ) { push @oids, "$baseoid.1.$col"; } } else { push @oids, $baseoid; } $session = $self->snmp_session( $dev, $community ) || return undef; $data = []; foreach $maxmsgsize ( $self->{prefered_maxmsgsize}, 1472, 1600, 1800, 2048, 4096, 9000 ) { $session->max_msg_size( $maxmsgsize ); $self->{verbose} > 1 && print "snmp get_entries tring maxmsgsize $maxmsgsize\n"; if ( $self->{use_snmpv2c} ) { $self->{result} = $session->get_entries( -columns => \@oids, -maxrepetitions => $self->{maxrepetitions} ); } else { $self->{result} = $session->get_entries( -columns => \@oids ); } $self->{error} = $session->error(); if ( defined( $self->{result} ) ) { last; } elsif ( $self->{error} =~ m/Requested table is empty|Requested entries are empty or do not exist/i ) { # ok, return empty set return $data; } elsif ( $self->{error} =~ m/Message size exceeded buffer maxMsgSize/ ) { # retry with different size $self->{verbose} && print "snmp get_entries failed with maxmsgsize $maxmsgsize\n"; } else { # some other error $self->{verbose} && print "snmp get_entries failed with error $self->{error}\n"; return undef; } # else try a bigger maxmessage size } if ( ! defined $self->{result} ) { # ran out of max_msg_size attempts return undef; } foreach $oid ( keys %{$self->{result}} ) { $val = $self->{result}->{ $oid }; $self->{verbose} > 1 && print "$oid = $val\n"; next if ( $val eq 'endOfMibView' ); if ( $oid =~ m/.*\.(\d+)\.(\d+)$/ ) { $col = $1; $row = $2; $data->[$row][$col] = $val; } } return $data; } =pod $data = $udsnmp->snmp_table_multi_index( $dev, $community, $name, $baseoid, \@columns ); Walk selected columns of a table. This version is for tables with more complex multi-integer row indecies, ie an IP or mac address. Returns a pointer to a hash of arrays: $data->{ row }[ column ] =cut sub snmp_table_multi_index { my( $self, $dev, $community, $name, $baseoid, $columns ) = @_; my( $session, $rows, $oid, $val, $col, $row, $data, @oids, $maxmsgsize ); $self->{verbose} && print "snmp_table( $dev, $community, $name, $baseoid, $columns )\n"; if ( defined $columns ) { foreach $col ( @$columns ) { push @oids, "$baseoid.1.$col"; } } else { push @oids, $baseoid; } $session = $self->snmp_session( $dev, $community ) || return undef; $data = {}; foreach $maxmsgsize ( $self->{prefered_maxmsgsize}, 1472, 1600, 1800, 2048, 4096, 9000 ) { $session->max_msg_size( $maxmsgsize ); $self->{verbose} > 1 && print "snmp get_entries tring maxmsgsize $maxmsgsize\n"; if ( $self->{use_snmpv2c} ) { $self->{result} = $session->get_entries( -columns => \@oids, -maxrepetitions => $self->{maxrepetitions} ); } else { $self->{result} = $session->get_entries( -columns => \@oids ); } $self->{error} = $session->error(); if ( defined( $self->{result} ) ) { last; } elsif ( $self->{error} =~ m/Requested table is empty|Requested entries are empty or do not exist/i ) { # ok, return empty set return $data; } elsif ( $self->{error} =~ m/Message size exceeded buffer maxMsgSize/ ) { # retry with different size $self->{verbose} && print "snmp get_entries failed with maxmsgsize $maxmsgsize\n"; } else { # some other error $self->{verbose} && print "snmp get_entries failed with error $self->{error}\n"; return undef; } # else try a bigger maxmessage size } if ( ! defined $self->{result} ) { # ran out of max_msg_size attempts return undef; } foreach $oid ( keys %{$self->{result}} ) { $val = $self->{result}->{ $oid }; $self->{verbose} > 1 && print "$oid = $val\n"; next if ( $val eq 'endOfMibView' ); if ( $oid =~ m/^$baseoid\.1\.(\d+)\.([\d\.]+)$/ ) { $col = $1; $row = $2; $data->{$row}[$col] = $val; } } return $data; } # internal routine for creating Net::SNMP sessions # sub snmp_session { my( $self, $dev, $community ) = @_; my( $ip, $session, $error ); if ( $dev =~ m/^\d[\d\.]+$/ ) { $ip = $dev; } else { $ip = $self->name2ip( $dev ); if ( ! $ip ) { $self->{error} = "unable to resolve $dev to an ip"; $self->{verbose} && print $self->{error}, "\n"; return undef; } } if ( exists $self->{snmp_sessions}->{ "$ip,$community" } ) { return $self->{snmp_sessions}->{ "$ip,$community" }; } ( $session, $error ) = Net::SNMP->session( -version => $self->{use_snmpv2c} ? 'SNMPv2c' : 'SNMPv1', -hostname => $ip, -community => $community, -timeout => $self->{timeout}, -retries => $self->{retries}, -port => $self->{snmp_port}, #-debug => 0x02 ); if ( ! defined( $session ) ) { $self->{error} = $error; $self->{verbose} && print "snmp setup error: $error\n"; return undef; } $session->translate( [ '-octetstring' => 0 ] ); #$session->translate( '-all' => 0 ); #$session->translate( '-unsigned' => 1 ); $self->{verbose} > 1 && print "snmp session default maxmsgsize = ", $session->max_msg_size(), "\n"; $self->{snmp_sessions}->{ "$ip,$community" } = $session; return $session; } =pod $string = $udsnmp->ticks_to_str( $ticks ); Converts snmp time ticks (100ths of seconds, usually of uptime) into a human readable string, ie "1d 2h 10m 30s". =cut sub ticks_to_str { my( $self, $ticks ) = @_; my( @intervals, @letters, $interval, $str, $i, $n, $started ); @intervals = ( 60480000, 8640000, 360000, 6000, 100, ); @letters = ( 'w ', 'd ', 'h ', 'm ', 's', ); $str = ''; for ( $i = 0; $i < 5; $i++ ) { $interval = $intervals[ $i ]; if ( $ticks >= $interval || $started ) { $n = int( $ticks / $interval ); $ticks -= $n * $interval; $str .= sprintf( "%u%s", $n, $letters[ $i ] ); $started = 1; # show days in 3weeks 0days 3hours } } return $str; } # internal routine for caching name to ip lookups sub name2ip { my( $self, $name ) = @_; my( $ip, $a, $b, $c, $d ); if ( defined( $self->{name2ip}->{ $name } ) ) { $ip = $self->{name2ip}->{ $name }; } elsif ( $name =~ m/^\s*(\d+\.\d+\.\d+\.\d+)\s*$/ ) { $ip = $1; $self->{name2ip}->{ $name } = $ip; $self->{name2ip}->{ $1 } = $ip; } else { $ip = gethostbyname( $name ); if ( ! $ip ) { return undef; } ($a,$b,$c,$d) = unpack( 'C4', $ip ); $ip = "$a.$b.$c.$d"; $self->{name2ip}->{ $name } = $ip; } return $ip; } 1;