File Coverage

blib/lib/Cisco/SNMP.pm
Criterion Covered Total %
statement 33 201 16.4
branch 0 82 0.0
condition 0 29 0.0
subroutine 11 22 50.0
pod 5 5 100.0
total 49 339 14.4


line stmt bran cond sub pod time code
1             package Cisco::SNMP;
2             ##################################################
3             # AUTHOR = Michael Vincent
4             # www.VinsWorld.com
5             ##################################################
6              
7 13     13   55056 use strict;
  13         38  
  13         444  
8 13     13   77 use warnings;
  13         29  
  13         422  
9              
10 13     13   3442 use version;
  13         20277  
  13         69  
11             our $VERSION = '1.05';
12              
13 13     13   1762 use Net::SNMP qw(:asn1 :snmp);
  13         56450  
  13         3044  
14              
15 13     13   3889 use Sys::Hostname;
  13         12312  
  13         664  
16 13     13   4447 use Socket qw(AF_INET IPPROTO_TCP inet_ntoa);
  13         39657  
  13         9867  
17              
18             my $AF_INET6 = eval { Socket::AF_INET6() };
19             my $AF_UNSPEC = eval { Socket::AF_UNSPEC() };
20             my $AI_NUMERICHOST = eval { Socket::AI_NUMERICHOST() };
21             my $NI_NUMERICHOST = eval { Socket::NI_NUMERICHOST() };
22              
23             # Default to IPv4 for backward compatiblity
24             # THIS MAY CHANGE IN THE FUTURE!!!
25             my $DEFAULT_FAMILY = AF_INET;
26              
27             sub DEFAULT_FAMILY {
28 0     0 1 0 my ( $s, $v ) = @_;
29 0         0 my $prev = $DEFAULT_FAMILY;
30              
31 0 0 0     0 if ( defined $s and ( $s !~ /^Cisco::SNMP/ ) ) {
32 0         0 $v = $s;
33             }
34 0 0 0     0 if ( defined $v and ( $v =~ /^4|6|${\AF_INET}|$AF_INET6$/ ) ) {
  0         0  
35 0 0 0     0 if ( $v == 4 or $v == AF_INET ) {
36 0         0 $DEFAULT_FAMILY = AF_INET;
37             } else {
38 0         0 $DEFAULT_FAMILY = $AF_INET6;
39             }
40             }
41 0         0 return $prev;
42             }
43              
44             our $LASTERROR;
45              
46             ##################################################
47             # Start Public Module
48             ##################################################
49              
50             sub new {
51 0     0 1 0 my $self = shift;
52 0   0     0 my $class = ref($self) || $self;
53              
54 0         0 my $family;
55 0         0 my %params = (
56             version => 1,
57             port => SNMP_PORT,
58             timeout => 5
59             );
60              
61 0         0 my %args;
62 0 0       0 if ( @_ == 1 ) {
63 0 0       0 if ( $_[0] =~ /^Cisco::SNMP/ ) {
64 0         0 return bless $_[0], $class;
65             } else {
66 0         0 ( $params{hostname} ) = @_;
67             }
68             } else {
69 0         0 %args = @_;
70 0         0 for ( keys(%args) ) {
71 0 0       0 if (/^-?family$/i) {
72 0 0       0 if ( $args{$_}
73 0         0 =~ /^(?:(?:(:?ip)?v?(?:4|6))|${\AF_INET}|$AF_INET6)$/ ) {
74 0 0       0 if ( $args{$_} =~ /^(?:(?:(:?ip)?v?4)|${\AF_INET})$/ ) {
  0         0  
75 0         0 $params{domain} = 'udp';
76 0         0 $family = AF_INET;
77             } else {
78 0         0 $params{domain} = 'udp6';
79 0         0 $family = $AF_INET6;
80             }
81             } else {
82 0         0 $LASTERROR = "Invalid family `$args{$_}'";
83 0         0 return undef;
84             }
85              
86             # pass through
87             } else {
88 0         0 $params{$_} = $args{$_};
89             }
90             }
91             }
92              
93             # set default community string if not provided and SNMP version 1 or 2
94 0 0 0     0 if ( ( $params{version} =~ /[1,2]/ ) and not defined $params{community} )
95             {
96 0         0 $params{community} = 'private';
97             }
98              
99             # hostname must be defined
100 0 0       0 if ( !defined $params{hostname} ) {
101 0         0 $params{hostname} = hostname;
102             }
103              
104             # resolve hostname our way
105 0 0       0 if ( defined( my $ret = _resolv( $params{hostname}, $family ) ) ) {
106 0         0 $params{hostname} = $ret->{addr};
107 0 0       0 if ( defined $ret->{port} ) {
108 0         0 $params{port} = $ret->{port};
109             }
110 0         0 $family = $ret->{family};
111 0 0       0 if ( $family == AF_INET ) {
112 0         0 $params{domain} = 'udp';
113             } else {
114 0         0 $params{domain} = 'udp6';
115             }
116             } else {
117 0         0 return undef;
118             }
119              
120 0         0 my ( $session, $error ) = Net::SNMP->session(%params);
121              
122 0 0       0 if ( not defined $session ) {
123 0         0 $LASTERROR = "Error creating Net::SNMP object: $error";
124 0         0 return undef;
125             }
126              
127 0         0 return bless {'_SESSION_' => $session}, $class;
128             }
129              
130             ### WARNINGS - use of Cisco::SNMP directly
131             # our $LOADED = 0;
132             # sub import {
133             # shift;
134             # if ((@_ == 0) && ($LOADED == 0) && ((caller(1))[3] eq 'main::BEGIN')) {
135             # my $warn = sprintf
136             # "\n" .
137             # "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" .
138             # "'use Cisco::SNMP;' directly is deprecated.\n" .
139             # "Instead, use the relevent sub module:\n" .
140             # "'use Cisco::SNMP::;'\n" .
141             # "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";
142             # warnings::warnif($warn)
143             # }
144             # $LOADED++;
145             # my @l = @_ ? @_ : qw(Config ARP Config CPU Entity Image Interface IP Line Memory Password ProxyPing Sensor System);
146             # eval join("", map { "require Cisco::SNMP::" . (/(\w+)/)[0] . ";\n" } @l) or die "Error - $@";
147             # }
148              
149             sub session {
150 0     0 1 0 my $self = shift;
151 0         0 return $self->{_SESSION_};
152             }
153              
154             sub close {
155 0     0 1 0 my $self = shift;
156 0         0 $self->{_SESSION_}->close();
157             }
158              
159             sub error {
160 0     0 1 0 my $self = shift;
161              
162 0         0 my $e = $LASTERROR;
163 0         0 undef $LASTERROR;
164 0         0 return $e;
165             }
166              
167 13     13   118 no strict 'refs';
  13         26  
  13         5018  
168              
169             sub _mk_accessors_array_1 {
170 63     63   96 my ( $TYPE, $NAME ) = @_;
171 63         270 *{$TYPE . $NAME} = sub {
172 0     0   0 my $self = shift;
173 0         0 my ($idx) = @_;
174              
175 0 0       0 if ( !defined $idx ) {
    0          
176 0         0 $idx = 0;
177             } elsif ( $idx !~ /^\d+$/ ) {
178 0         0 $Cisco::SNMP::LASTERROR = "Invalid $TYPE index `$idx'";
179 0         0 return undef;
180             }
181 0         0 return $self->[$idx]->{$NAME};
182             }
183 63         158 }
184              
185             sub _mk_accessors_hash_1 {
186 54     54   119 my ( $TYPE, $NAME ) = @_;
187 54         336 *{$TYPE . $NAME} = sub {
188 0     0   0 my $self = shift;
189 0         0 my ($idx) = @_;
190              
191 0 0       0 if ( !defined $idx ) {
    0          
192 0         0 $idx = 0;
193             } elsif ( $idx !~ /^\d+$/ ) {
194 0         0 $Cisco::SNMP::LASTERROR = "Invalid $TYPE index `$idx'";
195 0         0 return undef;
196             }
197 0         0 return $self->{$idx}->{$NAME};
198             }
199 54         196 }
200              
201             sub _mk_accessors_hash_2 {
202 12     12   29 my ( $TYPE1, $TYPE2, $NAME ) = @_;
203 12         102 *{$TYPE2 . $NAME} = sub {
204 0     0     my $self = shift;
205 0           my ( $idx1, $idx2 ) = @_;
206              
207 0 0         if ( !defined $idx2 ) {
    0          
    0          
208 0   0       $idx1 = $idx1 || 0;
209 0           $idx2 = 0;
210             } elsif ( $idx1 !~ /^\d+$/ ) {
211 0           $Cisco::SNMP::LASTERROR = "Invalid $TYPE1 index `$idx1'";
212 0           return undef;
213             } elsif ( $idx2 !~ /^\d+$/ ) {
214 0           $Cisco::SNMP::LASTERROR = "Invalid $TYPE2 index `$idx2'";
215 0           return undef;
216             }
217 0           return $self->{$idx1}->[$idx2]->{$NAME};
218             }
219 12         45 }
220              
221 13     13   89 use strict 'refs';
  13         26  
  13         13367  
222              
223             ##################################################
224             # End Public Module
225             ##################################################
226              
227             ##################################################
228             # Start Private subs
229             ##################################################
230              
231             sub _get_range {
232 0     0     my ($opt) = @_;
233              
234             # If argument, it must be a number range in the form:
235             # 1,9-11,7,3-5,15
236 0 0         if ( $opt !~ /^\d+([\,\-]\d+)*$/ ) {
237 0           $LASTERROR = "Invalid range format `$opt'";
238 0           return undef;
239             }
240              
241 0           my ( @option, @temp, @ends );
242              
243             # Split the string at the commas first to get: 1 9-11 7 3-5 15
244 0           @option = split( /,/, $opt );
245              
246             # Loop through remaining values for dashes which mean all numbers inclusive.
247             # Thus, need to expand ranges and put values in array.
248 0           for $opt (@option) {
249              
250             # If value has a dash '-', split and add 'missing' numbers.
251 0 0         if ( $opt =~ /-/ ) {
252              
253             # Ends are start and stop number of range. For example, $opt = 9-11:
254             # $ends[0] = 9
255             # $ends[1] = 11
256 0           @ends = split( /-/, $opt );
257              
258 0           for ( $ends[0] .. $ends[1] ) {
259 0           push @temp, $_;
260             }
261              
262             # No dash '-', move on
263             } else {
264 0           push @temp, $opt;
265             }
266             }
267              
268             # return the sorted values of the temp array
269 0           @temp = sort { $a <=> $b } (@temp);
  0            
270 0           return \@temp;
271             }
272              
273             sub _snmpwalk {
274 0     0     my ( $session, $oid ) = @_;
275              
276 0           my ( @oids, @vals );
277 0           my $base = $oid;
278 0           my $result = 0;
279              
280 0           while (
281             defined(
282             $result = $session->get_next_request( varbindlist => [$oid] )
283             )
284             ) {
285 0           my ( $o, $v ) = each( %{$result} );
  0            
286 0 0         if ( oid_base_match( $base, $o ) ) {
287 0           push @vals, $v;
288 0           push @oids, $o;
289 0           $oid = $o;
290             } else {
291 0           last;
292             }
293             }
294 0 0 0       if ( ( @oids == 0 ) and ( @vals == 0 ) ) {
295 0 0         if ( defined( $result = $session->get_request($oid) ) ) {
296 0           push @vals, $result->{$oid};
297 0           push @oids, $oid;
298             } else {
299 0           return undef;
300             }
301             }
302 0           return ( \@oids, \@vals );
303             }
304              
305             ##################################################
306             # DNS hostname resolution
307             # return:
308             # $host->{name} = original
309             # $host->{host} = host - as passed in without :port
310             # $host->{port} = OPTIONAL - if :port, then value of port
311             # $host->{addr} = resolved numeric address
312             # $host->{family} = AF_INET/6
313             ############################
314             sub _resolv {
315 0     0     my ( $name, $family ) = @_;
316              
317 0           my %h = ( name => $name );
318              
319 0 0         if ( !defined $family ) {
320 0           $family = $DEFAULT_FAMILY;
321             }
322              
323             # START - host:port
324 0           my $cnt = 0; # Count ":"
325 0           $cnt++ while ( $name =~ m/:/g );
326              
327             # 0 = hostname or IPv4 address
328 0 0         if ( $cnt == 0 ) {
    0          
    0          
329 0           $h{host} = $name
330              
331             # 1 = IPv4 address with port
332             } elsif ( $cnt == 1 ) {
333 0           ( $h{host}, $h{port} ) = split /:/, $name
334              
335             # >=2 = IPv6 address
336             } elsif ( $cnt >= 2 ) {
337              
338             #IPv6 with port - [2001::1]:port
339 0 0         if ( $name =~ /^\[.*\]:\d{1,5}$/ ) {
340 0           ( $h{host}, $h{port} ) = split /:([^:]+)$/, $name # split after last :
341             # IPv6 without port
342             } else {
343 0           $h{host} = $name;
344             }
345             }
346              
347             # Remove []'s from possible IPv6 string literal
348 0           $h{host} =~ s/[\[\]]//g;
349              
350             # Clean up port
351 0 0 0       if (defined $h{port}
      0        
352             and ( ( $h{port} !~ /^\d{1,5}$/ )
353             or ( $h{port} < 1 )
354             or ( $h{port} > 65535 ) )
355             ) {
356 0           $LASTERROR = "Invalid port `$h{port}' in `$name'";
357 0           return undef;
358             }
359              
360             # END - host:port
361              
362             # address check
363             # new way
364 0 0         if ( version->parse($Socket::VERSION) >= version->parse(1.94) ) {
365 0           my %hints = (
366             family => $AF_UNSPEC,
367             protocol => IPPROTO_TCP,
368             flags => $AI_NUMERICHOST
369             );
370              
371             # numeric address, return
372             my ( $err, @getaddr )
373 0           = Socket::getaddrinfo( $h{host}, undef, \%hints );
374 0 0         if ( defined $getaddr[0] ) {
375 0           $h{addr} = $h{host};
376 0           $h{family} = $getaddr[0]->{family};
377 0           return \%h;
378             }
379              
380             # old way
381             } else {
382              
383             # numeric address, return
384 0           my $ret = gethostbyname( $h{host} );
385 0 0 0       if ( defined $ret && ( inet_ntoa($ret) eq $h{host} ) ) {
386 0           $h{addr} = $h{host};
387 0           $h{family} = AF_INET;
388 0           return \%h;
389             }
390             }
391              
392             # resolve
393             # new way
394 0 0         if ( version->parse($Socket::VERSION) >= version->parse(1.94) ) {
395 0           my %hints = (
396             family => $family,
397             protocol => IPPROTO_TCP
398             );
399              
400             my ( $err, @getaddr )
401 0           = Socket::getaddrinfo( $h{host}, undef, \%hints );
402 0 0         if ( defined $getaddr[0] ) {
403             my ( $err, $address )
404 0           = Socket::getnameinfo( $getaddr[0]->{addr}, $NI_NUMERICHOST );
405 0 0         if ( defined $address ) {
406 0           $h{addr} = $address;
407 0           $h{addr} =~ s/\%(.)*$//; # remove %ifID if IPv6
408 0           $h{family} = $getaddr[0]->{family};
409 0           return \%h;
410             } else {
411 0           $LASTERROR = "getnameinfo($getaddr[0]->{addr}) failed - $err";
412 0           return undef;
413             }
414             } else {
415 0 0         $LASTERROR = sprintf "getaddrinfo($h{host},,%s) failed - $err",
416             ( $family == AF_INET ) ? "AF_INET" : "AF_INET6";
417 0           return undef;
418             }
419              
420             # old way
421             } else {
422 0 0         if ( $family == $AF_INET6 ) {
423 0           $LASTERROR
424             = "Socket >= 1.94 required for IPv6 - found Socket $Socket::VERSION";
425 0           return undef;
426             }
427              
428 0           my @gethost = gethostbyname( $h{host} );
429 0 0         if ( defined $gethost[4] ) {
430 0           $h{addr} = inet_ntoa( $gethost[4] );
431 0           $h{family} = AF_INET;
432 0           return \%h;
433             } else {
434 0           $LASTERROR = "gethostbyname($h{host}) failed - $^E";
435 0           return undef;
436             }
437             }
438             }
439              
440             ##################################################
441             # End Private subs
442             ##################################################
443              
444             1;
445              
446             __END__