File Coverage

blib/lib/NSNMP/Simple.pm
Criterion Covered Total %
statement 92 92 100.0
branch 33 34 97.0
condition 10 12 83.3
subroutine 15 15 100.0
pod 3 5 60.0
total 153 158 96.8


line stmt bran cond sub pod time code
1 1     1   549 use strict;
  1         2  
  1         51  
2             package NSNMP::Simple;
3             # Copyright (c) 2003-2004 AirWave Wireless, Inc.
4              
5             # Redistribution and use in source and binary forms, with or without
6             # modification, are permitted provided that the following conditions
7             # are met:
8              
9             # 1. Redistributions of source code must retain the above
10             # copyright notice, this list of conditions and the following
11             # disclaimer.
12             # 2. Redistributions in binary form must reproduce the above
13             # copyright notice, this list of conditions and the following
14             # disclaimer in the documentation and/or other materials provided
15             # with the distribution.
16             # 3. The name of the author may not be used to endorse or
17             # promote products derived from this software without specific
18             # prior written permission.
19              
20             # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
21             # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22             # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
24             # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25             # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
26             # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28             # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29             # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30             # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 1     1   952 use IO::Socket;
  1         35310  
  1         41  
32 1     1   643 use NSNMP;
  1         3  
  1         25  
33 1     1   7 use NSNMP::Mapper;
  1         1  
  1         25  
34 1     1   5 use vars qw($error $error_status $socket);
  1         3  
  1         66  
35             # these are negative so as not to collide with SNMP protocol error numbers,
36             # which count up from 1
37 1     1   6 use constant noResponse => -1;
  1         1  
  1         61  
38 1     1   28 use constant badHostName => -2;
  1         2  
  1         1415  
39              
40             =head1 NAME
41              
42             NSNMP::Simple - simple interface to get and set synchronously
43              
44             =head1 SYNOPSIS
45              
46             my $sysnameoid = '1.3.6.1.2.1.1.5.0';
47             my $hostname = NSNMP::Simple->get('127.0.0.1', $sysnameoid);
48             die $NSNMP::Simple::error unless defined $hostname;
49             NSNMP::Simple->set('127.0.0.1', $sysnameoid, NSNMP::OCTET_STRING,
50             'thor.cs.cmu.edu', community => 'CMUprivate')
51             or die $NSNMP::Simple::error;
52             my %sysoids = NSNMP::Simple->get_table('127.0.0.1', '1.3.6.1.2.1');
53              
54             =head1 DESCRIPTION
55              
56             NSNMP::Simple lets you get or set a single OID via SNMP with a single
57             line of code. It's easier to use, and roughly an order of magnitude
58             faster, than L 4.1.2, but Net::SNMP is still much
59             more mature and complete. I don't presently recommend using
60             NSNMP::Simple in production code.
61              
62             =head1 MODULE CONTENTS
63              
64             =cut
65              
66             # some speed costs on my 500MHz PIII laptop:
67             # it takes:
68             # 3 microseconds to do a function call and return
69             # 2 microseconds to do a hash lookup on a 20char string
70             # 50 microseconds in the kernel to send a packet and receive a response
71             # 600 microseconds to encode the packet, send it, and receive and
72             # decode the response (in user time) (getsysname-lots.pl)
73             # 150 microseconds to do the socket, address, timeout, and error-status
74             # checking
75             # this module does
76             # another 40 microseconds to do the request_id handling (in the normal case:
77             # request ID matches)
78             # a negligible amount of time to handle retry logic
79             # encoding and decoding of non-OCTET_STRING values
80             # all in all: about 1250 microseconds per request-response pair with this
81             # module
82             # although I still haven't implemented traps, v2 and v3, and handling of
83             # failure to socket; all of these will slow this module down more.
84              
85             # XXX refactor this a little more
86              
87             my ($nfound, $timeleft);
88             sub _read_timeout {
89 717     717   977 my ($fh, $timeout) = @_;
90 717         840 my $rin = '';
91 717         3088 vec($rin, fileno($fh), 1) = 1;
92 717         12696625 return select($rin, undef, undef, $timeout);
93             }
94              
95             sub _remember_error {
96 13     13   22 my ($response_decoded) = @_;
97 13         38 $error_status = $response_decoded->error_status;
98 13         104 my $error_name = NSNMP->error_description($error_status);
99 13         76 $error = "Received $error_name($error_status) error-status at error-index "
100             . $response_decoded->error_index;
101 13         102 return undef;
102             }
103              
104             my $request_id = 'aaaa';
105              
106             my $response;
107             sub _synchronous_request_response {
108 705     705   2736 my ($host, %args) = @_;
109 705   66     1810 $socket ||= IO::Socket::INET->new(Proto => 'udp'); # XXX error check
110 705         1571 my $port = 161; # XXX test
111 705 100       5337 $port = $1 if $host =~ s/:(\d+)\z//;
112 705         153210 my $iaddr = Socket::inet_aton($host);
113 705 100       2523 if (not defined $iaddr) {
114 2         31 $error = "Unable to resolve destination address '$host'";
115 2         19 $error_status = badHostName;
116 2         14 return undef;
117             }
118              
119             # This method of picking request IDs has the following nice properties:
120             # - there are 450 000 request IDs available
121             # - they're always positive, so if they get sign-extended, it's always
122             # with 0s
123             # - they can never be represented in less than 4 bytes
124             # - it's relatively fast
125 703         1177 $request_id++;
126 703 50       1857 $request_id = substr($request_id, 1) if length($request_id) > 4;
127              
128 703 100       2112 my $tries = 1 + (exists $args{retries} ? $args{retries} : 1);
129 703         1856 try: while ($tries--) {
130 709         4120 send $socket, NSNMP->encode(request_id => $request_id, %args), 0,
131             scalar Socket::sockaddr_in($port, $iaddr); # XXX err handling: bad port?
132              
133 709 100       142983 my $timeout = (exists $args{timeout} ? $args{timeout} : 5);
134 709         949 for (;;) {
135             # perldoc -f select says, "Most systems do not bother to return
136             # anything useful in $timeleft." Well, Linux 2.4 does; so if
137             # you're using something that doesn't, upgrade.
138 717         1852 ((my $success), $timeout) = _read_timeout($socket, $timeout);
139 717 100       2395 next try unless $success;
140              
141 704         3101 $socket->recv($response, 65536, 0); # XXX error handling?
142 704         18670 my $resp_decoded = NSNMP->decode($response);
143 704 100 100     9661 if (not $resp_decoded or $resp_decoded->request_id ne $request_id
      66        
144             and $resp_decoded->request_id !~ /\A\0+\Q$request_id\E\z/) {
145             # ignore it
146 8         41 next;
147             }
148 696 100       1984 return _remember_error($resp_decoded) if $resp_decoded->error_status;
149 683         1096 ($error, $error_status) = (undef, undef);
150 683         2584 return $resp_decoded;
151             }
152             }
153 7         85 ($error, $error_status) =
154             ("No response from remote host '$host'", noResponse);
155 7         210 return undef;
156             }
157              
158             sub decode_int {
159 40     40 0 80 my ($intstr) = @_;
160 40 100       153 my $padchar = ("\0" eq ($intstr & "\x80")) ? "\0" : "\377";
161 40 100       129 $intstr = substr($intstr, length($intstr) - 4) if length($intstr) > 4;
162 40         103 my $padded = $padchar x (4 - length($intstr)) . $intstr;
163 40         820 my $num = unpack "N", $padded;
164 40 100       153 $num -= 4294967296 if $padchar ne "\0"; # unpack gave us unsigned
165 40         424 return $num;
166             }
167              
168             my %decoders = (
169             NSNMP::INTEGER => \&decode_int,
170             NSNMP::Counter32 => \&decode_int,
171             NSNMP::Gauge32 => \&decode_int,
172             NSNMP::TimeTicks => \&decode_int,
173             NSNMP::OCTET_STRING => sub { $_[0] },
174             NSNMP::IpAddress => sub { join '.', unpack "C*", $_[0] },
175             NSNMP::OBJECT_IDENTIFIER => sub { NSNMP->decode_oid($_[0]) },
176             );
177              
178             sub encode_int {
179 11     11 0 158 my ($int) = @_;
180 11         29 my $rv = pack "N", $int;
181 11 100 100     517 return "\0$rv" if $int >= 0 and (($rv & "\x80") ne "\00");
182 10         361 return $rv;
183             }
184              
185             my %encoders = (
186             NSNMP::INTEGER => \&encode_int,
187             NSNMP::Counter32 => \&encode_int, # XXX test
188             NSNMP::Gauge32 => \&encode_int,
189             NSNMP::TimeTicks => \&encode_int, # XXX test
190             NSNMP::OCTET_STRING => sub { $_[0] },
191             NSNMP::IpAddress => sub { pack "C*", split /\./, $_[0] },
192             NSNMP::OBJECT_IDENTIFIER => sub { NSNMP->encode_oid($_[0]) }, # XXX test
193             );
194              
195             =head2 NSNMP::Simple->get($agent, $oid, %args)
196              
197             Returns the value of C<$oid> on the SNMP agent at C<$agent>, which can
198             be a hostname or an IP address, optionally followed by a colon and a
199             numeric port number, which defaults to 161, the default SNMP port.
200              
201             C<%args> can contain any or all of the following:
202              
203             =over
204              
205             =item C $ver>
206              
207             $ver is an SNMP version number (1 or 2 --- 3 isn't yet supported ---
208             see L). Default is 1.
209              
210             =item C $comm>
211              
212             Specifies the community string. Default is C.
213              
214             =item C $retries>
215              
216             Specifies retries. Default is 1 --- that is, two tries. Retries are
217             evenly spaced.
218              
219             =item C $timeout>
220              
221             Specifies a timeout in (possibly fractional) seconds. Default is 5.0.
222              
223             =back
224              
225             Translates the value of C<$oid> into a Perlish value, so, for example,
226             an INTEGER OID whose value is 1 will be returned as "1", not "\001".
227             IpAddresses are translated to dotted-quad notation, integer-like types
228             are translated to integers, and OCTET STRINGS, OPAQUES, and
229             NsapAddresses are left alone.
230              
231             It doesn't return the type of the value at all.
232              
233             In case of failure, it returns C and sets
234             C<$NSNMP::Simple::error> to a string describing the error in English,
235             in the same format as Net::SNMP's error messages.
236              
237             =cut
238              
239             # Note that I wanted to put that list of %args first in the text, as a
240             # bulleted list. But pod2html barfed on the required blank line after
241             # the =item * line, so I gave up on bulleted lists in POD. Yick.
242              
243             sub get {
244 52     52 1 18664 my ($class, $host, $oid, %args) = @_;
245 52         1831 my $response_decoded =
246             _synchronous_request_response($host,
247             type => NSNMP::GET_REQUEST,
248             varbindlist => [[NSNMP->encode_oid($oid),
249             NSNMP::NULL, '']],
250             %args);
251 52 100       681 return undef unless $response_decoded;
252 44         163 my $varbind = ($response_decoded->varbindlist)[0];
253 44         246 return $decoders{$varbind->[1]}->($varbind->[2]);
254             }
255              
256             =head2 NSNMP::Simple->set($agent, $oid, $type, $value, %args)
257              
258             Sets the value of C<$oid> on the SNMP agent at C<$agent> to the value
259             C<$value>, as BER-encoded type C<$type>. Returns true on success,
260             false on failure, and also sets C<$NSNMP::Simple::error> on failure.
261             Accepts the same C<%args> as C<-Eget>.
262              
263             =cut
264              
265             sub set {
266 16     16 1 319 my ($class, $host, $oid, $type, $value, %args) = @_;
267 16         76 return !!_synchronous_request_response($host,
268             type => NSNMP::SET_REQUEST,
269             varbindlist => [[NSNMP->encode_oid($oid),
270             $type, $encoders{$type}->($value)]],
271             %args);
272             }
273              
274             =head2 NSNMP::Simple->get_table($agent, $oid, %args)
275              
276             Gets the values of all OIDs under C<$oid> on the SNMP agent at
277             C<$agent>. Returns a list of alternating OIDs and values, in OID
278             lexical order; you can stuff it into a hash if you don't care about
279             the order. If there are no OIDs under C<$oid>, returns an empty list
280             and clears C<$NSNMP::Simple::error>. Note that this can be caused
281             either by misspelling the OID or by actually having an empty table,
282             and there's no way to tell which. (See the note in L about
283             the SNMP protocol design.)
284              
285             If any of the component SNMP requests returns an unexpected error,
286             C returns an empty list and sets C<$NSNMP::Simple::error>.
287              
288             Note for Net::SNMP users: C does not set
289             C<$NSNMP::Simple::error> on an empty table, but Net::SNMP's
290             C does.
291              
292             Accepts the same C<%args> as C<-Eget>.
293              
294             The OIDs in the returned list are spelled in ASCII with or without a
295             leading dot, depending on whether or not C<$oid> has a leading dot.
296              
297             =cut
298              
299             sub get_table {
300 13     13 1 5169 my ($class, $host, $oid, %args) = @_;
301 13         69 my @rv;
302             my $response;
303 13         120 my $mapper = NSNMP::Mapper->new($oid => 1);
304 13         266 my $initial_dot = $oid =~ /\A\./;
305 13         169 my $encoded_oid = NSNMP->encode_oid($oid);
306 13         20 for (;;) {
307 637         2691 $response = _synchronous_request_response($host,
308             type => NSNMP::GET_NEXT_REQUEST,
309             varbindlist => [[$encoded_oid, NSNMP::NULL, '']],
310             %args,
311             );
312 637 100       4702 if (not defined $response) {
313 12 100       40 if ($error_status eq NSNMP::noSuchName) {
314             # end of MIB
315 10         26 ($error_status, $error) = (undef, undef);
316 10         1455 return @rv;
317             }
318 2         45 return ();
319             }
320 625         1630 my @varbindlist = $response->varbindlist;
321 625         1594 $encoded_oid = $varbindlist[0][0];
322 625         1841 $oid = NSNMP->decode_oid($varbindlist[0][0]);
323 625 100       1911 return @rv unless ($mapper->map($oid))[0];
324 624 100       3026 push @rv, ($initial_dot ? ".$oid" : $oid),
325             $decoders{$varbindlist[0][1]}->($varbindlist[0][2]);
326             }
327             }
328              
329             =head2 $error
330              
331             C<$NSNMP::Simple::error> is undef after any successful subroutine
332             call on this module, and an English string describing the error after
333             any unsuccessful subroutine call.
334              
335             C<$NSNMP::Simple::error_status> is undef when C<$error> is undef,
336             and when C<$error> is defined, C<$error_status> contains an integer
337             describing the type of error. This may be a raw SNMP C
338             code, such as NSNMP::noSuchName, or it may be one of the following
339             values:
340              
341             =over
342              
343             =item NSNMP::Simple::noResponse
344              
345             This code means that the remote host sent no response, or at least, no
346             response we could decode, so we timed out. (The timeout value is
347             configurable, as described earlier.)
348              
349             =item NSNMP::Simple::badHostName
350              
351             This code means that C couldn't resolve the hostname
352             given. It might be malformed or a nonexistent DNS name, or it might
353             be an existing DNS name, but DNS might be broken for some other
354             reason.
355              
356             =back
357              
358             =head1 FILES
359              
360             None.
361              
362             =head1 AUTHOR
363              
364             Kragen Sitaker Ekragen@pobox.comE
365              
366             =head1 BUGS
367              
368             This module uses L, so it inherits most of the
369             bugs of that module.
370              
371             It's still too slow. On my 500MHz laptop, it can SNMP-walk 5675 OIDs
372             in about 7.2 CPU seconds, for less than 800 OIDs per second. ucd-snmp
373             (now confusingly called net-snmp, not to be confused with Net::SNMP)
374             takes 1.8 CPU seconds to perform the same task. That's four times as
375             fast. On the other hand, Net::SNMP manages about 110 OIDs per second,
376             seven times slower still.
377              
378             =cut
379              
380             1;