File Coverage

blib/lib/SNMP/Class.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package SNMP::Class;
2              
3             =head1 NAME
4              
5             SNMP::Class - A convenience class around the NetSNMP perl modules.
6              
7             =head1 VERSION
8              
9             Version 0.11
10              
11             =cut
12              
13             our $VERSION = '0.15';
14              
15             =head1 SYNOPSIS
16              
17             This module aims to enable snmp-related tasks to be carried out with the best possible ease and expressiveness while at the same time allowing advanced features like subclassing to be used without hassle.
18              
19             use SNMP::Class;
20            
21             #create a session to a managed device --
22             #community will default to public, version will be autoselected from 2,1
23             my $s = SNMP::Class->new({DestHost => 'myhost'});
24            
25             #modus operandi #1
26             #walk the entire table
27             my $ifTable = $s->walk("ifTable");
28             #-more compact-
29             my $ifTable = $s->ifTable;
30            
31             #get the ifDescr.3
32             my $if_descr_3 = $ifTable->object("ifDescr")->instance("3");
33             #more compact
34             my $if_descr_3 = $ifTable->object(ifDescr).3;
35            
36             #iterate over interface descriptions -- method senses list context and returns array
37             for my $descr ($ifTable->object"ifDescr")) {
38             print $descr->get_value,"\n";
39             }
40            
41             #get the speed of the instance for which ifDescr is en0
42             my $en0_speed = $ifTable->find("ifDescr","en0")->object("ifSpeed")->get_value;
43             #
44             #modus operandi #2 - list context
45             while($s->ifDescr) {
46             print $_->get_value;
47             }
48            
49            
50             =head1 METHODS
51              
52             =cut
53              
54 2     2   75949 use warnings;
  2         5  
  2         67  
55 2     2   11 use strict;
  2         3  
  2         68  
56 2     2   10 use Carp;
  2         12  
  2         138  
57 2     2   1199 use Data::Dumper;
  2         12620  
  2         248  
58 2     2   904 use SNMP;
  0            
  0            
59             use SNMP::Class::ResultSet;
60             use SNMP::Class::Varbind;
61             use SNMP::Class::OID;
62             use SNMP::Class::Utils;
63             use Class::Std;
64              
65             use Log::Log4perl qw(:easy);
66             Log::Log4perl->easy_init({
67             level=>$DEBUG,
68             layout => "%M:%L %m%n",
69             });
70             my $logger = get_logger();
71              
72             ####&SNMP::loadModules('ALL');
73              
74              
75             my (%session,%name,%version,%community,%deactivate_bulkwalks) : ATTRS;
76              
77              
78             =head2 new({DestHost=>$desthost,Community=>$community,Version=>$version,DestPort=>$port})
79              
80             This method creates a new session with a managed device. Argument must be a hash reference (see L for that requirement). The members of the hash reference are the same with the arguments of the new method of the L module. If Version is not present, the library will try to probe by querying sysName.0 from the device using version 2 and then version 1, whichever succeeds first. This method croaks if a session cannot be created. If the managed node cannot return the sysName.0 object, the method will also croak. Most people will want to use the method as follows and let the module figure out the rest.
81            
82             my $session = SNMP::Class->new({DestHost=>'myhost.mydomain'});
83            
84              
85             =cut
86            
87              
88             sub BUILD {
89             my ($self, $obj_ID, $arg_ref) = @_;
90              
91             croak "You must supply a DestHost in the arguments to new." unless defined($arg_ref->{DestHost});
92              
93             my $session;
94             my @versions = ( $arg_ref->{Version} );
95              
96              
97              
98             #if the user did not specify a version, then we will try one after the other
99             if ( !defined($arg_ref->{Version})) {
100             @versions = ( "2" , "1" );
101             }
102              
103             #if the user has not supplied a community, why not try a default one?
104             if (!defined($arg_ref->{Community})) {
105             $arg_ref->{Community} = "public";
106             }
107              
108             if (!defined($arg_ref->{RemotePort})) {
109             $logger->debug("setting port to default (161)");
110             $arg_ref->{RemotePort} = 161;
111             }
112              
113             $logger->info("Host is $arg_ref->{DestHost}, community is $arg_ref->{Community}");
114            
115             for my $version (@versions) {
116             $logger->debug("trying version $version");
117            
118             #set $arg_ref->{Version} to $version
119             $arg_ref->{Version}=$version;
120              
121             #construct a string for debug purposes and log it
122             my $debug_str = join(",",map( "$_=>$arg_ref->{$_}", (keys %{$arg_ref})));
123             $logger->debug("doing SNMP::Session->new($debug_str)");
124              
125             #construct the arguments we will be passing to SNMP::Session->new
126             my @argument_array = map { $_ => $arg_ref->{$_} } (keys %{$arg_ref});
127             $session{$obj_ID} = SNMP::Session->new(@argument_array);
128             if(!$session{$obj_ID}) {
129             $logger->debug("null session. Next.");
130             }
131             my $name;
132             if(eval { $name = $self->get_oid('sysName.0') }) {
133             $logger->debug("get_oID(sysName.0) success. Name = $name");
134             #if we got to this point, then this means that
135             #we were able to retrieve the sysname variable from the session
136             #session is probably good
137             $logger->debug("Session should be ok.");
138             $name{$obj_ID} = $name;
139             $version{$obj_ID} = $version;
140             $community{$obj_ID} = $arg_ref->{Community};
141             return 1;
142             } else {
143             $logger->debug("getOID(sysName,0) failed. Error is $@");
144             $logger->debug("Going to next SNMP version");
145             next;
146             }
147            
148             }
149             #if we got here, the session could not be created
150             $logger->debug("session could not be created after all");
151             croak "cannot initiate object for $arg_ref->{DestHost},$arg_ref->{Community}";
152              
153             }
154              
155             =head2 deactivate_bulkwalks
156              
157             If called, this method will permanently deactivate usage of bulkwalk for the session. Mostly useful for broken agents, some buggy versions of Net-SNMP etc.
158              
159             =cut
160              
161             sub deactivate_bulkwalks {
162             my $self = shift(@_) or croak "deactivate_bulkwalks called outside of an object context";
163             my $id = ident $self;
164             $deactivate_bulkwalks{$id} = 1 ;
165             return;
166             }
167              
168              
169             sub get_oid {
170            
171             my $self = shift(@_) or croak "getvar called outside of an object context";
172             my $oid = shift(@_) or croak "first arg to getvar (oid), missing";
173             ####my $instance = shift(@_); #instance could be 0, so we do not check
174             ####if (!defined($instance)) { confess "second arg to getvar (instance), missing" }
175             my $id = ident $self;
176              
177             ####my $vars = new SNMP::VarList([$oid,$instance]) or confess "Internal Error: Could not create a new SNMP::VarList for $oid.$instance";
178              
179             my @a = $session{$id}->get($oid);
180              
181             #print Dumper(@a);
182              
183             confess $session{$id}->{ErrorStr} if ($session{$id}->{ErrorNum} != 0);
184             croak "Got error when tried to ask $session{$id}->{DestHost} for $oid" if ($a[0] eq "NOSUCHINSTANCE");
185              
186             return $a[0];
187             }
188              
189             =head2 getSysName
190              
191             Returns the sysname of the machine corresponding to the session
192              
193             =cut
194              
195             sub get_name {
196             my $self = shift(@_) or confess "incorrect call";
197             my $id = ident $self;
198             return $name{$id};
199             }
200              
201              
202             =head2 get_version
203              
204             Returns the SNMP version of the session object.
205              
206             =cut
207              
208             #This method returns the SNMP version of the object
209             sub get_version {
210             my $self = shift(@_);
211             confess "sub getVersion called outside of an object context" unless (ref $self);
212             my $id = ident $self;
213             return $version{$id};
214             }
215              
216              
217             =head2 walk
218              
219             A generalized walk method. Takes 1 argument, which is the object to walk. Depending on whether the session object is version 1 or 2, it will respectively try to use either SNMP GETNEXT's or GETBULK. On all cases, an L is returned. If something goes wrong, the method will croak.
220              
221             One should probably also take a look at L to see what's possible.
222              
223             =cut
224              
225             #Does snmpwalk on the session object. Depending on the version, it will try to either do a
226             #normal snmpwalk, or, in the case of SNMPv2c, bulkwalk.
227             sub walk {
228             my $self = shift(@_) or confess "sub walk called outside of an object context";
229             my $id = ident $self;
230             my $oid_name = shift(@_) or confess "First argument missing in call to get_data";
231            
232             if ($deactivate_bulkwalks{$id}) {
233             return $self->_walk($oid_name);
234             }
235              
236             if ($self->get_version > 1) {
237             return $self->bulk($oid_name);
238             } else {
239             return $self->_walk($oid_name);
240             }
241             }
242              
243             #sub add_instance_to_bag {
244             # my $self = shift(@_) or confess "Incorrect call to add_instance_to_bag";
245             # my $oid = shift(@_) || confess "Missing 1st argument -- oid";
246             # my $bag = shift(@_) || confess "Missing 2nd argument -- bag";
247             #
248             # my @result;
249             # if ( eval { $self->get_oid($oid) } ) {
250             # $bag->push(SNMP::Class::Varbind->new(
251             #}
252              
253             sub get_varbind :PRIVATE() {
254             my $self = shift(@_) or confess "Incorrect call to get_varbind";
255             my $id = ident $self;
256             my $vb = shift(@_);
257             my $bag = shift(@_);
258              
259             my $varbind = $vb->generate_varbind;
260             my @a;
261             eval { @a = $session{$id}->get($varbind) };
262             if($@) {
263             confess "Could not make the initial GET request for ",$vb->to_string," because of error: ",$@;
264             }
265             if ($session{$id}->{ErrorNum} != 0) {
266             confess "Could not make the initial GET request because of error: ".$session{$id}->{ErrorStr};
267            
268             }
269             if (($a[0] eq 'NOSUCHINSTANCE')||($a[0] eq 'NOSUCHOBJECT')) {
270             DEBUG "Skipping initial object ".$vb->to_string;
271             return;
272             }
273             my $vb2 = SNMP::Class::Varbind->new(varbind=>$varbind);
274             DEBUG "Pushing initial varbind ".$vb2->dump." to the resultset";
275             $bag->push( $vb2 );
276             DEBUG $bag->dump;
277             }
278            
279              
280              
281             sub bulk:RESTRICTED() {
282             my $self = shift(@_) or confess "Incorrect call to bulk, self argument missing";
283             my $id = ident $self;
284             my $oid = shift(@_) or confess "First argument missing in call to bulk";
285            
286             $oid = SNMP::Class::OID->new($oid);
287             $logger->debug("Object to bulkwalk is ".$oid->to_string);
288              
289             #create the varbind
290             #was: my $vb = SNMP::Class::Varbind->new($oid) or confess "cannot create new varbind for $oid";
291             my $vb = SNMP::Class::Varbind->new(oid=>$oid);
292             croak "vb is not an SNMP::Class::Varbind" unless (ref $vb eq 'SNMP::Class::Varbind');
293              
294             #create the bag
295             my $ret = SNMP::Class::ResultSet->new;
296              
297             #make the initial GET request and put it in the bag
298             $self->get_varbind($vb,$ret);
299              
300             #the first argument is definitely 0, we don't want to just emulate an snmpgetnext call
301             #the second argument is tricky. Setting it too high (example: 100000) tends to berzerk some snmp agents, including netsnmp.
302             #setting it too low will degrade performance in large datasets since the client will need to generate more traffic
303             #So, let's set it to some reasonable value, say 10.
304             #we definitely should consider giving the user some knob to turn.
305             #After all, he probably will have a good sense about how big the is walk he is doing.
306            
307             my ($temp) = $session{$id}->bulkwalk(0,10,$vb->generate_varbind); #magic number 10 for the time being
308             #make sure nothing went wrong
309             confess $session{$id}->{ErrorStr} if ($session{$id}->{ErrorNum} != 0);
310              
311             for my $object (@{$temp}) {
312             my $vb = SNMP::Class::Varbind->new(varbind=>$object);
313             DEBUG $vb->dump;
314             #put it in the bag
315             $ret->push($vb);
316             }
317             return $ret;
318             }
319              
320              
321             #does an snmpwalk on the session object
322             sub _walk:RESTRICTED() {
323             my $self = shift(@_) or confess "Incorrect call to _walk, self argument missing";
324             my $id = ident $self;
325             my $oid_str = shift(@_) or confess "First argument missing in call to get_data";
326             my $oid = SNMP::Class::OID->new($oid_str); #that's the original requested oid. We won't change that object.
327            
328             DEBUG "Object to walk is ".$oid->to_string;
329              
330             #we will store the previous-loop-iteration oid here to make sure we didn't enter some loop
331             #we init it to something that can't be equal to anything
332             my $previous = SNMP::Class::OID->new("0.0");##let's just assume that no oid can ever be 0.0
333              
334             #create the varbind
335             my $vb = SNMP::Class::Varbind->new(oid=>$oid);
336             croak "returned vb is not an SNMP::Class::Varbind" unless (ref $vb eq 'SNMP::Class::Varbind');
337              
338             #create the bag
339             my $ret = SNMP::Class::ResultSet->new();
340              
341            
342             #make the initial GET request and put it in the bag
343             $self->get_varbind($vb,$ret);
344              
345             LOOP: while(1) {
346            
347             my $varbind = $vb->generate_varbind;
348              
349             #call an SNMP GETNEXT operation
350             #@my $value = $session{$id}->getnext($vb->get_varbind);
351             my $value = $session{$id}->getnext($varbind);
352             #make sure nothing went wrong
353             confess $session{$id}->{ErrorStr} if ($session{$id}->{ErrorNum} != 0);
354              
355             #now sync the varbind back to the vb
356             #$vb = SNMP::Class::Varbind->new_from_varbind($varbind);
357             $vb = SNMP::Class::Varbind->new(varbind=>$varbind);
358              
359             DEBUG $vb->dump;
360            
361             #handle some special types
362             #For example, a type of ENDOFMIBVIEW means we should stop
363             if($vb->type eq 'ENDOFMIBVIEW') {
364             DEBUG "We should stop because an end of MIB View was encountered";
365             last LOOP;
366             }
367              
368             #make sure that we got a different oid than in the previous iteration
369             if($previous->oid_is_equal( $vb )) {
370             confess "OID not increasing at ".$vb->to_string." (".$vb->numeric.")\n";
371             }
372              
373             #make sure we are still under the original $oid -- if not we are finished
374             if(!$oid->contains($vb)) {
375             $logger->debug($oid->numeric." does not contain ".$vb->numeric." ... we should stop");
376             last LOOP;
377             }
378              
379             $ret->push($vb);
380              
381             #Keep a copy for the next iteration. Remember that only the reference is copied.
382             $previous = $vb;
383              
384             #we need to make sure that next iteration we won't use the same $vb
385             $vb = SNMP::Class::Varbind->new(oid=>$vb);
386              
387             };
388             return $ret;
389             }
390              
391             #=head2 AUTOMETHOD
392             #
393             #Using a method call that coincides with an SNMP OBJECT-TYPE name is equivalent to issuing a walk with that name as argument. This is provided as a shortcut which can result to more easy to read programs.
394             #Also, if such a method is used in a list context, it won't return an SNMP::ResultSet object, but rather a list with the ResultSet's contents. This is pretty convenient for iterating through SNMP results using few lines of code.
395             #
396             #=cut
397             #
398             #sub AUTOMETHOD {
399             # my $self = shift(@_) or croak("Incorrect call to AUTOMETHOD");
400             # my $ident = shift(@_) or croak("Second argument to AUTOMETHOD missing");
401             # my $subname = $_; # Requested subroutine name is passed via $_;
402             # $logger->debug("AUTOMETHOD called as $subname");
403             #
404             # if (eval { my $dummy = SNMP::Class::Utils::get_attr($subname,"objectID") }) {
405             # $logger->debug("$subname seems like a valid OID ");
406             # }
407             # else {
408             # $logger->debug("$subname doesn't seem like a valid OID. Returning...");
409             # return;
410             # }
411             #
412             # #we'll just have to create this little closure and return it to the Class::Std module
413             # #remember: this closure will run in the place of the method that was called by the invoker
414             # return sub {
415             # if(wantarray) {
416             # $logger->debug("$subname called in list context");
417             # return @{$self->walk($subname)->varbinds};
418             # }
419             # return $self->walk($subname);
420             # }
421             #
422             #}
423              
424              
425              
426              
427              
428              
429             =head1 AUTHOR
430              
431             Athanasios Douitsis, C<< >>
432              
433             =head1 BUGS
434              
435             Please report any bugs or feature requests to
436             C, or through the web interface at
437             L.
438             I will be notified, and then you'll automatically be notified of progress on
439             your bug as I make changes.
440              
441             =head1 SUPPORT
442              
443             You can find documentation for this module with the perldoc command.
444              
445             perldoc SNMP::Class
446              
447             You can also look for information at:
448              
449             =over 4
450              
451             =item * AnnoCPAN: Annotated CPAN documentation
452              
453             L
454              
455             =item * CPAN Ratings
456              
457             L
458              
459             =item * RT: CPAN's request tracker
460              
461             L
462              
463             =item * Search CPAN
464              
465             L
466              
467             =back
468              
469             =head1 ACKNOWLEDGEMENTS
470              
471             This module obviously needs the perl libraries from the excellent Net-SNMP package. Many thanks go to the people that make that package available.
472              
473             =head1 COPYRIGHT & LICENSE
474              
475             Copyright 2008 Athanasios Douitsis, all rights reserved.
476              
477             This program is free software; you can redistribute it and/or modify it
478             under the same terms as Perl itself.
479              
480             =cut
481              
482             1; # End of SNMP::Class