1use strict;
2package NSNMP;
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.
31use vars qw($error $VERSION);
32$VERSION = '0.50';
33
34=head1 NAME
35
36NSNMP - fast, flexible, low-level, pure-Perl SNMP library
37
38=head1 SYNOPSIS
39
40    $bytes = NSNMP->encode(type => $type, request_id => $request_id,
41                          varbindlist => [
42                            [$ber_encoded_oid, $vtype, $value],
43                            ...
44                          ],
45                          # and optionally:
46                          community => $com, error_status => $status,
47                          error_index => $index);
48    $decoded = NSNMP->decode($bytes);
49    ($decoded->snmp_version, $decoded->community, $decoded->type,
50     $decoded->request_id, $decoded->error_status,
51     $decoded->error_index, $decoded->varbindlist);
52    $errname = NSNMP->error_description($decoded->error_status);
53    $comprehensible_oid =
54        NSNMP->decode_oid(($decoded->varbindlist)[0]->[0]);
55    $ber_encoded_oid = NSNMP->encode_oid('1.3.6.1.2.1.1.5.0');
56
57=head1 DESCRIPTION
58
59If you want something well-tested and production-quality, you probably
60want L<Net::SNMP|Net::SNMP>; if you just want to get and set some
61values with SNMP, you probably want L<NSNMP::Simple|NSNMP::Simple>.
62This module is for you if you want something fast, something suitable
63for dumping packet contents, or something suitable for writing an SNMP
64agent.
65
66This is an SNMP message encoding and decoding library, providing very
67low-level facilities; you pretty much need to read the SNMP RFCs to
68use it.  It is, however, very fast (it's more than an order of
69magnitude faster than Net::SNMP 4.1.2, and it can send a request and
70parse a response in only slightly more time than the snmpd from
71net-snmp-5.0.6 takes to parse the request and send a response), and
72it's relatively complete --- the interface is flexible enough that you
73can use it to write SNMP management applications, SNMP agents, and
74test suites for SNMP implementations.
75
76It doesn't export anything.
77
78=head1 MODULE CONTENTS
79
80=head2 Constants
81
82This module defines a number of constants for BER and SNMP type tags
83and error names.
84
85=head3 BER and SNMP types
86
87These are one-byte strings:
88INTEGER, OCTET_STRING, NULL, OBJECT_IDENTIFIER, SEQUENCE,
89IpAddress, Counter32, Gauge32, TimeTicks,
90GET_REQUEST, GET_NEXT_REQUEST, GET_RESPONSE, SET_REQUEST.
91
92=cut
93
94use constant INTEGER => "\x02";
95use constant OCTET_STRING => "\x04";
96use constant NULL => "\x05";
97use constant OBJECT_IDENTIFIER => "\x06";
98# UNIVERSAL, constructed, tag 10000b (16 decimal):
99use constant SEQUENCE => "\x30";
100use constant IpAddress => "\x40";
101use constant Counter32 => "\x41";
102use constant Gauge32 => "\x42";
103use constant TimeTicks => "\x43";
104use constant GET_REQUEST => "\xa0";  # context-specific, constructed, zero tag
105use constant GET_NEXT_REQUEST => "\xa1";
106use constant GET_RESPONSE => "\xa2";
107use constant SET_REQUEST => "\xa3";
108
109=head3 SNMP error names
110
111These are small integers: noError, tooBig, noSuchName, badValue,
112readOnly, genErr.
113
114=cut
115
116my @error_names = qw(noError tooBig noSuchName badValue readOnly genErr);
117for my $index (0..$#error_names) {
118  constant->import($error_names[$index] => $index);
119}
120
121=head2 NSNMP->error_description($error_status)
122
123Returns one of the strings 'noError', 'noSuchName', etc.
124
125=cut
126
127sub error_description {
128  my ($class, $error_status_number) = @_;
129  return $error_names[$error_status_number];
130}
131
132# so far I have:
133# - a debugging dumper for BER-encoded packets (subject to certain limitations)
134# - an OID encoder that's twice as fast as Net::SNMP's, and knowledge that
135#   hashing is 25 times faster still
136# - knowledge of a lot of "optimized" ways of sorting lists of OIDs that
137#   aren't faster than the obvious way, but also one way that's 3-16
138#   times as fast (packing the OIDs and memoizing that packing).
139# - an SNMP PDU decoder that more or less works, at about 6800 PDUs per second
140#   to just get the metadata, or 3900 PDUs per second to get the
141#   contents.  This is much faster than Net::SNMP, but it's around
142#   10%-20% slower than my first attempt, because it correctly handles
143#   more encodings.  (I hope it correctly handles everything, but I
144#   don't know.)
145# - an SNMP PDU encoder that also more or less works and is even
146#   faster than the decoder.  It doesn't quite work as well, though.
147# - some speed.  on my 500MHz notebook, a script to get the sysName
148#   10 000 times takes up 6.7 user seconds, 0.57 system seconds, and
149#   13.2 wallclock seconds, and the net-snmp snmpd (written in C)
150#   was using 40% of the CPU.  (So if we were running on a machine of
151#   our own, we'd be doing 1300 requests per second.) By contrast,
152#   Net::SNMP can fetch localhost's sysName 1000 times in 9.160 user
153#   seconds, 0.050 system seconds, and 10.384 wallclock seconds, or
154#   109 requests per second.  So this SNMP implementation is 12 times
155#   as fast for this simple task.  Even when I turned off OID
156#   translation caching, it only used an extra CPU second or so.
157
158# performance test results:
159# [kragen@localhost snmp]$ ./decodetest.pl   # now encode is slow too
160# Benchmark: timing 10000 iterations of justbasics, varbindlist_too...
161# justbasics:  2 wallclock secs ( 1.31 usr +  0.00 sys =  1.31 CPU) @ 7633.59/s (n=10000)
162# varbindlist_too:  2 wallclock secs ( 2.43 usr +  0.00 sys =  2.43 CPU) @ 4115.23/s (n=10000)
163# Benchmark: timing 10000 iterations of berdecode_encode, decode_encode, decode_encode_varbindlist, encode, slow_basicdecodes, unpackseq...
164# berdecode_encode: 11 wallclock secs (11.20 usr +  0.00 sys = 11.20 CPU) @ 892.86/s (n=10000)
165# decode_encode:  3 wallclock secs ( 3.00 usr +  0.00 sys =  3.00 CPU) @ 3333.33/s (n=10000)
166# decode_encode_varbindlist:  4 wallclock secs ( 4.13 usr +  0.00 sys =  4.13 CPU) @ 2421.31/s (n=10000)
167#     encode:  2 wallclock secs ( 1.67 usr +  0.00 sys =  1.67 CPU) @ 5988.02/s (n=10000)
168# (31 microseconds more.  Ouch!)
169# slow_basicdecodes:  6 wallclock secs ( 6.63 usr +  0.00 sys =  6.63 CPU) @ 1508.30/s (n=10000)
170#  unpackseq:  4 wallclock secs ( 3.83 usr +  0.00 sys =  3.83 CPU) @ 2610.97/s (n=10000)
171
172
173=head2 NSNMP->decode($message)
174
175Given the bytes of a message (for example, received on a socket, or
176returned from C<encode>), C<decode> returns an C<NSNMP::Message> object
177on which you can call methods to retrieve various fields of the SNMP
178message.
179
180If it can't parse the message, it returns C<undef>.
181
182See RFC 1157 (or a later SNMP RFC) for the meanings of each of these
183fields.
184
185My 500MHz laptop can run about 1-1.5 million iterations of a Perl loop
186per second, and it can decode almost 8000 small messages per second
187with this method.  It can decode a little over half as many if you
188also need varbindlists.
189
190The available methods for retrieving message fields follow.
191
192=over
193
194=cut
195
196sub decode {
197  my $class = shift;
198  my $rv = eval { NSNMP::Message->new(@_) };
199  $error = $@ if $@;
200  return $rv;
201}
202
203
204{
205  package NSNMP::Message;
206
207  # This package holds decoded SNMP messages (and code for decoding
208  # them).  The first couple of routines aren't usually used ---
209  # they're the "slow path".  The fast path takes about 150
210  # microseconds to decode a message, excluding varbindlist, on my
211  # 500MHz laptop.  The slow path takes 500 microseconds to do the
212  # same.
213
214  # Given a string beginning with a BER item, split into type, length,
215  # value, and remainder
216  sub BERitem {
217    my ($data) = @_;
218    my ($type, $len, $other) = unpack "aCa*", $data;
219    if ($len & 0x80) {
220      if ($len == 0x82) { ($len, $other) = unpack "na*", $other }
221      elsif ($len == 0x81) { ($len, $other) = unpack "Ca*", $other }
222      else {
223	(my $rawlen, $other) = unpack "a[$len]a*", $other;
224	# This would have a problem with values over 2^31.
225	# Fortunately, we're in an IP packet.
226	$len = unpack "N", "\0" x (4 - $len) . $rawlen;
227      }
228    }
229    return $type, $len, unpack "a[$len]a*", $other;
230  }
231
232  sub unpack_integer {
233    my ($intstr) = @_;
234    return unpack "N", "\0" x (4 - length($intstr)) . $intstr;
235  }
236
237  # general BER sequence type unpacking
238  sub unpack_sequence {
239    my ($sequence) = @_;
240    my ($type, $len, $contents, $remainder) = BERitem($sequence);
241    return undef, "Unpacking non-sequence" unless ($type & "\x20") ne "\0";
242    # unpack individual items...
243    return _unpack_sequence_contents($contents);
244  }
245
246  sub _unpack_sequence_contents {
247    my ($contents) = @_;
248    my @rv;
249    my ($type, $len, $value);
250    while ($contents) {
251      ($type, $len, $value, $contents) = BERitem($contents);
252      return undef, "Incomplete BER sequence" unless $len == length($value);
253      push @rv, $type, $value;
254    }
255    return \@rv, undef;
256  }
257
258  sub _basicdecodes_slow_but_robust {
259    my ($data) = @_;
260    my ($sequence, $error) = unpack_sequence($data);
261    die $error if $error;
262    my (undef, $version, undef, $community, $pdu_type, $pdu) = @$sequence;
263    ($sequence, $error) = _unpack_sequence_contents($pdu);
264    die $error if $error;
265    my (undef, $request_id, undef, $error_status,
266	undef, $error_index, undef, $varbindlist_str) = @$sequence;
267    return (version => unpack_integer($version) + 1, community => $community,
268	    pdu_type => $pdu_type, request_id => $request_id,
269	    error_status => unpack_integer($error_status),
270	    error_index => unpack_integer($error_index),
271	    varbindlist_str => $varbindlist_str);
272  }
273
274  sub _basicdecodes {
275    my ($data) = @_;
276    my ($packetlength, $verlen, $version, $community, $pdu_type, $pdulen,
277	$request_id, $eslen, $error_status, $eilen, $error_index, $vblen,
278	$varbindlist_str) = eval {
279	  unpack "xC xCc xc/a aC xc/a xCC xCC xCa*", $data;
280	};
281    if (not $@ and not (($packetlength | $verlen | $pdulen | $eslen |
282			 $eilen | $vblen) & 0x80)) {
283      return (version => $version + 1, community => $community,
284	      pdu_type => $pdu_type, request_id => $request_id,
285	      error_status => $error_status, error_index => $error_index,
286	      varbindlist_str => $varbindlist_str);
287    }
288    # If we're here, it means that we probably have a multibyte length
289    # field on our hands --- either that, or a malformed packet.
290    return _basicdecodes_slow_but_robust($data);
291  }
292  sub new {
293    my ($class, $data) = @_;
294    return bless { data => $data, _basicdecodes($data) }, $class;
295  }
296
297=item ->version
298
299Returns the numeric SNMP version: 1, 2, or 3.  (Note that 1 is encoded
300as 0 in the packet, and 2 is encoded as 1, etc., but this method
301returns the human-readable number, not the weird encoding in the
302packet.)
303
304=cut
305
306  sub version { $_[0]{version} }
307
308=item ->community
309
310Returns the community string.
311
312=cut
313
314  sub community { $_[0]{community} }
315
316=item ->type
317
318Returns the type tag of the PDU, such as NSNMP::GET_REQUEST,
319NSNMP::GET_RESPONSE, NSNMP::SET_REQUEST, etc.  (See L</Constants>.)
320
321=cut
322
323  sub type { $_[0]{pdu_type} }          # 1-byte string
324
325=item ->request_id
326
327Returns the bytes representing the request ID in the SNMP message.
328(This may seem perverse, but often, you don't have to decode them ---
329you can simply reuse them in a reply packet, or look them up in a hash
330of outstanding requests.  Of course, in the latter case, you might
331have to decode them anyway, if the agent was perverse and re-encoded
332them in a different way than you sent them out.)
333
334=cut
335
336  sub request_id { $_[0]{request_id} }  # string, not numeric
337
338=item ->error_status, ->error_index
339
340Return the numeric error-status and error-index from the SNMP packet.
341In non-error cases, these will be 0.
342
343=cut
344
345  sub error_status { $_[0]{error_status} }
346  sub error_index { $_[0]{error_index} }
347  sub _decode_varbindlist {
348    my ($str) = @_;
349    my (@varbinds) = eval {
350      # the unpack issues warnings when failing sometimes
351      local $SIG{__WARN__} = sub { };
352      unpack "(xcxc/aac/a)*", $str;
353    };
354    return _slow_decode_varbindlist($str) if $@;
355    my @rv;
356    while (@varbinds) {
357      my ($length, $oid, $type, $value) = splice @varbinds, 0, 4;
358      return _slow_decode_varbindlist($str) if $length < 0;
359      push @rv, [$oid, $type, $value];
360    }
361    return \@rv;
362  }
363
364  sub _slow_decode_varbindlist {
365    my ($str) = @_;
366    my ($varbinds, $error) = _unpack_sequence_contents($str);
367    die $error if $error;
368    my @rv;
369    while (@$varbinds) {
370      my (undef, $varbind) = splice @$varbinds, 0, 2;
371      my ($varbindary, undef) = _unpack_sequence_contents($varbind);
372      my (undef, $oid, $type, $value) = @$varbindary;
373      push @rv, [$oid, $type, $value];
374    }
375    return \@rv;
376  }
377
378=item ->varbindlist
379
380Returns a list of C<[$oid, $type, $value]> triples.  The type is a BER
381type, normally equal to NSNMP::OCTET_STRING or one of the other
382constants for BER types. (See L</Constants>.)  The OIDs are still
383encoded in BER; you can use C<-E<gt>decode_oid> to get human-readable
384versions, as documented below.
385
386=back
387
388=cut
389
390  sub varbindlist {
391    @{$_[0]{varbindlist} ||= _decode_varbindlist($_[0]{varbindlist_str})}
392  }
393}
394
395sub _encode_oid {
396  my ($oid) = @_;
397  if ($oid =~ s/^1\.3\./43./) {
398    return pack 'w*', split /\./, $oid;
399  } else {  # XXX need a test for this
400    my ($stupidity, $more_stupidity, @chunks) = split /\./, $oid;
401    return pack 'w*', $stupidity * 40 + $more_stupidity, @chunks;
402  }
403}
404
405sub _decode_oid {  # XXX need a test for this
406  my ($encoded) = @_;
407  if ($encoded =~ s/\A\x2b/\001\003/) {
408    return join '.', unpack 'w*', $encoded;
409  } else {
410    my ($stupidity, @chunks) = unpack 'w*', $encoded;
411    return join '.', int($stupidity/40), $stupidity % 40, @chunks;
412  }
413}
414
415{
416  my %encode_oids;
417  my %decode_oids;
418
419=head2 NSNMP->encode_oid($oid)
420
421This method produces the BER-encoded version of the ASCII-represented
422OID C<$oid>, which must be a sequence of decimal numbers separated by
423periods.  Leading periods are allowed.
424
425=cut
426
427  sub encode_oid {
428    my ($class, $oid) = @_;
429    if (keys %encode_oids > 1000) {
430      %encode_oids = ();
431      %decode_oids = ();
432    }
433    return $encode_oids{$oid} if exists $encode_oids{$oid};
434    $oid =~ s/\A\.//;
435    return $encode_oids{$oid} if exists $encode_oids{$oid};
436    my $encoded = _encode_oid($oid);
437    $encode_oids{$oid} = $encoded;
438    $decode_oids{$encoded} = $oid;
439    return $encoded;
440  }
441
442=head2 NSNMP->decode_oid($bytestring)
443
444Given the BER encoding of an OID in C<$bytestring>, this method
445produces the OID's ASCII representation, as a sequence of decimal
446numbers separated by periods, without a leading period.
447
448=cut
449
450  sub decode_oid {
451    my ($class, $encoded) = @_;
452    if (keys %encode_oids > 1000) {
453      %encode_oids = ();
454      %decode_oids = ();
455    }
456    return $decode_oids{$encoded} if exists $decode_oids{$encoded};
457    my $oid = _decode_oid($encoded);
458    $encode_oids{$oid} = $encoded;
459    $decode_oids{$encoded} = $oid;
460    return $oid;
461  }
462}
463
464{
465  sub _encode_length {
466    if ($_[0] < 128) { return pack "c", $_[0] }
467    if ($_[0] < 256) { return "\201" . pack "C", $_[0] }
468    return "\202" . pack "n", $_[0];
469  }
470
471  sub _encode_varbind {
472    my ($oid, $type, $value) = @{$_[0]};
473    # 127 is max length to encode in 1 byte
474    # OID plus value + 2 length bytes + 2 tag bytes must <= 127
475    # to use short form
476    if (length($oid) + length($value) < 123) {
477      return pack "ac/a*", SEQUENCE,
478	pack "ac/a* ac/a*", OBJECT_IDENTIFIER, @{$_[0]};
479    } else {
480      my $oidlength = _encode_length(length($oid));
481      my $valuelength = _encode_length(length($value));
482      return join('', SEQUENCE, _encode_length(length($oid) + length($value)
483					       + length($oidlength)
484					       + length($valuelength) + 2),
485		  OBJECT_IDENTIFIER, $oidlength, $oid,
486		  $type, $valuelength, $value);
487    }
488}
489
490
491=head2 NSNMP->encode(%args)
492
493Returns a string containing an encoded SNMP message, according to the
494args specified.  Available args correspond one for one to the
495C<NSNMP::Message> methods defined above under C<decode>; they include
496the following:
497
498=over 4
499
500=item request_id => $req_id_str
501
502Request ID as a string (not an integer).  Mandatory.
503
504=item varbindlist =E<gt> C<[[$oid, $type, $value], [$oid, $type, $value]...]>
505
506Varbindlist as an ARRAY ref containing (oid, type, value) tuples,
507represented also as ARRAY refs.  OIDs, types, and values are assumed
508to already be BER-encoded.  You can sensibly pass the results of the
509C<-E<gt>varbindlist> method from a decoded message in here, just wrap
510it in an ARRAY ref: C<varbindlist =E<gt> [$msg-E<gt>varbindlist]>.
511Mandatory.
512
513=item type => $type
514
515PDU type --- normally NSNMP::GET_REQUEST, NSNMP::GET_RESPONSE,
516etc.  (See L</Constants>.)  Mandatory.
517
518=item community => $community
519
520Community string.  Default is C<public>.
521
522=item error_status => $error
523
524=item error_index => $index
525
526Error-status and error-index, as integers.  Only meaningful on
527response messages.  Default 0.
528
529=item version => $ver
530
531Human-readable version of SNMP: 1, 2, or 3, default 1.  Presently 2
532and 3 have features this library doesn't support.
533
534=back
535
536=cut
537
538  my $onebyteint = INTEGER . pack "c", 1;
539  sub encode {
540    my ($class, %args) = @_;
541    my $community = $args{community};
542    $community = 'public' if not defined $community;
543    my $encoded_varbinds = join '',
544      map { _encode_varbind $_ } @{$args{varbindlist}};
545    my $pdu_start = pack 'ac/a* a*C a*C',   # XXX give error on long req IDs
546      INTEGER, $args{request_id},
547      $onebyteint, $args{error_status} || 0,
548      $onebyteint, $args{error_index} || 0,
549    my $message_start = pack 'aCC ac/a* a',
550      INTEGER, 1, ($args{version} || 1) - 1,
551      OCTET_STRING, $community,  # XXX cope with long community strings
552      $args{type};
553    if (length($encoded_varbinds) + length($pdu_start) + length($message_start)
554	< 123) { # 127 max - TL - L - TL = 122
555      # for a small GetRequestPDU with two varbinds, this path is 25
556      # microseconds shorter.
557      return pack 'ac/a*', SEQUENCE, (pack 'a* c/a*', $message_start,
558        pack 'a* ac/a*', $pdu_start, SEQUENCE, $encoded_varbinds);
559    } else {
560      my $pdu_contents = join('', $pdu_start, SEQUENCE,
561        _encode_length(length($encoded_varbinds)), $encoded_varbinds);
562      my $message_contents = join('', $message_start,
563        _encode_length(length($pdu_contents)), $pdu_contents);
564      return join('', SEQUENCE, _encode_length(length($message_contents)),
565		  $message_contents);
566    }
567  }
568}
569
570
571=head1 EXAMPLES
572
573Example usage of the main entry points, C<encode>, C<decode>,
574C<encode_oid>, and C<decode_oid>, follows:
575
576    my $bytes = NSNMP->encode(
577      type => NSNMP::GET_REQUEST,
578      request_id => (pack "N", 38202),
579      varbindlist => [
580        [NSNMP->encode_oid('.1.3.6.1.2.1.1.5.0'), NSNMP::NULL, ''],
581      ],
582    );
583    $socket->send($bytes);
584    my $decoded = NSNMP->decode($bytes);
585    # prints "111111\n"
586    print(
587      ($decoded->version==1),
588      ($decoded->community eq 'public'),
589      ($decoded->type eq NSNMP::GET_REQUEST),
590      ($decoded->request_id eq pack "N", 38202),
591      ($decoded->error_status == 0),
592      ($decoded->error_index == 0), "\n"
593    );
594    my @varbinds = $decoded->varbindlist;
595    # prints "111\n"
596    print(
597      (NSNMP->decode_oid($varbinds[0][0]) eq '1.3.6.1.2.1.1.5.0'),
598      ($varbinds[0][1] eq NSNMP::NULL),
599      ($varbinds[0][2] eq ''),
600      "\n",
601    );
602
603=head1 FILES
604
605None.
606
607=head1 AUTHOR
608
609Kragen Sitaker E<lt>kragen@pobox.comE<gt>
610
611=head1 BUGS
612
613This documentation does not adequately express the stupidity and
614rottenness of the SNMP protocol design.
615
616The ASN.1 BER, in which SNMP packets are encoded, allow the sender
617lots of latitude in deciding how to encode things.  This module
618doesn't have to deal with that very often, but it does have to deal
619with the version, error-status, and error-index fields of SNMP
620messages, which are generally encoded in a single byte each.  If the
621sender of an SNMP packet encodes them in multiple bytes instead, this
622module will fail to decode them, or worse, produce nonsense output.
623It should instead handle these packets correctly.
624
625Malformed VarBindLists can cause the C<-E<gt>varbindlist> method to
626C<die> with an unhelpful error message.  It should instead return a
627helpful error indication of some kind.
628
629It doesn't do much yet; in particular, it doesn't do SNMPv1 traps or
630anything from SNMPv2 or v3.
631
632It doesn't even consider doing any of the following: decoding BER
633values found in varbind values, understanding MIBs, or anything that
634involves sending or receiving packets.  These jobs belong to other
635modules, most of which haven't been written yet.
636
637=cut
638
6391;
640