File Coverage

blib/lib/POE/Component/SNMP.pm
Criterion Covered Total %
statement 60 126 47.6
branch 16 44 36.3
condition 4 7 57.1
subroutine 11 28 39.2
pod 1 16 6.2
total 92 221 41.6


line stmt bran cond sub pod time code
1             package POE::Component::SNMP;
2              
3 16     16   2956097 use strict;
  16         38  
  16         998  
4              
5             our $VERSION = '1.1006';
6              
7             package POE::Net::SNMP;
8              
9 16     16   88 use base q/Net::SNMP/;
  16         27  
  16         22593  
10              
11             # use Net::SNMP::XS;
12              
13             our %localport;
14              
15             # {{{ session
16              
17             sub session {
18 3     3   6 my $class = shift;
19 3         10 my @arg = @_;
20 3         5 my ($session, $error);
21              
22             # see if there is a localport supplied stash it on our list.
23 3         7 my ($localport, %arg) = POE::Component::SNMP::_arg_scan(localport => @arg);
24              
25 3 100       9 if (defined $localport) {
26              
27 2 100       7 if (exists $localport{$localport}) {
28 1         4 ($session, $error) = (undef, "Address already in use");
29             } else {
30 1         12 ($session, $error) =
31             $class->SUPER::session( -nonblocking => 1,
32             -localport => $localport,
33             %arg,
34             );
35             }
36              
37             } else {
38              
39             # each session binds to a different local port/socket. This
40             # do..while loop catches potential port conflicts.
41 1         3 do {
42              
43             # pick a port that's not already in use by *us*
44 1         2 do {
45 1         11 $localport = int(rand(65536 - 1025) + 1025)
46             } while (exists $localport{$localport});
47              
48 1         17 ($session, $error) =
49             $class->SUPER::session( -nonblocking => 1,
50             -localport => $localport,
51             %arg,
52             );
53              
54             } while ($error =~ /bind|already/);
55              
56             }
57              
58 3 100       7498 if ($session) {
59             # remember it
60 1         4 $localport{$localport} = 1;
61 1         4 $session->{_poe_component_snmp_localport} = $localport;
62             }
63              
64 3         16 ($session, $error);
65             }
66              
67             # }}} session
68             # {{{ DESTROY
69              
70             sub DESTROY {
71 1     1   3558 my $session = shift;
72 1 50       18 if ((my $localport = delete $session->{_poe_component_snmp_localport})) {
73 0         0 delete $localport{$localport};
74             }
75             }
76              
77             # }}} DESTROY
78              
79             package POE::Component::SNMP;
80              
81 16     16   1390036 use Carp;
  16         42  
  16         1372  
82 16     16   95 use POE::Session;
  16         37  
  16         174  
83 16     16   16093 use POE::Component::SNMP::Dispatcher; # the real magic starts here
  16         47  
  16         1440  
84              
85             our $DISPATCHER;
86              
87             # {{{ BEGIN
88              
89             BEGIN
90             {
91             # Validate the creation of the Dispatcher object.
92              
93 16 50   16   94 if (!defined($DISPATCHER = $Net::SNMP::DISPATCHER = POE::Component::SNMP::Dispatcher->instance)) {
94 0         0 die('FATAL: Failed to create Dispatcher instance');
95             }
96             }
97              
98             # }}} BEGIN
99              
100             # {{{ create
101              
102             sub create {
103 5     5 1 9904 my $class = shift;
104 5         22 my @arg = @_;
105 5         10 my %arg; # = @_;
106              
107             my $alias;
108              
109             # we don't do alias dupe checks anymore, we leave that to POE
110 5         23 ($alias, %arg) = _arg_scan(alias => @arg);
111 5   100     30 $alias ||= 'snmp';
112              
113             # die unless we get a hostname
114 5 100       14 unless ( (_arg_scan(hostname => @arg))[0] ) {
115 2         359 croak "hostname parameter required";
116             }
117              
118             # make sure we have a dispatcher!
119 3 50       28 if (!defined($DISPATCHER = $Net::SNMP::DISPATCHER = POE::Component::SNMP::Dispatcher->instance)) {
120 0         0 die('FATAL: Failed to create Dispatcher instance');
121             }
122              
123 3         6 my ($session, $error);
124 3         26 ($session, $error) = POE::Net::SNMP->session( %arg );
125              
126             # use Data::Dumper; print Dumper([ $session->transport, $Net::SNMP::Transport::SOCKETS ]);
127              
128             # delete $Net::SNMP::Transport::SOCKETS->{$session->transport->{_sock_name}};
129              
130 3 100       554 croak $error unless $session;
131              
132 1         34 POE::Session->create( inline_states => { _start => \&start_snmp_session,
133             _stop => \&end_snmp_session,
134             finish => \&close_snmp_session,
135              
136             get => \&snmp_get,
137             getnext => \&snmp_getnext,
138             walk => \&snmp_walk,
139             getbulk => \&snmp_getbulk,
140             getentries => \&snmp_getentries,
141             trap => \&snmp_trap,
142             trap2c => \&snmp_trap2c,
143             inform => \&snmp_inform,
144             set => \&snmp_set,
145              
146             errmsg => \&snmp_errmsg,
147             callback_args => \&snmp_callback_args,
148             },
149             args => [
150             $alias, # component alias
151             $session, # Net::SNMP session
152             ],
153             );
154             }
155              
156             # }}} create
157             # {{{ start_snmp_session
158              
159             sub start_snmp_session {
160 1     1 0 317 my ($kernel, $heap, $alias, $session) = @_[KERNEL, HEAP, ARG0..$#_];
161              
162             # make sure we aren't duplicating component aliases!
163 1 50 50     15 if ( ! ($POE::VERSION <= 0.95 and POE::Kernel::ASSERT_DATA) and
      33        
164             defined $kernel->alias_resolve($alias)
165             ) {
166 0         0 local $Carp::CarpLevel = 4; # munge up to the right level of code
167              
168 0         0 croak "A ", __PACKAGE__, " instance called '$alias' already exists!";
169             }
170              
171 1         46 $kernel->alias_set($alias);
172             # $heap->{comp_alias} = $alias; # component alias
173 1         34 $heap->{snmp_session} = $session; # Net::SNMP session
174 1         9 $heap->{postback_args} = [ $alias, $session->hostname ];
175             }
176              
177             # }}} start_snmp_session
178             # {{{ close_snmp_session
179              
180             sub close_snmp_session {
181 0     0 0 0 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
182 0         0 my $snmp_session = $heap->{snmp_session};
183              
184 0 0       0 return unless defined $snmp_session;
185              
186 0 0       0 if ($snmp_session->debug & 0x08) {
187 0         0 print "debug: [", __LINE__, "] ", __PACKAGE__, "::close_snmp_session: calling __clear_pending\n";
188             }
189              
190             # cancel all current and pending requests
191 0         0 my $rv = $kernel->call(_poe_component_snmp_dispatcher => __clear_pending => $snmp_session);
192              
193             # remove our alias... since we have no more pending requests, we
194             # will go away now.
195 0         0 $kernel->alias_remove($_) for $kernel->alias_list( $session );
196              
197              
198             # use Data::Dumper; print Dumper $snmp_session;
199             # now the only thing keeping this session alive are any postback
200             # references that have yet to be delivered.
201             }
202              
203             # }}} close_snmp_session
204             # {{{ end_snmp_session
205              
206             sub end_snmp_session {
207 0     0 0 0 my ($kernel, $heap) = @_[KERNEL, HEAP];
208              
209 0         0 $heap->{snmp_session}->close;
210             }
211              
212             # }}} end_snmp_session
213              
214             # {{{ requests
215              
216 0     0 0 0 sub snmp_get { snmp_request( get_request => @_ ) }
217 0     0 0 0 sub snmp_getnext { snmp_request( get_next_request => @_ ) }
218 0     0 0 0 sub snmp_walk { snmp_request( get_table => @_ ) }
219 0     0 0 0 sub snmp_getbulk { snmp_request( get_bulk_request => @_ ) }
220 0     0 0 0 sub snmp_getentries { snmp_request( get_entries => @_ ) }
221 0     0 0 0 sub snmp_inform { snmp_request( inform_request => @_ ) }
222 0     0 0 0 sub snmp_set { snmp_request( set_request => @_ ) }
223              
224             # }}} requests
225             # {{{ snmp_request
226              
227             sub snmp_request {
228             # first parameter is the Net::SNMP method to call
229 0     0 0 0 my $method = shift;
230             # then standard POE args
231 0         0 my ($kernel, $heap, $sender, $target_state, @snmp_args) = @_[KERNEL, HEAP, SENDER, ARG0..$#_];
232              
233             # extract the PoCo::SNMP request method called, for diagnostics
234             # 'POE::Component::SNMP::snmp_get' => 'get'
235 0         0 my $action = (caller(1))[3]; $action =~ s/POE::Component::SNMP::snmp_//;
  0         0  
236              
237 0         0 my (@callback_args, $callback_args);
238 0         0 ($callback_args, @snmp_args) = _arg_scan(callback_args => @snmp_args);
239              
240 0         0 my $ok = 1;
241             # if $callback_args is defined, we got a callback_args in the request.
242 0 0       0 if (defined $callback_args) {
243 0 0       0 if (ref $callback_args eq 'ARRAY') {
244 0         0 @callback_args = @$callback_args;
245             } else {
246 0         0 $ok = 0;
247 0         0 $heap->{snmp_session}->_error("Argument to -callback_args must be an arrayref");
248 0         0 @callback_args = ($callback_args); # stash the "bad" argument to return with the error
249             }
250             }
251              
252             # do this before the 'set' logic to return an original copy of
253             # @snmp_args to the callback.
254 0         0 my @postback_args = (@{$heap->{postback_args}}, $action, @snmp_args);
  0         0  
255              
256 0 0       0 if ($ok) {
257 0 0       0 if ($method eq 'set_request') {
258             # string => numeric constant processing
259 0         0 @snmp_args = _dwim_set_request_args(@snmp_args);
260             }
261              
262             # this $postback is a closure. it goes away after firing.
263 0         0 my $postback = $sender->postback($target_state => @postback_args);
264             $ok = $heap->{snmp_session}->$method( @snmp_args,
265             -callback =>
266 0 0   0   0 [ sub { $postback->( ( defined ($_[0]->var_bind_list) ?
267             $_[0]->var_bind_list : $_[0]->error
268             )
269             # x 0,
270             # $_[0],
271             ,
272             @callback_args,
273             );
274             }
275 0         0 ]
276             );
277              
278             }
279              
280              
281 0 0       0 unless ($ok) {
282 0         0 $kernel->post( $sender => $target_state => \@postback_args,
283             [ $heap->{snmp_session}->error,
284             @callback_args,
285             ]
286             );
287             }
288              
289             }
290              
291             # }}} snmp_request
292              
293             # {{{ snmp_trap
294              
295             # invoke with: $status = $kernel->call( $alias => trap );
296             sub snmp_trap {
297 0     0 0 0 my ($kernel, $heap, @snmp_args) = @_[KERNEL, HEAP, ARG0..$#_];
298 0         0 $heap->{snmp_session}->trap( @snmp_args );
299             }
300              
301             # }}} snmp_trap
302             # {{{ snmp_trap2c
303              
304             # invoke with: $error = $kernel->call( $alias => trap2c );
305             sub snmp_trap2c {
306 0     0 0 0 my ($kernel, $heap, @snmp_args) = @_[KERNEL, HEAP, ARG0..$#_];
307 0         0 $heap->{snmp_session}->snmpv2_trap( @snmp_args );
308             }
309              
310             # }}} snmp_trap2c
311              
312             # {{{ snmp_errmsg
313              
314             # invoke with: $error = $kernel->call( $alias => error );
315 0     0 0 0 sub snmp_errmsg { $_[HEAP]{snmp_session}->error }
316              
317             # }}} snmp_errmsg
318             # {{{ snmp_callback_args
319              
320             # invoke with: $kernel->post( $alias => callback_args => @args );
321             sub snmp_callback_args {
322 0     0 0 0 my ($heap, @args) = @_[HEAP, ARG0..$#_];
323              
324 0         0 $heap->{callback_args} = \@args;
325             }
326              
327             # }}} snmp_callback_args
328              
329             # internal methods
330             # {{{ _arg_scan
331              
332             # scan an array for a key matching qw/ -key key Key KEY / and fetch
333             # the value. return the value and the remaining arg list minus the
334             # key/value pair.
335             sub _arg_scan_old {
336 0     0   0 my ($key, @arg) = @_;
337              
338 0         0 my $value;
339             # scan the @arg for any keys that are callback args.
340 0         0 for (0..$#arg) {
341 0 0       0 next unless defined $arg[$_];
342 0 0       0 if ($arg[$_] =~ /^-?$key$/i) {
343 0         0 $value = $arg[$_ + 1];
344              
345             # splice out the key and value from @arg:
346 0         0 splice @arg, $_, 2;
347             }
348             }
349              
350 0         0 ($value, @arg);
351             }
352              
353             sub _arg_scan {
354 13     13   40 my ($key, @arg) = @_;
355 13         16 my ($value, $k_idx, $v_idx, @ret_arg);
356              
357             # scan the @arg for any keys that are callback args.
358 13         45 for $k_idx ( map { $_*2 } (0..(@arg/2-1)) ) {
  58         94  
359 58         86 $v_idx = ($k_idx+1);
360 58 100       423 if ($arg[$k_idx] =~ m/^-?$key$/i) {
361 9         21 $value = $arg[$v_idx];
362             } else {
363             # we only return args that didn't match our scan.
364 49         115 push @ret_arg, @arg[$k_idx, $v_idx];
365             }
366             }
367              
368 13         96 ($value, @ret_arg);
369              
370             }
371              
372             # }}} _arg_scan
373             # {{{ _dwim_set_request_args
374              
375             # change string constant like 'OCTET_STRING' to a number by calling
376             # OCTET_STRING()
377             #
378             # For a set request, the 2nd item of the varbindlist should be a
379             # string constant indicating the value type. This block does a lookup
380             # of the numeric equivalent and replaces it in the parameter list.
381             sub _dwim_set_request_args {
382 0     0     my %snmp_args = @_;
383              
384             # extract the varbindlist from args
385 0           my ($vbl) = _arg_scan(varbindlist => @_);
386              
387             # make $type refer to the string in $vbl->[1]
388 0 0         my $type = ref($vbl) eq 'ARRAY' ? \$vbl->[1] : \ 'foo';
389              
390             # if Net::SNMP::Message knows about it, use it to replace the
391             # string with its numeric equivalent, e.g. 'OCTET_STRING' => 4
392 0 0         if ( Net::SNMP::Message->can($$type) ) {
393 0           $$type = Net::SNMP::Message->${$type}();
  0            
394             }
395              
396 0           %snmp_args; # flatten back to a simple list.
397             }
398              
399             # }}} _dwim_set_request_args
400              
401             1;
402              
403             __END__