File Coverage

blib/lib/SNMP/Agent.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package SNMP::Agent;
2              
3             =pod
4              
5             =head1 NAME
6              
7             SNMP::Agent - A simple SNMP AgentX subagent
8              
9             =cut
10              
11 1     1   30318 use warnings;
  1         2  
  1         29  
12 1     1   5 use strict;
  1         2  
  1         29  
13              
14 1     1   5 use Carp qw(croak);
  1         5  
  1         52  
15 1     1   366 use NetSNMP::agent (':all');
  0            
  0            
16             use NetSNMP::ASN qw(ASN_OCTET_STR ASN_BIT_STR ASN_NULL ASN_GAUGE ASN_UNSIGNED ASN_COUNTER ASN_COUNTER64 ASN_TIMETICKS);
17              
18             =head1 VERSION
19              
20             Version 0.06
21              
22             =cut
23              
24             our $VERSION = '0.06';
25              
26             =head1 SYNOPSIS
27              
28             Eliminates most of the hassle in developing simple SNMP subagents in perl.
29             A list of SNMP OIDs are registered to callbacks that return the data.
30              
31             =cut
32              
33             =head1 FUNCTIONS
34              
35             =cut
36              
37             sub _generic_handler
38             {
39              
40             # $oid, $suboid_handler and $asn_type are provided by the anonymous callback
41             # registered by us, and remaining args come from the NetSNMP::agent module
42             my ($self, $root_oid, $suboid_handler, $asn_type, $handler,
43             $registration_info, $request_info, $requests)
44             = @_;
45             my $request;
46              
47             for ($request = $requests ; $request ; $request = $request->next())
48             {
49             my $oid = $request->getOID();
50             my $mode = $request_info->getMode();
51              
52             if ($mode == MODE_GET || $mode == MODE_GETNEXT)
53             {
54             if ($mode == MODE_GETNEXT)
55             {
56             my $next_oid =
57             ($oid < new NetSNMP::OID($root_oid))
58             ? $root_oid
59             : $self->_get_next_oid($oid);
60              
61             # next_oid is undefined if handler was given last oid in subtree
62             if (defined($next_oid))
63             {
64             $oid = new NetSNMP::OID($next_oid);
65             $request->setOID($next_oid);
66             }
67             else
68             {
69             $oid = undef;
70             }
71             }
72              
73             if (defined($oid))
74             {
75              
76             # Were not asked to GETNEXT beyond last OID in subtree -
77             # process the request with the registered handler.
78             my $value = $suboid_handler->($oid, $mode);
79              
80             my $new_asn_type = $self->_get_asn_type($oid);
81             $new_asn_type ||= $asn_type;
82              
83             if($new_asn_type == ASN_UNSIGNED ||
84             $new_asn_type == ASN_COUNTER ||
85             $new_asn_type == ASN_COUNTER64 ||
86             $new_asn_type == ASN_TIMETICKS )
87             {
88             $value = sprintf("%u", $value);
89             }
90              
91             if($new_asn_type == ASN_OCTET_STR ||
92             $new_asn_type == ASN_BIT_STR )
93             {
94             $value = sprintf("%s", $value);
95             }
96              
97             # Possible that a GET request came for an unhandled OID
98             # (undef value from handler) - don't set a value.
99             $request->setValue($new_asn_type, $value) if (defined($value));
100             }
101             }
102             elsif ($mode == MODE_SET_RESERVE1)
103             {
104             if ($oid != new NetSNMP::OID($root_oid))
105             {
106             $request->setError($request_info, SNMP_ERR_NOSUCHNAME);
107             }
108             }
109             elsif ($mode == MODE_SET_ACTION)
110             {
111             $suboid_handler->($oid, $mode, $request->getValue());
112             }
113             }
114             }
115              
116             =head2 new
117              
118             Get an SNMP::Agent object. See EXAMPLES for use.
119              
120             =cut
121              
122             sub new
123             {
124             my $class = shift;
125             my ($name, $root_oid, $suboid_handler_map) = @_;
126              
127             my $self = {
128             name => 'example_agent',
129             root_oid => '1.3.6.1.4.1.8072.9999.9999.1',
130             suboid_map => {},
131             get_next_oid => sub { return },
132             get_asn_type => sub { return },
133             shutdown => 0,
134             };
135              
136             croak "Invalid agent name" unless ($name =~ /^\w+$/);
137             croak "Need hash reference to suboid handlers"
138             unless (ref $suboid_handler_map eq "HASH");
139              
140             foreach my $suboid (keys %$suboid_handler_map)
141             {
142             my $handler = $suboid_handler_map->{$suboid}->{handler};
143             my $asn_type = $suboid_handler_map->{$suboid}->{type};
144             $asn_type ||= ASN_OCTET_STR;
145              
146             my $ref_type = ref $handler;
147             croak "Invalid suboid: $suboid" unless ($suboid =~ /^[\d\.]*/);
148             croak "Not function reference or scalar for suboid $suboid"
149             unless ($ref_type eq 'CODE' || $ref_type eq 'SCALAR');
150              
151             $suboid =~ s/^\.//;
152             $self->{suboid_map}->{$suboid} = {handler => $handler, type => $asn_type};
153             }
154              
155             $self->{name} = $name;
156             $self->{root_oid} = $root_oid;
157              
158             bless $self, $class;
159             return $self;
160             }
161              
162             =head2 register_get_next_oid
163              
164             If your agent needs to support an OID subtree, the provided handler will
165             be called to find out what the next OID is from the previous one.
166              
167             Must return undef if there is not a next OID below the registered root OID.
168              
169             =cut
170              
171             sub register_get_next_oid
172             {
173             my $self = shift;
174             my $handler = shift;
175              
176             croak "Must be a CODE reference" unless (ref $handler eq 'CODE');
177              
178             $self->{get_next_oid} = $handler;
179             }
180              
181             sub _get_next_oid
182             {
183             my $self = shift;
184             return $self->{get_next_oid}->(@_);
185             }
186              
187             =head2 register_get_asn_type
188              
189             If your agent needs to support an OID subtree, the provided handler will
190             be called to find out what the ASN type is for an OID. Required if the
191             ASN type differs from the default provided for an OID subtree.
192              
193             Can return undef to use the default assigned ASN for the registered root OID.
194              
195             =cut
196              
197             sub register_get_asn_type
198             {
199             my $self = shift;
200             my $handler = shift;
201              
202             croak "Must be a CODE reference" unless (ref $handler eq 'CODE');
203              
204             $self->{get_asn_type} = $handler;
205             }
206              
207             sub _get_asn_type
208             {
209             my $self = shift;
210             return $self->{get_asn_type}->(@_);
211             }
212              
213             =head2 run
214              
215             Called on an SNMP::Agent object with no arguments to start the agent.
216             Does not return until shutdown called.
217              
218             =cut
219              
220             sub run
221             {
222             my $self = shift;
223              
224             my $agent = new NetSNMP::agent(
225              
226             # makes the agent read a my_agent_name.conf file
227             'Name' => $self->{name},
228             'AgentX' => 1
229             );
230              
231             # register each oid handler individually to the same callback function
232             my $root_oid = $self->{root_oid};
233             foreach my $suboid (keys %{$self->{suboid_map}})
234             {
235             my $oid = join('.', ($root_oid, $suboid));
236             my $suboid_handler = $self->{suboid_map}->{$suboid}->{handler};
237             my $asn_type = $self->{suboid_map}->{$suboid}->{type};
238              
239             # All suboid handlers are a sub ref.
240             if (ref $suboid_handler ne 'CODE')
241             {
242             $suboid_handler =
243             ($asn_type == ASN_OCTET_STR)
244             ? sub { return "$suboid_handler" }
245             : sub { return $suboid_handler };
246             }
247              
248             $agent->register($self->{name}, $oid,
249             sub { $self->_generic_handler($oid, $suboid_handler, $asn_type, @_) });
250             }
251              
252             while (!$self->{shutdown})
253             {
254             $agent->agent_check_and_process(1);
255             }
256              
257             $agent->shutdown();
258             }
259              
260             =head2 shutdown
261              
262             Stop the agent.
263              
264             =cut
265              
266             sub shutdown
267             {
268             my $self = shift;
269             $self->{shutdown} = 1;
270             }
271              
272             =head1 EXAMPLES
273              
274             =head2 Simple handler
275              
276             use SNMP::Agent;
277             use NetSNMP::ASN qw/ASN_GAUGE/;
278              
279             sub do_one { return int(rand(10)) }
280             sub do_two { return "two" }
281              
282             my $root_oid = '1.3.6.1.4.1.8072.9999.9999.123';
283             my %handlers = (
284             '1' => { handler => \&do_one, type => ASN_GAUGE },
285             '2' => { handler => \&do_two }, # default type ASN_OCTET_STR
286             );
287              
288             my $agent = new SNMP::Agent('my_agent', $root_oid, \%handlers);
289             $agent->run();
290              
291             =head3 Output
292              
293             $ snmpwalk -v 2c -c public localhost 1.3.6.1.4.1.8072.9999.9999.123
294             iso.3.6.1.4.1.8072.9999.9999.123.1 = Gauge32: 2
295             iso.3.6.1.4.1.8072.9999.9999.123.2 = STRING: "two"
296              
297             =head2 OID Tree
298              
299             use SNMP::Agent;
300              
301             my $root_oid = 'netSnmpPlaypen.7375.1';
302              
303             my @wasting_time = qw/Sittin' on the dock of the bay/;
304              
305             sub stats_handler {
306             my $oid = shift; # a NetSNMP::OID object
307              
308             return "root oid" if($oid =~ /$root_oid$/);
309              
310             my $idx = ($oid->to_array())[$oid->length - 1];
311             return $wasting_time[$idx - 1];
312             }
313              
314             sub next_oid_handler {
315             my $oid = shift;
316              
317             if($oid eq $root_oid) {
318             return join('.', ($root_oid, '.1'));
319             }
320              
321             if($oid =~ /$root_oid\.(\d+)$/) {
322             my $idx = $1;
323             if ($idx <= $#wasting_time)
324             {
325             my $next_oid = join('.', ($root_oid, $idx + 1));
326             return $next_oid;
327             }
328             }
329              
330             return; # no next OID
331             }
332              
333             my %handlers = (
334             $root_oid => { handler => \&stats_handler },
335             );
336              
337             my $agent = new SNMP::Agent('my_agent', '', \%handlers);
338             $agent->register_get_next_oid(\&next_oid_handler);
339             $agent->run();
340              
341             =head3 Output
342              
343             snmpwalk -v 2c -c public localhost netSnmpPlaypen.7375
344             NET-SNMP-MIB::netSnmpPlaypen.7375.1 = STRING: "root oid"
345             NET-SNMP-MIB::netSnmpPlaypen.7375.1.1 = STRING: "Sittin'"
346             NET-SNMP-MIB::netSnmpPlaypen.7375.1.2 = STRING: "on"
347             NET-SNMP-MIB::netSnmpPlaypen.7375.1.3 = STRING: "the"
348             NET-SNMP-MIB::netSnmpPlaypen.7375.1.4 = STRING: "dock"
349             NET-SNMP-MIB::netSnmpPlaypen.7375.1.5 = STRING: "of"
350             NET-SNMP-MIB::netSnmpPlaypen.7375.1.6 = STRING: "the"
351             NET-SNMP-MIB::netSnmpPlaypen.7375.1.7 = STRING: "bay"
352              
353             =head1 NOTES
354              
355             =head2 Callbacks
356              
357             The callback functions specified to handle OID requests are called
358             for SNMP sets as well as get requests. The requested OID and the
359             request type are passed as arguments to the callback. If the mode
360             is MODE_SET_ACTION there is a third argument, the value to be set.
361              
362             use NetSNMP::agent qw(MODE_SET_ACTION);
363             my $persistent_val = 0;
364              
365             sub do_one
366             {
367             my ($oid, $mode, $value) = @_;
368             if ($mode == MODE_SET_ACTION)
369             {
370             $persistent_val = $value;
371             }
372             else
373             {
374             return $persistent_val;
375             }
376             }
377              
378             If asked to provide a value for an OID out of range, the handler
379             should return an undefined value.
380              
381             =head2 OIDs
382              
383             The OID passed to each callback function is a NetSNMP::OID object.
384             This may be a symbolic or numeric OID, and will be dependent on
385             your system configuration. If in doubt, convert it to a numeric
386             representation before using it:
387              
388             use NetSNMP::OID;
389             my $oid = new NetSNMP::OID('netSnmpPlaypen');
390             my $numeric = join '.', $oid->to_array();
391              
392             print "symbolic: $oid\n";
393             print "numeric: $numeric\n";
394              
395             symbolic: netSnmpPlaypen
396             numeric: 1.3.6.1.4.1.8072.9999.9999
397              
398             =head2 Caching
399              
400             No caching of responses is done by SNMP::Agent. Any results from
401             expensive operations should probably be cached for some time in case
402             of duplicate requests for the same information.
403              
404             =head1 AUTHOR
405              
406             Alexander Else, C<< >>
407              
408             =head1 BUGS
409              
410             Please report any bugs or feature requests to C, or through
411             the web interface at L. I will be notified, and then you'll
412             automatically be notified of progress on your bug as I make changes.
413              
414             =head2 COUNTER64
415              
416             Strange values are returned for non-zero 64 bit counters. I suspect something in either NetSNMP::agent or communication
417             between it and the snmp daemon. From cursory investigation it does not appear to be a simple endian problem. I may be wrong.
418              
419             =head1 SUPPORT
420              
421             You can find documentation for this module with the perldoc command.
422              
423             perldoc SNMP::Agent
424              
425              
426             You can also look for information at:
427              
428             =over 4
429              
430             =item * RT: CPAN's request tracker
431              
432             L
433              
434             =item * AnnoCPAN: Annotated CPAN documentation
435              
436             L
437              
438             =item * CPAN Ratings
439              
440             L
441              
442             =item * Search CPAN
443              
444             L
445              
446             =back
447              
448              
449             =head1 ACKNOWLEDGEMENTS
450              
451              
452             =head1 LICENSE AND COPYRIGHT
453              
454             Copyright 2011 Alexander Else.
455              
456             This program is free software; you can redistribute it and/or modify it
457             under the terms of either: the GNU General Public License as published
458             by the Free Software Foundation; or the Artistic License.
459              
460             See http://dev.perl.org/licenses/ for more information.
461              
462              
463             =cut
464              
465             1; # End of SNMP::Agent