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