[libsnmp-session-perl] 01/15: New upstream version 1.14~git20130523.186a005

Roland Rosenfeld roland at moszumanska.debian.org
Mon Dec 4 19:28:55 UTC 2017


This is an automated email from the git hooks/post-receive script.

roland pushed a commit to branch master
in repository libsnmp-session-perl.

commit 564e12680238a3e4bc358a31126b0debcbd56e07
Author: Roland Rosenfeld <roland at debian.org>
Date:   Thu Aug 24 08:29:26 2017 +0200

    New upstream version 1.14~git20130523.186a005
---
 ChangeLog                        | 2534 ++++++++++++++++++++++++++++++++++++++
 META.yml                         |   12 -
 Net_SNMP_util.pm                 | 2130 ++++++++++++++++++++++++++++++++
 README                           |  231 +---
 changes.html                     |  709 +++++++++++
 faq.html                         |   72 ++
 index.html                       |  307 +----
 lib/BER.pm                       |  322 ++++-
 lib/SNMP_Session.pm              |  956 +++++++++++---
 lib/SNMP_Table.pm                |  132 ++
 lib/SNMP_util.pm                 |  122 +-
 security.html                    |   42 +
 t/00ber.t                        |   52 +
 test/asn1-test.pl                |   32 +
 test/atm-cfgmaker                |  128 ++
 test/atol-test.c                 |   37 +
 test/bad-trap.pl                 |   40 +
 test/bay-atm-test.pl             |   54 +
 test/bgpls                       |  209 ++++
 test/bridge-list-fdb             |  104 ++
 test/cammer                      |  245 ++++
 test/cisco-config-history        |  170 +++
 test/cisco-list-cards            |  258 ++++
 test/cisco-tftp.pl               |   50 +
 test/counter64-test.pl           |  201 +++
 test/cricket-genconf-sensor      |  232 ++++
 test/d.pl                        |   62 +
 test/digital-bug                 |   32 +
 test/entls                       |  269 ++++
 test/fore-test.pl                |   50 +
 test/hex-test.pl                 |   61 +
 test/inexist.pl                  |   62 +
 test/ip-addr-to-if-index         |  141 +++
 test/lambda-monitor.pl           |  169 +++
 test/lambda-webmon.pl            |  208 ++++
 test/list-bgp4-neighbors         |   53 +
 test/list-bgp4-table             |   84 ++
 test/list-ospf-neighbors         |   47 +
 test/look-at-counters.pl         |  105 ++
 test/ls1010-list-vcls            |  258 ++++
 test/make-cisco-products.pl      |   21 +
 test/max-list-sessions           |  130 ++
 test/mcount.pl                   |  218 ++++
 test/mdebug                      |  142 +++
 test/mrouted-genconf             |  114 ++
 test/mrtg-ipmcast                |  319 +++++
 test/msdpls                      |  303 +++++
 test/mtf.pl                      |   51 +
 test/negative-counter.pl         |    8 +
 test/party-test.pl               |   15 +
 test/pnni-find-ilmi-neighbors.pl |   41 +
 test/qosls                       |  553 +++++++++
 test/router-stats.pl             |  161 +++
 test/sequence-bug.pl             |   22 +
 test/shipmr                      |  119 ++
 test/snmpmap_table-test.pl       |   28 +
 test/snmpspeed.pl                |  213 ++++
 test/snmptrap.note               |    4 +
 test/snmptrap.pl                 |   83 ++
 test/sorrento-nest-list          |   85 ++
 test/trap-listener               |   90 +-
 test/v6-list-prefixes            |   13 +
 test/vc-counters.pl              |  446 +++++++
 test/verio-problem.pl            |   35 +
 64 files changed, 13378 insertions(+), 818 deletions(-)

diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..b4b3bb8
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,2534 @@
+2008-03-19  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm: Improved initialization of the flags for
+	non-blocking behavior in receive_response_3().  The __DIE__ and
+	__WARN__ signal handlers should be bound to the defaults, because
+	the caller might be binding those and get in our way.  Also, we
+	only want to compute the flags once, on initialization.
+
+2007-12-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm, lib/SNMP_Session.pm, lib/BER.pm:
+	Upgraded to Artistic License 2.0.
+
+	Copyright notice updated for 2008.
+
+	* Artistic: Upgraded to Artistic License 2.0, from
+	http://svn.perl.org/viewcvs/parrot/trunk/LICENSE?view=markup&rev=19096
+
+2007-11-01  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm: New MIB parsing code from Mike Mitchell.
+
+2007-10-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm ($VERSION):
+	Upgraded to 1.11, to pick up change to SNMP_util.pm.
+
+	* changes.html: Document loop detection fix in SNMP_util.pm.
+
+	* faq.html, index.html: Updated my e-mail address.
+
+	* README, README.SNMP_util, lib/SNMP_Session.pm, lib/SNMP_util.pm,
+	changes.html: Changed Tobi Oetiker's mail address.
+
+	* README, index.html: Changed MRTG URL.
+
+	* lib/SNMP_util.pm: [All changes from Mike Mitchell]
+
+	Global replace || => or, && => and, to avoid precedence errors.
+
+	(snmpwalk_flg): Improved loop detection.
+
+2007-10-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm ($VERSION): Upgraded to 1.11, to pick up
+	change to SNMP_util.pm.
+
+	* README, README.SNMP_util, changes.html, lib/SNMP_Session.pm:
+	Changed Tobi Oetiker's mail address.
+
+	* lib/SNMP_util.pm: [All changes from Mike Mitchell]
+
+	Changed Tobi Oetiker's mail address.
+
+	Global replace || => or, && => and, to avoid precedence errors.
+
+	(snmpwalk_flg): Improved loop detection.
+
+	* README: Changed MRTG URL.
+
+2007-05-18  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README, changes.html, index.html: Updated copyright.
+
+	* lib/SNMP_util.pm (Check_OID):
+	Fix regexp for qualified OID case (Mike Mitchell).
+
+2007-05-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm: Added Mike Fischer as a contributor.
+
+	($VERSION): Incremented to 1.10.
+
+	(receive_response_3): Added optional "dont_block" argument.  If
+	this is present and non-zero, pass MSG_DONTWAIT to the recv()
+	call.  MSG_DONTWAIT is wrapped in an eval, to avoid breaking the
+	code on systems that don't have the flag.
+
+	(request_response_5): Pass dont_block=1 to receive_response_3.
+	According to Mike Fisher, Linux sometimes blocks on recv() even
+	though a select() for readability has returned, for example when a
+	checksum fails.
+
+2007-05-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm: Added Mike Fischer as a contributor.
+
+	($VERSION): Incremented to 1.10.
+
+	(receive_response_3): Added optional "dont_block" argument.  If
+	this is present and non-zero, pass MSG_DONTWAIT to the recv()
+	call.  MSG_DONTWAIT is wrapped in an eval, to avoid breaking the
+	code on systems that don't have the flag.
+
+	(request_response_5): Pass dont_block=1 to receive_response_3.
+	According to Mike Fisher, Linux sometimes blocks on recv() even
+	though a select() for readability has returned, for example when a
+	checksum fails.
+
+2007-01-06  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/BER.pm: Updated copyright notice.
+
+	(pretty_print): Use PDU names according to RFC3416.
+
+2006-12-21  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/cricket-genconf-sensor:
+	Try to install newly generated configuration, where possible.
+
+	* test/cricket-genconf-sensor: Added header comment.
+
+	* test/cricket-genconf-sensor: New script.
+
+2006-12-15  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/entls (router_pretty_name):
+	New subroutine, greps RANCID configuration file
+	for `hostname' command.  Caches the result so that files are only
+	opened once.
+
+	(print_physical): Use new forms of per-router defaults.
+
+	* test/entls:
+	Changed so that `-t' generates a Cricket configuration file to measure
+	all transceivers that support DOM (Digital Optical Monitoring).  This
+	involves some structural changes.
+
+	(print_phys_tree): Implemented in terms of the new
+	`print_phys_tree_1'.
+
+	(print_phys_tree_1): Maintain a stack of parent nodes when traversing
+	the node tree.  This stack is stored in each node's `parent_stack'
+	slot, and can be used by the node class' `tostring' method.
+
+	* test/entls ($print_vendor_type, $print_ent_physical_index):
+	New variables.
+
+	(Entity::PhysicalEntry::tostring): Added optional printing of index
+	and entPhysicalVendorType, controlled by the above variables.
+
+2006-10-12  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm, lib/BER.pm: Updated copyright string.
+
+2006-08-06  Simon Leinen  <simon.leinen at switch.ch>
+
+	* faq.html:
+	Updated SNMPv3 text, mentioning that SNMPv3 is supported by MRTG 2.13
+	and up, using Net::SNMP.
+
+	* lib/SNMP_util.pm (snmpLoad_OID_Cache):
+	Strip single or double quotes around the OID and
+	value.  This allows us to read SunNet Manager OID files, which are
+	also distributed by e.g. Cisco (ftp://ftp.cisco.com/pub/mibs/oid).
+	Idea by Jan van Keulen, code cleanup by Mike Mitchell.
+
+2006-07-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 1.08.
+
+	* lib/SNMP_Session.pm (BEGIN):
+	Bind the __DIE__ signal handler, so that detection of IPv6
+	capability works even when someone else has bound that handler.
+	(Patch from Tobi Oetiker.)
+
+2006-04-09  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Table.pm (snmp_row_to_object): Added.
+
+2006-04-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/trap-listener: Added prettyfication of OIDs.
+
+	Suppressed less-than-useful output such as the trap community or the
+	source port.
+
+2006-03-16  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README: Update copyright notice for 2006.
+
+2006-02-17  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/entls: New "entls" script.
+
+2006-01-20  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/qosls: Slight improvements in output for readability.
+
+	* README: Credit Jan van Keulen.
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 1.08.
+
+	* lib/SNMP_util.pm:
+	Some fixes from Mike Mitchell, notably in the area of OID translation.
+
+2006-01-17  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/find-admin-up-oper-down.pl:
+	Find interfaces that are admin up but oper down.
+
+2006-01-15  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html:
+	Fixed $session -> $trap_session in trap receipt example.  Thanks to
+	rahul shah <shahrahulb at yahoo.co.in> for noticing this.
+
+2005-12-10  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm (SNMPv2c_Session::map_table_start_end):
+	Slight reindentation.
+
+	* lib/SNMP_Session.pm (SNMPv2c_Session::map_table_start_end): Call
+	`SNMP_Session::index_compare' rather than just `index_compare'.
+
+2005-11-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* faq.html: Upgraded SNMP URI syntax reference from draft to RFC 4088.
+
+2005-07-12  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html: Added missing `mailto:'.
+
+	* index.html, changes.html: README.SNMP_util -> dist/
+
+2005-07-08  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm (snmpwalk_flg): Bug fix by Laurent Girod.
+
+2005-04-24  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Table.pm: Repository : :ext:diotima:/home/leinen/CVS
+	Module     : SNMP_Session/lib
+	Working dir: ~/perl/SNMP_Session/lib/
+
+
+
+	In directory .:
+	              Up-To-Date  1.38        BER.pm
+	              Up-To-Date  1.145       SNMP_Session.pm
+	              Added                   SNMP_Table.pm
+	              Up-To-Date  1.51        SNMP_util.pm
+
+	--------------------- End ---------------------
+	-- last cmd: cvs -f status -v --
+
+2005-03-29  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/qosls: *** empty log message ***
+
+	* test/if-counters.pl: New options: -B to avoid use of get-bulk.
+
+	-C to avoid use of curses.
+
+	* test/bgpls: *** empty log message ***
+
+2005-01-21  Simon Leinen  <simon.leinen at switch.ch>
+
+	* faq.html: Updated.
+
+2004-12-19  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/msdpls: Added `-n' option to suppress resolving IP addresses.
+
+	(usage): Made more readable.
+
+	* test/msdpls (usage): Explain options.
+
+	* test/msdpls (usage): Added.
+
+2004-12-07  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm: Removed strange formatting.
+
+2004-11-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/sorrento-nest-list (%short_types): Added a few new types.
+
+	(@nestmasters): Beginning of update.
+
+2004-10-29  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm: Require BER.pm 1.05.
+
+	($VERSION): Incremented to 1.07.
+
+	* lib/SNMP_Session.pm:
+	context_flag -> context_flag() to fix error message from "use strict
+	subs" with recent Perl versions.  Acknowledged Gerry Dalton.
+
+	* changes.html:
+	Documented 1.06 (SNMPv2 inform parsing) and 1.07 (strict subs bug with
+	newer Perl versions) changes to SNMP_Session.pm.
+
+	* MANIFEST (META.yml): Added by MakeMaker.
+
+	* README: Added Gerry Dalton.
+
+2004-09-17  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/if-counters.pl:
+	Added `-s' option to look at L3 switching statistics on a Catalyst
+	6500 and probably some other Cisco L3 switches.
+
+	(out_switching_engine): New subroutine.
+
+2004-09-15  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/msdpls: Added `-d' flag.
+
+	Added debugging output.
+
+2004-09-04  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm:
+	Mention Andrew Cornford-Matheson in the header comment.
+
+	* lib/SNMP_Session.pm ($VERSION): 1.05 -> 1.06.
+
+	(decode_trap_request): Understand inform requests, too.
+
+2004-09-02  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README, index.html: CMU SNMP -> Net-SNMP.
+
+	* index.html (decode_trap_request):
+	Mention that it supports SNMPv2 informs, too.
+
+	* README (Contributors): Added Andrew Cornford-Matheson.
+
+	(decode_trap_request): Mention that it supports SNMPv2 informs, too.
+
+	* changes.html: Upgraded to 1.05.
+
+2004-08-23  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html:
+	New distribution site http://www.switch.ch/misc/leinen/snmp/perl/dist/
+
+	Made pointers more absolute.
+
+2004-07-15  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/BER.pm ($VERSION): Upped to 1.05.
+
+	(BEGIN): Register various pretty printers.
+
+	(pretty_printer): Simplified because most types are handled by
+	registered pretty printers now.
+
+	* lib/SNMP_Session.pm ($VERSION): Upped to 1.05.
+
+	* changes.html: Document 1.04 change (in SNMP_util.pm).
+
+	* ChangeLog: Updated using C-x v a.
+
+	* index.html: Updated copyright years.
+
+	* lib/SNMP_util.pm, lib/SNMP_Session.pm ($VERSION): Upped to 1.04.
+
+	* lib/SNMP_util.pm (snmpget, snmpgetnext, snmpset):
+	Use wantarray() to determine whether
+	to return an array or just the first value.
+
+2004-07-15  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm, lib/SNMP_Session.pm ($VERSION): Upped to 1.04.
+
+	* lib/SNMP_util.pm (snmpget, snmpgetnext, snmpset):
+	Use wantarray() to determine whether
+	to return an array or just the first value.
+
+2004-06-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README: Updated copyright years.
+
+2004-03-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm:
+	Require recent versions of BER.pm and SNMP_Session.pm.
+
+	($VERSION): Incremented to 1.03.
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 1.03.
+
+	* changes.html (SNMP_util.pm): Documented 1.03 fix.
+
+	* lib/SNMP_util.pm (snmpwalk_flg):
+	Added missing line from Mike Mitchell's patch.
+
+2004-03-21  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 1.02.
+
+	* changes.html: Document BER.pm 1.01 and 1.02.
+
+	* lib/SNMP_Session.pm:
+	Require BER.pm 1.01, because previous versions lack support for a
+	variant of integer encoding.
+
+	($VERSION): Incremented to 1.01.
+
+2004-02-17  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER-1.01-Mike-Mitchell.diff, BER-1.01-02.diff: Applied.
+
+	* lib/SNMP_util.pm (snmpwalkhash, snmpwalk_flg):
+	Implemented the new hash-reference
+	argument.
+
+	* changes.html: Documented BER.pm and SNMP_util.pm news in 1.02.
+
+	* test/ber-test.pl: Use strict.
+
+	Added function prototypes.
+
+	* lib/BER.pm (%pretty_printer):
+	New variable.  This is a hash of pretty-printers
+	per BER type code.  It is manipulated by register_pretty_printer() and
+	unregister_pretty_printer(), and used by pretty_print().
+
+	(register_pretty_printer, unregister_pretty_printer): New subroutines,
+	contributed by Mike Mitchell.
+
+	(pretty_print): If a pretty-printer has been registered for the type
+	code, call it.
+
+	(encode_intlike): In the Math::BigInt case, copy the integer before
+	taking its bmod(), because bmod() is destructive.
+
+	* README.SNMP_util:
+	Document additional optional `hash ref' argument to snmpwalkhash().
+
+2004-02-08  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/BER.pm (decode_intlike_s): Use decode_length().
+
+	(decode_length): Accept an optional second argument, specifying the
+	offset into the first argument at which to begin parsing.  This
+	eliminates a substr() operation for every object.  The callers have
+	been adapted accordingly.
+
+	* README: Added Milen Pavlov.
+
+2003-12-14  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm ($VERSION): 0.99 -> 1.00.
+
+	* changes.html: 0.100 -> 1.00.
+
+	* test/iftop: Added support for 64-bit counters.
+
+	* lib/SNMP_Session.pm ($default_use_16bit_request_ids): New variable.
+
+	(encode_request_3): Obey `use_16bit_request_ids' in request ID
+	generation.
+
+	* changes.html: Added note on `use_16bit_request_ids'.
+
+	* README: Added Luc Pauwels <Luc.Pauwels at xalasys.com> as a contributor.
+
+2003-12-04  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html:
+	Added new "Served Individual Item Link" to the "Essential SNMP" book
+	on Amazon, but commented it out because it breaks the formatting.
+
+	Removed the "NEW" icon from the existing Essential SNMP pointer.
+
+2003-11-26  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/iftop: Adding iftop
+
+	* test/README (iftop): Added URL.
+
+	* test/README, MANIFEST: Added Dave Plonka's `iftop'.
+
+2003-11-09  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html:
+	Document Mike Mitchell's snmpset support for all known types.
+
+	* lib/SNMP_util.pm (snmpset): Encode all known types.
+
+2003-11-07  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html: Mentioned Christopher J. Tengi.
+
+	* README, lib/SNMP_util.pm:
+	Added Christopher J. Tengi to contributors list.
+
+	* changes.html: Document 0.99 changes.
+
+	* lib/SNMP_util.pm ($VERSION): Incremented to 0.99.
+
+	* lib/SNMP_util.pm (snmpset): Support Gauge32 values.
+
+2003-10-20  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 0.99.
+
+	* lib/SNMP_Session.pm (SNMPv1_Session::open):
+	Simplify initial request_id generation.
+
+	* lib/SNMP_Session.pm (encode_request_3):
+	Handle avoid_negative_request_ids in a saner way.
+	Thanks to Mike Mitchell.
+
+2003-09-26  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/trap-send:
+	Added forward declarations.  Thanks to Bret Allibone for pointing out
+	the problem.
+
+2003-09-09  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html: Documented 0.98 changes.
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 0.98.
+
+	* lib/SNMP_Session.pm: Require BER version 0.95.
+
+	Fixed a portability issue in the IPv6 code.
+
+	* lib/SNMP_util.pm: Require BER version 0.95.
+
+2003-09-08  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm: Changes from Mike Mitchell:
+
+	Depend on SNMP_Session.pm 0.97.
+
+	Fix parsing of OIDs with multiple quoted strings.
+
+	($VERSION): Incremented to 0.98.
+
+2003-09-02  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm: Undid most of the last change.
+
+	* lib/SNMP_Session.pm: Removed ``use strict "subs"'' in a few places.
+
+	* README: Added Joerg Kummer to credits.
+
+	* lib/SNMP_util.pm ($VERSION): Increased to 0.97.
+
+	(snmpset): Added TimeTicks support, courtesy Joerg Kummer.
+
+	* changes.html: TimeTicks support in snmpset.
+
+	* changes.html: Added IPv6; $default_avoid_negative_request_ids notes.
+
+	* contrib/SNMP_util.pm, test/util-trap.pl, test/v6-list-prefixes, test/vc-counters.pl, test/verio-problem.pl:
+	Initial import of the essential parts of SNMP_Session
+
+	* contrib/SNMP_util.pm, test/util-trap.pl, test/v6-list-prefixes, test/vc-counters.pl, test/verio-problem.pl:
+	New file.
+
+	* faq.html, p2.htm, security.html, test/atol-test.c, test/bad-trap.pl, test/bay-atm-test.pl, test/bgp-check-routes, test/bulkwalkbug.pl, test/cammer, test/capturetest.pl, test/counter64-test.pl, test/d.pl, test/digital-bug, test/fore-test.pl, test/inexist.pl, test/list-bgp4-table, test/mcount.pl, test/mdebug, test/negative-counter.pl, test/sequence-bug.pl, test/set-test.pl, test/shipmr, test/snmptrap.note, test/snmptrap.pl:
+	Initial import of the essential parts of SNMP_Session
+
+	* faq.html, p2.htm, security.html, test/atol-test.c, test/bad-trap.pl, test/bay-atm-test.pl, test/bgp-check-routes, test/bulkwalkbug.pl, test/cammer, test/capturetest.pl, test/counter64-test.pl, test/d.pl, test/digital-bug, test/fore-test.pl, test/inexist.pl, test/list-bgp4-table, test/mcount.pl, test/mdebug, test/negative-counter.pl, test/sequence-bug.pl, test/set-test.pl, test/shipmr, test/snmptrap.note, test/snmptrap.pl:
+	New file.
+
+2003-06-30  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README: Updated Philippe Simonet's e-mail.
+
+	Added Valerio Bontempi and Lorenzo Colitti.
+
+	* lib/SNMP_Session.pm ($SNMP_Session::default_avoid_negative_request_ids):
+	Exported.
+
+2003-06-04  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm:
+	Changes from Lorenzo Colitti: Avoid eval() in some places to make code
+	more robust.
+
+2003-05-29  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/wwwtest: Also use CGI.pm to generate HTML.
+
+	* test/wwwtest (snmp_get, write_query_form): Use CGI.pm methods.
+
+	* test/wwwtest: Use CGI.pm for query parsing.
+
+	Open SNMPv2c_Session.
+
+	(parse_query): Deleted.
+
+	($ipv4_only_p): New variable.
+
+	* test/test.pl: Parse option arguments: `-4', `-v (1|2)'.
+
+	* test/test.pl: Add subroutine prototypes; use strict.
+
+	Use additional arguments to SNMP_Session->open to activate IPv6 code.
+
+	* lib/SNMP_Session.pm (SNMPv1_Session::open):
+	Create an IO::Socket::INET object, rather than
+	an IO::Socket::INET6 object, whenever sockfamily is AF_INET.
+
+	(SNMPv2c_Session::open): Avoid trying to bless() an undefined object,
+	to improve error diagnostics.
+
+	* test/wwwtest: Added subroutine prototypes.
+
+	($community_file_name): New variable.
+
+	(write_query_form): Compute <option> tags from @allowed_hosts.
+
+	* lib/SNMP_Session.pm (SNMPv1_Session::BEGIN):
+	Insist on version 1.26 when trying to import
+	IO::Socket::INET6.
+
+	(SNMPv1_Session::open): No longer pass PeerAddress and PeerPort when
+	creating the INET6 Socket object.  This means that the socket will not
+	be connected.  This, in turn, means that `lenient_address_matching'
+	and socket-recycling will have a chance of working.
+
+	* test/if-counters.pl: Add IPv6 support:
+
+	* Require SNMP_Session 0.96 or later.
+
+	* Accept `-4' option to set `ipv4only' in SNMP_Session->open() call.
+
+	* Updated usage message.
+
+	* test/walk-intf.pl: Added subroutine prototype for usage().
+
+2003-05-25  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm:
+	Added IPv6 changes by Valerio Bontempi and Lorenzo Colitti.
+
+	* lib/BER.pm: application_flag -> application_flag (),
+	context_flag -> context_flag ().
+
+	* lib/SNMP_Session.pm (encode_request_3):
+	encode_int_0 -> encode_int_0 ().
+
+2003-05-10  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm:
+	Integrated IPv6 patch by Valerio Bontempi and Lorenzo Colitti.
+
+2003-03-14  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/BER.pm: Mention Jan Kasprzak as a contributor.
+
+	* README: Added Jan Kasprzak.
+
+2003-03-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html, index.html: Copyright -> 2003.
+
+	* changes.html: Document decode_sequence() bug fix.
+
+	* lib/SNMP_Session.pm, lib/BER.pm ($VERSION): Increased to 0.95.
+
+	* lib/SNMP_util.pm: Added Jakob Ilves' return_array_refs support.
+
+	* lib/BER.pm (decode_sequence): Fix operator precedence bug.
+
+2003-02-27  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/if-counters.pl:
+	Clear the screen after traversing the interfaces.  This helps when
+	interfaces are removed while the program is running.
+
+2003-02-24  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html:
+	Fixed decode_by_template() call in trap reception example.
+
+2002-10-27  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html: Added pointer to test/capturetest.pl.
+
+	* MANIFEST: Added test/capturetest.pl.
+
+	* changes.html: Documented 0.94 changes to BER.pm and SNMP_Session.pm.
+
+	* README: Added Jakob Ilves to list of contributors.
+
+	* lib/BER.pm ($VERSION): Incremented to 0.94.
+
+2002-10-24  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm (request_response_5, open):
+	Support PDU capture buffer.
+
+	* lib/BER.pm (pretty_generic_sequence, decode_generic_tlv):
+	New functions for
+	debugging, contributed by Jakob Ilves <jakob.ilves at oracle.com>
+	(/IlvJa).
+
+2002-10-02  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README, index.html: Use "or" instead of "||" in examples.
+
+2002-09-12  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html:
+	Document $default_avoid_negative_request_ids in SNMP_Session.pm.
+
+	Document changes to SNMP_util.pm.
+
+	* test/trap-listener:
+	Added argument parsing and usage() message.  The UDP port to listen on
+	can now be specified using `-p'.
+
+	Print better diagnostics for decoding errors.
+
+	* lib/SNMP_Session.pm (open):
+	Undid problematic "optimization" when computing the
+	possibly-negative request_id.
+
+	* index.html: Added pointer to IOG.
+
+2002-09-06  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm ($default_avoid_negative_request_ids):
+	New variable.
+
+	(encode_request_3): Honor `avoid_negative_request_ids'.
+
+	(open): Set `avoid_negative_request_ids', and initialize the
+	`request_id' slot accordingly.
+
+2002-05-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm (snmpwalk_flg):
+	Added missing initialization to suppress warning when
+	`-w' is in use.
+
+2002-05-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm: Updated to 0.93b1 from Mike.
+
+	* README.SNMP_util:
+	Updated to 0.93b1 from Mike - describe new snmpwalk() behavior (now
+	accepts multiple OIDs).
+
+	* lib/SNMP_util.pm (snmpwalk_flg):
+	Added missing initialization to suppress warning when
+	`-w' is in use.
+
+2002-03-06  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/msdpls ($css_stylesheet): Deleted.
+
+	(msdp_duplicate_report_header): Inline the CSS stylesheet.
+
+	(msdp_report_duplicate_sas): Use `pretty_ip_html'.
+
+	Format entries as a table.
+
+	(pretty_ip_html): New subroutine.
+
+2002-03-01  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/uli-fh-to-dns.pl: New file.
+
+2002-02-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/BER.pm:
+	(decode_oid, decode_by_template_2, decode_sequence, decode_string):
+	Abort and return error when the PDU is shorter than the length bytes
+	promised.
+
+2002-02-17  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/msdpls (msdp_fill_in_duplicates):
+	New subroutine that removes all
+	non-duplicate SAs from the hash and fills in the duplicates with more
+	information by doing specific walks.
+
+	* test/msdpls: Generate HTML output.
+
+2002-02-16  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/msdpls: Changed code to list duplicate SAs.
+
+2002-02-15  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/msdpls: New file.
+
+	* changes.html: Fixed text according to Mike Mitchell's mail.
+
+	* lib/SNMP_Session.pm (decode_trap_request):
+	Better error handling for decoding problems.
+
+	* lib/BER.pm (decode_by_template_2):
+	Better diagnostics for undefined or short PDUs.
+
+	* lib/BER.pm (decode_by_template):
+	Better diagnostics for undefined or short PDUs.
+
+2002-02-12  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm ($VERSION):
+	Incremented to 0.92, to synchronize with SNMP_util.pm.
+
+	* changes.html: Documented SNMP_util 0.92b2 changes.
+
+	* lib/SNMP_util.pm ($VERSION): Incremented to 0.92.
+
+	(@EXPORT): Export new symbol `snmpmaptable4'.
+
+	(snmpmaptable4): New subroutine.
+
+	Use map_table_start_end.
+
+	(snmpmaptable): Now implemented in terms of `snmpmaptable4'.
+
+	(toOID): Convert strings to indexes.
+
+	(snmpMIB_to_OID): New one-pass/two-pass MIB parsing code.
+
+	* README.SNMP_util: New version from Mike Mitchell (0.92b2):
+
+	Document how to set sessino parameters using a hash as the first OID.
+
+	Document snmpmaptable4.
+
+2002-01-30  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html: Updated copyright notice.
+
+	Documented map_table_start_end fix.
+
+	* index.html, README, lib/BER.pm, lib/SNMP_Session.pm:
+	Updated copyright notice.
+
+	* test/if-counters.pl:
+	Fixed argument parsing so that target and options can be mixed.
+
+	* test/if-counters.pl (out_interface):
+	Suppress "unrouted VLAN..." interfaces.
+
+2002-01-26  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 0.91.
+
+2001-12-14  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm (SNMPv2_Session::map_table_start_end):
+	Added comments.
+
+	Fixed bug that caused a superfluous query to be sent when the table
+	was already fully received.
+
+2001-12-12  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/sorrento-nest-list (%short_types): Recognize "GM-GE2-2.5G-A".
+
+2001-12-11  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/sorrento-nest-list (%short_types):
+	Recognize "GM-GE2" as channel ("c") card.
+
+	* test/sorrento-nest-list:
+	Applied the new naming scheme Chris Watts (Ascom) and myself had
+	agreed upon.
+
+2001-12-06  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Added /ref=nosim/ to Amazon book pointers.
+
+2001-11-29  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/lambda-webmon.pl (get_amp_status):
+	New subroutine that does all the SNMP requests.
+
+	(make_amp_html_page): Renamed from `make_html_page'.
+
+2001-11-28  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/lambda-webmon.pl (@eastbound_amps, @westbound_amps):
+	New naming convention.
+
+2001-11-24  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/lambda-webmon.pl: New file.
+
+2001-11-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/lambda-monitor.pl ($inverse_video):
+	New variable.  Default is normal video now.
+
+	(show_light_trail): dbM -> dBm.
+
+	(print_html_trailer): Added color legend.
+
+	(class_for_amp_status): New subroutine that converts the amplifier
+	status bits into a class by finding the highest bit set.
+
+	* test/lambda-monitor.pl: Added HTML output.
+
+	* test/lambda-monitor.pl: New file.
+
+2001-11-14  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html: Documented new SNMP_util.pm and if-counters.pl.
+
+	* index.html: Added pointer to "Essential SNMP".
+
+	* test/snmpspeed.pl: *** empty log message ***
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 0.90.
+
+	* test/if-counters.pl:
+	Implemented Counter64 support (activated by "-l" argument):
+
+	Use Math::BigInt.
+
+	($counter64_p, $ifHCInOctets, ifHCOutOctets): New variables.
+
+	(rate_32): Added optional multiplier argument.
+
+	(rate_64, rate, rate_or_0): New subroutines that generalize rate_32.
+
+	(out_interface): Use rate_or_0 rather than rate_32.
+
+	Use ifHCOutOctets/ifHCOutOctets if $counter64_p is set.
+
+	Always request ifAlias for the interface description.
+
+	(usage): Document -l flag.
+
+	* lib/SNMP_util.pm: Fixed typo in comment.
+
+	* test/snmpwalkh.pl: Fixed typo in comment (Example 2 -> Example 3).
+
+	* test/snmpwalkh.pl: New version ("0.90b2") from Mike Mitchell.
+
+	* lib/SNMP_util.pm: New version ("0.90b2") from Mike Mitchell:
+
+	(snmpwalk, snmpwalkhash): Now implemented as trampolines to
+	snmpwalk_flg.
+
+	(snmpopen): Bug fix in handling of the optional "port" argument.
+
+2001-11-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm (error_return, error, ber_error):
+	Moved downward in the file, but
+	forgot why.
+
+2001-10-19  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/if-counters.pl (rate_32):
+	New subroutine that computes a rate from two Counter32
+	values and an interval.
+
+	(out_interface): Use it.
+
+2001-10-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/sorrento-nest-list (%nestmasters): Added muxBE1.
+
+2001-09-18  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/sorrento-nest-list (%nestmasters): Added muxCE1.
+
+2001-09-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/sorrento-nest-list: New file.
+
+2001-08-27  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README.SNMP_util:
+	Added documentation for snmpwalkhash (Mike Mitchell).
+
+	* changes.html (SNMP_util.pm):
+	Mentioned new version 0.89 with new snmpwalkhash
+	subroutine.
+
+	* MANIFEST (test/snmpwalkh.pl): Added.
+
+	* test/snmpwalkh.pl: New file.
+
+	* lib/SNMP_util.pm (snmpwalkhash): New function.
+
+2001-08-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html:
+	lenient_source_port_matching now defaults to 1 rather than 0.
+
+	* lib/SNMP_Session.pm (SNMPv1_Session::open):
+	Default lenient_source_port_matching to 1
+	rather than 0.
+
+2001-07-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/BER.pm (pretty_print):
+	Handle SNMPv2 exception codes by calling error(),
+	which returns undef and leaves a message in $errmsg.
+
+	* changes.html: error code -> exception code.
+
+	* lib/SNMP_Session.pm: Added missing/new credits.
+
+	* changes.html: Document request-id range bug fix.
+
+	* lib/SNMP_Session.pm (encode_request_3, open):
+	Make sure that the request_id is always in
+	the range -2^31 to (2^31)-1.  Thanks to Sergio Macedo
+	<macedo at tmp.com.br> for noticing this bug.
+
+2001-07-06  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html: *** empty log message ***
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 0.88.
+
+	* README: Acknowledged Michael Deegan <michael at cnspc18.murdoch.edu.au>.
+
+	* lib/SNMP_Session.pm (package SNMPv1_Session, package SNMPv2_Session):
+	Added missing `use
+	Carp;'.
+
+2001-06-14  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/if-counters.pl:
+	Print comment for non-Cisco routers, now that we get it from ifAlias.
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to reflect BER.pm change.
+
+	* lib/BER.pm: Slightly modified patch from Bert Driehuis:
+
+	(snmp_nosuchobject, snmp_nosuchinstance, snmp_endofmibview): New
+	(constant) subroutines.
+
+	(pretty_print): Convert SNMPv2 error codes to undef.
+
+	* changes.html: Documented new SNMPv2 error code handling.
+
+	* README (Contributors): Added Bert Driehuis.
+
+2001-05-28  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/if-counters.pl:
+	Use ifAlias rather than locIfDescr.  The advantages are that ifAlias
+	is not Cisco-specific, and it is defined even on ATM subinterfaces on
+	Ciscos.
+
+2001-05-25  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/snmpmap_table-test.pl: Shortened the code for easier reading.
+
+2001-05-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html: Note new behavior of BER::pretty_print.
+
+	* lib/BER.pm ($VERSION): Increased to 0.86.
+
+	* lib/BER.pm (pretty_print):
+	Return undef if original value is undefined.
+
+	* test/snmpmap_table-test.pl: New file.
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 0.86.
+
+	* changes.html: Added note on snmpmaptable.
+
+	* lib/SNMP_util.pm: Indentation changes.
+
+	(snmpmaptable): Renamed from snmpmap_table.
+
+	Solved FUNARG problem.
+
+	* README.SNMP_util (snmpmaptable): Renamed from snmpmap_table.
+
+	* README.SNMP_util, lib/SNMP_util.pm:
+	snmpmap_table: New from Mike Mitchell.
+
+2001-05-09  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm: 0.86b2 from Mike Mitchell.
+
+	* changes.html: *** empty log message ***
+
+	* lib/SNMP_util.pm (snmpopen): Avoid using $port when it's undefined.
+
+	* README.SNMP_util, lib/SNMP_util.pm: 0.86b1 from Mike Mitchell.
+
+2001-05-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm (open):
+	No longer try to convert local address using inet_aton(),
+	because IO::Socket::INET->new does that.
+
+	* lib/SNMP_Session.pm: Use Carp; warn -> carp, die -> croak.
+
+	* README: Added Alistair Mills.
+
+	Clarified documentation on pretty-printing OIDs.
+
+	* lib/SNMP_Session.pm (open, sa_equal_p):
+	Added support for `lenient_source_port_matching'.
+
+2001-05-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* changes.html: New file.
+
+	* index.html: Moved changes to `changes.html'.
+
+	Clarified pretty-printing.
+
+	* lib/SNMP_util.pm (snmpopen): Default port to 161 for non-type-1.
+
+	* lib/SNMP_util.pm: New version 0.85 from Mike Mitchell:
+
+	Accept HASH as first OID to set SNMP options.
+
+	Use Carp for error messages.
+
+2001-03-07  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/mrtg-ipmcast: Removed `cisco-' from name.
+
+	* test/mrtg-ipmcast (usage): Improved help message.
+
+	* test/mrtg-ipmcast: Support `-p' flag.
+
+	(usage): Defined.
+
+	* test/mrtg-ipmcast: Added SNMPv2 support.
+
+	* test/mrtg-ipmcast: Removed SWITCH specifics.
+
+2001-01-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm: Version 0.84 received from Mike.
+
+2000-12-19  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm:
+	New version from Mike Mitchell.  The local host can now be specified
+	along with the port.  See README.SNMP_util for the syntax.
+
+	* README.SNMP_util:
+	New version from Mike Mitchell.  Documents SNMP version and local
+	address specifications.
+
+2000-12-18  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: *** empty log message ***
+
+	* lib/SNMP_Session.pm ($VERSION): Increased to 0.83.
+
+	(open): Allow local address to be specified by an additional optional
+	argument.
+
+	Set new slot `lenient_source_address_matching' to 1.
+
+	(request_response_5): Don't resend on reception on unmatched replies.
+
+	(sa_equal_p): This is now a method on session objects.
+
+	If the session's `lenient_source_address_matching' slot is set, don't
+	compare host addresses, just ports.
+
+2000-12-10  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Document retry fix.
+
+	* lib/SNMP_Session.pm (request_response_5):
+	Rearranged so that we will no longer send an
+	additional retry for whose response we won't wait anymore.
+
+2000-12-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README: Added Brett T Warden to contributors list.
+
+	* index.html: Documented Brett T Warden changes.
+
+	* lib/SNMP_Session.pm ($VERSION):
+	Incremented to pick up change in BER.pm.
+
+	($default_retries): Clarified header comment.
+
+	* lib/BER.pm ($VERSION): Incremented.
+
+	* lib/BER.pm (pretty_print): Pass UInteger32 to pretty_unsignedlike().
+
+	(encode_oid): Accept OIDs of length 2 (such as 0.0).
+
+	* lib/BER.pm (encode_oid): Fix by Rik Hoorelbeke for large subids.
+
+2000-11-15  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Added credits for Rik Hoorelbeke.
+
+	Document changes to encode_oid() and SNMPv2c_Session::map_table.
+
+	* README: Added credits for Rik Hoorelbeke.
+
+2000-10-30  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Document sa_equal_p change.
+
+	* lib/SNMP_Session.pm (sa_equal_p): New function.
+
+	(receive_response_3): Use `!sa_equal_p' rather than `ne' to compare
+	addresses.
+
+	* test/if-counters.pl: Added forward declarations.
+
+2000-10-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/map-table.pl: Recognize `-v 2'.
+
+	* test/if-status.pl: Added forward declarations.
+
+	Added SNMPv2 support.
+
+	* test/if-status.pl: New file.
+
+2000-10-17  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Documented recent changes.
+
+	* lib/SNMP_Session.pm (request_response_5):
+	Updated comment according to recent change in
+	receive_response_3.
+
+	* lib/SNMP_Session.pm ($recycle_socket):
+	New exported variable.  When this is, all new
+	SNMP_Session objects will share a single UDP socket.
+
+	(open_trap_session): Use `undef' rather than 0.0.0.0 as the remote
+	address in the SNMP_Session structure.
+
+	(receive_response_3): Changed handling of mismatched source
+	addresses.  If the source address of an incoming packet doesn't match
+	the expected address (as specified by the session's `remote_addr'
+	slot), the PDU is always ignored and zero is returned.  Also, if the
+	$recycle_socket variable is set, mismatched replies never cause a
+	warning even when the session's `debug' slot is set.
+
+	(to_string): Handle session objects with undefined `remote_addr'.
+
+	* lib/BER.pm (encode_uinteger32, encode_counter32, encode_counter64, encode_gauge32):
+	New exported subroutines.
+
+	* test/trap-listener: Added forward declarations.
+
+2000-10-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html, README: Removed MIB Parsing stuff.
+
+	* test/list-bgp4-neighbors:
+	Improved output; in particular the hostname is now be printed if
+	possible.
+
+	* trap-send.pl: foo
+
+	* MANIFEST (test/sun-find-process): Added.
+
+	* lib/SNMP_Session.pm (map_table_start_end):
+	Rewrote to cope with holes in tables.  Escape
+	to SNMPv1 (get-next) code if the new `use_getbulk' slot in the session
+	object is NOT set.
+
+2000-10-02  simon  <simon.leinen at switch.ch>
+
+	* test/walk-test.pl:
+	Don't ask for ipAdEntAddr in map_table because that can be derived
+	from the index.
+
+2000-09-24  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/max-list-sessions: Added header comment.
+
+	* test/max-list-sessions (out_session, out_call): Removed.
+
+	* test/max-list-sessions:
+	Print out just one table.  The callStatusTable is now used to populate
+	entries in the session entries that had been created from
+	ssnStatusTable.
+
+2000-07-09  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm (%SNMP_util::OIDS):
+	Moved around to keep lexicographical order.
+
+2000-06-20  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/mrtg-ipmcast: New file.
+
+	* test/README: Added `sun-find-process'.
+
+	* test/sun-find-process: New file.
+
+2000-05-29  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_util.pm (%SNMP_util::OIDS):
+	Added OIDs from IF-MIB (RFC1573).
+
+	* lib/SNMP_util.pm: Recognize SNMP version specifier.
+
+2000-05-27  Simon Leinen  <simon.leinen at switch.ch>
+
+	* MANIFEST (test/asn1-test.pl):
+	Removed because it refers to the ASN_1.pm module,
+	which I started writing three years ago but never finished, and which
+	is (correctly) not included in the distribution.
+
+2000-05-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/if-counters.pl: Added `-D' option to show drops (ifOutDiscards).
+
+	Be more modular with respect to printing/reading the different
+	optional columns.
+
+2000-04-12  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/SNMP_Session.pm: Fixed header comment.
+
+2000-04-06  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/list-ospf-neighbors, test/list-bgp4-neighbors: New file.
+
+2000-03-30  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/if-counters.pl: Allow port to be specified (-p).
+
+	* test/trap-listener ($port): New variable.
+
+	This should be settable by a command-line option.
+
+	* test/README: Added documentation and sorted alphabetically.
+
+	* README: Added contributors: Paul E. Erkkila, Johannes Demel.
+
+	* MANIFEST (test/cisco-cpus, test/cisco-memory): Added.
+
+	* index.html: Document Counter64 support and map_table improvement.
+
+	* lib/SNMP_Session.pm (SNMPv2c_Session::map_table_start_end):
+	Added $expected_oid_count hack
+
+2000-03-29  Simon Leinen  <simon.leinen at switch.ch>
+
+	* lib/BER.pm (encode_intlike, decode_intlike_s):
+	Added code to handle big integers
+	using Math::BigInt.
+
+	* test/ber-test.pl: Added a test for encoding of big integers.
+
+	* test/ber-test.pl: Added tests for huge integers.
+
+2000-02-24  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test/cisco-cpus: New file.
+
+2000-02-08  simon  <simon.leinen at switch.ch>
+
+	* README.SNMP_util: Refer to Artistic license.
+
+	Adapted to main README.
+
+	* lib/SNMP_util.pm (snmpMIB_to_OID):
+	Map OBJECT-IDENTITY to OBJECT IDENTIFIER.
+
+2000-01-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html:
+	Added a pointer to the parent (SNMP) page (to attract people to my
+	Amazon pointers 8-).
+
+	* index.html: Fixed HTML error.
+
+	* test/if-counters.pl, README, lib/BER.pm, index.html, lib/SNMP_Session.pm:
+	Copyright 2000.
+
+	* lib/SNMP_Session.pm ($VERSION): Incremented to 0.76.
+
+	* test/walk-test.pl: Added SNMPv2 (getBulk) support.
+
+	* ChangeLog: *** empty log message ***
+
+	* index.html: foo
+
+	* test/if-counters.pl:
+	No longer retrieves Cisco-specific variables by default.  Use the new
+	option `-c' to re-activate this.
+
+	* lib/SNMP_Session.pm (default_max_repetitions): Make settable.
+
+	(debug): New read/write method.
+
+	(map_table_start_end): Added some debugging messages.
+
+1999-12-15  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Use > rather than > in a few places.
+
+
+1999-12-15  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Use > rather than > in a few places.
+
+1999-10-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Clarified SNMPv2 traps.
+
+	* README: Documented new features (SNMPv2 Traps).
+
+	* index.html:
+	Changed documentation of v2_trap_request_send for new signature.
+
+	* index.html: Documented changes in version 0.75.
+
+1999-09-14  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Added note about removed uninitialized variable warning.
+
+	* index.html: Added change log entry for SNMP_util.pm 0.72.
+
+1999-09-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* MANIFEST (Artistic): Added.
+
+	* Makefile.PL (dist): Use "gzip -9f" for compression.
+
+	* Makefile.PL: New file.
+
+	* index.html, README:
+	Added copyright and pointer to "Artistic" license.
+
+	* Artistic: New file.
+
+1999-09-02  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Documented introduction of IO::Socket.
+
+1999-07-29  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Documented $BER::pretty_print_timeticks.
+
+1999-06-30  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Mention changes in new SNMP_util.pm.
+
+	* README.SNMP_util: Upgraded to 0.71 from Mike Mitchell.
+
+1999-04-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* mibparse.pl: New file.
+
+1999-04-07  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Added pointer to README.SNMP_util file.
+
+	* index.html:
+	Document 0.70 changes (MIB parsing addition to lib/SNMP_util.pm</tt>.
+
+	* README.SNMP_util:
+	Update from Mike Mitchell to include snmpMIB_to_OID().
+
+1999-03-10  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Document new community string parsing.
+
+1999-02-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Documented changes for SNMP_util.pm 0.57 -> 0.58.
+
+1999-02-21  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Document agent methods.
+
+	* README: Added Mike McCauley's credit.
+
+	* MANIFEST (test/SNMPAgent.pm): Added.
+
+1999-02-21  simon  <simon.leinen at switch.ch>
+
+	* index.html: Fixed a few Weblint warnings.
+
+	* index.html: Added note about really big tables.
+
+	* index.html: Documented map_table implementation for SNMPv2c.
+
+	Documented map_table_4.
+
+	* MANIFEST (test/if-counters.pl): Added.
+
+1999-02-21  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Documented recent changes.
+
+	* README: Added Alan Nichols (Sun) to the list of contributors.
+
+1999-02-17  Simon Leinen  <simon.leinen at switch.ch>
+
+	* mibtree.pl: New file.
+
+1999-01-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Added note about bind() error message fix.
+
+1998-12-21  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Document SNMP_util.pm change.
+
+1998-12-11  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Added Mike Mitchell's changes.
+
+	* README.SNMP_util: New version from Mike Mitchell.
+
+1998-11-16  Simon Leinen  <simon.leinen at switch.ch>
+
+	* MANIFEST (test/sun-ps): Added.
+
+	* MANIFEST: New file.
+
+	* index.html: Document fixed response matching logic.
+
+1998-10-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* Makefile.orig (TESTSRCS): Added test/discover.
+
+	* index.html: Point to FTP directory rather than the archive directly.
+
+	* index.html: Mentioned Clinton Wong's changes.
+
+1998-10-21  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Added a note on broadcast/multicast support.
+
+1998-09-08  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README, index.html: Fixed sample trap-reception code.
+
+1998-08-19  Simon Leinen  <simon.leinen at switch.ch>
+
+	* trap-send.pl: New file.
+
+1998-08-18  Simon Leinen  <simon.leinen at switch.ch>
+
+	* Makefile.orig (DOCS): Added README.SNMP_util.
+
+	* README.SNMP_util: New file.
+
+1998-08-14  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Simple.pm: New file.
+
+1998-08-10  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Added pointer to FTP location.
+
+1998-07-21  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Get html40 validator icon locally.
+
+1998-07-16  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Made HTML 4.0 (Transitional) compliant.
+
+1998-07-01  Simon Leinen  <simon.leinen at switch.ch>
+
+	* Makefile.orig (TESTSRCS): Added trap-send and trap-listener.
+
+	* index.html, README: Added documentation about receiving traps.
+
+1998-07-01  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm ($VERSION): Upped version to 0.60.
+
+	(open): Accept optional additional argument BIND_TO_PORT.
+
+	(open_trap_session): New method, calls open with BIND_TO_PORT 162
+	(or an alternate port given as an optional argument).
+
+	(decode_trap_request, receive_trap): New methods for decoding and
+	receiving SNMPv1 traps, respectively.
+
+	* BER.pm (pretty_uptime_value):
+	New function split off from pretty_uptime.
+
+	($template_debug): New variable, controls debugging output in
+	decode_by_template_2.
+
+	(decode_by_template_2): Added debugging output.
+
+	Fixed computation of position of error in template string.
+
+	Added %A (IP address), %u (uptime).
+
+	(decode_int, decode_string): Better error reporting, include
+	erroneous tags.
+
+	* Makefile (TESTSRCS): Added trap-send and trap-listener.
+
+	* index.html, README: Added documentation about receiving traps.
+
+1998-06-24  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm (set_timeout, set_retries, set_backoff):
+	Fixed warning.
+
+	* SNMP_util.pm (snmptrap): Added upTime handling.
+
+	* index.html: Documented new methods and map_table fix.
+
+	* SNMP_Session.pm (set_timeout, set_retries, set_backoff): 
+	New methods.
+
+	(map_table_start_end): Fixed comparison logic so that an index of
+	"0" no longer terminates the table.
+
+1998-06-11  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Updated RFC 1213 reference for new server.
+
+	* index.html: Updated pointer to mrtg page.
+
+1998-06-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Noted SNMP_util.pm.
+
+	Shortened section about Higher-Level APIs.
+
+	* README: Updated to be in line with the current index.html.
+
+	* Makefile (PKGSRCS): Added SNMP_util.pm.
+
+	* BER.pm: Added exported subroutine `encode_timeticks'.
+
+	Upped version to 0.58.
+
+1998-06-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm: Added exported subroutine `encode_timeticks'.
+
+	Upped version to 0.58.
+
+1998-06-02  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_util.pm: New version from Mike Mitchell:
+
+	I've changed the API so that the first argument is
+	'community at host:port'.
+
+	* SNMP_util.pm: New file.
+
+1998-05-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm (get_request_response,
+	set_request_response,getnext_request_response): Pass 1 rather
+	than 0 as the $error argument.
+
+	(receive_response_3): Fix a bug which prevented the $errorp
+	argument from being passed correctly.
+
+1998-05-06  Simon Leinen  <simon.leinen at switch.ch>
+
+	* Makefile (TESTSRCS): Added test/if-to-routes.pl.
+
+1998-05-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm: Upped verision number to 0.57.
+
+	Added credit to Mike Diehn for encode_ip_address.
+
+	* index.html: Added sections about walking tables and sending traps.
+
+1998-04-30  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm ($VERSION): Upped to 0.58.
+
+	(trap_request_send): Return 1, not 0, on success.
+
+	* BER.pm (encode_ip_address): New exported subroutine.
+
+	(encode_intlike): New subroutine, could be used to encode
+	integer-like data such as uptime.
+
+1998-04-21  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm: Added encoding of traps, courtesy Mike Mitchell
+	<mcm at unx.sas.com>.
+
+1998-04-09  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm (request_response_5, receive_response_3,
+	unwrap_response_6): Replace request_response_4,
+	receive_response_2, and unwrap_response_5, respectively.  Added
+	$errorp argument to control error handling.
+
+	(map_table): Count calls to walk function and return the total
+	number.
+
+	No longer pretty_print the table entries before passing them to
+	the walk function.
+
+	(get_request_response, set_request_response,
+	getnext_request_response, send_query): Added formal argument
+	lists.
+
+	(SNMPv1_Session::open): Initialize new slots `error_status' and
+	`error_index' to be used for user-supplied error handling.
+
+1998-04-06  Simon Leinen  <simon.leinen at switch.ch>
+
+	* Makefile (TESTSRCS): Renamed test/test_table.pl to
+	test/test-table.pl for consistency.
+
+	* SNMP_Session.pm (map_table, map_table_start_end, index_compare,
+	oid_diff): New subroutines.
+
+	(index_compare, oid_diff): New exported names.
+
+	* Makefile (TESTSRCS): Added new table test scripts.
+
+1998-03-25  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Document two new fixes.
+
+	* BER.pm (encode_int): New version by Mike Mitchell
+	<mcm at unx.sas.com>.
+
+	* README: Added Mike Mitchell to the list of contributors.
+
+1998-03-25  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README: Added Mike Mitchell to the list of contributors.
+
+1998-03-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm (pretty_uptime): Be careful to use integer arithmetic.
+
+	($VERSION): 0.54 -> 0.55.
+
+1998-03-05  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README: Added Niels Bakker to the credits.
+
+1998-02-14  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm ($VERSION): -> 0.54.
+
+	* BER.pm: Changed the spacing in prototype argument lists so that
+ 	Perl 5.003 likes it.
+
+1998-02-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm: Removed forgotten diagnostic message.
+
+	Made spacing before parentheses consistent.
+
+1998-02-13  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm: Removed forgotten diagnostic message.
+
+	Made spacing before parentheses consistent.
+
+1998-02-11  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm: Use prototypes consistently.
+
+	Fixed two benign errors found by this.
+
+1998-01-30  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README (Credits): Added Dan Cox and Iouri Pakhomenko.
+
+	* index.html: Mentioned that Iouri Pakhomenko also noted the
+ 	error_return bug.
+
+1998-01-29  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Mention Dan Cox' fix to return_error.
+
+	* SNMP_Session.pm ($VERSION): 0.55 -> 0.56.
+
+	* SNMP_Session.pm (request_response_4):
+	Call $this->error when no response is received.
+
+	(error_return): Converted to a method, so that SNMPv1_Session
+ 	inherits it.
+
+	* index.html: SNMPv2 -> SNMPv3.
+
+	* index.html: Noted that leading dots are now ignored in OIDs.
+
+1998-01-07  Simon Leinen  <simon.leinen at switch.ch>
+
+	* Makefile (INSTALL):
+	ginstall -> install.  GNU install is no longer installed in
+	our new environment.  However, Solaris 2.6 install seems to be
+	compatible enough to BSD install at last.
+
+	* BER.pm ($VERSION): Incremented to 0.52.
+
+	* BER.pm (encode_oid): Ignore leading dot.
+
+1997-12-22  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm ($errmsg, $suppress_warnings):
+	New variables for silent error
+	handling.
+
+1997-12-03  Simon Leinen  <simon.leinen at switch.ch>
+
+	* ChangeLog: *** empty log message ***
+
+	* index.html: Documented recent changes.
+
+	* SNMP_Session.pm: Upped version number.
+
+	* BER.pm: No longer use integer.
+
+	No longer call "warn".
+
+	* SNMP_Session.pm: Handle encoding errors in a sensible way.
+
+	($SNMP_Session::errmsg, $SNMP_Session::suppress_warnings): New
+ 	variables that give the caller more control about error messages.
+
+Wed Dec  3 20:05:50 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Documented recent changes.
+
+	* BER.pm: No longer use integer.
+
+	No longer call "warn".
+
+	* SNMP_Session.pm: Handle encoding errors in a sensible way.
+
+	($SNMP_Session::errmsg, $SNMP_Session::suppress_warnings): New
+	variables that give the caller more control about error messages.
+
+	Upped version number.
+
+Mon Dec  1 12:38:42 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm (decode_intlike_s): Multiply with 256 rather than shifting 8.
+
+Fri Nov 28 21:59:11 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm:
+	Better error handling.  No longer calls die(), but returns undefined
+	values and leaves an error message in $BER::errmsg.
+
+	* SNMP_Session.pm (ber_error):
+	New subroutine that passes an error up from the BER module.
+
+	(unwrap_response_5): Use it.
+
+Tue Nov 25 13:58:40 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README, index.html: Found an application for sending traps.
+
+Fri Nov 14 19:25:22 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Added test/arp.
+
+	* Makefile (TESTSRCS): Added test/arp.
+
+Fri Oct 31 18:21:36 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* Makefile (dist): Used to be called `shar'.
+
+	Install index.html, tol.
+
+	* index.html: Moved description of last change forward.
+
+	* SNMP_Session.pm ($VERSION): Incremented.
+
+	* SNMP_Session.pm: Added Daniel L. Needles' change to avoid
+ 	passing numeric IP addresses to inet_ntoa().
+
+	* README: Give credit to Daniel L. Needles.
+
+	* index.html: Mention Dan L. Needle's contribution.
+
+Mon Sep 22 19:25:36 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm (hex_string, hex_string_of_type): New subroutines.
+
+Fri Aug 22 10:54:00 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Bradford T. -> Brad.
+
+Thu Aug 21 14:05:37 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html, README (Set Requests): Point to test/set-test.pl.
+
+	* index.html: Added missing `mailto:' to URL.
+
+Tue Aug 19 14:43:17 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm: Use more canonic e-mail address for matter.
+
+	* Makefile (TESTSRCS): Added set-test.pl.
+
+	* README, index.html: Use more canonic e-mail address for matter.
+
+Sat Aug 16 00:06:21 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* Makefile (TESTSRCS): Added some, removed party-test.pl.
+
+	(DOCS): Added test/README, index.html.
+
+	(PKGSRCS): Removed Party.pm.
+
+	(TESTSRCS): Renamed from PROGSRCS.
+
+	Moved all test scripts to `test' subdirectory.
+
+	* index.html, README: snmp-set Support: Added credits for matter,
+ 	small description on snmpset usage, updated to-do items.
+
+	* SNMP_Session.pm: Added set-request support based on code
+ 	contributed my Matthew Trunnell <matter at media.mit.edu>:
+
+	(encode_request): The former list of encoded OIDs can now also
+ 	contain OID/value pairs.  Those are represented by references to
+ 	two-element arrays, the encoded OID and the encoded value.
+
+	(encode_set_request): New subroutine.
+
+	(set_request_response): New subroutine.
+
+	(request_response_4): Return undef as soon as an error response
+ 	packet has been received, rather than retrying.
+
+	(unwrap_response_5): Recognize OID/value pairs (but print only the
+ 	OID).
+
+	This fix doesn't really have anything to do with set-request
+ 	support:
+
+	(receive_response_2): Undefine the "unwrapped" cache so that you
+ 	don't get the data from the last request if you ignore the error
+ 	return.
+
+	($VERSION): Incremented to 0.52.
+
+Thu Jul 31 11:56:31 1997  leinen  <leinen at bolivar>
+
+	* test.pl: Fixed pathname to Perl.
+
+	* SNMP_Session.pm (open): Better error messages.
+
+	* SNMP_Session.pm: Better diagnostics:
+
+	(request_response_4): Renamed from request_response_3, take
+ 	additional argument $oids.
+
+	(receive_response_2): Renamed from receive_response_1, take
+ 	additional argument $oids.
+
+	(unwrap_response_5): Renamed from unwrap_response_4, take
+ 	additional argument $oids.  Use this to generate error message
+ 	when we have an errorIndex.
+
+	(error): New method to print an error message referring to a
+ 	specific session.
+
+	(to_string): New function, factored out from `describe'.  Be much
+ 	more verbose.
+
+	($VERSION): Incremented to 0.51.
+
+Wed Jul  9 16:17:30 1997  leinen  <leinen at bolivar>
+
+	* index.html: Document recent changes: version numbers, unsigned
+ 	printing.
+
+	* SNMP_Session.pm: Added versioning as supported by Exporter.pm.
+
+	* BER.pm: Don't die in a few places.
+
+	($VERSION): New variable, initially 0.50.
+
+	(version): New subroutine.
+
+Wed Jul  2 16:02:23 1997  leinen  <leinen at bolivar>
+
+	* BER.pm (pretty_uptime): Avoid negative times.
+
+	* BER.pm: Removed debugging version of decode_intlike_s.
+
+	(pretty_uptime): Use decode_unsignedlike rather than
+ 	decode_intlike.
+
+	* BER.pm: Buggy version that should work with mrtg on all systems.
+
+Thu Jun 12 09:05:17 1997  leinen  <leinen at bolivar>
+
+	* README: Updated sample code.
+
+	Added pointer to HTML page.
+
+Thu Jun 12 09:00:34 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* test.pl, walk-test.pl:
+ 	Better error message for session open failure.
+
+	* index.html: Updated for new error handling and adapted sample
+ 	code, note dependency on Perl 5.002, note use of strict.
+
+Thu Jun 12 08:47:53 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm, SNMP_Session.pm: Require Perl 5.002.
+
+	* SNMP_Session.pm: Be less strict.
+
+Mon Apr 28 18:55:20 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* README, index.html: Moved my home page to
+ 	http://www.switch.ch/misc/leinen/.
+
+Thu Apr  3 14:08:26 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Point to .tar.gz rather than .shar.
+
+	* Makefile (shar): Make a tar.gz file, too, and install both under
+ 	$(DESTDIR).
+
+	* BER.pm (encode_int_0): New exported subroutine that generates
+ 	the BER encoding for the integer zero.
+
+	* SNMP_Session.pm: Never die on exceptional situations, but write
+ 	a warning and return undef.
+
+	(encode_request): Use encode_int_0.
+
+	(unwrap_response_4): Better diagnostics for unparseable and error
+ 	responses.
+
+Mon Mar 24 09:16:03 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm: Handle unsigned integers correctly.
+
+Fri Feb 21 09:07:03 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm (unwrap_response_4): Integrated fix by Tobi
+ 	Oetiker for doubly declared lexical variable $request_id.
+
+Mon Feb 17 13:31:39 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* index.html: Mrtg: Don't mention beta version 2.0 anymore, since
+ 	this has now been released.
+
+	* README, index.html: SNMP_Session::open -> SNMP_Session->open
+ 	(thanks to "Robert Weatherford(grim)" <rweather at ctron.com>)
+
+Mon Feb 17 13:26:16 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* BER.pm (@EXPORT): Export `encoded_oid_prefix_p'.
+
+	(encoded_oid_prefix_p): Handle subids > 127.
+
+	Return length of prefix in second encoded OID as a result.
+
+	(decode_subid): New subroutine that decodes a single subid from an
+ 	encoded OID.
+
+Sun Feb  2 16:47:30 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* asn1-test.pl: Added test for sequence decoding.
+
+	* ASN_1.pm (ASN_1::Sequence): Fixed `new' method.
+
+	(decode): Added Sequence decoding.
+
+	* asn1-test.pl, ASN_1.pm: Initial revision
+
+Sat Feb  1 17:42:01 1997  Simon Leinen  <simon.leinen at switch.ch>
+
+	* Makefile (PROGSRCS): Added ber-test.pl.
+
+	* BER.pm: Use integer.
+
+	(decode_intlike): Simplified by writing a proper loop to decode
+ 	octet by octet.  This should also solve any problems with signed
+ 	vs. unsigned.
+
+	* ber-test.pl (decode_intlike_test): New subroutine.
+
+	(regression_test): Use it.
+
+	* ber-test.pl: Use integer.
+
+	Added several test cases.
+
+	* ber-test.pl: Initial revision
+
+	* BER.pm (regression_test): Removed.  This is now in ber-test.pl.
+
+Wed Jan 15 08:56:54 1997  leinen  <leinen at ohiggins>
+
+	* SNMP_Session.pm: Backed out the changes between revisions 1.27
+ 	and 1.28 that used the new subroutines in Socket.pm.  It turned
+ 	out that those routines require Perl 5.002 or later.  Put the new
+ 	code back in and commented it out with clear notices - look for
+ 	the markers "Perl 5.002 or later" and "end of pre-5.002 code".
+
+Tue Jan 14 09:01:57 1997  leinen  <leinen at ohiggins>
+
+	* BER.pm, SNMP_Session.pm: Commented out use strict/use vars and
+ 	explain why.
+
+Mon Jan 13 13:28:04 1997  leinen  <leinen at ohiggins>
+
+	* BER.pm: Updated my e-mail address in the header comment.
+
+Tue Jan  7 16:38:48 1997  leinen  <leinen at ohiggins>
+
+	* index.html: Updated from latest README.
+
+	Added section with recent changes and credits.
+
+	* README: Updated from latest index.html.
+
+	* SNMP_Session.pm: Make better use of the functionality in
+ 	Socket.pm.
+
+Thu Dec 26 17:31:32 1996  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm (receive_response_1): Perform the address check
+ 	only if debugging is set on the session.
+
+	* SNMP_Session.pm (receive_response_1): Don't return failure if
+ 	response comes from a different address.  Also, the warning is
+ 	only issued if debugging is set for the session.
+
+Fri Dec 20 14:50:03 1996  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm: Import @ISA from vars.
+
+	Added debugging.
+
+	Fixed header comment.
+
+	(unwrap_response_4): Removed spurious argument to
+	decode_by_template().
+
+	* BER.pm: Use strict.
+
+	(pretty_print): Declare $result as lexical variable.
+
+	(decode_by_template): Implement %i and %s without prefix arguments.
+
+	(encoded_oid_prefix_p): Declare $subid1, $subid2 lexical.
+
+Fri Dec 20 09:56:28 1996  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm ($default_timeout, $default_retries,
+ 	$default_backoff): New values.  Improved following discussions on
+ 	the mrtg mailing list.
+
+	(encode_request): Increment request ID.
+
+	(request_response_3): Renamed from `request_response'.  Added
+ 	`response_tag' argument.  Unwrap each received response.  If this
+ 	fails because of mismatching community or request ID, just ignore
+ 	the request and retry.
+
+	(SNMPv1_Session::open): Cast the random request ID to an int.
+
+	(unwrap_response_4): Renamed from `unwrap_response'.  Added
+ 	`request_id' argument.
+
+	Check community and request ID and return undefined if there is a
+ 	mismatch.
+
+	(receive_response_1): Renamed from `receive_response'.  Added
+ 	`response_tag' parameter.
+
+	Fixed report of response from bad address.
+
+Tue Dec 17 18:16:55 1996  Simon Leinen  <simon.leinen at switch.ch>
+
+	* SNMP_Session.pm: Stripped contributor entries.
+
+	(decode_get_response): Call unwrap_response via eval and simply
+ 	return zero when it dies.
+
+	(receive_response): Return zero if response comes from different
+ 	address.
+
+	(request_response): Wait for another packet when receive_response
+ 	fails.
+
+	* BER.pm: Contributors list: slightly fixed.
+
+	(encode_oid): Removed comments.
+
+Tue Dec 17 13:54:42 1996  Simon Leinen  <simon.leinen at switch.ch>
+
+	* walk-test.pl: Changed path to Perl.
+
+	($hostname): Changed default.
+
+	* SNMP_Session.pm: Updated my e-mail address.
+
+	* BER.pm (encode_oid): Added check for negative OID subids, by
+ 	Yufang HU <yhu at casc.com>
+
+	support up to 32bit subids, by Philippe Simonet
+ 	<sip00 at vg.swissptt.ch>.
+
+Sun Aug 25 23:49:15 1996  Simon Leinen  <simon at instrumatic.ch>
+
+	* SNMP_Session.pm (request_response):
+	Perform retries with exponential backoff.
+
+	(retries, backoff): New slots of the SNMP_Session object.  Default
+ 	values are 3 and 1.5, respectively.
+
+Wed Jul 10 08:18:16 1996  Simon Leinen  <simon at instrumatic.ch>
+
+	* README: Added Heine Peters to contributors list.
+
+	* walk-test.pl: Accept hostname and community as command-line
+ 	arguments.  These default to ``neon-tetra'' and ``public'',
+ 	respectively.
+
+	* SNMP_Session.pm: New header comment.
+
+Wed Jul 10 08:06:11 1996  Tobias Oetiker  <oetiker at ee.ethz.ch>
+
+	Changed default timeout from 2 to 10 seconds.
+
+Wed Jul 10 08:06:11 1996  Heine Peters  <peters at dkrz.de>
+
+	Allow remote host to be specified as IP address in dotted-quad
+	notation rather than by name (integrated change dated April 25).
+
+	No longer bind the UDP socket to a specific address.
+
+Tue Jul  9 09:40:28 1996  Simon Leinen  <simon at instrumatic.ch>
+
+	* README: Added list of contributors.
+
+	Added pointer to mrtg 2.0.
+
+Tue Jul  9 08:41:22 1996  Simon Leinen  <simon at instrumatic.ch>
+
+	* BER.pm (decode_intlike): Fixed shifting of first byte.
+
+	* BER.pm: Reformatted the header comment and added my name.
+
+	* BER.pm (decode_string): Cleaned up by removing my old code that
+ 	had been commented out by Andrzej Tobola when he fixed it.
+
+Tue Jul  9 08:41:22 1996  Andrzej Tobola  <san at iem.pw.edu.pl>
+
+	* BER.pm (decode_string): Support long strings by using
+	decode_length.
+
+Tue Jul  9 08:41:22 1996  Tobias Oetiker  <oetiker at ee.ethz.ch>
+
+	* BER.pm (decode_intlike): Handle five-byte integers.
+
+Tue Jul  9 08:41:22 1996  Dave Rand  <dlr at Bungi.com>
+
+	* BER.pm (pretty_uptime): New subroutine to print sysUpTime
+	readably.
+
+Mon Jul  8 17:15:43 1996  Simon Leinen  <simon at instrumatic.ch>
+
+	* README: Updated my e-mail address.
+
+	Note that get-next is now supported.
+
+	* Makefile (PROGSRCS): Added walk-test.pl.
+
+	* walk-test.pl: Initial revision
+
+	* test.pl: Actually use the hostname passed on the command line.
+
+	* BER.pm (encoded_oid_prefix_p): New subroutine that checks
+ 	whether one encoded OID is a prefix of the other.
+
+	* SNMP_Session.pm (request_response): New function called by both
+ 	get_request_response and getnext_request_response.
+
+	* SNMP_Session.pm:
+	(getnext_request, encode_getnext_request,
+ 	getnext_request_response): New functions.
+
+	(encode_request): New subroutine called by both encode_get_request
+ 	and encode_getnext_request.
+
+	* BER.pm (pretty_oid): Print subids beyond 127 CORRECTLY.
+
+	* BER.pm (pretty_oid): Print subids beyond 127.
+
+	* BER.pm (pretty_print): Call `pretty_ip_address' for objects with
+ 	snmp_ip_address_tag.
+
+	(pretty_ip_address): New subroutine that prints an IP address
+ 	object in decimal quad notation.
+
+Wed Jun 12 15:55:20 1996  Simon Leinen  <simon at instrumatic.ch>
+
+	* BER.pm (encode_oid): Support OID subids up to 16383, up from 127.
+
+Fri Dec 22 11:45:02 1995  Simon Leinen  <simon at instrumatic.ch>
+
+	* wwwtest: Added forward definitions of all subroutines.
+
+	Check whether the hostname is in the list of allowed hosts.
+
+	(html_error_message): added horizontal line below the title.
+
+Fri Jul 28 17:23:14 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* wwwtest (write_query_form): added a footer with links to the
+ 	SNMP module's page, the gateway's source and my home page.
+
+Mon Jul 17 13:51:04 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* Makefile (DOCS): removed ChangeLog.
+
+Thu Jul  6 13:15:59 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* test.pl, wwwtest, SNMP_Session.pm:
+	Use accessor methods for slots of SNMP_Session.
+
+Thu Jul  6 12:57:37 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* test.pl (%ugly_oids): use qw() to define them, as in wwwtest.
+
+	* test.pl: Simplified a tiny bit by adding the instance numbers to
+ 	the parameters of snmp_get.
+
+Wed Jul  5 22:11:47 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* wwwtest, party-test.pl: Added header comment.
+
+	* BER.pm, Party.pm: Added comments.
+
+	* Makefile (ALLDIST): mention $(DOCS) before $(ALLSRCS) so that
+ 	the README is the first file in a shar archive.
+
+Wed Jul  5 21:39:49 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* wwwtest (html_error_message, html_quote): moved to the end of
+ 	the file.
+
+	* wwwtest (write_query_form): default to liasg7 to simplify
+ 	testing at home.
+
+	* wwwtest (query_to_html_response): added heading containing the
+ 	name of the queried host.
+
+	(write_query_form): use here document to reduce quoting.
+
+	* wwwtest: Moved handling of parameterless case to
+ 	`write_query_form()'.
+
+	(write_query_form): new procedure.
+
+	(parse_query): return result rather than putting it in global hash
+ 	%query.
+
+	* wwwtest: Changed form to use SELECT tags so that there is a very
+ 	limited choice of host and community names.
+
+	* wwwtest: If called without QUERY_STRING, act as a form that
+ 	allows the user to specify a host and community name.
+
+	If called with a QUERY_STRING, extract host and community name and
+ 	use this as the target of the query.
+
+	Wrap SNMP session creation and get query in evals, and generate a
+ 	HTML error message if one of them fails.
+
+	* SNMP_Session.pm (host_not_found_error): generate an error
+ 	message trying to explain why a given host hasn't been found by
+ 	one of the gethostby...() routines.
+
+	(open): use `host_not_found_error' to generate better error
+ 	messages.
+
+	* test.pl: Added header comment.
+
+	Parse command-line arguments.
+
+Mon Jul  3 13:53:26 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* README: Explain how to encode OIDs.
+
+	Explain (roughly) how to decode the results.
+
+Wed Jun 28 22:47:48 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* wwwtest: Removed HP-agent-specific variables so that I can test
+ 	the code against CMU SNMPv2.
+
+Wed Jun 28 22:46:55 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* Makefile (DOCS, ALLDIST): new variables.
+
+	(shar): new target.
+
+	* SNMP_Session.pm:
+	Moved the SNMPv1-specific parts to a subclass SNMPv1_Session.
+
+	* Makefile, ChangeLog: Initial revision
+
+Wed Jun 28 21:41:51 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* party-test.pl: Initial revision
+
+	* Party.pm: use BER.
+
+	* Party.pm: Initial revision
+
+	* SNMP_Session.pm (snmp_version_1, snmp_version_2): new subroutines.
+
+	(open): fixed typo in getprotobyname('udp').
+
+Mon Jun 19 19:43:12 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* README (Example): use package BER, remove BER:: prefixes.
+
+	* wwwtest, test.pl: use BER.
+
+	Removed BER:: package prefixes.
+
+	* BER.pm: use Exporter.
+
+	* README: Tabified.
+
+Wed Jun 14 21:59:15 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* wwwtest (snmp_get):
+	produce HTML error message when get_request_response fails.
+
+	* SNMP_Session.pm (get_request_response):
+	don't warn if wait_for_response fails, return
+	0.
+
+	* wwwtest (%ugly_oids):
+	added a couple of statistics about the first two
+	interfaces.
+
+	* BER.pm (pretty_print): format null objects as "(null)".
+
+	* wwwtest: Generate more properly structured HTML.
+
+	(%ugly_oids): Reformatted, added some variables.
+
+Mon Jun 12 15:43:36 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* README: Initial revision
+
+Fri May 19 17:07:46 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* BER.pm (decode_by_template):
+	improve error message for length mismatch.
+
+	(decode_length): support lengths up to 65536.
+
+	* BER.pm (decode_intlike): support length 3 integers.
+
+	Fix error message for unsupported integer length.
+
+	* BER.pm: Added SNMP-specific tags (counter, gauge, IP address etc.)
+
+	(pretty_print): handle counters and gauges, which are treated just
+	like integers.
+
+	* wwwtest: Initial revision
+
+Thu May 18 22:18:41 1995  Simon Leinen  <simon at lia.di.epfl.ch>
+
+	* BER.pm, SNMP_Session.pm: Added -*- mode -*- comment for Emacs.
+
+	* test.pl: Layout change.
+
+	* BER.pm: Export several symbols.
+
+	(decode_by_template): assume the constructor flag when the user gives
+	a tag value in a %{ option.
+
+	* SNMP_Session.pm: Use the BER package, removed BER:: prefixes.
+
+	* BER.pm (decode_by_template): avoid recursion.
+
+	* SNMP_Session.pm (open):
+	generate a file handle so that multiple sessions can be used
+	at the same time.
+
+	* SNMP_Session.pm:
+	Use symbolic constants for PDU tags and default UDP port.
+
+	(describe): include timeout value in the output.
+
+	* test.pl: Removed `&'s before subroutine names.
+
+	* BER.pm: Improved layout.
+
+	(decode_string): Improved error message.
+
+	* BER.pm: Use symbolic constants for tags and flags.
+
+	* SNMP_Session.pm:
+	Use `Socket.pm' to get the socket constants (AF_INET, SOCK_DGRAM).
+
+	* test.pl (snmp_get): new subroutine.
+
+	* BER.pm (pretty_using_decoder): new function.
+
+	(pretty_string, pretty_int): use it.
+
+	* test.pl: Use a different mechanism to pretty print OIDs.
+
+	* SNMP_Session.pm:
+	Renamed the `socket' member to `sock' in order to avoid confusion.
+
+	* test.pl: Moved the BER and SNMP_Session packages to subfiles.
+
+	* BER.pm (decode_by_template):
+	in the %i handler, cast $expected to int in
+	order to fix false mismatches.
+
+	* BER.pm, SNMP_Session.pm, test.pl: Initial revision
+
diff --git a/META.yml b/META.yml
deleted file mode 100644
index 0ca2e98..0000000
--- a/META.yml
+++ /dev/null
@@ -1,12 +0,0 @@
---- #YAML:1.0
-name:                SNMP_Session
-version:             1.13
-abstract:            ~
-license:             ~
-author:              ~
-generated_by:        ExtUtils::MakeMaker version 6.42
-distribution_type:   module
-requires:     
-meta-spec:
-    url:     http://module-build.sourceforge.net/META-spec-v1.3.html
-    version: 1.3
diff --git a/Net_SNMP_util.pm b/Net_SNMP_util.pm
new file mode 100644
index 0000000..b0299fc
--- /dev/null
+++ b/Net_SNMP_util.pm
@@ -0,0 +1,2130 @@
+### - *- mode: Perl -*-
+######################################################################
+### Net_SNMP_util -- SNMP utilities using Net::SNMP
+######################################################################
+### Copyright (c) 2005-2011 Mike Mitchell.
+###
+### This program is free software; you can redistribute it under the
+### "Artistic License" included in this distribution (file "Artistic").
+######################################################################
+### Created by:  Mike Mitchell   <Mike.Mitchell at sas.com>
+###
+### Contributions and fixes by:
+###
+### Laszlo Herczeg <laszlo.herczeg at austinenergy.com>
+###	ignore unimplemented SNMP_Session.pm options
+###
+### Daniel McDonald <dmcdonald at digicontech.com>
+### 	make sure snmpwalk_flg stops when last instance in table is fetched
+###
+### Alexander Kozlov <avk at post.eao.ru>
+###	Leave snmpwalk_flg early if no OIDs are returned
+###
+### <jaccobs at online.nl>
+###	parse NOTIFICATION-TYPE in MIB
+###
+### Dan Thorson <Dan.Thorson at seagate.com>
+###	Handle quotes in MIB comments better
+###
+### Daniel J McDonald <dan.mcdonald at austinenergy.com>
+###	fix getbulk_request -> get_bulk_request typo
+###
+### Tobias Oetiker <tobi at oetiker.ch>
+###	fix '-privpassword' error against snmpv2 hosts
+###
+######################################################################
+
+package Net_SNMP_util;
+
+=head1 NAME
+
+Net_SNMP_util - SNMP utilities based on Net::SNMP
+
+=head1 SYNOPSIS
+
+The Net_SNMP_util module implements SNMP utilities using the Net::SNMP module.
+It implements snmpget, snmpgetnext, snmpwalk, snmpset, snmptrap, and
+snmpgetbulk.  The Net_SNMP_util module assumes that the user has a basic
+understanding of the Simple Network Management Protocol and related network
+management concepts.
+
+=head1 DESCRIPTION
+
+The Net_SNMP_util module simplifies SNMP queries even more than Net::SNMP  
+alone.  Easy-to-use "get", "getnext", "walk", "set", "trap", and "getbulk"
+routines are provided, hiding all the details of a SNMP query.
+
+=cut
+
+# ==========================================================================
+
+use strict;
+
+## Validate the version of Perl
+
+BEGIN
+{
+    die('Perl version 5.6.0 or greater is required') if ($] < 5.006);
+}
+
+## Handle importing/exporting of symbols
+
+use vars qw( @ISA @EXPORT $VERSION $ErrorMessage);
+use Exporter;
+
+our @ISA = qw( Exporter );
+
+our @EXPORT = qw(
+    snmpget snmpgetnext snmpwalk snmpset snmptrap snmpgetbulk snmpmaptable
+    snmpmaptable4 snmpwalkhash snmpmapOID snmpMIB_to_OID snmpLoad_OID_Cache
+    snmpQueue_MIB_File ErrorMessage
+);
+
+## Version of the Net_SNMP_util module
+
+our $VERSION = v1.0.20;
+
+use Carp;
+
+use Net::SNMP v5.0;
+
+# The OID numbers from RFC1213 (MIB-II) and RFC1315 (Frame Relay)
+# are pre-loaded below.
+%Net_SNMP_util::OIDS = 
+  (
+    'iso' => '1',
+    'org' => '1.3',
+    'dod' => '1.3.6',
+    'internet' => '1.3.6.1',
+    'directory' => '1.3.6.1.1',
+    'mgmt' => '1.3.6.1.2',
+    'mib-2' => '1.3.6.1.2.1',
+    'system' => '1.3.6.1.2.1.1',
+    'sysDescr' => '1.3.6.1.2.1.1.1.0',
+    'sysObjectID' => '1.3.6.1.2.1.1.2.0',
+    'sysUpTime' => '1.3.6.1.2.1.1.3.0',
+    'sysUptime' => '1.3.6.1.2.1.1.3.0',
+    'sysContact' => '1.3.6.1.2.1.1.4.0',
+    'sysName' => '1.3.6.1.2.1.1.5.0',
+    'sysLocation' => '1.3.6.1.2.1.1.6.0',
+    'sysServices' => '1.3.6.1.2.1.1.7.0',
+    'interfaces' => '1.3.6.1.2.1.2',
+    'ifNumber' => '1.3.6.1.2.1.2.1.0',
+    'ifTable' => '1.3.6.1.2.1.2.2',
+    'ifEntry' => '1.3.6.1.2.1.2.2.1',
+    'ifIndex' => '1.3.6.1.2.1.2.2.1.1',
+    'ifInOctets' => '1.3.6.1.2.1.2.2.1.10',
+    'ifInUcastPkts' => '1.3.6.1.2.1.2.2.1.11',
+    'ifInNUcastPkts' => '1.3.6.1.2.1.2.2.1.12',
+    'ifInDiscards' => '1.3.6.1.2.1.2.2.1.13',
+    'ifInErrors' => '1.3.6.1.2.1.2.2.1.14',
+    'ifInUnknownProtos' => '1.3.6.1.2.1.2.2.1.15',
+    'ifOutOctets' => '1.3.6.1.2.1.2.2.1.16',
+    'ifOutUcastPkts' => '1.3.6.1.2.1.2.2.1.17',
+    'ifOutNUcastPkts' => '1.3.6.1.2.1.2.2.1.18',
+    'ifOutDiscards' => '1.3.6.1.2.1.2.2.1.19',
+    'ifDescr' => '1.3.6.1.2.1.2.2.1.2',
+    'ifOutErrors' => '1.3.6.1.2.1.2.2.1.20',
+    'ifOutQLen' => '1.3.6.1.2.1.2.2.1.21',
+    'ifSpecific' => '1.3.6.1.2.1.2.2.1.22',
+    'ifType' => '1.3.6.1.2.1.2.2.1.3',
+    'ifMtu' => '1.3.6.1.2.1.2.2.1.4',
+    'ifSpeed' => '1.3.6.1.2.1.2.2.1.5',
+    'ifPhysAddress' => '1.3.6.1.2.1.2.2.1.6',
+    'ifAdminHack' => '1.3.6.1.2.1.2.2.1.7',  
+    'ifAdminStatus' => '1.3.6.1.2.1.2.2.1.7',
+    'ifOperHack' => '1.3.6.1.2.1.2.2.1.8',             
+    'ifOperStatus' => '1.3.6.1.2.1.2.2.1.8',
+    'ifLastChange' => '1.3.6.1.2.1.2.2.1.9',
+    'at' => '1.3.6.1.2.1.3',
+    'atTable' => '1.3.6.1.2.1.3.1',
+    'atEntry' => '1.3.6.1.2.1.3.1.1',
+    'atIfIndex' => '1.3.6.1.2.1.3.1.1.1',
+    'atPhysAddress' => '1.3.6.1.2.1.3.1.1.2',
+    'atNetAddress' => '1.3.6.1.2.1.3.1.1.3',
+    'ip' => '1.3.6.1.2.1.4',
+    'ipForwarding' => '1.3.6.1.2.1.4.1',
+    'ipOutRequests' => '1.3.6.1.2.1.4.10',
+    'ipOutDiscards' => '1.3.6.1.2.1.4.11',
+    'ipOutNoRoutes' => '1.3.6.1.2.1.4.12',
+    'ipReasmTimeout' => '1.3.6.1.2.1.4.13',
+    'ipReasmReqds' => '1.3.6.1.2.1.4.14',
+    'ipReasmOKs' => '1.3.6.1.2.1.4.15',
+    'ipReasmFails' => '1.3.6.1.2.1.4.16',
+    'ipFragOKs' => '1.3.6.1.2.1.4.17',
+    'ipFragFails' => '1.3.6.1.2.1.4.18',
+    'ipFragCreates' => '1.3.6.1.2.1.4.19',
+    'ipDefaultTTL' => '1.3.6.1.2.1.4.2',
+    'ipAddrTable' => '1.3.6.1.2.1.4.20',
+    'ipAddrEntry' => '1.3.6.1.2.1.4.20.1',
+    'ipAdEntAddr' => '1.3.6.1.2.1.4.20.1.1',
+    'ipAdEntIfIndex' => '1.3.6.1.2.1.4.20.1.2',
+    'ipAdEntNetMask' => '1.3.6.1.2.1.4.20.1.3',
+    'ipAdEntBcastAddr' => '1.3.6.1.2.1.4.20.1.4',
+    'ipAdEntReasmMaxSize' => '1.3.6.1.2.1.4.20.1.5',
+    'ipRouteTable' => '1.3.6.1.2.1.4.21',
+    'ipRouteEntry' => '1.3.6.1.2.1.4.21.1',
+    'ipRouteDest' => '1.3.6.1.2.1.4.21.1.1',
+    'ipRouteAge' => '1.3.6.1.2.1.4.21.1.10',
+    'ipRouteMask' => '1.3.6.1.2.1.4.21.1.11',
+    'ipRouteMetric5' => '1.3.6.1.2.1.4.21.1.12',
+    'ipRouteInfo' => '1.3.6.1.2.1.4.21.1.13',
+    'ipRouteIfIndex' => '1.3.6.1.2.1.4.21.1.2',
+    'ipRouteMetric1' => '1.3.6.1.2.1.4.21.1.3',
+    'ipRouteMetric2' => '1.3.6.1.2.1.4.21.1.4',
+    'ipRouteMetric3' => '1.3.6.1.2.1.4.21.1.5',
+    'ipRouteMetric4' => '1.3.6.1.2.1.4.21.1.6',
+    'ipRouteNextHop' => '1.3.6.1.2.1.4.21.1.7',
+    'ipRouteType' => '1.3.6.1.2.1.4.21.1.8',
+    'ipRouteProto' => '1.3.6.1.2.1.4.21.1.9',
+    'ipNetToMediaTable' => '1.3.6.1.2.1.4.22',
+    'ipNetToMediaEntry' => '1.3.6.1.2.1.4.22.1',
+    'ipNetToMediaIfIndex' => '1.3.6.1.2.1.4.22.1.1',
+    'ipNetToMediaPhysAddress' => '1.3.6.1.2.1.4.22.1.2',
+    'ipNetToMediaNetAddress' => '1.3.6.1.2.1.4.22.1.3',
+    'ipNetToMediaType' => '1.3.6.1.2.1.4.22.1.4',
+    'ipRoutingDiscards' => '1.3.6.1.2.1.4.23',
+    'ipInReceives' => '1.3.6.1.2.1.4.3',
+    'ipInHdrErrors' => '1.3.6.1.2.1.4.4',
+    'ipInAddrErrors' => '1.3.6.1.2.1.4.5',
+    'ipForwDatagrams' => '1.3.6.1.2.1.4.6',
+    'ipInUnknownProtos' => '1.3.6.1.2.1.4.7',
+    'ipInDiscards' => '1.3.6.1.2.1.4.8',
+    'ipInDelivers' => '1.3.6.1.2.1.4.9',
+    'icmp' => '1.3.6.1.2.1.5',
+    'icmpInMsgs' => '1.3.6.1.2.1.5.1',
+    'icmpInTimestamps' => '1.3.6.1.2.1.5.10',
+    'icmpInTimestampReps' => '1.3.6.1.2.1.5.11',
+    'icmpInAddrMasks' => '1.3.6.1.2.1.5.12',
+    'icmpInAddrMaskReps' => '1.3.6.1.2.1.5.13',
+    'icmpOutMsgs' => '1.3.6.1.2.1.5.14',
+    'icmpOutErrors' => '1.3.6.1.2.1.5.15',
+    'icmpOutDestUnreachs' => '1.3.6.1.2.1.5.16',
+    'icmpOutTimeExcds' => '1.3.6.1.2.1.5.17',
+    'icmpOutParmProbs' => '1.3.6.1.2.1.5.18',
+    'icmpOutSrcQuenchs' => '1.3.6.1.2.1.5.19',
+    'icmpInErrors' => '1.3.6.1.2.1.5.2',
+    'icmpOutRedirects' => '1.3.6.1.2.1.5.20',
+    'icmpOutEchos' => '1.3.6.1.2.1.5.21',
+    'icmpOutEchoReps' => '1.3.6.1.2.1.5.22',
+    'icmpOutTimestamps' => '1.3.6.1.2.1.5.23',
+    'icmpOutTimestampReps' => '1.3.6.1.2.1.5.24',
+    'icmpOutAddrMasks' => '1.3.6.1.2.1.5.25',
+    'icmpOutAddrMaskReps' => '1.3.6.1.2.1.5.26',
+    'icmpInDestUnreachs' => '1.3.6.1.2.1.5.3',
+    'icmpInTimeExcds' => '1.3.6.1.2.1.5.4',
+    'icmpInParmProbs' => '1.3.6.1.2.1.5.5',
+    'icmpInSrcQuenchs' => '1.3.6.1.2.1.5.6',
+    'icmpInRedirects' => '1.3.6.1.2.1.5.7',
+    'icmpInEchos' => '1.3.6.1.2.1.5.8',
+    'icmpInEchoReps' => '1.3.6.1.2.1.5.9',
+    'tcp' => '1.3.6.1.2.1.6',
+    'tcpRtoAlgorithm' => '1.3.6.1.2.1.6.1',
+    'tcpInSegs' => '1.3.6.1.2.1.6.10',
+    'tcpOutSegs' => '1.3.6.1.2.1.6.11',
+    'tcpRetransSegs' => '1.3.6.1.2.1.6.12',
+    'tcpConnTable' => '1.3.6.1.2.1.6.13',
+    'tcpConnEntry' => '1.3.6.1.2.1.6.13.1',
+    'tcpConnState' => '1.3.6.1.2.1.6.13.1.1',
+    'tcpConnLocalAddress' => '1.3.6.1.2.1.6.13.1.2',
+    'tcpConnLocalPort' => '1.3.6.1.2.1.6.13.1.3',
+    'tcpConnRemAddress' => '1.3.6.1.2.1.6.13.1.4',
+    'tcpConnRemPort' => '1.3.6.1.2.1.6.13.1.5',
+    'tcpInErrs' => '1.3.6.1.2.1.6.14',
+    'tcpOutRsts' => '1.3.6.1.2.1.6.15',
+    'tcpRtoMin' => '1.3.6.1.2.1.6.2',
+    'tcpRtoMax' => '1.3.6.1.2.1.6.3',
+    'tcpMaxConn' => '1.3.6.1.2.1.6.4',
+    'tcpActiveOpens' => '1.3.6.1.2.1.6.5',
+    'tcpPassiveOpens' => '1.3.6.1.2.1.6.6',
+    'tcpAttemptFails' => '1.3.6.1.2.1.6.7',
+    'tcpEstabResets' => '1.3.6.1.2.1.6.8',
+    'tcpCurrEstab' => '1.3.6.1.2.1.6.9',
+    'udp' => '1.3.6.1.2.1.7',
+    'udpInDatagrams' => '1.3.6.1.2.1.7.1',
+    'udpNoPorts' => '1.3.6.1.2.1.7.2',
+    'udpInErrors' => '1.3.6.1.2.1.7.3',
+    'udpOutDatagrams' => '1.3.6.1.2.1.7.4',
+    'udpTable' => '1.3.6.1.2.1.7.5',
+    'udpEntry' => '1.3.6.1.2.1.7.5.1',
+    'udpLocalAddress' => '1.3.6.1.2.1.7.5.1.1',
+    'udpLocalPort' => '1.3.6.1.2.1.7.5.1.2',
+    'egp' => '1.3.6.1.2.1.8',
+    'egpInMsgs' => '1.3.6.1.2.1.8.1',
+    'egpInErrors' => '1.3.6.1.2.1.8.2',
+    'egpOutMsgs' => '1.3.6.1.2.1.8.3',
+    'egpOutErrors' => '1.3.6.1.2.1.8.4',
+    'egpNeighTable' => '1.3.6.1.2.1.8.5',
+    'egpNeighEntry' => '1.3.6.1.2.1.8.5.1',
+    'egpNeighState' => '1.3.6.1.2.1.8.5.1.1',
+    'egpNeighStateUps' => '1.3.6.1.2.1.8.5.1.10',
+    'egpNeighStateDowns' => '1.3.6.1.2.1.8.5.1.11',
+    'egpNeighIntervalHello' => '1.3.6.1.2.1.8.5.1.12',
+    'egpNeighIntervalPoll' => '1.3.6.1.2.1.8.5.1.13',
+    'egpNeighMode' => '1.3.6.1.2.1.8.5.1.14',
+    'egpNeighEventTrigger' => '1.3.6.1.2.1.8.5.1.15',
+    'egpNeighAddr' => '1.3.6.1.2.1.8.5.1.2',
+    'egpNeighAs' => '1.3.6.1.2.1.8.5.1.3',
+    'egpNeighInMsgs' => '1.3.6.1.2.1.8.5.1.4',
+    'egpNeighInErrs' => '1.3.6.1.2.1.8.5.1.5',
+    'egpNeighOutMsgs' => '1.3.6.1.2.1.8.5.1.6',
+    'egpNeighOutErrs' => '1.3.6.1.2.1.8.5.1.7',
+    'egpNeighInErrMsgs' => '1.3.6.1.2.1.8.5.1.8',
+    'egpNeighOutErrMsgs' => '1.3.6.1.2.1.8.5.1.9',
+    'egpAs' => '1.3.6.1.2.1.8.6',
+    'transmission' => '1.3.6.1.2.1.10',
+    'frame-relay' => '1.3.6.1.2.1.10.32',
+    'frDlcmiTable' => '1.3.6.1.2.1.10.32.1',
+    'frDlcmiEntry' => '1.3.6.1.2.1.10.32.1.1',
+    'frDlcmiIfIndex' => '1.3.6.1.2.1.10.32.1.1.1',
+    'frDlcmiState' => '1.3.6.1.2.1.10.32.1.1.2',
+    'frDlcmiAddress' => '1.3.6.1.2.1.10.32.1.1.3',
+    'frDlcmiAddressLen' => '1.3.6.1.2.1.10.32.1.1.4',
+    'frDlcmiPollingInterval' => '1.3.6.1.2.1.10.32.1.1.5',
+    'frDlcmiFullEnquiryInterval' => '1.3.6.1.2.1.10.32.1.1.6',
+    'frDlcmiErrorThreshold' => '1.3.6.1.2.1.10.32.1.1.7',
+    'frDlcmiMonitoredEvents' => '1.3.6.1.2.1.10.32.1.1.8',
+    'frDlcmiMaxSupportedVCs' => '1.3.6.1.2.1.10.32.1.1.9',
+    'frDlcmiMulticast' => '1.3.6.1.2.1.10.32.1.1.10',
+    'frCircuitTable' => '1.3.6.1.2.1.10.32.2',
+    'frCircuitEntry' => '1.3.6.1.2.1.10.32.2.1',
+    'frCircuitIfIndex' => '1.3.6.1.2.1.10.32.2.1.1',
+    'frCircuitDlci' => '1.3.6.1.2.1.10.32.2.1.2',
+    'frCircuitState' => '1.3.6.1.2.1.10.32.2.1.3',
+    'frCircuitReceivedFECNs' => '1.3.6.1.2.1.10.32.2.1.4',
+    'frCircuitReceivedBECNs' => '1.3.6.1.2.1.10.32.2.1.5',
+    'frCircuitSentFrames' => '1.3.6.1.2.1.10.32.2.1.6',
+    'frCircuitSentOctets' => '1.3.6.1.2.1.10.32.2.1.7',
+    'frOutOctets' => '1.3.6.1.2.1.10.32.2.1.7',
+    'frCircuitReceivedFrames' => '1.3.6.1.2.1.10.32.2.1.8',
+    'frCircuitReceivedOctets' => '1.3.6.1.2.1.10.32.2.1.9',
+    'frInOctets' => '1.3.6.1.2.1.10.32.2.1.9',
+    'frCircuitCreationTime' => '1.3.6.1.2.1.10.32.2.1.10',
+    'frCircuitLastTimeChange' => '1.3.6.1.2.1.10.32.2.1.11',
+    'frCircuitCommittedBurst' => '1.3.6.1.2.1.10.32.2.1.12',
+    'frCircuitExcessBurst' => '1.3.6.1.2.1.10.32.2.1.13',
+    'frCircuitThroughput' => '1.3.6.1.2.1.10.32.2.1.14',
+    'frErrTable' => '1.3.6.1.2.1.10.32.3',
+    'frErrEntry' => '1.3.6.1.2.1.10.32.3.1',
+    'frErrIfIndex' => '1.3.6.1.2.1.10.32.3.1.1',
+    'frErrType' => '1.3.6.1.2.1.10.32.3.1.2',
+    'frErrData' => '1.3.6.1.2.1.10.32.3.1.3',
+    'frErrTime' => '1.3.6.1.2.1.10.32.3.1.4',
+    'frame-relay-globals' => '1.3.6.1.2.1.10.32.4',
+    'frTrapState' => '1.3.6.1.2.1.10.32.4.1',
+    'snmp' => '1.3.6.1.2.1.11',
+    'snmpInPkts' => '1.3.6.1.2.1.11.1',
+    'snmpInBadValues' => '1.3.6.1.2.1.11.10',
+    'snmpInReadOnlys' => '1.3.6.1.2.1.11.11',
+    'snmpInGenErrs' => '1.3.6.1.2.1.11.12',
+    'snmpInTotalReqVars' => '1.3.6.1.2.1.11.13',
+    'snmpInTotalSetVars' => '1.3.6.1.2.1.11.14',
+    'snmpInGetRequests' => '1.3.6.1.2.1.11.15',
+    'snmpInGetNexts' => '1.3.6.1.2.1.11.16',
+    'snmpInSetRequests' => '1.3.6.1.2.1.11.17',
+    'snmpInGetResponses' => '1.3.6.1.2.1.11.18',
+    'snmpInTraps' => '1.3.6.1.2.1.11.19',
+    'snmpOutPkts' => '1.3.6.1.2.1.11.2',
+    'snmpOutTooBigs' => '1.3.6.1.2.1.11.20',
+    'snmpOutNoSuchNames' => '1.3.6.1.2.1.11.21',
+    'snmpOutBadValues' => '1.3.6.1.2.1.11.22',
+    'snmpOutGenErrs' => '1.3.6.1.2.1.11.24',
+    'snmpOutGetRequests' => '1.3.6.1.2.1.11.25',
+    'snmpOutGetNexts' => '1.3.6.1.2.1.11.26',
+    'snmpOutSetRequests' => '1.3.6.1.2.1.11.27',
+    'snmpOutGetResponses' => '1.3.6.1.2.1.11.28',
+    'snmpOutTraps' => '1.3.6.1.2.1.11.29',
+    'snmpInBadVersions' => '1.3.6.1.2.1.11.3',
+    'snmpEnableAuthenTraps' => '1.3.6.1.2.1.11.30',
+    'snmpInBadCommunityNames' => '1.3.6.1.2.1.11.4',
+    'snmpInBadCommunityUses' => '1.3.6.1.2.1.11.5',
+    'snmpInASNParseErrs' => '1.3.6.1.2.1.11.6',
+    'snmpInTooBigs' => '1.3.6.1.2.1.11.8',
+    'snmpInNoSuchNames' => '1.3.6.1.2.1.11.9',
+    'ifName' => '1.3.6.1.2.1.31.1.1.1.1',
+    'ifInMulticastPkts' => '1.3.6.1.2.1.31.1.1.1.2',
+    'ifInBroadcastPkts' => '1.3.6.1.2.1.31.1.1.1.3',
+    'ifOutMulticastPkts' => '1.3.6.1.2.1.31.1.1.1.4',
+    'ifOutBroadcastPkts' => '1.3.6.1.2.1.31.1.1.1.5',
+    'ifHCInOctets' => '1.3.6.1.2.1.31.1.1.1.6',
+    'ifHCInUcastPkts' => '1.3.6.1.2.1.31.1.1.1.7',
+    'ifHCInMulticastPkts' => '1.3.6.1.2.1.31.1.1.1.8',
+    'ifHCInBroadcastPkts' => '1.3.6.1.2.1.31.1.1.1.9',
+    'ifHCOutOctets' => '1.3.6.1.2.1.31.1.1.1.10',
+    'ifHCOutUcastPkts' => '1.3.6.1.2.1.31.1.1.1.11',
+    'ifHCOutMulticastPkts' => '1.3.6.1.2.1.31.1.1.1.12',
+    'ifHCOutBroadcastPkts' => '1.3.6.1.2.1.31.1.1.1.13',
+    'ifLinkUpDownTrapEnable' => '1.3.6.1.2.1.31.1.1.1.14',
+    'ifHighSpeed' => '1.3.6.1.2.1.31.1.1.1.15',
+    'ifPromiscuousMode' => '1.3.6.1.2.1.31.1.1.1.16',
+    'ifConnectorPresent' => '1.3.6.1.2.1.31.1.1.1.17',
+    'ifAlias' => '1.3.6.1.2.1.31.1.1.1.18',
+    'ifCounterDiscontinuityTime' => '1.3.6.1.2.1.31.1.1.1.19',
+    'experimental' => '1.3.6.1.3',
+    'private' => '1.3.6.1.4',
+    'enterprises' => '1.3.6.1.4.1',
+  );
+
+# GIL
+my %revOIDS = ();	# Reversed %Net_SNMP_util::OIDS hash
+my $RevNeeded = 1;
+
+undef $Net_SNMP_util::Host;
+undef $Net_SNMP_util::Session;
+undef $Net_SNMP_util::Version;
+undef $Net_SNMP_util::LHost;
+undef $Net_SNMP_util::IPv4only;
+undef $Net_SNMP_util::ContextEngineID;
+undef $Net_SNMP_util::ContextName;
+$Net_SNMP_util::Debug = 0;
+$Net_SNMP_util::SuppressWarnings = 0;
+$Net_SNMP_util::CacheFile = "OID_cache.txt";
+$Net_SNMP_util::CacheLoaded = 0;
+$Net_SNMP_util::ReturnArrayRefs = 0;
+$Net_SNMP_util::ReturnHashRefs = 0;
+$Net_SNMP_util::MaxRepetitions = 12;
+
+### Prototypes
+sub snmpget ($@);
+sub snmpgetnext ($@);
+sub snmpopen ($$$);
+sub snmpwalk ($@);
+sub snmpwalk_flg ($$@);
+sub snmpset ($@);
+sub snmptrap ($$$$$@);
+sub snmpgetbulk ($$$@);
+sub snmpwalkhash ($$@);
+sub toOID (@);
+sub snmpmapOID (@);
+sub snmpMIB_to_OID ($);
+sub Check_OID ($);
+sub snmpLoad_OID_Cache ($);
+sub snmpQueue_MIB_File (@);
+sub ASNtype ($);
+sub error_msg ($);
+sub MIB_fill_OID ($);
+
+sub version () { $VERSION; }
+
+=head1 Option Notes
+
+=over
+
+=item host Parameter
+
+SNMP parameters can be specified as part of the hostname/ip address passed
+as the first argument.  The syntax is
+
+    community at host:port:timeout:retries:backoff:version
+
+If the community is left off, it defaults to "public".
+If the port is left off, it defaults to 161 for everything but snmptrap().
+The snmptrap() routine uses a default port of 162.
+Timeout and retries defaults to whatever Net::SNMP uses, currently 5.0 seconds
+and 1 retry (2 tries total).
+The backoff parameter is currently unimplemented.
+The version parameter defaults to SNMP version 1.  Some SNMP values such as
+64-bit counters have to be queried using SNMP version 2.  Specifying "2" or
+"2c" as the version parameter will accomplish this.  The snmpgetbulk routine
+is only supported in SNMP version 2 and higher.  Additional security features
+are available under SNMP version 3.
+
+Some machines have additional security features that only allow SNMP
+queries to come from certain IP addresses.  If the host doing the query
+has multiple interfaces, it may be necessary to specify the interface
+the query should come from.  The port parameter is further broken down into
+
+    remote_port!local_address!local_port
+
+Here are some examples:
+
+    somehost
+    somehost:161
+    somehost:161!192.168.2.4!4000  use 192.168.2.4 and port 4000 as source
+    somehost:!192.168.2.4          use 192.168.2.4 as source
+    somehost:!!4000                use port 4000 as source
+
+Most people will only need to use the first form ("somehost").
+
+=item OBJECT IDENTIFIERs
+
+To further simplify SNMP queries, the query routines use a small table that
+maps the textual representation of OBJECT IDENTIFIERs to their dotted notation.
+The OBJECT IDENTIFIERs from RFC1213 (MIB-II) and RFC1315 (Frame Relay) are
+preloaded.  This allows OBJECT IDENTIFIERs like "ifInOctets.4" to be used
+instead of the more cumbersome "1.3.6.1.2.1.2.2.1.10.4".
+
+Several functions are provided to manage the mapping table.  Mapping entries
+can be added directly, SNMP MIB files can be read, and a cache file with the
+text-to-OBJECT-IDENTIFIER mappings are maintained.  By default, the file
+"OID_cache.txt" is loaded, but it can by changed by setting the variable
+$Net_SNMP_util::CacheFile to the desired file name.  The functions to
+manipulate the mappings are:
+
+    snmpmapOID			Add a textual OID mapping directly
+    snmpMIB_to_OID		Read a SNMP MIB file
+    snmpLoad_OID_Cache		Load an OID-mapping cache file
+    snmpQueue_MIB_File		Queue a SNMP MIB file for loading on demand
+
+=item Net::SNMP extensions
+
+This module is built on top of Net::SNMP.  Net::SNMP has a different method
+of specifying SNMP parameters.  To support this different method, this module
+will accept an optional hash reference containing the SNMP parameters. The
+hash may contain the following:
+
+	[-port		=> $port,]
+	[-localaddr	=> $localaddr,]
+	[-localport     => $localport,]
+	[-version       => $version,]
+	[-domain        => $domain,]
+	[-timeout       => $seconds,]
+	[-retries       => $count,]
+	[-maxmsgsize    => $octets,]
+	[-debug         => $bitmask,]
+	[-community     => $community,]   # v1/v2c  
+	[-username      => $username,]    # v3
+	[-authkey	=> $authkey,]     # v3  
+	[-authpassword  => $authpasswd,]  # v3  
+	[-authprotocol  => $authproto,]   # v3  
+	[-privkey       => $privkey,]     # v3  
+	[-privpassword  => $privpasswd,]  # v3  
+	[-privprotocol  => $privproto,]   # v3
+	[-contextengineid => $engine_id,] # v3 
+	[-contextname     => $name,]      # v3
+
+Please see the documentation for Net::SNMP for a description of these
+parameters.
+
+=item SNMPv3 Arguments
+
+A SNMP context is a collection of management information accessible by a SNMP 
+entity.  An item of management information may exist in more than one context 
+and a SNMP entity potentially has access to many contexts.  The combination of 
+a contextEngineID and a contextName unambiguously identifies a context within 
+an administrative domain.  In a SNMPv3 message, the contextEngineID and 
+contextName are included as part of the scopedPDU.  All methods that generate 
+a SNMP message optionally take a B<-contextengineid> and B<-contextname> 
+argument to configure these fields.
+
+=over
+
+=item Context Engine ID
+
+The B<-contextengineid> argument expects a hexadecimal string representing
+the desired contextEngineID.  The string must be 10 to 64 characters (5 to 
+32 octets) long and can be prefixed with an optional "0x".  Once the 
+B<-contextengineid> is specified it stays with the object until it is changed 
+again or reset to default by passing in the undefined value.  By default, the 
+contextEngineID is set to match the authoritativeEngineID of the authoritative
+SNMP engine.
+
+=item Context Name
+
+The contextName is passed as a string which must be 0 to 32 octets in length 
+using the B<-contextname> argument.  The contextName stays with the object 
+until it is changed.  The contextName defaults to an empty string which 
+represents the "default" context.
+
+=back
+
+=back
+
+=cut
+
+# [public methods] ---------------------------------------------------
+
+=head1 Functions
+
+=head2 snmpget() - send a SNMP get-request to the remote agent
+
+    @result = snmpget(
+		[community@]host[:port[:timeout[:retries[:backoff[:version]]]]],
+		[\%param_hash],
+		@oids
+	    );
+
+This function performs a SNMP get-request query to gather data from the remote
+agent on the host specified.  The message is built using the list of OBJECT
+IDENTIFIERs passed as an array.  Each OBJECT IDENTIFIER is placed into a single
+SNMP GetRequest-PDU in the same order that it held in the original list.
+
+The requested values are returned in an array in the same order as they were
+requested.  In scalar context the first requested value is returned.
+
+=cut
+
+#
+# snmpget.
+#
+sub snmpget ($@) {
+  my($host, @vars) = @_;
+  my($session, @enoid, %args, $ret, $oid, @retvals);
+
+  @retvals = ();
+  $session = &snmpopen($host, 0, \@vars);
+  if (!defined($session)) {
+    carp "SNMPGET Problem for $host"
+      unless ($Net_SNMP_util::SuppressWarnings > 1);
+    return wantarray ? @retvals : undef;
+  }
+
+  @enoid = &toOID(@vars);
+  if ($#enoid < 0) {
+    return wantarray ? @retvals : undef;
+  }
+
+  $args{'-varbindlist'} = \@enoid;
+  if ($Net_SNMP_util::Version > 2) {
+    $args{'-contextengineid'} = $Net_SNMP_util::ContextEngineID
+      if (defined($Net_SNMP_util::ContextEngineID));
+    $args{'-contextname'} = $Net_SNMP_util::ContextName
+      if (defined($Net_SNMP_util::ContextName));
+  }
+
+  $ret = $session->get_request(%args);
+
+  if ($ret) {
+      foreach $oid (@enoid) {
+      push @retvals, $ret->{$oid} if (exists($ret->{$oid}));
+    }
+    return wantarray ? @retvals : $retvals[0];
+  }
+  $ret = join(' ', @vars);
+  error_msg("SNMPGET Problem for $ret on ${host}: " . $session->error());
+  return wantarray ? @retvals : undef;
+}
+
+=head2 snmpgetnext() - send a SNMP get-next-request to the remote agent
+
+    @result = snmpgetnext(
+		[community@]host[:port[:timeout[:retries[:backoff[:version]]]]],
+		[\%param_hash],
+		@oids
+	    );
+
+This function performs a SNMP get-next-request query to gather data from the
+remote agent on the host specified.  The message is built using the list of
+OBJECT IDENTIFIERs passed as an array.  Each OBJECT IDENTIFIER is placed into a
+single SNMP GetNextRequest-PDU in the same order that it held in the original
+list.
+
+The requested values are returned in an array in the same order as they were
+requested.  The OBJECT IDENTIFIER number is added as a prefix to each value
+using a colon as a separator, like '1.3.6.1.2.1.2.2.1.2.1:ethernet'.
+In scalar context the first requested value is returned.
+
+=cut
+
+#
+# snmpgetnext.
+#
+sub snmpgetnext ($@) {
+  my($host, @vars) = @_;
+  my($session, @enoid, %args, $ret, $oid, @retvals);
+
+  @retvals = ();
+  $session = &snmpopen($host, 0, \@vars);
+  if (!defined($session)) {
+    carp "SNMPGETNEXT Problem for $host"
+      unless ($Net_SNMP_util::SuppressWarnings > 1);
+    return wantarray ? @retvals : undef;
+  }
+
+  @enoid = &toOID(@vars);
+  if ($#enoid < 0) {
+    return wantarray ? @retvals : undef;
+  }
+
+  $args{'-varbindlist'} = \@enoid;
+  if ($Net_SNMP_util::Version > 2) {
+    $args{'-contextengineid'} = $Net_SNMP_util::ContextEngineID
+      if (defined($Net_SNMP_util::ContextEngineID));
+    $args{'-contextname'} = $Net_SNMP_util::ContextName
+      if (defined($Net_SNMP_util::ContextName));
+  }
+
+  $ret = $session->get_next_request(%args);
+
+  if ($ret) {
+    foreach $oid (@enoid) {
+      push @retvals, $oid . ':' . $ret->{$oid} if (exists($ret->{$oid}));
+    }
+    return wantarray ? @retvals : $retvals[0];
+  }
+  $ret = join(' ', @vars);
+  error_msg("SNMPGETNEXT Problem for $ret on ${host}: " . $session->error());
+  return wantarray ? @retvals : undef;
+}
+
+=head2 snmpgetbulk() - send a SNMP get-bulk-request to the remote agent
+
+    @result = snmpgetbulk(
+		[community@]host[:port[:timeout[:retries[:backoff[:version]]]]],
+		$nonrepeaters,
+		$maxrepetitions,
+		[\%param_hash],
+		@oids
+	    );
+
+This function performs a SNMP get-bulk-request query to gather data from the
+remote agent on the host specified.
+
+=over
+
+=item *
+
+The B<$nonrepeaters> value specifies the number of variables in the @oids list
+for which a single successor is to be returned.  If it is null or undefined,
+a value of 0 is used.
+
+=item *
+
+The B<$maxrepetitions> value specifies the number of successors to be returned
+for the remaining variables in the @oids list.  If it is null or undefined,
+the default value of 12 is used.
+
+=item *
+
+The message is built using the list of
+OBJECT IDENTIFIERs passed as an array.  Each OBJECT IDENTIFIER is placed into a
+single SNMP GetNextRequest-PDU in the same order that it held in the original
+list.
+
+=back
+
+The requested values are returned in an array in the same order as they were
+requested.
+
+B<NOTE:> This function can only be used when the SNMP version is set to
+SNMPv2c or SNMPv3.
+
+=cut
+
+#
+# snmpgetbulk.
+#
+sub snmpgetbulk ($$$@) {
+  my($host, $nr, $mr, @vars) = @_;
+  my($session, %args, @enoid, $ret);
+  my($oid, @retvals);
+
+  @retvals = ();
+  $session = &snmpopen($host, 0, \@vars);
+  if (!defined($session)) {
+    carp "SNMPGETBULK Problem for $host"
+      unless ($Net_SNMP_util::SuppressWarnings > 1);
+    return @retvals;
+  }
+
+  if ($Net_SNMP_util::Version < 2) {
+    carp "SNMPGETBULK Problem for $host : must use SNMP version > 1"
+      unless ($Net_SNMP_util::SuppressWarnings > 1);
+    return @retvals;
+  }
+
+  $args{'-nonrepeaters'} = $nr if ($nr > 0);
+  $mr = $Net_SNMP_util::MaxRepetitions if ($mr <= 0);
+  $args{'-maxrepetitions'} = $mr;
+
+  if ($Net_SNMP_util::Version > 2) {
+    $args{'-contextengineid'} = $Net_SNMP_util::ContextEngineID
+      if (defined($Net_SNMP_util::ContextEngineID));
+    $args{'-contextname'} = $Net_SNMP_util::ContextName
+      if (defined($Net_SNMP_util::ContextName));
+  }
+
+  @enoid = &toOID(@vars);
+  return @retvals if ($#enoid < 0);
+
+  $args{'-varbindlist'} = \@enoid;
+  $ret = $session->get_bulk_request(%args);
+
+  if ($ret) {
+    @enoid = &Net::SNMP::oid_lex_sort(keys %$ret);
+    foreach $oid (@enoid) {
+      push @retvals, $oid . ":" . $ret->{$oid};
+    }
+    return @retvals;
+  } else {
+    $ret = join(' ', @vars);
+    error_msg("SNMPGETBULK Problem for $ret on ${host}: " . $session->error());
+    return @retvals;
+  }
+}
+
+
+=head2 snmpwalk() - walk OBJECT IDENTIFIER tree(s) on the remote agent
+
+    @result = snmpwalk(
+		[community@]host[:port[:timeout[:retries[:backoff[:version]]]]],
+		[\%param_hash],
+		@oids
+	    );
+
+This function performs a sequence of SNMP get-next-request or get-bulk-request
+(if the SNMP version is 2 or higher) queries to gather data from the remote
+agent on the host specified.  The initial message is built using the list of
+OBJECT IDENTIFIERs passed as an array.  Each OBJECT IDENTIFIER is placed into a
+single SNMP GetNextRequest-PDU in the same order that it held in the original
+list.  Queries continue until all the returned OBJECT IDENTIFIERs are no longer
+a child of the base OBJECT IDENTIFIERs.
+
+The requested values are returned in an array in the same order as they were
+requested.  The OBJECT IDENTIFIER number is added as a prefix to each value
+using a colon as a separator, like '1.3.6.1.2.1.2.2.1.2.1:ethernet'.  If only
+one OBJECT IDENTIFIER is requested, just the "instance" part of the OBJECT
+IDENTIFIER is added as a prefix, like '1:ethernet', '2:ethernet', '3:fddi'.
+
+=cut
+
+#
+# snmpwalk.
+#
+sub snmpwalk ($@) {
+  my($host, @vars) = @_;
+  return(&snmpwalk_flg($host, undef, @vars));
+}
+
+=head2 snmpset() - send a SNMP set-request to the remote agent
+
+    @result = snmpset(
+		[community@]host[:port[:timeout[:retries[:backoff[:version]]]]],
+		[\%param_hash],
+		$oid1, $type1, $value1,
+		[$oid2, $type2, $value2 ...]
+	    );
+
+This function is used to modify data on the remote agent using a SNMP
+set-request.  The message is built using the list of values consisting of groups
+of an OBJECT IDENTIFIER, an object type, and the actual value to be set.
+The object type can be one of the following strings:
+
+    integer | int
+    string | octetstring | octet string
+    oid | object id | object identifier
+    ipaddr | ip addr4ess
+    timeticks
+    uint | uinteger | uinteger32 | unsigned int | unsigned integer | unsigned integer32
+    counter | counter 32
+    counter64
+    gauge | gauge32
+
+The object type may also be an octet corresponding to the ASN.1 type.  See
+the Net::SNMP documentation for more information.
+
+The requested values are returned in an array in the same order as they were
+requested.  In scalar context the first requested value is returned.
+
+=cut
+
+#
+# snmpset.
+#
+sub snmpset($@) {
+  my($host, @vars) = @_;
+  my($session, @vals, %args, $ret);
+  my($oid, $type, $value, @enoid, @retvals);
+
+  @retvals = ();
+  $session = &snmpopen($host, 0, \@vars);
+  if (!defined($session)) {
+    carp "SNMPSET Problem for $host"
+      unless ($Net_SNMP_util::SuppressWarnings > 1);
+    return wantarray ? @retvals : undef;
+  }
+
+  if ($Net_SNMP_util::Version > 2) {
+    $args{'-contextengineid'} = $Net_SNMP_util::ContextEngineID
+      if (defined($Net_SNMP_util::ContextEngineID));
+    $args{'-contextname'} = $Net_SNMP_util::ContextName
+      if (defined($Net_SNMP_util::ContextName));
+  }
+
+  while(@vars) {
+    ($oid) = toOID((shift @vars));
+    $ret   = shift @vars;
+    $value = shift @vars;
+    $type  = ASNtype($ret);
+    if (!defined($type)) {
+      carp "Unknown SNMP type: $type\n"
+	unless ($Net_SNMP_util::SuppressWarnings > 1);
+    }
+    push @vals, $oid, $type, $value;
+    push @enoid, $oid;
+  }
+  if ($#vals < 0) {
+    return wantarray ? @retvals : undef;
+  }
+
+  $args{'-varbindlist'} = \@vals;
+
+  $ret = $session->set_request(%args);
+  if ($ret) {
+      foreach $oid (@enoid) {
+	push @retvals, $ret->{$oid} if (exists($ret->{$oid}));
+    }
+    return wantarray ? @retvals : $retvals[0];
+  }
+  $ret = join(' ', @enoid);
+  error_msg("SNMPSET Problem for $ret on ${host}: " . $session->error());
+  return wantarray ? @retvals : undef;
+}
+
+=head2 snmptrap() - send a SNMP trap to the remote manager
+
+    @result = snmptrap(
+		[community@]host[:port[:timeout[:retries[:backoff[:version]]]]],
+		$enterprise,
+		$agentaddr,
+		$generictrap,
+		$specifictrap,
+		[\%param_hash],
+		$oid1, $type1, $value1, 
+		[$oid2, $type2, $value2 ...]
+	    );
+
+This function sends a SNMP trap to the remote manager on the host specified.
+The message is built using the list of values consisting of groups of an
+OBJECT IDENTIFIER, an object type, and the actual value to be set.
+The object type can be one of the following strings:
+
+    integer | int
+    string | octetstring | octet string
+    oid | object id | object identifier
+    ipaddr | ip addr4ess
+    timeticks
+    uint | uinteger | uinteger32 | unsigned int | unsigned integer | unsigned integer32
+    counter | counter 32
+    counter64
+    gauge | gauge32
+
+The object type may also be an octet corresponding to the ASN.1 type.  See
+the Net::SNMP documentation for more information.
+
+A true value is returned if sending the trap is successful.  The undefined value
+is returned when a failure has occurred.
+
+When the trap is sent as SNMPv2c, the B<$enterprise>, B<$agentaddr>,
+B<$generictrap>, and B<$specifictrap> arguments are ignored.  Furthermore,
+the first two (oid, type, value) tuples should be:
+
+=over
+
+=item *
+
+sysUpTime.0 - ('1.3.6.1.2.1.1.3.0', 'timeticks', $timeticks)
+
+=item *
+
+snmpTrapOID.0 - ('1.3.6.1.6.3.1.1.4.1.0', 'oid', $oid)
+
+=back
+
+B<NOTE:> This function can only be used when the SNMP version is set to
+SNMPv1 or SNMPv2c.
+
+=cut
+
+#
+# Send an SNMP trap
+#
+sub snmptrap($$$$$@) {
+  my($host, $ent, $agent, $gen, $spec, @vars) = @_;
+  my($oid, $type, $value, $ret, @enoid, @vals);
+  my($session, %args);
+
+  $session = &snmpopen($host, 1, \@vars);
+  if (!defined($session)) {
+    carp "SNMPTRAP Problem for $host"
+      unless ($Net_SNMP_util::SuppressWarnings > 1);
+    return undef;
+  }
+
+  if ($Net_SNMP_util::Version == 1) {
+    $args{'-enterprise'} = $ent if (defined($ent) and (length($ent) > 0));
+    $args{'-agentaddr'} = $agent if (defined($agent) and (length($agent) > 0));
+    $args{'-generictrap'} = $gen if (defined($gen) and (length($gen) > 0));
+    $args{'-specifictrap'} = $spec if (defined($spec) and (length($spec) > 0));
+  } elsif ($Net_SNMP_util::Version > 2) {
+    carp "SNMPTRAP Problem for $host : must use SNMP version 1 or 2"
+      unless ($Net_SNMP_util::SuppressWarnings > 1);
+  }
+
+  while(@vars) {
+    ($oid) = toOID((shift @vars));
+    $ret   = shift @vars;
+    $value = shift @vars;
+    $type  = ASNtype($ret);
+    if (!defined($type)) {
+      carp "unknown SNMP type: $type"
+	unless ($Net_SNMP_util::SuppressWarnings > 1);
+    }
+    push @vals, $oid, $type, $value;
+    push @enoid, $oid;
+  }
+  return undef unless defined $vals[0];
+
+  $args{'-varbindlist'} = \@vals;
+
+  if ($Net_SNMP_util::Version == 1) {
+    $ret = $session->trap_request(%args);
+  } else {
+    $ret = $session->snmpv2_trap(%args);
+  }
+
+  if (!$ret) {
+    $ret = join(' ', @enoid);
+    error_msg("SNMPTRAP Problem for $ret on ${host}: " . $session->error());
+  }
+  return $ret;
+}
+
+=head2 snmpmaptable() - walk OBJECT IDENTIFIER tree(s) on the remote agent
+
+    $result = snmpmaptable(
+		[community@]host[:port[:timeout[:retries[:backoff[:version]]]]],
+		\&function,
+		[\%param_hash],
+		@oids
+	    );
+
+This function performs a sequence of SNMP get-next-request or get-bulk-request
+(if the SNMP version is 2 or higher) queries to gather data from the remote
+agent on the host specified.  The initial message is built using the list of
+OBJECT IDENTIFIERs passed as an array.  Each OBJECT IDENTIFIER is placed into a
+single SNMP GetNextRequest-PDU in the same order that it held in the original
+list.  Queries continue until all the returned OBJECT IDENTIFIERs are no longer
+a child of the base OBJECT IDENTIFIERs.  The OBJECT IDENTIFIERs must correspond
+to column entries for a conceptual row in a table.  They may however be columns
+in different tables as long as each table is indexed the same way.
+
+=over
+
+=item *
+
+The B<\&function> argument will be called once per row of the table.  It
+will be passed the row index as a partial OBJECT IDENTIFIER in dotted notation,
+e.g. "1.3" or "10.0.1.34", and the values of the requested table columns in
+that row.
+
+=back
+
+The number of rows in the table is returned on success.  The undefined value
+is returned when a failure has occurred.
+
+=cut
+
+#
+# walk a table, calling a user-supplied function for each
+# column of a table.
+#
+sub snmpmaptable($$@) {
+  my($host, $fun, @vars) = @_;
+  return snmpmaptable4($host, $fun, 0, @vars);
+}
+
+=head2 snmpmaptable4() - walk OBJECT IDENTIFIER tree(s) on the remote agent
+
+    $result = snmpmaptable4(
+		[community@]host[:port[:timeout[:retries[:backoff[:version]]]]],
+		\&function,
+		$maxrepetitions,
+		[\%param_hash],
+		@oids
+	    );
+
+This function performs a sequence of SNMP get-next-request or get-bulk-request
+(if the SNMP version is 2 or higher) queries to gather data from the remote
+agent on the host specified.  The initial message is built using the list of
+OBJECT IDENTIFIERs passed as an array.  Each OBJECT IDENTIFIER is placed into a
+single SNMP GetNextRequest-PDU in the same order that it held in the original
+list.  Queries continue until all the returned OBJECT IDENTIFIERs are no longer
+a child of the base OBJECT IDENTIFIERs.  The OBJECT IDENTIFIERs must correspond
+to column entries for a conceptual row in a table.  They may however be columns
+in different tables as long as each table is indexed the same way.
+
+=over
+
+=item *
+
+The B<\&function> argument will be called once per row of the table.  It
+will be passed the row index as a partial OBJECT IDENTIFIER in dotted notation,
+e.g. "1.3" or "10.0.1.34", and the values of the requested table columns in
+that row.
+
+=item *
+
+The B<$maxrepetitions> argument specifies the number of rows to be returned
+by a single get-bulk-request.  If it is null or undefined, the default value
+of 12 is used.
+
+=back
+
+The number of rows in the table is returned on success.  The undefined value
+is returned when a failure has occurred.
+
+=cut
+
+sub snmpmaptable4($$$@) {
+  my($host, $fun, $max_reps, @vars) = @_;
+  my($session, @enoid, %args, $ret);
+  my($oid, $soid, $toid, $inst, @row, $nr);
+
+  $session = &snmpopen($host, 0, \@vars);
+  if (!defined($session)) {
+    carp "SNMPMAPTABLE Problem for $host"
+      unless ($Net_SNMP_util::SuppressWarnings > 1);
+    return undef;
+  }
+
+  @enoid = toOID(@vars);
+  return undef unless defined $enoid[0];
+
+  if ($Net_SNMP_util::Version > 1) {
+    $max_reps = $Net_SNMP_util::MaxRepetitions if ($max_reps <= 0);
+    $args{'-maxrepetitions'} = $max_reps;
+  }
+  if ($Net_SNMP_util::Version > 2) {
+    $args{'-contextengineid'} = $Net_SNMP_util::ContextEngineID
+      if (defined($Net_SNMP_util::ContextEngineID));
+    $args{'-contextname'} = $Net_SNMP_util::ContextName
+      if (defined($Net_SNMP_util::ContextName));
+  }
+
+  $args{'-columns'} = \@enoid;
+
+  $ret = $session->get_entries(%args);
+
+  if ($ret) {
+    $soid = $enoid[0];
+    $nr = 0;
+    foreach $oid (&Net::SNMP::oid_lex_sort(keys %$ret)) {
+      if (&Net::SNMP::oid_base_match($soid, $oid)) {
+	$inst = substr($oid, length($soid)+1);
+	undef @row;
+	foreach $toid (@enoid) {
+	  push @row, $ret->{$toid . "." . $inst};
+	}
+	&$fun($inst, @row);
+	$nr++;
+      } else {
+	return($nr) if ($nr > 0);
+      }
+    }
+    return($nr);
+  } else {
+    $ret = join(' ', @vars);
+    error_msg("SNMPMAPTABLE Problem for $ret on ${host}: " . $session->error());
+    return undef;
+  }
+}
+
+=head2 snmpwalkhash() - send a SNMP get-next-request to the remote agent
+
+    @result = snmpwalkhash(
+		[community@]host[:port[:timeout[:retries[:backoff[:version]]]]],
+		\&function(),
+		[\%param_hash],
+		@oids,
+		[\%hash]
+	    );
+
+This function performs a sequence of SNMP get-next-request or get-bulk-request
+(if the SNMP version is 2 or higher) queries to gather data from the remote
+agent on the host specified.  The message is built using the list of
+OBJECT IDENTIFIERs passed as an array.  Each OBJECT IDENTIFIER is placed into a
+single SNMP GetNextRequest-PDU in the same order that it held in the original
+list.  Queries continue until all the returned OBJECT IDENTIFIERs are outside
+of the tree specified by the initial OBJECT IDENTIFIERs.
+
+The B<\&function> is called once for every returned value.  It is passed a
+reference to a hash, the hostname, the textual OBJECT IDENTIFIER, the
+dotted-numberic OBJECT IDENTIFIER, the instance, the value and the requested
+textual OBJECT IDENTIFIER.  That function can customize the result so the
+values can be extracted later by hosts, by oid_names, by oid_numbers,
+by instances... like these:
+
+    $hash{$host}{$name}{$inst} = $value;
+    $hash{$host}{$oid}{$inst} = $value;
+    $hash{$name}{$inst} = $value;
+    $hash{$oid}{$inst} = $value;
+    $hash{$oid . '.' . $ints} = $value;
+    $hash{$inst} = $value;
+    ...
+
+If the last argument to B<snmpwalkhash> is a reference to a hash, that hash
+reference is passed to the passed-in function instead of a local hash
+reference.  That way the function can look up other objects unrelated
+to the current invocation of B<snmpwalkhash>.
+
+The snmpwalkhash routine returns the hash.
+
+=cut
+
+#
+# Walk the MIB, putting everything you find into hashes.
+#
+sub snmpwalkhash($$@) {
+#  my($host, $hash_sub, @vars) = @_;
+  return(&snmpwalk_flg( @_ ));
+}
+
+
+=head2 snmpmapOID() - add texual OBJECT INDENTIFIER mapping
+
+    snmpmapOID(
+	$text1, $oid1,
+	[ $text2, $oid2 ...]
+    );
+
+This routine adds entries to the table that maps textual representation of
+OBJECT IDENTIFIERs to their dotted notation.  For example, 
+
+    snmpmapOID('ciscoCPU', '1.3.6.1.4.1.9.9.109.1.1.1.1.5.1');
+
+allows the string 'ciscoCPU' to be used as an OBJECT IDENTIFIER in any SNMP
+query routine.
+
+This routine doesn't return anything.
+
+=cut
+
+#
+#  Add passed-in text, OID pairs to the OID mapping table.
+#
+sub snmpmapOID(@)
+{
+  my(@vars) = @_;
+  my($oid, $txt);
+
+  $Net_SNMP_util::ErrorMessage = '';
+  while($#vars >= 0) {
+    $txt = shift @vars;
+    $oid = shift @vars;
+
+    next unless($txt =~ /^[a-zA-Z][\w\-]*(\.[a-zA-Z][\w\-])*$/);
+    next unless($oid =~ /^\d+(\.\d+)*$/);
+
+    $Net_SNMP_util::OIDS{$txt} = $oid;
+    $RevNeeded = 1;
+    print "snmpmapOID: $txt => $oid\n" if $Net_SNMP_util::Debug;
+  }
+
+  return undef;
+}
+
+=head2 snmpLoad_OID_Cache() - Read a file of cached OID mappings
+
+    $result = snmpLoad_OID_Cache(
+		$file
+    );
+
+This routine opens the file named by the B<$file> argument and reads it.
+The file should contain text, OBJECT IDENTIFIER pairs, one pair
+per line.  It adds the pairs as entries to the table that maps textual
+representation of OBJECT IDENTIFIERs to their dotted notation.
+Blank lines and anything after a '#' or between '--' is ignored.
+
+This routine returns 0 on success and -1 if the B<$file> could not be opened.
+
+=cut
+
+#
+# Open the passed-in file name and read it in to populate
+# the cache of text-to-OID map table.  It expects lines
+# with two fields, the first the textual string like "ifInOctets",
+# and the second the OID value, like "1.3.6.1.2.1.2.2.1.10".
+#
+# blank lines and anything after a '#' or between '--' is ignored.
+#
+sub snmpLoad_OID_Cache ($) {
+  my($arg) = @_;
+  my($txt, $oid);
+
+  $Net_SNMP_util::ErrorMessage = '';
+  if (!open(CACHE, $arg)) {
+    error_msg("snmpLoad_OID_Cache: Can't open ${arg}: $!");
+    return -1;
+  }
+
+  while(<CACHE>) {
+    s/#.*//;				# '#' starts a comment
+    s/--.*?--/ /g;			# comment delimited by '--', like MIBs
+    s/--.*//;				# comment started by '--'
+    next if (/^$/);
+    next unless (/\s/);			# must have whitespace as separator
+    chomp;
+    ($txt, $oid) = split(' ', $_, 2);
+    $txt = $1 if ($txt =~ /^[\'\"](.*)[\'\"]/);
+    $oid = $1 if ($oid =~ /^[\'\"](.*)[\'\"]/);
+    if (($txt =~ /^\.?\d+(\.\d+)*\.?$/)
+    and  ($oid !~ /^\.?\d+(\.\d+)*\.?$/)) {
+	my($a) = $oid;
+	$oid = $txt;
+	$txt = $a;
+    }
+    $oid =~ s/^\.//;
+    $oid =~ s/\.$//;
+    &snmpmapOID($txt, $oid);
+  }
+  close(CACHE);
+  return 0;
+}
+
+=head2 snmpMIB_to_OID() - Read a MIB file for textual OID mappings
+
+    $result = snmpMIB_to_OID(
+		$file
+    );
+
+This routine opens the file named by the B<$file> argument and reads it.
+The file should be an SNMP Management Information Base (MIB) file
+that describes OBJECT IDENTIFIERs supported by an SNMP agent.
+per line.  It adds the textual representation of the OBJECT IDENTIFIERs
+to the text-to-OID mapping table.
+
+This routine returns the number of entries added to the table or -1 if
+the B<$file> could not be opened.
+
+=cut
+
+#
+# Read in the passed MIB file, parsing it
+# for their text-to-OID mappings
+#
+sub snmpMIB_to_OID ($) {
+  my($arg) = @_;
+  my($cnt, $quote, $buf, %tOIDs, $tgot);
+  my($var, @parts, $strt, $indx, $ind, $val);
+
+  $Net_SNMP_util::ErrorMessage = '';
+  if (!open(MIB, $arg)) {
+    error_msg("snmpMIB_to_OID: Can't open ${arg}: $!");
+    return -1;
+  }
+  print "snmpMIB_to_OID: loading $arg\n" if $Net_SNMP_util::Debug;
+  $cnt = 0;
+  $quote = 0;
+  $tgot = 0;
+  $buf = '';
+  while(<MIB>) {
+    if ($quote) {
+      next unless /"/;
+      $quote = 0;
+    }
+    chomp;
+    $buf .= ' ' . $_;
+
+    $buf =~ s/"[^"]*"//g;	# throw away quoted strings
+    $buf =~ s/--.*?--/ /g;	# throw away comments (-- anything --)
+    $buf =~ s/--.*//;		# throw away comments (-- anything to EOL)
+    $buf =~ s/\s+/ /g;		# clean up multiple spaces
+
+    if ($buf =~ /"/) {
+      $quote = 1;
+      next;
+    }
+
+    if ($buf =~ /DEFINITIONS *::= *BEGIN/) {
+	$cnt += MIB_fill_OID(\%tOIDs) if ($tgot);
+	$buf = '';
+	%tOIDs = ();
+	$tgot = 0;
+	next;
+    }
+    $buf =~ s/OBJECT-TYPE/OBJECT IDENTIFIER/;
+    $buf =~ s/OBJECT-IDENTITY/OBJECT IDENTIFIER/;
+    $buf =~ s/OBJECT-GROUP/OBJECT IDENTIFIER/;
+    $buf =~ s/MODULE-IDENTITY/OBJECT IDENTIFIER/;
+    $buf =~ s/NOTIFICATION-TYPE/OBJECT IDENTIFIER/;
+    $buf =~ s/ IMPORTS .*\;//;
+    $buf =~ s/ SEQUENCE *{.*}//;
+    $buf =~ s/ SYNTAX .*//;
+    $buf =~ s/ [\w\-]+ *::= *OBJECT IDENTIFIER//;
+    $buf =~ s/ OBJECT IDENTIFIER.*::= *{/ OBJECT IDENTIFIER ::= {/;
+
+    if ($buf =~ / ([\w\-]+) OBJECT IDENTIFIER *::= *{([^}]+)}/) {
+      $var = $1;
+      $buf = $2;
+      $buf =~ s/ +$//;
+      $buf =~ s/\s+\(/\(/g;	# remove spacing around '('
+      $buf =~ s/\(\s+/\(/g;
+      $buf =~ s/\s+\)/\)/g;	# remove spacing before ')'
+      @parts = split(' ', $buf);
+      $strt = '';
+      foreach $indx (@parts) {
+	if ($indx =~ /([\w\-]+)\((\d+)\)/) {
+	  $ind = $1;
+	  $val = $2;
+	  if (exists($tOIDs{$strt})) {
+	    $tOIDs{$ind} = $tOIDs{$strt} . '.' . $val;
+	  } elsif ($strt ne '') {
+	    $tOIDs{$ind} = "${strt}.${val}";
+	  } else {
+	    $tOIDs{$ind} = $val;
+	  }
+	  $strt = $ind;
+	  $tgot = 1;
+	} elsif ($indx =~ /^\d+$/) {
+	  if (exists($tOIDs{$strt})) {
+	    $tOIDs{$var} = $tOIDs{$strt} . '.' . $indx;
+	  } else {
+	    $tOIDs{$var} = "${strt}.${indx}";
+	  }
+	  $tgot = 1;
+	} else {
+	  $strt = $indx;
+	}
+      }
+      $buf = '';
+    }
+  }
+  $cnt += MIB_fill_OID(\%tOIDs) if ($tgot);
+  $RevNeeded = 1 if ($cnt > 0);
+  return $cnt;
+}
+
+=head2 snmpQueue_MIB_File() - queue a MIB file for reading "on demand"
+
+    snmpQueue_MIB_File(
+	$file1,
+	[$file2, ...]
+    );
+
+This routine queues the list of SNMP MIB files for later processing.
+Whenever a text-to-OBJECT IDENTIFIER lookup fails, the list of queued MIB
+files is consulted.  If it isn't empty, the first MIB file in the list is
+removed and passed to B<snmpMIB_to_OID()>.  The lookup is attempted again,
+and if that still fails the next MIB file in the list is removed and passed
+to B<snmpMIB_to_OID()>. This process continues until the lookup succeeds
+or the list is exhausted.
+
+This routine doesn't return anything.
+
+=cut
+
+#
+# Save the passed-in list of MIB files until an OID can't be
+# found in the existing table.  At that time the MIB file will
+# be loaded, and the lookup attempted again.
+#
+sub snmpQueue_MIB_File (@) {
+  my(@files) = @_;
+  my($file);
+
+  $Net_SNMP_util::ErrorMessage = '';
+  foreach $file (@files) {
+    push(@Net_SNMP_util::MIB_Files, $file);
+  }
+}
+
+# [private methods] -------------------------------------
+
+#
+# Start an snmp session
+#
+sub snmpopen ($$$) {
+  my($host, $type, $vars) = @_;
+  my($nhost, $port, $community, $lhost, $lport, $nlhost);
+  my($timeout, $retries, $backoff, $version, $v4onlystr);
+  my($opts, %args, $tmp, $sess);
+  my($debug, $maxmsgsize);
+
+  $type = 0 if (!defined($type));
+  $community = "public";
+  $nlhost = "";
+
+  ($community, $host) = ($1, $2) if ($host =~ /^(.*)@([^@]+)$/);
+
+  # We can't split on the : character because a numeric IPv6
+  # address contains a variable number of :'s
+  if( ($host =~ /^(\[.*\]):(.*)$/) or ($host =~ /^(\[.*\])$/) ) {
+    # Numeric IPv6 address between []
+    ($host, $opts) = ($1, $2);
+  } else {
+    # Hostname or numeric IPv4 address
+    ($host, $opts) = split(':', $host, 2);
+  }
+  ($port, $timeout, $retries, $backoff, $version, $v4onlystr)
+    = split(':', $opts, 6) if(defined($opts) and (length $opts > 0) );
+
+  undef($timeout) if (defined($timeout) and length($timeout) <= 0);
+  undef($retries) if (defined($retries) and length($retries) <= 0);
+  undef($backoff) if (defined($backoff) and length($backoff) <= 0);
+  undef($version) if (defined($version) and length($version) <= 0);
+
+  $v4onlystr = "" unless defined $v4onlystr;
+
+  if (defined($port) and ($port =~ /^([^!]*)!(.*)$/)) {
+    ($port, $lhost) = ($1, $2);
+    $nlhost = $lhost;
+    ($lhost, $lport) = ($1, $2) if ($lhost =~ /^(.*)!(.*)$/);
+    undef($lport) if (defined($lport) and (length($lport) <= 0));
+  }
+  undef($port) if (defined($port) and length($port) <= 0);
+
+  if (ref $vars->[0] eq 'HASH') {
+    undef($debug);
+    undef($maxmsgsize);
+    undef $Net_SNMP_util::ContextEngineID;
+    undef $Net_SNMP_util::ContextName;
+    $opts = shift @$vars;
+    foreach $type (keys %$opts) {
+      if ($type =~ /^-?return_array_refs$/i) {
+	$Net_SNMP_util::ReturnArrayRefs = $opts->{$type};
+      } elsif ($type =~ /^-?return_hash_refs$/i) {
+	$Net_SNMP_util::ReturnHashRefs = $opts->{$type};
+      } elsif ($type =~ /^-?contextengineid$/i) {
+	$Net_SNMP_util::ContextEngineID = $opts->{$type};
+      } elsif ($type =~ /^-?contextname$/i) {
+	$Net_SNMP_util::ContextName = $opts->{$type};
+      } elsif ($type =~ /^-?maxrepetitions$/i) {
+	$Net_SNMP_util::MaxRepetitions = $opts->{$type};
+      } elsif ($type =~ /^-?default_max_repetitions$/i) {
+	$Net_SNMP_util::MaxRepetitions = $opts->{$type};
+      } elsif ($type =~ /^-?version$/i) {
+	$version = $opts->{$type};
+      } elsif ($type =~ /^-?port$/i) {
+	$port = $opts->{$type};
+      } elsif ($type =~ /^-?localaddr$/i) {
+	$lhost = $opts->{$type};
+      } elsif ($type =~ /^-?community$/i) {
+	$community = $opts->{$type};
+      } elsif ($type =~ /^-?timeout$/i) {
+	$timeout = $opts->{$type};
+      } elsif ($type =~ /^-?retries$/i) {
+	$retries = $opts->{$type};
+      } elsif ($type =~ /^-?maxmsgsize$/i) {
+	$maxmsgsize = $opts->{$type};
+      } elsif ($type =~ /^-?debug$/i) {
+	$debug = $opts->{$type};
+      } elsif ($type =~ /^-?backoff$/i) {
+	next;		# XXXX not implemented in Net::SNMP
+      } elsif ($type =~ /^-?avoid_negative_request_ids$/i) {
+	next;		# XXXX not implemented in Net::SNMP
+      } elsif ($type =~ /^-?lenient_source_/i) {
+	next;		# XXXX not implemented in Net::SNMP
+      } elsif ($type =~ /^-?use_16bit_request_ids$/i) {
+	next;		# XXXX not implemented in Net::SNMP
+      } elsif ($type =~ /^-?use_getbulk$/i) {
+	next;		# XXXX not implemented in Net::SNMP
+      } else {
+	$tmp = $type;
+	$tmp = '-' . $tmp unless ($tmp =~ /^-/);
+	$args{$tmp} = $opts->{$type};
+      }
+    }
+  }
+
+  $port = 162 if ($type == 1 and !defined($port));
+  $nhost = "$community\@$host";
+  $nhost .= ":" . $port if (defined($port));
+  undef($lhost) if (defined($lhost) and (length($lhost) <= 0));
+
+  $version = '1' unless defined $version;
+  if ($version =~ /1/) {
+    $version = 1;
+  } elsif ($version =~ /2/) {
+    $version = 2;
+  } elsif ($version =~ /3/) {
+    $version = 3;
+  }
+  $Net_SNMP_util::ErrorMessage = '';
+  if ((!defined($Net_SNMP_util::Session))
+    or ($Net_SNMP_util::Host ne $nhost)
+    or ($Net_SNMP_util::Version ne $version)
+    or ($Net_SNMP_util::LHost ne $nlhost)
+    or ($Net_SNMP_util::IPv4only ne $v4onlystr)) {
+    if (defined($Net_SNMP_util::Session)) {
+      $Net_SNMP_util::Session->close();    
+      undef $Net_SNMP_util::Session;
+      undef $Net_SNMP_util::Host;
+      undef $Net_SNMP_util::Version;
+      undef $Net_SNMP_util::LHost;
+      undef $Net_SNMP_util::IPv4only;
+    }
+
+    $args{'-hostname'} = $host;
+    $args{'-port'} = $port if (defined($port));
+    $args{'-localaddr'} = $lhost if (defined($lhost));
+    $args{'-localport'} = $lport if (defined($lport));
+    $args{'-version'} = $version;
+    $args{'-domain'} = "udp/ipv4" if (length($v4onlystr) > 0);
+    $args{'-timeout'} = $timeout if (defined($timeout));
+    $args{'-retries'} = $retries if (defined($retries));
+    $args{'-maxmsgsize'} = $maxmsgsize if (defined($maxmsgsize));
+    $args{'-debug'} = $debug if (defined($debug));
+    $args{'-community'} = $community unless ($community eq "public");
+    if ($version == 3) {
+	delete $args{'-community'}
+    } else {
+	delete $args{'-username'};
+	delete $args{'-authkey'};
+	delete $args{'-authpassword'};
+	delete $args{'-authprotocol'};
+	delete $args{'-privkey'};
+	delete $args{'-privpassword'};
+	delete $args{'-privprotocol'};
+    }
+
+    ($sess, $tmp) = Net::SNMP->session(%args);
+
+    if (defined($sess)) {
+      $Net_SNMP_util::Session = $sess;
+      $Net_SNMP_util::Host = $nhost;
+      $Net_SNMP_util::Version = $version;
+      $Net_SNMP_util::LHost = $nlhost;
+      $Net_SNMP_util::IPv4only = $v4onlystr;
+    } else {
+      error_msg("SNMPopen failed: $tmp\n");
+      return(undef);
+    }
+    return $Net_SNMP_util::Session;
+  } else {
+    $Net_SNMP_util::Session->timeout($timeout)
+      if (defined($timeout) and (length($timeout) > 0));
+    $Net_SNMP_util::Session->retries($retries)
+      if (defined($retries) and (length($retries) > 0));
+    $Net_SNMP_util::Session->maxmsgsize($maxmsgsize)
+      if (defined($maxmsgsize) and (length($maxmsgsize) > 0));
+    $Net_SNMP_util::Session->debug($debug)
+      if (defined($debug) and (length($debug) > 0));
+    $Net_SNMP_util::Session->{_context_engine_id} = undef
+      if (!defined($Net_SNMP_util::ContextEngineID));
+    $Net_SNMP_util::Session->{_context_name} = undef
+      if (!defined($Net_SNMP_util::ContextName));
+  }
+  return $Net_SNMP_util::Session;
+}
+
+#
+#  Given an OID in either ASN.1 or mixed text/ASN.1 notation, return an OID.
+#
+sub toOID(@) {
+  my(@vars) = @_;
+  my($oid, $var, $tmp, $tmpv, @retvar);
+
+  @retvar = ();
+  foreach $var (@vars) {
+    ($oid, $tmp) = &Check_OID($var);
+    if (!$oid and $Net_SNMP_util::CacheLoaded == 0) {
+      $tmp = $Net_SNMP_util::SuppressWarnings;
+      $Net_SNMP_util::SuppressWarnings = 1000;
+
+      &snmpLoad_OID_Cache($Net_SNMP_util::CacheFile);
+
+      $Net_SNMP_util::CacheLoaded = 1;
+      $Net_SNMP_util::SuppressWarnings = $tmp;
+
+      ($oid, $tmp) = &Check_OID($var);
+    }
+    while (!$oid and $#Net_SNMP_util::MIB_Files >= 0) {
+      $tmp = $Net_SNMP_util::SuppressWarnings;
+      $Net_SNMP_util::SuppressWarnings = 1000;
+
+      snmpMIB_to_OID(shift(@Net_SNMP_util::MIB_Files));
+
+      $Net_SNMP_util::SuppressWarnings = $tmp;
+
+      ($oid, $tmp) = &Check_OID($var);
+      if ($oid) {
+	open(CACHE, ">>$Net_SNMP_util::CacheFile");
+	print CACHE "$tmp\t$oid\n";
+	close(CACHE);
+      }
+    }
+    if ($oid) {
+      $var =~ s/^$tmp/$oid/;
+    } else {
+      carp("Unknown SNMP var $var\n")
+	unless ($Net_SNMP_util::SuppressWarnings > 1);
+      next;
+    }
+    while ($var =~ /\"([^\"]*)\"/) {
+      $tmp = sprintf("%d.%s", length($1), join(".", map(ord, split(//, $1))));
+      $var =~ s/\"$1\"/$tmp/;
+    }
+    print "toOID: $var\n" if $Net_SNMP_util::Debug;
+    push(@retvar, $var);
+  }
+  return @retvar;
+}
+
+#
+# Check to see if an OID is in the text-to-OID cache.
+# Returns the OID and the corresponding text as two separate
+# elements.
+#
+sub Check_OID ($) {
+  my($var) = @_;
+  my($tmp, $tmpv, $oid);
+
+  if ($var =~ /^[a-zA-Z][\w\-]*(\.[a-zA-Z][\w\-]*)*/) {
+    $tmp = $&;
+    $tmpv = $tmp;
+    for (;;) {
+      last if exists($Net_SNMP_util::OIDS{$tmpv});
+      last if !($tmpv =~ s/^[^\.]*\.//);
+    }
+    $oid = $Net_SNMP_util::OIDS{$tmpv};
+    if ($oid) {
+      return ($oid, $tmp);
+    } else {
+      my @empty = ();
+      return @empty;
+    }
+  }
+  return ($var, $var);
+}
+
+sub snmpwalk_flg ($$@) {
+  my($host, $hash_sub, @vars) = @_;
+  my($session, %args, @enoid, @poid, $toid, $oid, $got);
+  my($val, $ret, %soid, %nsoid, @retvals, $tmp);
+  my(%rethash, $h_ref, @tmprefs);
+  my($stop);
+
+  $h_ref = (ref $vars[$#vars] eq "HASH") ? pop(@vars) : \%rethash;
+
+  $session = &snmpopen($host, 0, \@vars);
+  if (!defined($session)) {
+    carp "SNMPWALK Problem for $host"
+      unless ($Net_SNMP_util::SuppressWarnings > 1);
+    if (defined($hash_sub)) {
+      return ($h_ref) if ($SNMP_util::Return_hash_refs);
+      return (%$h_ref);
+    } else {
+      @retvals = ();
+      return (@retvals);
+    }
+  }
+
+  @enoid = toOID(@vars);
+  if ($#enoid < 0) {
+    if (defined($hash_sub)) {
+      return ($h_ref) if ($SNMP_util::Return_hash_refs);
+      return (%$h_ref);
+    } else {
+      @retvals = ();
+      return (@retvals);
+    }
+  }
+
+  #
+  # Create/Refresh a reversed hash with oid -> name
+  #
+  if (defined($hash_sub) and ($RevNeeded)) {
+      %revOIDS = reverse %Net_SNMP_util::OIDS;
+      $RevNeeded = 0;
+  }
+
+  #
+  # Create temporary array of refs to return values
+  #
+  foreach $oid (0..$#enoid)  {
+    my $tmparray = [];
+    $tmprefs[$oid] = $tmparray;
+    $nsoid{$oid} = $oid;
+  }
+
+  $got = 0;
+  @poid = @enoid;
+
+  if ($Net_SNMP_util::Version > 1 and $Net_SNMP_util::MaxRepetitions > 0) {
+    $args{'-maxrepetitions'} = $Net_SNMP_util::MaxRepetitions;
+  }
+  if ($Net_SNMP_util::Version > 2) {
+    $args{'-contextengineid'} = $Net_SNMP_util::ContextEngineID
+      if (defined($Net_SNMP_util::ContextEngineID));
+    $args{'-contextname'} = $Net_SNMP_util::ContextName
+      if (defined($Net_SNMP_util::ContextName));
+  }
+
+  while($#poid >= 0) {
+    $args{'-varbindlist'} = \@poid;
+    if (($Net_SNMP_util::Version > 1)
+    and ($Net_SNMP_util::MaxRepetitions > 1)) {
+      $ret = $session->get_bulk_request(%args);
+    } else {
+      $ret = $session->get_next_request(%args);
+    }
+    last if (!defined($ret));
+
+    %soid = %nsoid;
+    undef %nsoid;
+    $stop = 0;
+    foreach $oid (&Net::SNMP::oid_lex_sort(keys %$ret)) {
+      $got = 1;
+      $tmp = -1;
+      foreach $toid (@enoid) {
+	$tmp++;
+	if (&Net::SNMP::oid_base_match($toid, $oid)
+	and (!exists($soid{$toid}) or ($oid ne $soid{$toid}))) {
+	  $nsoid{$toid} = $oid;
+	  if (defined($hash_sub)) {
+	    #
+	    # extract name of the oid, if possible, the rest becomes the
+	    # instance
+	    #
+	    my $inst = "";
+	    my $upo = $toid;
+	    while (!exists($revOIDS{$upo}) and length($upo)) {
+	      $upo =~ s/(\.\d+?)$//;
+	      if (defined($1) and length($1)) {
+		$inst = $1 . $inst;
+	      } else {
+		$upo = "";
+		last;
+	      }
+	    }	
+	    if (length($upo) and exists($revOIDS{$upo})) {
+	      $upo = $revOIDS{$upo} . $inst;
+	    } else {
+	      $upo = $toid;
+	    }
+
+	    my $qoid = $oid;
+	    my $tmpo;
+	    $inst = "";
+	    while (!exists($revOIDS{$qoid}) and length($qoid)) {
+	      $qoid =~ s/(\.\d+?)$//;
+	      if (defined($1) and length($1)) {
+		$inst = $1 . $inst;
+	      } else {
+		$qoid = "";
+		last;
+	      }
+	    }	
+	    if (length($qoid) and exists($revOIDS{$qoid})) {
+	      $tmpo = $qoid;
+	      $qoid = $revOIDS{$qoid};
+	    } else {
+	      $qoid = $oid;
+	      $tmpo = $toid;
+	      $inst = substr($oid, length($tmpo)+1);
+	    }
+	    #
+	    # call hash_sub
+	    #
+	    &$hash_sub($h_ref, $host, $qoid, $tmpo, $inst, $ret->{$oid}, $upo);
+	  } else {
+	    my $tmpo;
+	    my $tmpv = $ret->{$oid};
+	    $tmpo = substr($oid, length($toid)+1);
+	    push @{$tmprefs[$tmp]}, "$tmpo:$tmpv";
+	  }
+	} else {
+	  $stop = 1 if ($#enoid == 0);
+	}
+      }
+    }
+    undef @poid;
+    @poid = values %nsoid if (!$stop);
+  }
+  if ($got) {
+    if (defined($hash_sub)) {
+	return ($h_ref) if ($Net_SNMP_util::ReturnHashRefs);
+    	return (%$h_ref);
+    } elsif ($Net_SNMP_util::Return_array_refs)  {
+      return (@tmprefs);
+    } else {
+      do {
+	$got = 0;
+	foreach $toid (0..$#enoid) {
+	  next if (scalar(@{$tmprefs[$toid]}) <= 0);
+	  $got = 1;
+	  $oid = shift(@{$tmprefs[$toid]});
+	  if ($#enoid > 0) {
+	    ($oid, $val) = split(':', $oid, 2);
+	    $oid = $enoid[$toid] . '.' . $oid;
+	    push(@retvals, "$oid:$val");
+	  } else {
+	    push(@retvals, $oid);
+	  }
+	}
+      } while($got);
+      return (@retvals);
+    }
+  } else {
+    $ret = join(' ', @vars);
+    error_msg("SNMPWALK Problem for $ret on ${host}: " . $session->error());
+    if (defined($hash_sub)) {
+      return ($h_ref) if ($SNMP_util::Return_hash_refs);
+      return (%$h_ref);
+    } else {
+      @retvals = ();
+      return (@retvals);
+    }
+  }
+}
+
+#
+# When passed a string, return the ASN.1 type that corresponds to the
+# string.
+#
+sub ASNtype($) {
+  my($type) = @_;
+
+  $type =~ tr/A-Z/a-z/;
+  if ($type eq "int") {
+    $type = 0x02;
+  } elsif ($type eq "integer") {
+    $type = 0x02;
+  } elsif ($type eq "string") {
+    $type = 0x04;
+  } elsif ($type eq "octetstring") {
+    $type = 0x04;
+  } elsif ($type eq "octet string") {
+    $type = 0x04;
+  } elsif ($type eq "oid") {
+    $type = 0x06;
+  } elsif ($type eq "object id") {
+    $type = 0x06;
+  } elsif ($type eq "object identifier") {
+    $type = 0x06;
+  } elsif ($type eq "ipaddr") {
+    $type = 0x40;
+  } elsif ($type eq "ip address") {
+    $type = 0x40;
+  } elsif ($type eq "timeticks") {
+    $type = 0x43;
+  } elsif ($type eq "uint") {
+    $type = 0x47;
+  } elsif ($type eq "uinteger") {
+    $type = 0x47;
+  } elsif ($type eq "uinteger32") {
+    $type = 0x47;
+  } elsif ($type eq "unsigned int") {
+    $type = 0x47;
+  } elsif ($type eq "unsigned integer") {
+    $type = 0x47;
+  } elsif ($type eq "unsigned integer32") {
+    $type = 0x47;
+  } elsif ($type eq "counter") {
+    $type = 0x41;
+  } elsif ($type eq "counter32") {
+    $type = 0x41;
+  } elsif ($type eq "counter64") {
+    $type = 0x46;
+  } elsif ($type eq "gauge") {
+    $type = 0x42;
+  } elsif ($type eq "gauge32") {
+    $type = 0x42;
+  } elsif (($type <= 0) or ($type > 255)) {
+    return undef;
+  }
+  return $type;
+}
+
+#
+# set the ErrorMessage global and print an error message
+#
+sub error_msg($)
+{
+  my($msg) = @_;
+  $Net_SNMP_util::ErrorMessage = $msg;
+  if ($Net_SNMP_util::SuppressWarnings <= 1) {
+    $Carp::CarpLevel++;
+    carp($msg);
+    $Carp::CarpLevel--;
+  }
+}
+
+#
+# Fill the OIDS hash with results from the MIB parsing
+#
+sub MIB_fill_OID($)
+{
+  my($href) = @_;
+  my($cnt, $changed, @del, $var, $val, @parts, $indx);
+  my(%seen);
+
+  $cnt = 0;
+  do {
+    $changed = 0;
+    @del = ();
+    foreach $var (keys %$href) {
+      $val = $href->{$var};
+      @parts = split('\.', $val);
+      $val = '';
+      foreach $indx (@parts) {
+	if ($indx =~ /^\d+$/) {
+	  $val .= '.' . $indx;
+	} else {
+	  if (exists($Net_SNMP_util::OIDS{$indx})) {
+	    $val = $Net_SNMP_util::OIDS{$indx};
+	  } else {
+	    $val .= '.' . $indx;
+	  }
+	}
+      }
+      if ($val =~ /^[\d\.]+$/) {
+	$val =~ s/^\.+//;
+	if (!exists($Net_SNMP_util::OIDS{$var})
+	|| (length($val) > length($Net_SNMP_util::OIDS{$var}))) {
+	  $Net_SNMP_util::OIDS{$var} = $val;
+	  print "'$var' => '$val'\n" if $Net_SNMP_util::Debug;
+	  $changed = 1;
+	  $cnt++;
+	}
+	push @del, $var;
+      }
+    }
+    foreach $var (@del) {
+      delete $href->{$var};
+    }
+  } while($changed);
+
+  $Carp::CarpLevel++;
+  foreach $var (sort keys %$href) {
+    $val = $href->{$var};
+    $val =~ s/\..*//;
+    next if (exists($seen{$val}));
+    $seen{$val} = 1;
+    $seen{$var} = 1;
+    error_msg(
+	"snmpMIB_to_OID: prefix \"$val\" unknown, load the parent MIB first.\n"
+    );
+  }
+  $Carp::CarpLevel--;
+  return $cnt;
+}
+
+
+# [documentation] ------------------------------------------------------------
+
+=head1 EXPORTS
+
+The Net_SNMP_util module uses the F<Exporter> module to export useful
+constants and subroutines.  These exportable symbols are defined below and
+follow the rules and conventions of the F<Exporter> module (see L<Exporter>).
+
+=over
+
+=item Exportable
+
+&snmpget, &snmpgetnext, &snmpgetbulk, &snmpwalk, &snmpset, &snmptrap,
+&snmpmaptable, &snmpmaptable4, &snmpwalkhash, &snmpmapOID, &snmpMIB_to_OID,
+&snmpLoad_OID_Cache, &snmpQueue_MIB_File, ErrorMessage
+
+=back
+
+=head1 EXAMPLES
+
+=head2 1. SNMPv1 get-request for sysUpTime
+
+This example gets the sysUpTime from a remote host.
+
+    #! /usr/local/bin/perl
+    use strict;
+    use Net_SNMP_util;
+    my ($host, $ret)
+    $host = shift || 'localhost';
+    $ret = snmpget($host, 'sysUpTime');
+
+    print("sysUpTime for $host is $ret\n");
+
+    exit 0;
+
+=head2 2. SNMPv3 set-request of sysContact
+
+This example sets the sysContact information on the remote host to 
+"Help Desk x911".  The parameters passed to the snmpset function are for
+the demonstration of syntax only.  These parameters will need to be
+set according to the SNMPv3 parameters of the remote host used by the script. 
+
+    #! /usr/local/bin/perl
+    use strict;
+    use Net_SNMP_util;
+    my($host, %v3hash, $ret);
+    $host = shift || 'localhost';
+    $v3hash{'-version'}		= 'snmpv3';
+    $v3hash{'-username'}	= 'myv3Username';
+    $v3hash{'-authkey'}		= '0x05c7fbde31916f64da4d5b77156bdfa7';
+    $v3hash{'-authprotocol'}	= 'md5';
+    $v3hash{'-privkey'}		= '0x93725fd3a02a48ce02df4e065a1c1746';
+
+    $ret = snmpset($host, \%v3hash, 'sysContact', 'string', 'Help Desk x911');
+
+    print "sysContact on $host is now $ret\n";
+    exit 0;
+
+=head2 3. SNMPv2c walk for ifTable
+
+This example gets the contents of the ifTable by sending get-bulk-requests
+until the responses are no longer part of the ifTable.  The ifTable can also
+be retrieved using C<snmpmaptable>.
+
+    #! /usr/local/bin/perl
+    use strict;
+    use Net_SNMP_util;
+    my($host, @ret, $oid, $val);
+    $host = shift || 'localhost';
+
+    @ret = snmpwalk($host . ':::::2', 'ifTable');
+    foreach $val (@ret) {
+	($oid, $val) = split(':', $val, 2);
+	print "$oid => $val\n";
+    }
+    exit 0;
+
+=head2 4. SNMPv2c maptable collecting ifDescr, ifInOctets, and ifOutOctets.
+
+This example collects a table containing the columns ifDescr, ifInOctets, and
+ifOutOctets.  A printing function is called once per row.
+
+    #! /usr/local/bin/perl
+    use strict;
+    use Net_SNMP_util;
+
+    sub printfun($$$$) {
+	my($inst, $desc, $in, $out) = @_;
+	printf "%3d %-52.52s %10d %10d\n", $inst, $desc, $in, $out;
+    }
+
+    my($host, @ret);
+    $host = shift || 'localhost';
+
+    printf "%-3s %-52s %10s %10s\n", "Int", "Description", "In", "Out";
+    @ret = snmpmaptable($host . ':::::2', \&printfun,
+			'ifDescr', 'ifInOctets', 'ifOutOctets');
+
+    exit 0;
+
+=head1 REQUIREMENTS
+
+=over
+
+=item *
+
+The Net_SNMP_util module uses syntax that is not supported in versions of Perl 
+earlier than v5.6.0. 
+
+=item *
+
+The Net_SNMP_util module uses the F<Net::SNMP> module, and as such may depend
+on other modules.  Please see the documentaion on F<Net::SNMP> for more
+information.
+
+=back
+
+=head1 AUTHOR
+
+Mike Mitchell <Mike.Mitchell at sas.com>
+
+=head1 ACKNOWLEGEMENTS
+
+The original concept for this module was based on F<SNMP_Session.pm> written
+by Simon Leinen <simon at switch.ch>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2007 Mike Mitchell.  All rights reserved.  This program 
+is free software; you may redistribute it and/or modify it under the same
+terms as Perl itself.
+
+=cut
+
+# ======================================================================
+1; # [end Net_SNMP_util]
diff --git a/README b/README
index 7a9c52a..5ede513 100644
--- a/README
+++ b/README
@@ -42,12 +42,13 @@ This program is free software; you can redistribute it under the
        Andrew Cornford-Matheson  <andrew.matheson at corenetworks.com>
                    Gerry Dalton  <gerry.dalton at consolidated.com>
 		 Jan van Keulen  <cologne at email.com>
+	       Armin Wolfermann  <armin at wolfermann.org>
 
 	http://www.switch.ch/misc/leinen/snmp/perl/index.html
 
 This archive contains Perl 5 modules SNMP_Session.pm and BER.pm,
 which, when used together, provide rudimentary access to remote SNMP
-(v1) agents.
+(v1/v2) agents.
 
 This module differs from existing SNMP packages in that it is
 completely stand-alone, i.e. you don't need to have another SNMP
@@ -65,233 +66,7 @@ tool: <URL:http://oss.oetiker.ch/mrtg/>
 				Usage
 				.....
 
-The basic usage of these routines works like this:
-
-	use BER;
-	require 'SNMP_Session.pm';
-	
-	# Set $host to the name of the host whose SNMP agent you want
-	# to talk to.  Set $community to the community name under
-	# which you want to talk to the agent.  Set port to the UDP
-	# port on which the agent listens (usually 161).
-	
-	$session = SNMP_Session->open ($host, $community, $port)
-	    or die "couldn't open SNMP session to $host";
-	
-	# Set $oid1, $oid2... to the BER-encoded OIDs of the MIB
-	# variables you want to get.
-	
-	if ($session->get_request_response ($oid1, $oid2, ...)) {
-	    ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
-	
-	    while ($bindings ne '') {
-	        ($binding,$bindings) = &decode_sequence ($bindings);
-	        ($oid,$value) = &decode_by_template ($binding, "%O%@");
-	        print &pretty_print ($oid)," => ", &pretty_print ($value), "\n";
-	    }
-	} else {
-	    die "No response from agent on $host";
-	}
-			    Encoding OIDs
-			    .............
-
-In order to BER-encode OIDs, you can use the function BER::encode_oid.
-It takes (a vector of) numeric subids as an argument.  For example,
-
-	use BER;
-	encode_oid (1, 3, 6, 1, 2, 1, 1, 1, 0)
-
-will return the BER-encoded OID for the sysDescr.0 (1.3.6.1.2.1.1.1.0)
-instance of MIB-2.
-
-			 Decoding the results
-			 ....................
-
-When get_request_response returns success, you must decode the
-response PDU from the remote agent.  The function
-`decode_get_response' can be used to do this.  It takes a get-response
-PDU, checks its syntax and returns the "bindings" part of the PDU.
-This is where the remote agent actually returns the values of the
-variables in your query.
-
-You should iterate over the individual bindings in this "bindings"
-part and extract the value for each variable.  In the example above,
-the returned bindings are simply printed using the BER::pretty_print
-function.
-
-For better readability of the OIDs, you can also use the following
-idiom, where the %pretty_oids hash maps BER-encoded numerical OIDs to
-symbolic OIDs. Note that this simple-minded mapping only works for
-response OIDs that exactly match known OIDs, so it's unsuitable for
-table walking (where the response OIDs include an additional row
-index).
-
-	%ugly_oids = qw(sysDescr.0      1.3.6.1.2.1.1.1.0
-	                sysContact.0    1.3.6.1.2.1.1.4.0);
-	foreach (keys %ugly_oids) {
-	    $ugly_oids{$_} = encode_oid (split (/\./, $ugly_oids{$_}));
-	    $pretty_oids{$ugly_oids{$_}} = $_;
-	}
-	...
-	if ($session->get_request_response ($ugly_oids{'sysDescr.0'},
-	                                    $ugly_oids{'sysContact.0'})) {
-	    ($bindings) = $session->decode_get_response
-		($session->{pdu_buffer});
-	    while ($bindings ne '') {
-	        ($binding,$bindings) = &decode_sequence ($bindings);
-	        ($oid,$value) = &decode_by_template ($binding, "%O%@");
-	        print $pretty_oids{$oid}," => ",
-	              &pretty_print ($value), "\n";
-	    }
-	} ...
-
-			     Set Requests
-			     ............
-
-Set requests are generated much like get or getNext requests are, with
-the exception that you have to specify not just OIDs, but also the
-values the variables should be set to.  Every binding is passed as a
-reference to a two-element array, the first element being the encoded
-OID and the second one the encoded value.  See the `test/set-test.pl'
-script for an example, in particular the subroutine `snmpset'.
-
-			    Walking Tables
-			    ..............
-
-Beginning with version 0.57 of SNMP_Session.pm, there is API support
-for walking tables.  The map_table method can be used for this as
-follows:
-
-	sub walk_function ($$$) {
-	  my ($index, $val1, $val3) = @_;
-	  ...
-	}
-	
-	...
-	$columns = [$base_oid1, $base_oid3];
-	$n_rows = $session->map_table ($columns, \&walk_function);
-
-The COLUMNS argument must be a reference to a list of OIDs for table
-columns sharing the same index.  The method will traverse the table
-and call the WALK_FUNCTION for each row. The arguments for these calls
-will be:
-
-* the row index as a partial OID in dotted notation, e.g. "1.3", or
-  "10.0.1.34".
-
-* the values of the requested table columns in that row, in
-  BER-encoded form.  If you want to use the standard pretty_print
-  subroutine to decode the values, you can use the following idiom:
-
-	grep (defined $_ && ($_=pretty_print $_), ($val1, $val3));
-
-			    Sending Traps
-			    .............
-
-To send a trap, you have to open an SNMP session to the trap receiver.
-Usually this is a process listening to UDP port 162 on a network
-management station.  Then you can use the trap_request_send method to
-encode and send the trap.  There is no way to find out whether the
-trap was actually received at the management station - SNMP traps are
-fundamentally unreliable.
-
-When constructing an SNMPv1 trap, you must provide
-
-* the "enterprise" Object Identifier for the entity that generates the
-  trap
-
-* your IP address
-
-* the generic trap type
-
-* the specific trap type
-
-* the sysUpTime at the time of trap generation
-
-* a sequence (may be empty) of variable bindings further describing
- the trap.
-
-For SNMPv2 traps, you need:
-
-* the trap's OID
-
-* the sysUpTime at the time of trap generation
-
-* the bindings list as above
-
-For SNMPv2 traps, the uptime and trap OID are encoded as bindings
-which are added to the front of the other bindings you provide.
-
-Here is a short example:
-
-	my $trap_receiver = "netman.noc";
-	my $trap_community = "SNMP_Traps";
-	my $trap_session = $version eq '1'
-	    ? SNMP_Session->open ($trap_receiver, $trap_community, 162)
-	    : SNMPv2c_Session->open ($trap_receiver, $trap_community, 162);
-	my $trap_session = SNMP_Session->open ($trap_receiver, $trap_community, 162);
-	my $myIpAddress = ...;
-	my $start_time = time;
-	
-	...
-	
-	sub link_down_trap ($$) {
-	  my ($if_index, $version) = @_;
-	  my $genericTrap = 2;		# linkDown
-	  my $specificTrap = 0;
-	  my @ifIndexOID = ( 1,3,6,1,2,1,2,2,1,1 );
-	  my $upTime = int ((time - $start_time) * 100.0);
-	  my @myOID = ( 1,3,6,1,4,1,2946,0,8,15 );
-	
-	  warn "Sending trap failed"
-	    unless ($version eq '1')
-		? $trap_session->trap_request_send (encode_oid (@myOID),
-						    encode_ip_address ($myIpAddress),
-						    encode_int ($genericTrap),
-						    encode_int ($specificTrap),
-						    encode_timeticks ($upTime),
-						    [encode_oid (@ifIndex_OID,$if_index),
-						     encode_int ($if_index)],
-						    [encode_oid (@ifDescr_OID,$if_index),
-						     encode_string ("foo")])
-		    : $trap_session->v2_trap_request_send (\@linkDown_OID, $upTime,
-							   [encode_oid (@ifIndex_OID,$if_index),
-							    encode_int ($if_index)],
-							   [encode_oid (@ifDescr_OID,$if_index),
-							    encode_string ("foo")]);
-	}
-
-			   Receiving Traps
-			   ...............
-
-Since version 0.60, SNMP_Session.pm supports the receipt and decoding
-of SNMPv1 trap requests.  Since version 0.75, SNMPv2 Trap PDUs are
-also recognized.
-
-To receive traps, you have to create a special SNMP session that
-passively listens on the SNMP trap transport address (usually UDP port
-162).  Then you can receive traps (actually, SNMPv1 traps, SNMPv2
-traps, and SNMPv2 informs) using the receive_trap method and decode
-them using decode_trap_request.  The enterprise, agent, generic,
-specific and sysUptime return values are only defined for SNMPv1
-traps.  In SNMPv2 traps and informs, the equivalent information is
-contained in the bindings.
-
-	my $trap_session = SNMPv1_Session->open_trap_session ()
-	  or die "cannot open trap session";
-	my ($trap, $sender_addr, $sender_port) = $trap_session->receive_trap ()
-	  or die "cannot receive trap";
-	my ($community, $enterprise, $agent,
-	    $generic, $specific, $sysUptime, $bindings)
-	  = $session->decode_trap_request ($trap)
-	    or die "cannot decode trap received"
-	...
-	my ($binding, $oid, $value);
-	while ($bindings ne '') {
-	    ($binding,$bindings) = &decode_sequence ($bindings);
-	    ($oid, $value) = decode_by_template ("%O%@");
-	    print BER::pretty_oid ($oid)," => ",pretty_print ($value),"\n";
-	}
+See the EXAMPLES section of the POD documentation in SNMP_Session.pm.
 
 			     Future Plans
 			     ............
diff --git a/changes.html b/changes.html
new file mode 100644
index 0000000..f2cdb61
--- /dev/null
+++ b/changes.html
@@ -0,0 +1,709 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
+<HTML>
+ <HEAD>
+  <TITLE>SNMP support for Perl 5: Changes</TITLE>
+ </HEAD>
+ <BODY bgcolor="#ffffff">
+<DIV ALIGN=CENTER>
+  <H1>SNMP support for Perl 5: Changes</H1>
+
+  <p> Copyright (c) 1995-2009, Simon Leinen<br>
+  All rights reserved </p>
+
+  <em> This program is free software; you can redistribute it under
+  the <a href="http://language.perl.com/misc/Artistic.html">"Artistic
+  License"</a> included in this distribution. </em>
+
+  <p>Author: <A HREF="http://www.switch.ch/misc/leinen/">Simon
+Leinen</A> <<A
+HREF="mailto:simon.leinen at switch.ch">simon.leinen at switch.ch</A>></p>
+
+</DIV>
+
+<h2> Recent Changes: </h2>
+
+<ul>
+
+<li> SNMP_Session.pm 1.14: <tt>open_trap_session</tt> now takes an
+  optional <tt>ipv4only</tt> argument.  It defaults to 1, but by
+  passing 0, you can create a session that accepts traps over IPv6 in
+  addition to IPv4.  The <tt>receive_trap</tt> method has been
+  enhanced by returning an additional value which is the address
+  family.  There is a new <tt>receive_trap_1</tt> method, which
+  differs from <tt>receive_trap</tt> in that it simply returns the
+  trap and a sockaddr structure, rather than the trap, the host
+  address, the port, and the address family. </li>
+
+<li> BER.pm 1.14: Added POD documentation.  Started adding automated
+  unit tests. </li>
+
+<li> SNMP_Session.pm 1.14: Added POD documentation. </li>
+
+<li> SNMP_util.pm 1.13: Parse OIDs in NOTIFICATION-TYPE names.  Change
+ by Mike Mitchell/jaccobs. </li>
+
+<li> SNMP_Session.pm 1.13: Fixed the optional socket-reuse code to
+ handle IPv4 and IPv6 sockets separately. </li>
+
+<li> SNMP_Session.pm 1.12: Upgraded to Artistic License 2.0 upon a
+  suggestion from Tom Callaway. </li>
+
+<li> SNMP_util.pm 1.12: Rewritten MIB parsing code from Mike
+ Mitchell. </li>
+
+<li> SNMP_util.pm 1.11: Improvement to loop detection in snmpwalk.
+  Should cope with more broken agents.  Change by Mike Mitchell. </li>
+
+<li> SNMP_Session.pm 1.10: Mike Fischer reported that on some systems,
+ notably Linux, <tt>recv()</tt> may block even though a
+ preceding <tt>select()</tt> for readability has returned
+ successfully.  Once condition where this can happen is when a UDP
+ checksum on an incoming datagram doesn't verify.  To avoid our
+ library from blocking in this case, we have to
+ pass <tt>MSG_DONTWAIT</tt> to <tt>recv()</tt>.  This behaviour is
+ selected by a new optional argument to <tt>receive_response_3</tt>,
+ and used by <tt>request_response_5</tt>. </li>
+
+<li> SNMP_util.pm 1.09: Fix from Mike Mitchell for parsing qualified
+ symbolic OIDs. </li>
+
+<li> SNMP_Session.pm 1.08: Fixed a bug in the SNMPv2c version of
+<tt>map_table_start_end</tt> that would cause errors when the
+<samp>$end</samp> argument is actually being used.  Thanks to Jan van
+Keulen for submitting the patch. </li>
+
+<li> SNMP_Session.pm 1.07: Fixed <tt>strict subs</tt> error with
+newer versions of Perl.  Thanks to Gerry Dalton for spotting
+this. </li>
+
+<li> SNMP_Session.pm 1.06: <tt>decode_trap_request</tt> now
+understands SNMPv2 inform requests in addition to SNMPv1 traps and
+SNMPv2 traps.  Contributed by Andrew Cornford-Matheson. </li>
+
+<li> SNMP_util.pm 1.06: <tt>snmpwalkhash</tt> fix by Laurent
+Girod. </li>
+
+<li> <tt>index.html</tt>: The code is no longer available from
+<tt>ftp.switch.ch</tt>, but via HTTP from
+<tt>www.switch.ch</tt>. </li>
+
+<li> SNMP_Session.pm 1.05: No change; version incremented for
+<tt>SNMP_util.pm</tt> and <tt>BER.pm</tt>. </li>
+
+<li> BER.pm 1.05: Internal restructuring: The new (BER.pm 1.02)
+pretty-printer registration method is now used for most standard SNMP
+types, too. </li>
+
+<li> SNMP_util.pm 1.04: The subroutines <tt>snmpget</tt>,
+<tt>snmpgetnext</tt> and <tt>snmpset</tt> will now detect when they
+are called in scalar context, and return only the first (and typically
+the only) of the retrieved values in this case. </li>
+
+<li> SNMP_util.pm 1.03: Added a missing part of Mike Mitchell
+pretty-printer registration patch. </li>
+
+<li> BER.pm 1.02: Additional decoders (``pretty printers'') can now be
+registered and unregistered for type codes using the
+<tt>register_pretty_printer</tt> and
+<tt>unregister_pretty_printer</tt> subroutines.  Implemented by Mike
+Mitchell. </li>
+
+<li> BER.pm 1.02: Corrected encoding of large integer-like
+values. </li>
+
+<li> SNMP_util.pm 1.02: <tt>snmpwalkhash</tt> now takes an additional
+optional argument that should be a reference to a hash.  If used,
+<tt>snmpwalkhash</tt> will insert the retrieved values into that
+hash. </li>
+
+<li> BER.pm 1.01: Properly parse the variant length format in
+integers.  Thanks to <a href="mailto:milen at batmbg.com">Milen
+Pavlov</a> for pointing out this bug.  Also, decoding should be
+slightly more efficient in general, because one <tt>substr()</tt>
+operation per (sub-) object has been eliminated. </li>
+
+<li> SNMP_Session 1.00: Added Luc Pauwels' implementation of the
+<tt>use_16bit_request_ids</tt> option.  Some SMC devices seem to
+require this. </li>
+
+<li> SNMP_Session 0.99: Improved request ID generation so that
+<tt>avoid_negative_request_ids</tt> is always obeyed. </li>
+
+<li> SNMP_util 0.99: Added <tt>Gauge32</tt> support to
+<tt>snmpset</tt>.  Thanks to <a
+href="mailto:tengi at CS.Princeton.EDU">Christopher J. Tengi</a> for the
+idea.  Mike Mitchell provided new code that supports all known
+types. </li>
+
+<li> SNMP_Session 0.98: Portability fix in IPv6 support. </li>
+
+<li> SNMP_util 0.98: Support encoding of OIDs containing multiple
+quoted strings. </li>
+
+<li> SNMP_util 0.97: Added support for TimeTicks values in
+<tt>snmpset</tt>.  Patch from <a
+href="mailto:JOERG.KUMMER at Roche.COM">Joerg Kummer</a> </li>
+
+<li> SNMP_Session 0.97: Exported
+<tt>$SNMP_Session::default_avoid_negative_request_ids</tt>, by
+Philippe Simonet. </li>
+
+<li> SNMP_Session 0.97: IPv6 support added by Valerio Bontempi and
+Lorenzo Colitti. </li>
+
+<li> SNMP_Session 0.96: Intermediate version with IPv6 support; not
+published. </li>
+
+<li> BER.pm 0.95: Fixed operator precedence bug in
+decode_sequence(). </li>
+
+<li> SNMP_Session.pm 0.94: A new slot <tt>capture_buffer</tt> has been
+added to the SNMP_Session classes, courtesy <a
+href="mailto:jakob.ilves at oracle.com">Jakob Ilves</a>.  See the sample
+script <tt>test/capturetest.pl</tt> for an example of how to use
+this. </li>
+
+<li> BER.pm 0.94: New subroutines <tt>pretty_generic_sequence</tt>,
+<tt>decode_generic_tlv</tt>, courtesy <a
+href="mailto:jakob.ilves at oracle.com">Jakob Ilves</a>. </li>
+
+<li> BER.pm 0.94: <tt>decode_by_template</tt>, <tt>decode_oid</tt>,
+<tt>decode_sequence</tt>, <tt>decode_string</tt>: Explicitly signal
+error when the PDU is undefined or too short.  This improves error
+handling for some malformed PDUs, as generated by certain test
+suites. </li>
+
+<li> SNMP_Session.pm 0.93: There's a new variable
+<tt>$default_avoid_negative_request_ids</tt>.  If it is set to a
+non-zero value, newly created <tt>SNMP_Session</tt> objects will be
+configured so that only request IDs in the range 0..2<sup>31</sup>-1
+will be used.  This is needed to work around a bug in several SNMP
+agents.  If you sometimes see requests fail, and the error message
+always shows a negative request ID when that happens, please notify
+the vendor of your agent of the bug.  While the vendor fixes the
+problem, you can set the variable mentioned above to work around the
+bug.  The problem has been described in a few mails on the
+<em>mrtg-developers</em> mailing list that can hopefully be found <a
+href="http://www.ee.ethz.ch/~slist/mrtg-developers/msg01609.html">here</a>
+in the archive. </li>
+
+<li> SNMP_util.pm 0.93: <tt>snmpwalk</tt> now will walk multiple OID
+trees simultaneously.  If more than one OID is passed to the function,
+the returned <em>OID:value</em> pairs will contain the entire OID
+instead of just the leaf from the starting point. </li>
+
+<li> SNMP_util.pm 0.93: <tt>snmpwalkhash</tt> now passes the textual
+OID of the starting point as the seventh argument to the passed-in
+hash function. </li>
+
+<li> SNMP_util.pm 0.92: New subroutine <tt>snmpmaptable4</tt>, which
+allows specifying the <em>max-repeaters</em>. </li>
+
+<li> SNMP_util.pm 0.92: New MIB parsing code.  Most MIBs are processed
+in one pass, but if an OID cannot be fully resolved, an extra pass is
+done.  Also, <samp>OBJECT-GROUP</samp> entries are now
+recognized. </li>
+
+<li> SNMP_util.pm 0.92: The string-to-OID conversion routines now
+transparently convert quoted strings to instance indexes.  For
+example, <samp>"/usr"</samp> (including the quotes) would be converted
+to <samp>4.47.118.97.114</samp>.
+
+<li> SNMP_Session.pm 0.91: Fixed a bug in <tt>map_table_start_end</tt>
+for <tt>SNMPv2_Session</tt> (which uses <tt>get-bulk</tt>) which had
+caused a superfluous query after the entire table has already been
+provably traversed.  Thanks to Michael Deegan for pointing this
+out. </li>
+
+<li> SNMP_util.pm 0.90: New version from Mike Mitchell.
+ <tt>snmpwalkhash</tt> and <tt>snmpwalk</tt> now share most
+ code. </li>
+
+<li> SNMP_util.pm 0.90: Corrected handling of the optional port number in
+ <tt>snmpopen</tt>. </li>
+
+<li> test/if-counters.pl: Support 64-bit counters, and use
+<tt>ifAlias</tt> to portably get interface descriptions. </li>
+
+<li> SNMP_util.pm 0.89: New version from Mike Mitchell, with new
+ <tt>snmpwalkhash</tt> subroutine by <a
+ href="mailto:girod.laurent at pmintl.ch">Laurent Girod</a>. </li>
+
+<li> SNMP_Session.pm 0.89: Made <tt>lenient_source_port_matching</tt>
+the default. </li>
+
+<li> SNMP_util.pm 0.89: Fixed a long-standing bug where the code would
+generate PDUs with <tt>request-id</tt>s with large positive values,
+violating the SNMP spec which mandates that <tt>request-id</tt>s be
+<tt>Integer32</tt>s.  Thanks to Sergio Macedo <macedo at tmp.com.br> for
+finding this bug. </li>
+
+<li> SNMP_util.pm 0.88: Added missing <samp>use Carp;</samp>
+statements in packages <samp>SNMPv1_Session</samp> and
+<samp>SNMPv2_Session</samp>.  Thanks to <a
+href="mailto:michael at cnspc18.murdoch.edu.au">Michael Deegan</a>. </li>
+
+<li> SNMP_util.pm 0.87: No change from 0.86, but increased version
+number to reflect change in BER.pm (SNMPv2 exception codes). </li>
+
+<li> BER.pm 0.87: <samp>pretty_print</samp> now silently returns undef
+when decoding SNMPv2 exception codes (<tt>noSuchObject</tt>,
+<tt>noSuchInstance</tt>, or <tt>endOfMibView</tt>, see RFC 1905).
+Original patch by <a href="mailto:driehuis at playbeing.org">Bert
+Driehuis</a>. </li>
+
+<li> BER.pm 0.86: <samp>pretty_print</samp> now silently returns undef
+when given an undefined value, rather than issuing incomprehensible
+warnings. </li>
+
+<li> SNMP_util.pm 0.86: New <samp>snmpmaptable</samp> subroutine.
+This is a more user-friendly version of <samp>map_table</samp> and is
+described in the <a href="dist/README.SNMP_util">README.SNMP_util</a>
+file.  From Mike Mitchell. </li>
+
+<li> SNMP_util.pm 0.86: Support for the awesome <tt>get-bulk</tt>
+operator, both directly through the new <samp>snmpgetbulk</samp>
+subroutine, and transparently via <samp>snmpwalk</samp> when the
+session's SNMP version is >= 2 and the <samp>use_getbulk</samp>
+slot is set (as it is by default).  From Mike Mitchell. </li>
+
+<li> SNMP_Session.pm 0.85: If a local address is specified in
+<samp>snmpopen</samp>, don't convert it using <samp>inet_aton</samp>,
+because this is handled by the Socket library.  Fix from <a
+href="mailto:mikem at open.com.au">Mike McCauley</a>. </li>
+
+<li> SNMP_Session.pm 0.85: Added
+<samp>lenient_source_port_matching</samp> slot to the session object.
+Set this to communicate with weird SNMP agents that send the response
+from a port other than 161.  Suggestion from <a
+href="mailto:hgomez at slib.fr">Henri Gomez</a>. </li>
+
+<li> SNMP_util.pm 0.84: New version from Mike Mitchell.
+<samp>snmpopen</samp> now parses an optional hash argument for
+options.  Also reintroduced defaulting of the UDP port to 161 for
+``normal'' sessions. </li>
+
+<li> SNMP_Session.pm 0.84: Clarified documentation concerning the
+<samp>%pretty_oids</samp> hash, upon a suggestion from <a
+href="mailto:alistair at alizta.com">Alistair Mills</a>. </li>
+
+<li> SNMP_Session.pm 0.83: The source address in response packets is
+now ignored when matching responses against outstanding queries.  In a
+couple of previous revisions, the source address in response packets
+had to match the destination address in the corresponding query.
+Unfortunately some agents may use a different source address in
+responses.  If you want the strict behavior back, you can send the
+<tt>lenient_source_address_matching</tt> slot of the session object to
+zero. </li>
+
+<li> SNMP_Session.pm 0.83: The source address for outgoing packets can
+now be specified as an additional optional argument to
+<tt>open</tt>.  If you don't specify it, the system will choose the
+source address by itself, usually corresponding to the interface on
+which packets are sent. </li>
+
+<li> SNMP_Session.pm 0.83: Fixed a bug which had caused requests to be
+resent upon receipt of packets which don't match the outstanding
+query. </li>
+
+<li> SNMP_Session.pm 0.82: Fixed retry logic to avoid sending a last
+retry without waiting for the response anymore.  Thanks to <a
+href="mailto:wardenb at eluminant.com">Brett T Warden</a> for the
+fix. </li>
+
+<li> BER.pm 0.82: OIDs with only two subids can now be encoded.  The
+most common case is the "null" OID (0.0).  Thanks to <a
+href="mailto:wardenb at eluminant.com">Brett T Warden</a> for pointing out
+that this didn't work. </li>
+
+<li> BER.pm 0.82: pretty_print() now handles UInteger32 objects.
+Patch by <a href="mailto:wardenb at eluminant.com">Brett T
+Warden</a>. </li>
+
+<li> BER.pm 0.81: Subids in the range 2^31 - 2^32-1 are now encoded
+correctly.  Thanks to <a href="mailto:rik.hoorelbeke at pandora.be">Rik
+Hoorelbeke</a> for noticing the problem. </li>
+
+<li> SNMP_Session.pm 0.81: A cosmetic bug in the SNMPv2 version of
+<tt>map_table</tt> (which uses <tt>get-bulk</tt>) was corrected.  The
+user-supplied function is now always called on the same number of
+arguments.  Before this, missing values at the end of a table row
+would lead to the function being called with fewer arguments.  Now
+there will be <tt>undef</tt> values for those, too.  Thanks to <a
+href="mailto:schmid at switch.ch">Ulrich Schmid</a> for pointing this
+out. </li>
+
+<li> SNMP_Session.pm 0.80: A portability bug was fixed in the code
+that matches incoming responses to outstanding requests.  The bug had
+manifested itself, notably on some FreeBSD versions, by timeouts
+waiting for responses, because the library thought the responses came
+from another address than the corresponding requests had been sent
+to. </li>
+
+<li> SNMP_Session.pm 0.79: A new variable
+<tt>$SNMP_Session::recycle_socket</tt> has been introduced.  When this
+variable is set to a non-zero value (the default is zero), all newly
+created SNMP_Session objects will share the same UDP socket.  This
+saves file descriptors and system calls, but will cause problems with
+multiple outstanding SNMP requests on different session objects.
+Applications which don't perform parallel/asynchronous SNMP requests
+can safely set this variable to reduce OS overhead somewhat.
+Suggestion from <a href="mailto:schmid at switch.ch">Ulrich
+Schmid</a>. </li>
+
+<li> SNMP_Session.pm 0.79: The handling of incoming packets from
+unexpected addresses has been cleaned up.  If a packet is received
+from an IP address other than the one to which the request has been
+sent, this packet is silently ignored, as mandated by the SNMP
+standard. </li>
+
+<li> SNMP_Session.pm 0.79: Receive-only session objects (such as the
+ones created by <tt>open_trap_session</tt> now have <samp>undef</samp>
+as the <samp>remote_addr</samp> value, rather than IP address
+<samp>0.0.0.0</samp>. </li>
+
+<li> BER.pm 0.79: There are new exported subroutines for encoding
+different types of values: <samp>encode_uinteger32</samp>,
+<samp>encode_counter32</samp>, <samp>encode_counter64</samp>,
+<samp>encode_gauge32</samp>. </li>
+
+<li> SNMP_Session.pm 0.78: The <tt>map_table</tt> implementation for
+SNMPv2 sessions has been completely rewritten to reliably support
+tables with holes in them.  Note that this hasn't been completely
+validated or tested yet, but at least it has been found to handle
+common cases reasonably. </li>
+
+<li> BER.pm 0.77: Added support for long integers, so that
+<tt>Counter64</tt> values can be handled without risk of losing
+precision (due to automatic coercion to floating-point representation
+by Perl).  When a BER-encoded integer is so long that it might not fit
+in a 32-bit unsigned integer, then we use <tt>Math::BigInt</tt>
+arithmetics to convert it. <b>Warning:</b> code which calls the
+decoding functions should be prepared to handle <tt>Math::BigInt</tt>
+values if <tt>Counter64</tt> values can be accessed.  In most respect,
+those long integers behave just like ordinary integers.  A notable
+exception is that they print with a leading ``<tt>+</tt>'' sign. </li>
+
+<li> SNMP_Session 0.77: The SNMPv2 implementation of
+<tt>map_table_start_end</tt> has been enhanced to cope with the case
+where a response PDU ends with a truncated table row, courtesy <a
+href="mailto:pee at gblx.net">Paul E. Erkkila</a>.  Note that tables with
+missing entries still aren't handled correctly when SNMPv2 (and thus
+the <tt>get-bulk</tt> operator is used). </li>
+
+<li> SNMP_util 0.73: Added a fix from <a
+href="mailto:mcm at unx.sas.com">Mike Mitchell</a> (originally <a
+href="mailto:demel at zid.tuwien.ac.at">Johannes Demel</a>) to treat
+<tt>OBJECT-IDENTITY</tt> like <tt>OBJECT IDENTIFIER</tt>.
+
+<li> SNMP_Session 0.76: Added some debugging support for
+<tt>map_table</tt>, which still doesn't work reliably when the SNMPv2
+<tt>get-bulk</tt> operator is used. </li>
+
+<li> test/if-counters.pl: Added <samp>-c</samp> option to enable
+Cisco-specific variables (which are no longer retrieved by default as
+in previous versions). </li>
+
+<li> SNMP_Session 0.75: Fixed a bug in versions 0.73-0.74 where
+creation of trap listener sockets would fail.  Rather than using
+<tt>bind</tt> to bind to the trap port, we now pass a
+<tt>LocalPort</tt> argument to <tt>INET->new</tt>. </li>
+
+<li> SNMP_Session 0.75: Parse SNMPv2-Trap-Requests in addition to
+SNMPv1 ones.  A caller can tell whether an SNMPv1 or an SNMPv2 request
+has been received by testing whether the SNMPv1-specific fields are
+defined.  The sample script <tt>test/trap-listener</tt> has been
+updated to understand SNMPv2 Traps. </li>
+
+<li> SNMP_Session 0.75: New subroutine <tt>v2_trap_request_send</tt>
+which sends SNMPv2 Trap PDUs.  The sample script
+<tt>test/trap-send</tt> has been extended to generate either type of
+trap on request. </li>
+
+<li> SNMP_Session 0.74: Put under copyright and Artistic
+License. </li>
+
+<li> SNMP_util 0.72: Changed <tt>snmpgetnext</tt> so it will return
+the next lexicographical larger OID number, even if it is not in the
+same OID tree.  Before the change snmpget would discard the return
+value if the OID wasn't in the same tree.  In <tt>snmpMIB_to_OID</tt>,
+an "unitialized variable" warning was removed. (Changes by <a
+href="mailto:mcm at unx.sas.com">Mike Mitchell</a>, the author) </li>
+
+<li> SNMP_Session 0.73: The UDP socket associated with each SNMP
+session object is now created using <tt>IO::Socket::INET->new</tt>.
+Before this change, a Perl file descriptor name was generated for each
+new socket.  Apparently this caused a file descriptor leak.  The new
+code is much cleaner and doesn't have that problem anymore.  Hopefully
+the newly introduced dependency on the Socket::IO module is not a
+problem.  Thanks to <a href="mailto:elble at icculus.nsg.nwu.edu">Andrew
+W. Elble</a> for suggesting this change. </li>
+
+<li> BER.pm 0.72: A new variable,
+<tt>$BER::pretty_print_timeticks</tt>, has been introduced to give
+users control over the degree of pretty-printing of <tt>TimeTicks</tt>
+values.  If left at the defaults, <tt>TimeTicks</tt> values will be
+converted to strings such as <samp>14 days, 6:56:07</samp>.  If you
+set it to zero, the same value will simply pretty-print as
+<samp>123456789</samp>, which should be interpreted in units (ticks)
+of 10ms. </li>
+
+<li> SNMP_util.pm 0.71: New subroutines <tt>snmpLoad_OID_Cache</tt> and
+<tt>snmpQueue_MIB_File</tt> for loading MIBs in compact format. </li>
+
+<li> SNMP_util.pm 0.71: <tt>snmpset</tt> now accepts <tt>ipaddr</tt>
+as a type specifier. </li>
+
+<li> SNMP_util.pm 0.70: <a href="mailto:mcm at unx.sas.com">Mike
+Mitchell</a> added code for parsing MIB files.  See the description of
+<tt>snmpMIB_to_OID</tt> in <a
+href="dist/README.SNMP_util"><tt>README.SNMP_util</tt></a>. </li>
+
+<li> SNMP_util.pm 0.69: Enable parsing of community strings which
+contain <samp>@</samp> characters. </li>
+
+<li> SNMP_util.pm 0.58: Check for errors from <tt>encode_oid</tt>, so
+that illegal OIDs generate error messages.  Allow for suppression of
+warnings by setting <tt>$SNMP_Session::suppress_warnings</tt> to a
+value greater than one. </li>
+
+<li> SNMP_Session.pm 0.68: Added methods <tt>receive_request</tt> and
+<tt>decode_request</tt> to support SNMP agents.  Contributed by <a
+href="mailto:mikem at open.com.au">Mike McCauley</a>. </li>
+
+<li> SNMP_Session.pm 0.67: Implement a <tt>map_table_start_end</tt>
+method for <tt>SNMPv2c_Session</tt> which uses <tt>get-bulk</tt>.  See
+<a href="#map-table-4-use">Walking Tables With <tt>get-bulk</tt></a>
+for more information. </li>
+
+<li> SNMP_Session.pm 0.66: Fix from <a
+href="mailto:Alan.Nichols at Ebay.Sun.COM">Alan Nichols</a> to the
+handling of error replies.  In the last few revisions of SNMP_Session,
+requests to which the agent responded with a non-zero
+<tt>errorStatus</tt> were erroneously retried. </li>
+
+<li> SNMP_Session.pm 0.66: When the <tt>errorStatus</tt> is zero, we
+don't care about the <tt>errorIndex</tt>.  This makes us liberal
+enough to cope with very old versions of the CMU agent code which
+sometimes put a non-zero <tt>errorIndex</tt> in normal response
+packets. </li>
+
+<li> BER.pm 0.66: Changed the <tt>$VERSION</tt> number to be in line
+with SNMP_Session.pm's. </li>
+
+<li> BER.pm 0.66: Changed <tt>encode_oid</tt> so that it signals an
+error when passed an illegal Object ID, such as one whose first subid
+isn't 0, 1 or 2.  This should help people who try to use output from
+CMU/UCD SNMP directly in MRTG. </li>
+
+<li> SNMP_Session.pm 0.65: Fix error message when binding to UDP port
+fails (<a href="mailto:jzhukovs at staff.juno.com">Jonathan
+Zhukovsky</a>). </li>
+
+<li> SNMP_util.pm 0.57: Small change to avoid warnings with "-w" when
+session parameters are defaulted. </li>
+
+<li> SNMP_util.pm 0.56: New subroutine <tt>snmpmapOID</tt>, <a
+href="mailto:mcm at unx.sas.com">Mike Mitchell</a>. </li>
+
+<li> SNMP_Session.pm 0.64: Fix of a bug in the detection of missing
+responses by <a href="mailto:mcm at unx.sas.com">Mike Mitchell</a>. </li>
+
+<li> SNMP_Session.pm 0.63: Fixed response matching logic to ignore
+ out-of-sequence responses.  This has been a long-standing bug which
+ made it very hard to use a single session object for multiple queries
+ to devices that are (sometimes) slow in responding. </li>
+
+<li> SNMP_Session.pm 0.62: Scoping problem with pretty_address()
+ fixed. </li>
+
+<li> Makefile.PL: New file which allows for easy installation
+ according to standard Perl convention.  Kudos to <a
+ href="mailto:clintdw at netcom.com">Clinton Wong</a>. </li>
+
+<li> SNMP_Session.pm 0.61: The sender address of the last response
+ received for a session is stored in
+ <em>$session->{'last_sender_addr'}</em>.  In connection with
+ broadcast or multicast addresses, this can be used to discover SNMP
+ agents listening to specific communities, as illustrated in
+ <tt>test/discover</tt>. </li>
+
+<li> SNMP_Session.pm 0.60: Added support for <a
+ href="#trap-recv">receiving SNMPv1 traps</a>. </li>
+
+<li> SNMP_session.pm 0.59: Added methods <tt>set_timeout</tt>,
+ <tt>set_retries</tt>, and <tt>set_backoff</tt> that can be used to
+ tune the retransmission algorithm.  Fixed a bug in <tt>map_table</tt>
+ that would cause an index of "0" to terminate the table walk. </li>
+
+<li> SNMP_util.pm 0.54: First version to be distributed with the
+ package, courtesy <a href="mailto:mcm at unx.sas.com">Mike
+ Mitchell</a>. </li>
+
+<li> BER.pm 0.58: Added <tt>encode_timeticks()</tt> subroutine.  This
+ was used in the sample script for <a href="#trap-send">sending
+ traps</a>, but was only defined in <tt>test/trap-test.pl</tt>.
+ Thanks to <a href="mailto:bergerg at att.net">Gary Berger</a> for
+ noticing this. </li>
+
+<li> BER.pm 0.57: Added <tt>encode_ip_address()</tt> subroutine on a
+ suggestion by <a href="mailto:mdiehn at mindspring.net">Mike
+ Diehn</a>. </li>
+
+<li> SNMP_Session 0.58: Added support for generating traps, courtesy
+ <a href="mailto:mcm at unx.sas.com">Mike Mitchell</a>, see <a
+ href="#trap-send">Sending Traps</a>. </li>
+
+<li> <tt>test/ber-test.pl</tt>: Added more test cases contributed by
+ <a href="mailto:mcm at unx.sas.com">Mike Mitchell</a>. </li>
+
+<li> SNMP_Session 0.57: table walking support See ``<a
+ href="#map-table-use">Walking Tables</a>'' below for how this is
+ used. </li>
+
+<li> BER 0.56: New <tt>encode_int()</tt> subroutine contributed by <a
+href="mailto:mcm at unx.sas.com">Mike Mitchell</a>.  Fixes incorrect
+encoding in the range +-2^15-2^23 and generalized to integers of any
+size. </li>
+
+<li> BER 0.55: Fix an arithmetic bug in the uptime
+pretty-printer. Kudos to <a href="mailto:niels at euro.net">Niels
+Bakker</a> for noticing this. </li>
+
+<li> SNMP_Session 0.56: Fix a bug which occurs when an error should be
+signaled while an SNMPv1_Session is being opened.  Noticed by <a
+href="mailto:dcox at lexmark.com">Dan Cox</a> and <a
+href="mailto:pakhomenko at gmd.de">Iouri Pakhomenko</a>. </li>
+
+<li> BER 0.52: Ignore a leading dot when encoding an OID.  This is to
+avoid trouble when people cut&paste OIDs from CMU/UCD SNMP, where
+a leading dot is used to mark a "fully qualified" OID. </li>
+
+<li> SNMP_Session 0.55: The SNMP_Session module no longer calls
+<tt>warn</tt> when the variable
+<tt>$SNMP_Session::suppress_warnings</tt> is set to non-zero (it is
+zero by default).  The error message from SNMP_Session can be
+retrieved as <tt>$SNMP_Session::errmsg</tt>. </li>
+
+<li> SNMP_Session 0.54, BER.pm 0.51: Errors in the BER module are now
+passed upwards by the SNMP_Session module.  Before this change, one
+could not distinguish malformed SNMP responses from no response at
+all.  The BER module now no longer calls die(), but returns undefined
+values. </li>
+
+<li> Added <tt>test/arp</tt> which prints the NetToMedia table from a
+remote host. </li>
+
+<li> SNMP_Session 0.53: Avoid passing numeric IP addresses to
+<tt>inet_ntoa()</tt>, by <a href="mailto:dan_needles at INS.COM">Daniel
+L. Needles</a> </li>
+
+<li> SNMP_Session 0.52: setRequest support based on code contributed
+by <a href="mailto:matter at media.mit.edu">Matthew Trunnell</a>.  See
+``<a href="#set-req-use">Set Requests</a>'' below for how this is
+used. </li>
+
+<li> SNMP_Session 0.51: Improved error messages by printing the
+session in error messages if possible, and the OID and error message
+whenever the agent sends back an error. </li>
+
+<li> SNMP_Session 0.50, BER 0.50: The <b>BER</b> and
+<b>SNMP_Session</b> modules both have version numbers according to the
+convention in <b>Exporter.pm</b>.  That is, you can now insist on a
+minimal version of the modules by saying e.g.
+
+<pre>
+use BER "0.50";
+use SNMP_Session "0.52";
+</pre>
+
+ The initial version numbers are 0.50 for both modules. </li>
+
+<li> The pretty printer should now print unsigned 32-bit values (such
+ as Counters and Gauges) correctly, i.e. values larger than
+ 2<sup><font size="-2">31</font></sup> are printed as large positive
+ numbers rather than negative numbers.  Note that this can cause
+ problems depending on how you handle the output of the pretty
+ printer, since those string representations of large numbers may not
+ be convertible to integers using <b>atoi()</b> or similar
+ functions. </li>
+
+<li> The subroutines in SNMP_Session never call <b>die()</b> anymore
+ if it encouters error situations.  Instead, they issue a warning and
+ return <b>undef</b>.  <a href="mailto:btr at iol.unh.edu">Brad
+ Ritchie</a> managed to convince me that library code should never
+ <b>die</b>.  Unfortunately I haven't revised <b>BER.pm</b> yet, so
+ subroutines related with BER transfer syntax encoding and decoding
+ may still <b>die</b>. </li>
+
+<li> The code has been cleaned up to use more of the standard
+ functionality of the Perl 5 <b>Socket.pm</b> module.  That should
+ have eliminated some potential portability problems (and delegated
+ responsability for potential bugs :-).  Note that this means that the
+ code requires Perl 5.002 or later. </li>
+
+<li> Both source files now make extensive use of <b>strict</b> for
+ better compile-time error checking.  Please notify me in case you
+ have any problems because of this. </li>
+
+<li> The library now attempts to retransmit queries for which no
+ reponse has been received during a given time.  The default
+ parameters for the retransmission logic have been discussed at length
+ in the <em>mrtg</em> mailing list, and seem to work quite well, both
+ against overloaded routers that simply drop some SNMP requests and
+ against routers that are behind slow or lossy links.  If you have
+ feedback on the default parameters, please drop me an e-mail. </li>
+
+<li> When I implemented the retransmission logic, I also fixed
+ handling of request IDs.  In older versions, the request ID was never
+ changed between reqeuest, which could lead to (late) resposes being
+ associated with the wrong request.  Now the request ID is incremented
+ for each request, and mismatching responses are ignored.  For
+ retransmissions, the request ID isn't changed.  If we did change it,
+ we could estimate response time and implement an adaptive
+ retransmission algorithm.  This has been left for further
+ study. </li>
+
+<li> Added code contributed by <em>mrtg</em> users:
+
+ <ul>
+
+  <li> Encoding of larger subids, by <a
+ href="mailto:sip00 at vg.swissptt.ch">Philippe Simonet</a> and <a
+ href="mailto:yhu at casc.com">Yufang HU</a> </li>
+
+  <li> Decoding <b>sysUpTime</b>, by <a
+ href="mailto:dlr at Bungi.com">Dave Rand</a> </li>
+
+  <li> Decoding longer (unsigned) integers, by <a
+ href="mailto:tobi at oetiker.ch">Tobias Oetiker</a> </li>
+
+  <li> Decoding longer strings, by <a
+ href="mailto:san at iem.pw.edu.pl">Andrzej Tobola</a> </li>
+
+  <li> More reasonable socket initialization, by <a
+ href="mailto:peters at dkrz.de">Heine Peters</a> </li>
+
+  <li> Correct integer BER-encoding, by <a
+ href="mailto:mcm at unx.sas.com">Mike Mitchell</a> </li>
+
+ </ul> </li>
+
+</ul>
+
+
+<HR>
+<ADDRESS>
+<!-- hhmts start -->20091228
+<!-- hhmts end -->
+<A HREF="http://www.switch.ch/misc/leinen/">
+ Simon Leinen <simon.leinen at switch.ch></A>
+
+<A HREF="http://validator.w3.org/"><IMG ALIGN=RIGHT BORDER=0
+     SRC="../../images/vh40.gif"
+     ALT="Valid HTML 4.0!" HEIGHT=31 WIDTH=88></A>
+
+</ADDRESS>
+
+</BODY>
+</HTML>
diff --git a/faq.html b/faq.html
new file mode 100644
index 0000000..9160dcd
--- /dev/null
+++ b/faq.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html> <head>
+<title>Frequently Asked Questions (FAQ) on SNMP_Session.pm</title>
+</head>
+
+<body bgcolor="#ffffff">
+<h1>Frequently Asked Questions (FAQ) on <tt>SNMP_Session.pm</tt></h1>
+
+<hr>
+
+<h2> <a name="snmpv3"></a>When will MRTG/<tt>SNMP_Session.pm</tt>
+support SNMPv3? </h2>
+
+<p> MRTG 2.13 and higher includes support for SNMPv3, using the
+<tt>Net::SNMP</tt> library.  See the sections about <a
+href="http://oss.oetiker.ch/mrtg/doc/mrtg-reference.en.html#enablesnmpv3"><tt>EnableSnmpV3</tt></a>,
+<a
+href="http://oss.oetiker.ch/mrtg/doc/mrtg-reference.en.html#target"><tt>Target</tt></a>,
+and <a
+href="http://oss.oetiker.ch/mrtg/doc/mrtg-reference.en.html#snmpoptions"><tt>SnmpOptions</tt></a>,
+in the MRTG Reference Manual. </p>
+
+<p> <tt>SNMP_Session.pm</tt> only supports SNMPv1 and
+(community-based) SNMPv2 ("SNMPv2c").  There are no plans to add
+SNMPv3 support to <tt>SNMP_Session.pm</tt>.  I'd recommend looking at
+<a href="http://search.cpan.org/dist/Net-SNMP/">Net::SNMP</a> instead,
+which has been supporting SNMPv3 for quite a while.  <a
+href="http://search.cpan.org/dist/Net-SNMP/">Net::SNMP</a> is also
+written much cleaner than <tt>SNMP_Session.pm</tt>, and like
+<tt>SNMP_Session.pm</tt> doesn't require any external binaries.  Some
+non-standard (crypto) Perl modules might be required to use SNMP's
+security features, however. </p>
+
+<p> A nice project would be to write a variant of
+<tt>SNMP_util.pm</tt> that sits on top of <a
+href="http://search.cpan.org/dist/Net-SNMP/">Net::SNMP</a> rather than
+on top of <tt>SNMP_Session.pm</tt>.  Since <a
+href="http://www.mrtg.org/">MRTG</a> uses the <tt>SNMP_util.pm</tt>
+layer, that should make it work with SNMPv3. </p>
+
+<p> However, since SNMPv3 doesn't have communities, MRTG's target
+syntax would have to be changed to accomodate SNMPv3 targets.  The
+current syntax of <samp>community{at}host[:options]</samp> would have
+to be modified (I think it should be changed, rather than further
+extended, because it already looks too ugly because of its multiple
+extensions. </p>
+
+<p> For the target syntax, one should probably look at the SNMP URI
+syntax (<a
+href="ftp://ftp.rfc-editor.org/in-notes/rfc4088.txt"><tt>RFC 4088</tt></a>. </p>
+
+<p> Anther issue with SNMPv3 is that it uses engine IDs, which are
+usually "discovered" using a well-defined discovery mechanism.  It
+would be good to cache discovered engine IDs somewhere, to avoid
+repeating the discovery process for every round of MRTG.  If MRTG is
+run from cron every five minutes, then such an engine ID cache should
+be written to disk between runs.  I'm not sure whether Net::SNMP
+already supports this.  If MRTG is run as a long-running "daemon"
+process, making the cache persistent might not be necessary. </p>
+
+<hr>
+<!-- hhmts start -->
+2007/10/13 19:15:00
+<!-- hhmts end -->
+<address>
+
+ <a href="http://www.switch.ch/misc/leinen/">
+  Simon Leinen <simon.leinen at switch.ch>
+ </a>
+</address>
+
+</body> </html>
diff --git a/index.html b/index.html
index 24fb5cd..5ed8cfd 100644
--- a/index.html
+++ b/index.html
@@ -7,7 +7,7 @@
 <DIV ALIGN=CENTER>
   <H1>SNMP support for Perl 5</H1>
 
-  <p> Copyright (c) 1995-2008, Simon Leinen<br>
+  <p> Copyright (c) 1995-2009, Simon Leinen<br>
   All rights reserved </p>
 
   <em> This program is free software; you can redistribute it under
@@ -22,11 +22,10 @@ HREF="mailto:simon.leinen at switch.ch">simon.leinen at switch.ch</A>></p>
 
 <p> This package contains Perl 5 modules <tt>SNMP_Session.pm</tt>,
 <tt>BER.pm</tt>, and <tt>SNMP_util.pm</tt> which, when used together,
-provide rudimentary access to remote <a
-href="http://www.switch.ch/misc/leinen/snmp/">SNMP</a> (v1/v2)
+provide rudimentary access to remote SNMP (v1/v2)
 agents. </p>
 
-<p> <b> Download it from <a href="http://www.switch.ch/misc/leinen/snmp/perl/dist/"><tt>http://www.switch.ch/misc/leinen/snmp/perl/dist/</tt></a></b> </p>
+<p> <b> Download it from <a href="http://code.google.com/p/snmp-session/downloads/"><tt>http://code.google.com/p/snmp-session/downloads/</tt></a></b> </p>
 
 <!--            <iframe marginwidth="0" marginheight="0" width="120" height="240" scrolling="no" frameborder="0" src="http://rcm.amazon.com/e/cm?o=1&l=as1&f=ifr&t=simonleinensbook&p=8&asins=0596000200&IS2=1&lt1=_blank"><MAP NAME="boxmap-p8"><AREA SHAPE="RECT" COORDS="14, 200, 103, 207" HREF="http://rcm.amazon.com/e/cm/privacy-policy.html?o=1" ><AREA COORDS="0,0,10000,10000" HREF="http://www.amazon.com/exec/obidos/redirect-home/simonleinensbook" ></map><img src="http://rcm-images.amaz [...]
 
@@ -76,301 +75,10 @@ remainder of this page desribes the low-level API in
 href="changes.html">changes.html</a></samp> file packaged with this
 system. </p>
 
-<H2>Usage</H2>
+<h2> Usage </h2>
 
-<P> The basic usage of these routines works like this: </P>
-
-<PRE>
-use BER;
-require 'SNMP_Session.pm';
-
-# Set $host to the name of the host whose SNMP agent you want
-# to talk to.  Set $community to the community name under
-# which you want to talk to the agent.	Set port to the UDP
-# port on which the agent listens (usually 161).
-
-$session = SNMP_Session->open ($host, $community, $port)
-    or die "couldn't open SNMP session to $host";
-
-# Set $oid1, $oid2... to the BER-encoded OIDs of the MIB
-# variables you want to get.
-
-if ($session->get_request_response ($oid1, $oid2, ...)) {
-    ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
-
-    while ($bindings ne '') {
-	($binding,$bindings) = &decode_sequence ($bindings);
-	($oid,$value) = &decode_by_template ($binding, "%O%@");
-	print &pretty_print ($oid)," => ", &pretty_print ($value), "\n";
-    }
-} else {
-    die "No response from agent on $host";
-}
-</PRE>
-
-<H2>Encoding OIDs</H2>
-
-<P> In order to BER-encode OIDs, you can use the function
-<B>BER::encode_oid</B>.  It takes (a vector of) numeric subids as an
-argument.  For example, </P>
-
-<PRE>
-use BER;
-encode_oid (1, 3, 6, 1, 2, 1, 1, 1, 0)
-</PRE>
-
-<P> will return the BER-encoded OID for the <B>sysDescr.0</B>
- (1.3.6.1.2.1.1.1.0) instance of <A
- HREF="ftp://ftp.isi.edu/in-notes/rfc1213.txt">MIB-2</A>. </P>
-
-<H2>Decoding the results</H2>
-
-<P> When get_request_response returns success, you must decode the
- response PDU from the remote agent.  The function
- <B>decode_get_response</B> can be used to do this.  It takes a
- get-response PDU, checks its syntax and returns the <EM>bindings</EM>
- part of the PDU.  This is where the remote agent actually returns the
- values of the variables in your query. </P>
-
-<p> You should iterate over the individual bindings in this
- <EM>bindings</EM> part and extract the value for each variable.  In
- the example above, the returned bindings are simply printed using the
- <B>BER::pretty_print</B> function. </p>
-
-<p> For better readability of the OIDs, you can also use the following
- idiom, where the <samp>%pretty_oids</samp> hash maps BER-encoded
- numerical OIDs to symbolic OIDs.  Note that this simple-minded
- mapping only works for response OIDs that exactly match known OIDs,
- so it's unsuitable for table walking (where the response OIDs include
- an additional row index). </p>
-
-<pre>
-<font color="#c00000">%ugly_oids = qw(sysDescr.0	1.3.6.1.2.1.1.1.0
-		sysContact.0	1.3.6.1.2.1.1.4.0);
-foreach (keys %ugly_oids) {
-    $ugly_oids{$_} = encode_oid (split (/\./, $ugly_oids{$_}));
-    $pretty_oids{$ugly_oids{$_}} = $_;
-}</font>
-...
-if ($session->get_request_response ($ugly_oids{'sysDescr.0'},
-				    $ugly_oids{'sysContact.0'})) {
-    ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
-    while ($bindings ne '') {
-	($binding,$bindings) = &decode_sequence ($bindings);
-	($oid,$value) = &decode_by_template ($binding, "%O%@");
-	print <font color="#c00000">$pretty_oids{$oid},</font>" => ",
-	      &pretty_print ($value), "\n";
-    }
-} ...
-</pre>
-
-<H2><a name="set-req-use">Set Requests</a></H2>
-
-<p> Set requests are generated much like get or getNext requests are,
- with the exception that you have to specify not just OIDs, but also
- the values the variables should be set to.  Every binding is passed
- as a reference to a two-element array, the first element being the
- encoded OID and the second one the encoded value.  See the
- <tt>test/set-test.pl</tt> script for an example, in particular the
- subroutine <tt>snmpset</tt>. </p>
-
-<H2><a name="map-table-use">Walking Tables</a></H2>
-
-<p> Beginning with version 0.57 of <tt>SNMP_Session.pm</tt>, there is API
- support for walking tables.  The <tt>map_table</tt> method can be
- used for this as follows: </p>
-
-<pre>
-sub walk_function ($$$) {
-  my ($index, $val1, $val3) = @_;
-  ...
-}
-
-...
-$columns = [$base_oid1, $base_oid3];
-$n_rows = $session->map_table ($columns, \&walk_function);
-</pre>
-
-<p> The <em>columns</em> argument must be a reference to a list of
- OIDs for table columns sharing the same index.  The method will
- traverse the table and call the <em>walk_function</em> for each row.
- The arguments for these calls will be:
-
-<ol>
-
-<li> the <em>row index</em> as a partial OID in dotted notation,
- e.g. "1.3", or "10.0.1.34".
-
-<li> the values of the requested table columns in that row, in
- BER-encoded form.  If you want to use the standard
- <tt>pretty_print</tt> subroutine to decode the values, you can use
- the following idiom:
-
-<pre>
-  grep (defined $_ && ($_=pretty_print $_), ($val1, $val3));
-</pre>
-
-</ol>
-
-<H3><a name="map-table-4-use">Walking Tables With
-<tt>get-bulk</tt></a></H3>
-
-<p> Since version 0.67, <tt>SNMP_Session</tt> uses a different
-<tt>get_table</tt> implementation for <tt>SNMPv2c_Session</tt>s.  This
-version uses the ``powerful <tt>get-bulk</tt> operator'' to retrieve
-many table rows with each request.  In general, this will make table
-walking much faster under SNMPv2c, especially when round-trip times to
-the agent are long. </p>
-
-<p> There is one difficulty, however: With <tt>get-bulk</tt>, a
-management application can specify the maximum number of rows to
-return in a single response.  <tt>SNMP_Session.pm</tt> provides a new
-function, <tt>map_table_4</tt>, in which this <tt>maxRepetitions</tt>
-value can be specified explicitly. </p>
-
-<p> For maximum efficiency, it should be set to a value that is one
-greater than the number of rows in the table.  If it is smaller, then
-<tt>map_table</tt> will use more request/response cycles than
-necessary; if it is bigger, the agent will have to compute variable
-bindings beyond the end of the table (which <tt>map_table</tt> will
-throw away). </p>
-
-<p> Of course it is usually impossible to know the size of the table
-in advance.  If you don't specify <tt>maxRepetitions</tt> when walking
-a table, then <tt>map_table</tt> will use a per-session default
-(<tt>$session->default_max_repetitions</tt>).  The default value for
-this default is 12. </p>
-
-<p> If you walk a table multiple times, and the size of the table is
-relatively stable, you should use the return value of
-<tt>map_table</tt> (which is the number of rows it has encountered) to
-compute the next value of <tt>maxRepetitions</tt>.  Remember to add
-one so that <tt>map_table</tt> notices when the table is finished!
-</p>
-
-<p> Note that for really big tables, this doesn't make a big
-difference, since the table won't fit in a single response packet
-anyway. </p>
-
-<H2><a name="trap-send">Sending Traps</a></H2>
-
-<p> To send a trap, you have to open an SNMP session to the trap
- receiver.  Usually this is a process listening to UDP port 162 on a
- network management station.  Then you can use the
- <tt>trap_request_send</tt> method to encode and send SNMPv1 traps.
- There is no way to find out whether the trap was actually received at
- the management station - SNMP traps are fundamentally unreliable.
-</p>
-
-<p> When constructing an SNMPv1 trap, you must provide
-
-<ul>
-
-<li> the "enterprise" Object Identifier for the entity that generates
- the trap
-
-<li> your IP address
-
-<li> the generic trap type
-
-<li> the specific trap type
-
-<li> the sysUpTime at the time of trap generation
-
-<li> a sequence (may be empty) of variable bindings further describing 
- the trap.
-
-</ul>
-
-<p> For SNMPv2 traps, you need:
-
-<ul>
-
-<li> the trap's OID
-
-<li> the sysUpTime at the time of trap generation
-
-<li> the bindings list as above
-
-</ul>
-
-<p> For SNMPv2 traps, the uptime and trap OID are encoded as bindings
-which are added to the front of the other bindings you provide. </p>
-
-<p> Here is a short example: </p>
-
-<pre>
-my $trap_receiver = "netman.noc";
-my $trap_community = "SNMP_Traps";
-my $trap_session = $version eq '1'
-    ? SNMP_Session->open ($trap_receiver, $trap_community, 162)
-    : SNMPv2c_Session->open ($trap_receiver, $trap_community, 162);
-my $myIpAddress = ...;
-my $start_time = time;
-
-...
-
-sub link_down_trap ($$) {
-  my ($if_index, $version) = @_;
-  my $genericTrap = 2;		# linkDown
-  my $specificTrap = 0;
-  my @ifIndexOID = ( 1,3,6,1,2,1,2,2,1,1 );
-  my $upTime = int ((time - $start_time) * 100.0);
-  my @myOID = ( 1,3,6,1,4,1,2946,0,8,15 );
-
-  warn "Sending trap failed"
-    unless ($version eq '1')
-	? $trap_session->trap_request_send (encode_oid (@myOID),
-					    encode_ip_address ($myIpAddress),
-					    encode_int ($genericTrap),
-					    encode_int ($specificTrap),
-					    encode_timeticks ($upTime),
-					    [encode_oid (@ifIndex_OID,$if_index),
-					     encode_int ($if_index)],
-					    [encode_oid (@ifDescr_OID,$if_index),
-					     encode_string ("foo")])
-	    : $trap_session->v2_trap_request_send (\@linkDown_OID, $upTime,
-						   [encode_oid (@ifIndex_OID,$if_index),
-						    encode_int ($if_index)],
-						   [encode_oid (@ifDescr_OID,$if_index),
-						    encode_string ("foo")]);
-}
-</pre>
-
-<H2><a name="trap-recv">Receiving Traps</a></H2>
-
-<p> Since version 0.60, SNMP_Session.pm supports the receipt and
- decoding of SNMPv1 trap requests.  Since version 0.75, SNMPv2 Trap
- PDUs are also recognized. </p>
-
-<p> To receive traps, you have to create a special SNMP session that
- passively listens on the SNMP trap transport address (usually UDP
- port 162).  Then you can receive traps (actually, SNMPv1 traps,
- SNMPv2 traps, and SNMPv2 informs) using the <tt>receive_trap</tt>
- method and decode them using <tt>decode_trap_request</tt>.  The
- <em>enterprise</em>, <em>agent</em>, <em>generic</em>,
- <em>specific</em> and <em>sysUptime</em> return values are only
- defined for SNMPv1 traps.  In SNMPv2 traps and informs, the
- equivalent information is contained in the bindings.
-</p>
-
-<pre>
-my $trap_session = SNMPv1_Session->open_trap_session ()
-  or die "cannot open trap session";
-my ($trap, $sender_addr, $sender_port) = $trap_session->receive_trap ()
-  or die "cannot receive trap";
-my ($community, $enterprise, $agent,
-    $generic, $specific, $sysUptime, $bindings)
-  = $trap_session->decode_trap_request ($trap)
-    or die "cannot decode trap received"
-...
-my ($binding, $oid, $value);
-while ($bindings ne '') {
-    ($binding,$bindings) = &decode_sequence ($bindings);
-    ($oid, $value) = decode_by_template ($binding, "%O%@");
-    print BER::pretty_oid ($oid)," => ",pretty_print ($value),"\n";
-}
-</pre>
+See the <b>EXAMPLES</b> section in the POD documentation
+of <tt>SNMP_Session.pm</tt>.
 
 <H2>Future Plans</H2>
 
@@ -392,8 +100,7 @@ while ($bindings ne '') {
 
 <HR>
 <ADDRESS>
-<!-- hhmts start -->
-20080402
+<!-- hhmts start -->20091228
 <!-- hhmts end -->
 <A HREF="http://www.switch.ch/misc/leinen/">
  Simon Leinen <simon.leinen at switch.ch></A>
diff --git a/lib/BER.pm b/lib/BER.pm
index 739b48d..5298735 100644
--- a/lib/BER.pm
+++ b/lib/BER.pm
@@ -2,7 +2,7 @@
 ######################################################################
 ### BER (Basic Encoding Rules) encoding and decoding.
 ######################################################################
-### Copyright (c) 1995-2008, Simon Leinen.
+### Copyright (c) 1995-2009, Simon Leinen.
 ###
 ### This program is free software; you can redistribute it under the
 ### "Artistic License 2.0" included in this distribution
@@ -12,27 +12,32 @@
 ### structures using the Basic Encoding Rules (BER).  Only the subset
 ### necessary for SNMP is implemented.
 ######################################################################
-### Created by:  Simon Leinen  <simon at switch.ch>
-###
-### Contributions and fixes by:
-###
-### Andrzej Tobola <san at iem.pw.edu.pl>:  Added long String decode
-### Tobias Oetiker <tobi at oetiker.ch>:  Added 5 Byte Integer decode ...
-### Dave Rand <dlr at Bungi.com>:  Added SysUpTime decode
-### Philippe Simonet <sip00 at vg.swissptt.ch>:  Support larger subids
-### Yufang HU <yhu at casc.com>:  Support even larger subids
-### Mike Mitchell <Mike.Mitchell at sas.com>: New generalized encode_int()
-### Mike Diehn <mdiehn at mindspring.net>: encode_ip_address()
-### Rik Hoorelbeke <rik.hoorelbeke at pandora.be>: encode_oid() fix
-### Brett T Warden <wardenb at eluminant.com>: pretty UInteger32
-### Bert Driehuis <driehuis at playbeing.org>: Handle SNMPv2 exception codes
-### Jakob Ilves (/IlvJa) <jakob.ilves at oracle.com>: PDU decoding
-### Jan Kasprzak <kas at informatics.muni.cz>: Fix for PDU syntax check
-### Milen Pavlov <milen at batmbg.com>: Recognize variant length for ints
+### Create by: See AUTHORS below
 ######################################################################
 
 package BER;
 
+=head1 NAME
+
+BER
+
+=head1 SYNOPSIS
+
+    use BER;
+    $encoded = encode_sequence (encode_int (123), encode_string ("foo"));
+    ($i, $s) = decode_by_template ($encoded, "%{%i%s");
+    # $i will now be 123, $s the string "foo".
+
+=head1 DESCRIPTION
+
+This is a simple library to encode and decode data using the Basic
+Encoding Rules (BER) of Abstract Syntax Notation One (ASN.1).  It does
+not claim to be a complete implementation of the standard, but
+implements enough of the BER standard to encode and decode SNMP
+messages.
+
+=cut
+
 require 5.002;
 
 use strict;
@@ -40,7 +45,7 @@ use vars qw(@ISA @EXPORT $VERSION $pretty_print_timeticks
 	    %pretty_printer %default_printer $errmsg);
 use Exporter;
 
-$VERSION = '1.05';
+$VERSION = '1.14';
 
 @ISA = qw(Exporter);
 
@@ -56,17 +61,31 @@ $VERSION = '1.05';
 	     encoded_oid_prefix_p errmsg
 	     register_pretty_printer unregister_pretty_printer);
 
-### Variables
+=head1 VARIABLES
+
+=cut
+
+=head2 $pretty_print_timeticks (default: 1)
+
+If non-zero (the default), C<pretty_print> will convert TimeTicks to
+"human readable" strings containing days, hours, minutes and seconds.
+
+If the variable is zero, C<pretty_print> will simply return an
+unsigned integer representing hundredths of seconds.  If you prefer
+this, bind C<$pretty_print_timeticks> to zero.
+
+=cut
 
-## Bind this to zero if you want to avoid that TimeTicks are converted
-## into "human readable" strings containing days, hours, minutes and
-## seconds.
-##
-## If the variable is zero, pretty_print will simply return an
-## unsigned integer representing hundredths of seconds.
-##
 $pretty_print_timeticks = 1;
 
+=head2 $errmsg - error message from last failed operation.
+
+When they encounter errors, the routines in this module will generally
+return C<undef>) and leave an informative error message in
+C<$errmsg>).
+
+=cut
+
 ### Prototypes
 sub encode_header ($$);
 sub encode_int_0 ();
@@ -110,6 +129,9 @@ sub template_error ($$$);
 
 sub version () { $VERSION; }
 
+=head1 METHODS
+=cut
+
 ### Flags for different types of tags
 
 sub universal_flag	{ 0x00 }
@@ -173,36 +195,74 @@ BEGIN {
 
 sub encode_header ($$) {
     my ($type,$length) = @_;
-    return pack ("C C", $type, $length) if $length < 128;
-    return pack ("C C C", $type, long_length | 1, $length) if $length < 256;
-    return pack ("C C n", $type, long_length | 2, $length) if $length < 65536;
+    return pack ("CC", $type, $length) if $length < 128;
+    return pack ("CCC", $type, long_length | 1, $length) if $length < 256;
+    return pack ("CCn", $type, long_length | 2, $length) if $length < 65536;
     return error ("Cannot encode length $length yet");
 }
 
+=head2 encode_int_0() - encode the integer 0.
+
+This is functionally identical to C<encode_int(0)>.
+
+=cut
+
 sub encode_int_0 () {
-    return pack ("C C C", 2, 1, 0);
+    return pack ("CCC", 2, 1, 0);
 }
 
+=head2 encode_int() - encode an integer using the generic
+    "integer" type tag.
+
+=cut
+
 sub encode_int ($) {
     return encode_intlike ($_[0], int_tag);
 }
 
+=head2 encode_uinteger32() - encode an integer using the SNMP
+    UInteger32 tag.
+
+=cut
+
 sub encode_uinteger32 ($) {
     return encode_intlike ($_[0], snmp_uinteger32_tag);
 }
 
+=head2 encode_counter32() - encode an integer using the SNMP
+    Counter32 tag.
+
+=cut
+
 sub encode_counter32 ($) {
     return encode_intlike ($_[0], snmp_counter32_tag);
 }
 
+=head2 encode_counter64() - encode an integer using the SNMP
+    Counter64 tag.
+
+=cut
+
 sub encode_counter64 ($) {
     return encode_intlike ($_[0], snmp_counter64_tag);
 }
 
+=head2 encode_gauge32() - encode an integer using the SNMP Gauge32
+    tag.
+
+=cut
+
 sub encode_gauge32 ($) {
     return encode_intlike ($_[0], snmp_gauge32_tag);
 }
 
+### encode_intlike ($int, $tag)
+###
+### Generic function to BER-encode an arbitrary integer using a given
+### tag.  This function can handle large integers.  It doesn't check
+### whether the integer is in a suitable range for the given type tag
+### - that is expected to be done by the caller.
+###
 sub encode_intlike ($$) {
     my ($int, $tag)=@_;
     my ($sign, $val, @vals);
@@ -226,6 +286,12 @@ sub encode_intlike ($$) {
     }
 }
 
+=head2 encode_oid() - encode an object ID, passed as a list of
+    sub-IDs.
+
+    $encoded = encode_oid (1,3,6,1,...);
+=cut
+
 sub encode_oid (@) {
     my @oid = @_;
     my ($result,$subid);
@@ -284,7 +350,27 @@ sub encode_oid (@) {
     encode_header (object_id_tag, length $result).$result;
 }
 
+=head2 encode_null() - encode a null object.
+
+This is used e.g. in binding lists for variables that don't have a
+value (yet)
+
+=cut
+
 sub encode_null () { encode_header (null_tag, 0); }
+
+=head2 encode_sequence()
+
+=head2 encode_tagged_sequence()
+
+    $encoded = encode_sequence (encoded1, encoded2, ...);
+    $encoded = encode_tagged_sequence (tag, encoded1, encoded2, ...);
+
+Take already encoded values, and extend them to an encoded sequence.
+C<encoded_sequence> uses the generic sequence tag, while with
+C<encode_tagged_sequence> you can specify your own tag.
+=cut
+
 sub encode_sequence (@) { encode_tagged_sequence (sequence_tag, @_); }
 
 sub encode_tagged_sequence ($@) {
@@ -295,11 +381,22 @@ sub encode_tagged_sequence ($@) {
     return encode_header ($tag | constructor_flag, length $result).$result;
 }
 
+=head2 encode_string() - encode a Perl string as an OCTET STRING.
+=cut
+
 sub encode_string ($) {
     my ($string)=@_;
     return encode_header (octet_string_tag, length $string).$string;
 }
 
+=head2 encode_ip_address() - encode an IPv4 address.
+
+This can either be passed as a four-octet sequence in B<network byte
+order>, or as a text string in dotted-quad notation,
+e.g. "192.0.2.234".
+
+=cut
+
 sub encode_ip_address ($) {
     my ($addr)=@_;
     my @octets;
@@ -315,6 +412,14 @@ sub encode_ip_address ($) {
     }
 }
 
+=head2 encode_timeticks() - encode an integer as a C<TimeTicks>
+    object.
+
+The integer should count hundredths of a second since the epoch
+defined by C<sysUpTime.0>.
+
+=cut
+
 sub encode_timeticks ($) {
   my ($tt) = @_;
   return encode_intlike ($tt, snmp_timeticks_tag);
@@ -322,13 +427,22 @@ sub encode_timeticks ($) {
 
 #### Decoding
 
+=head2 pretty_print() - convert an encoded byte sequence into
+    human-readable form.
+
+This function can be extended by registering pretty-printing methods
+for specific type codes.  Most BER type codes used in SNMP already
+have such methods pre-registered by default.  See
+C<register_pretty_printer> for how new methods can be added.
+
+=cut
+
 sub pretty_print ($) {
     my ($packet) = @_;
     return undef unless defined $packet;
     my $result = ord (substr ($packet, 0, 1));
     if (exists ($pretty_printer{$result})) {
-	my $c_ref = $pretty_printer{$result};
-	return &$c_ref ($packet);
+	return &{$pretty_printer{$result}} ($packet);
     }
     return ($pretty_print_timeticks
 	    ? pretty_uptime ($packet)
@@ -524,12 +638,21 @@ sub pretty_generic_sequence ($) {
 	$first_elem = '' if $first_elem;
     }
     return $pretty_result;
-}    
+}
+
+=head2 hex_string() - convert OCTET STRING to hexadecimal notation.
+
+=cut
 
 sub hex_string ($) {
-    &hex_string_of_type ($_[0], octet_string_tag);
+    hex_string_of_type ($_[0], octet_string_tag);
 }
 
+=head2 hex_string_of_type() - convert octet string to hex, and check
+type against given tag.
+
+=cut
+
 sub hex_string_of_type ($$) {
     my ($pdu, $wanted_type) = @_;
     my ($length);
@@ -582,6 +705,44 @@ sub decode_generic_tlv ($) {
     @result;
 }
 
+=head2 decode_by_template() - decode complex object according to a
+    template.
+
+    ($var1, ...) = decode_by_template ($pdu, $template, ...);
+
+The template can contain various %X directives.  Some directives
+consume additional arguments following the template itself.  Most
+directives will cause values to be returned.  The values are returned
+as a sequence in the order of the directives that generated them.
+
+=over 4
+
+=item %{ - decode sequence.
+
+This doesn't assign any return value, just checks and skips the
+tag/length fields of the sequence.  By default, the tag should be the
+generic sequence tag, but a tag can also be specified in the
+directive.  The directive can either specify the tag as a prefix,
+e.g. C<%99{> will require a sequence tag of 99, or if the directive is
+given as C<%*{>, the tag will be taken from the next argument.
+
+=item %s - decode string
+
+=item %i - decode integer
+
+=item %u - decode unsigned integer
+
+=item %O - decode Object ID (OID)
+
+=item %A - decode IPv4 address
+
+=item %@ - assigns the remaining undecoded part of the PDU to the next
+    return value.
+
+=back
+
+=cut
+
 sub decode_by_template {
     my ($pdu) = shift;
     local ($_) = shift;
@@ -606,7 +767,7 @@ sub decode_by_template_2 {
 		if $template_debug;
 	    $_ = substr ($_,1);
 	    ++$template_index;
-	    if (($expected) = /^(\d*|\*)\{(.*)/) {
+	    if (($expected) = /^(\d+|\*|)\{(.*)/) {
 		## %{
 		$template_index += length ($expected) + 1;
 		print STDERR "%{\n" if $template_debug;
@@ -622,8 +783,8 @@ sub decode_by_template_2 {
 				      $template,
 				      $template_index)
 		    unless (ord (substr ($pdu, 0, 1)) == $expected);
-		(($length,$pdu) = decode_length ($pdu, 1))
-		    || return template_error ("cannot read length",
+		($length,$pdu) = decode_length ($pdu, 1)
+		    or return template_error ("cannot read length",
 					      $template, $template_index);
 		return template_error ("Expected length $length, got ".length $pdu ,
 				      $template, $template_index)
@@ -632,8 +793,8 @@ sub decode_by_template_2 {
 		## %s
 		$template_index += length ($expected) + 1;
 		($expected = shift) if $expected eq '*';
-		(($read,$pdu) = decode_string ($pdu))
-		    || return template_error ("cannot read string",
+		($read, $pdu) = decode_string ($pdu)
+		    or return template_error ("cannot read string",
 					      $template, $template_index);
 		print STDERR "%s => $read\n" if $template_debug;
 		if ($expected eq '') {
@@ -668,19 +829,19 @@ sub decode_by_template_2 {
 		## %O
 		$template_index += 1;
 		$_ = $1;
-		(($read,$pdu) = decode_oid ($pdu))
-		  || return template_error ("cannot read OID",
-					    $template, $template_index);
+		($read,$pdu) = decode_oid ($pdu)
+		    or return template_error ("cannot read OID",
+					      $template, $template_index);
 		print STDERR "%O => ".pretty_oid ($read)."\n"
 		    if $template_debug;
 		push @results, $read;
-	    } elsif (($expected,$rest) = /^(\d*|\*|)i(.*)/) {
+	    } elsif (($expected,$rest) = /^(\d+|\*|)i(.*)/) {
 		## %i
 		$template_index += length ($expected) + 1;
 		print STDERR "%i\n" if $template_debug;
 		$_ = $rest;
-		(($read,$pdu) = decode_int ($pdu))
-		  || return template_error ("cannot read int",
+		($read, $pdu) = decode_int ($pdu)
+		    or return template_error ("cannot read int",
 					    $template, $template_index);
 		if ($expected eq '') {
 		    push @results, $read;
@@ -696,8 +857,8 @@ sub decode_by_template_2 {
 		$template_index += 1;
 		print STDERR "%u\n" if $template_debug;
 		$_ = $rest;
-		(($read,$pdu) = decode_unsignedlike ($pdu))
-		  || return template_error ("cannot read uptime",
+		($read, $pdu) = decode_unsignedlike ($pdu)
+		    or return template_error ("cannot read uptime",
 					    $template, $template_index);
 		push @results, $read;
 	    } elsif (/^\@(.*)/) {
@@ -727,6 +888,16 @@ sub decode_by_template_2 {
     @results;
 }
 
+=head2 decode_sequence() - Split sequence into components.
+
+    ($first, $rest) = decode_sequence ($pdu);
+
+Checks whether the PDU has a sequence type tag and a plausible length
+field.  Splits the initial element off the list, and returns both this
+and the remainder of the PDU.
+
+=cut
+
 sub decode_sequence ($) {
     my ($pdu) = @_;
     my ($result);
@@ -809,9 +980,13 @@ sub decode_length ($@) {
     @result;
 }
 
-# This takes a hashref that specifies functions to call when
-# the specified value type is being printed.  It returns the
-# number of functions that were registered.
+=head2 register_pretty_printer() - register pretty-printing methods for
+    typecodes.
+
+This function takes a hashref that specifies functions to call when
+the specified value type is being printed.  It returns the number of
+functions that were registered.
+=cut
 sub register_pretty_printer($)
 {
     my ($h_ref) = shift;
@@ -911,3 +1086,48 @@ sub template_error ($$$) {
 }
 
 1;
+
+=head1 AUTHORS
+
+Created by:  Simon Leinen  E<lt>simon.leinen at switch.chE<gt>
+
+Contributions and fixes by:
+
+=over
+
+=item Andrzej Tobola E<lt>san at iem.pw.edu.plE<gt>:  Added long String decode
+
+=item Tobias Oetiker E<lt>tobi at oetiker.chE<gt>:  Added 5 Byte Integer decode ...
+
+=item Dave Rand E<lt>dlr at Bungi.comE<gt>:  Added C<SysUpTime> decode
+
+=item Philippe Simonet E<lt>sip00 at vg.swissptt.chE<gt>:  Support larger subids
+
+=item Yufang HU E<lt>yhu at casc.comE<gt>:  Support even larger subids
+
+=item Mike Mitchell E<lt>Mike.Mitchell at sas.comE<gt>: New generalized C<encode_int()>
+
+=item Mike Diehn E<lt>mdiehn at mindspring.netE<gt>: C<encode_ip_address()>
+
+=item Rik Hoorelbeke E<lt>rik.hoorelbeke at pandora.beE<gt>: C<encode_oid()> fix
+
+=item Brett T Warden E<lt>wardenb at eluminant.comE<gt>: pretty C<UInteger32>
+
+=item Bert Driehuis E<lt>driehuis at playbeing.orgE<gt>: Handle SNMPv2 exception codes
+
+=item Jakob Ilves (/IlvJa) E<lt>jakob.ilves at oracle.comE<gt>: PDU decoding
+
+=item Jan Kasprzak E<lt>kas at informatics.muni.czE<gt>: Fix for PDU syntax check
+
+=item Milen Pavlov E<lt>milen at batmbg.comE<gt>: Recognize variant length for ints
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright (c) 1995-2009, Simon Leinen.
+
+This program is free software; you can redistribute it under the
+"Artistic License 2.0" included in this distribution (file "Artistic").
+
+=cut
diff --git a/lib/SNMP_Session.pm b/lib/SNMP_Session.pm
index 4b9ccf9..9b624eb 100644
--- a/lib/SNMP_Session.pm
+++ b/lib/SNMP_Session.pm
@@ -2,47 +2,38 @@
 ######################################################################
 ### SNMP Request/Response Handling
 ######################################################################
-### Copyright (c) 1995-2008, Simon Leinen.
+### Copyright (c) 1995-2009, Simon Leinen.
 ###
 ### This program is free software; you can redistribute it under the
 ### "Artistic License 2.0" included in this distribution
 ### (file "Artistic").
 ######################################################################
-### The abstract class SNMP_Session defines objects that can be used
-### to communicate with SNMP entities.  It has methods to send
-### requests to and receive responses from an agent.
-###
-### Two instantiable subclasses are defined:
-### SNMPv1_Session implements SNMPv1 (RFC 1157) functionality
-### SNMPv2c_Session implements community-based SNMPv2.
-######################################################################
-### Created by:  Simon Leinen  <simon at switch.ch>
-###
-### Contributions and fixes by:
-###
-### Matthew Trunnell <matter at media.mit.edu>
-### Tobias Oetiker <tobi at oetiker.ch>
-### Heine Peters <peters at dkrz.de>
-### Daniel L. Needles <dan_needles at INS.COM>
-### Mike Mitchell <mcm at unx.sas.com>
-### Clinton Wong <clintdw at netcom.com>
-### Alan Nichols <Alan.Nichols at Ebay.Sun.COM>
-### Mike McCauley <mikem at open.com.au>
-### Andrew W. Elble <elble at icculus.nsg.nwu.edu>
-### Brett T Warden <wardenb at eluminant.com>: pretty UInteger32
-### Michael Deegan <michael at cnspc18.murdoch.edu.au>
-### Sergio Macedo <macedo at tmp.com.br>
-### Jakob Ilves (/IlvJa) <jakob.ilves at oracle.com>: PDU capture
-### Valerio Bontempi <v.bontempi at inwind.it>: IPv6 support
-### Lorenzo Colitti <lorenzo at colitti.com>: IPv6 support
-### Philippe Simonet <Philippe.Simonet at swisscom.com>: Export avoid...
-### Luc Pauwels <Luc.Pauwels at xalasys.com>: use_16bit_request_ids
-### Andrew Cornford-Matheson <andrew.matheson at corenetworks.com>: inform
-### Gerry Dalton <gerry.dalton at consolidated.com>: strict subs bug
-### Mike Fischer <mlf2 at tampabay.rr.com>: pass MSG_DONTWAIT to recv()
+### Created by: See AUTHORS below
 ######################################################################
 
-package SNMP_Session;		
+package SNMP_Session;
+
+=head1 NAME
+
+SNMP_Session - SNMPv1/v2 Protocol Handling
+
+=head1 SYNOPSIS
+
+    use SNMP_Session;
+    $session = SNMP_Session->open ($host, $community, $port)
+	or die "couldn't open SNMP session to $host";
+    if ($session->get_request_response ($oid1, $oid2, ...)) {
+	($bindings) = $session->decode_get_response ($session->{pdu_buffer});
+	while ($bindings ne '') {
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    ($oid,$value) = decode_by_template ($binding, "%O%@");
+	    print pretty_print ($oid)," => ", pretty_print ($value), "\n";
+	}
+    } else {
+	die "No response from agent on $host";
+    }
+
+=cut
 
 require 5.002;
 
@@ -62,62 +53,65 @@ sub map_table_start_end ($$$$$$);
 sub index_compare ($$);
 sub oid_diff ($$);
 
-$VERSION = '1.13';
+$VERSION = '1.14';
 
 @ISA = qw(Exporter);
 
 @EXPORT = qw(errmsg suppress_warnings index_compare oid_diff recycle_socket ipv6available);
 
+=head1 VARIABLES
+
+The C<default_...> variables all specify default values that are used
+for C<SNMP_Session> objects when no other value is specified.  These
+values can be overridden on a per-session basis, for example by
+passing additional arguments to the constructor.
+
+=cut
+
 my $default_debug = 0;
 
-### Default initial timeout (in seconds) waiting for a response PDU
-### after a request is sent.  Note that when a request is retried, the
-### timeout is increased by BACKOFF (see below).
+### Default values for the TIMEOUT, RETRIES, and BACKOFF slots of
+### SNMP_Session objects - see their documentation below.
 ###
 my $default_timeout = 2.0;
-
-### Default number of attempts to get a reply for an SNMP request.  If
-### no response is received after TIMEOUT seconds, the request is
-### resent and a new response awaited with a longer timeout (see the
-### documentation on BACKOFF below).  The "retries" value should be at
-### least 1, because the first attempt counts, too (the name "retries"
-### is confusing, sorry for that).
-###
 my $default_retries = 5;
-
-### Default backoff factor for SNMP_Session objects.  This factor is
-### used to increase the TIMEOUT every time an SNMP request is
-### retried.
-###
 my $default_backoff = 1.0;
 
-### Default value for maxRepetitions.  This specifies how many table
-### rows are requested in getBulk requests.  Used when walking tables
-### using getBulk (only available in SNMPv2(c) and later).  If this is
-### too small, then a table walk will need unnecessarily many
-### request/response exchanges.  If it is too big, the agent may
-### compute many variables after the end of the table.  It is
-### recommended to set this explicitly for each table walk by using
-### map_table_4().
-###
+=head2 $default_max_repetitions - default value for C<maxRepetitions>.
+
+This specifies how many table rows are requested in C<getBulk>
+requests.  Used when walking tables using C<getBulk> (only available
+in SNMPv2(c) and later).  If this is too small, then a table walk will
+need unnecessarily many request/response exchanges.  If it is too big,
+the agent may compute many variables after the end of the table.  It
+is recommended to set this explicitly for each table walk by using
+C<map_table_4()>.
+
+=cut
+
 my $default_max_repetitions = 12;
 
-### Default value for "avoid_negative_request_ids".
-###
-### Set this to non-zero if you have agents that have trouble with
-### negative request IDs, and don't forget to complain to your agent
-### vendor.  According to the spec (RFC 1905), the request-id is an
-### Integer32, i.e. its range is from -(2^31) to (2^31)-1.  However,
-### some agents erroneously encode the response ID as an unsigned,
-### which prevents this code from matching such responses to requests.
-###
+=head2 $default avoid_negative_request_ids - default value for
+    C<avoid_negative_request_ids>.
+
+Set this to non-zero if you have agents that have trouble with
+negative request IDs, and don't forget to complain to your agent
+vendor.  According to the spec (RFC 1905), the request-id is an
+C<Integer32>, i.e. its range is from -(2^31) to (2^31)-1.  However,
+some agents erroneously encode the response ID as an unsigned, which
+prevents this code from matching such responses to requests.
+
+=cut
+
 $SNMP_Session::default_avoid_negative_request_ids = 0;
 
-### Default value for "use_16bit_request_ids".
-###
-### Set this to non-zero if you have agents that use 16bit request IDs,
-### and don't forget to complain to your agent vendor.
-###
+=head2 $default_use_16bit_request_ids - default value for C<use_16bit_request_ids>.
+
+Set this to non-zero if you have agents that use 16bit request IDs,
+and don't forget to complain to your agent vendor.
+
+=cut
+
 $SNMP_Session::default_use_16bit_request_ids = 0;
 
 ### Whether all SNMP_Session objects should share a single UDP socket.
@@ -159,9 +153,38 @@ BEGIN {
 ###
 my %the_socket = ();
 
+=head2 $errmsg - error message from last failed operation.
+
+When they encounter errors, the routines in this module will generally
+return C<undef>) and leave an informative error message in C<$errmsg>).
+
+=cut
+
 $SNMP_Session::errmsg = '';
+
+=head2 $suppress_warnings - whether warnings should be suppressed.
+
+If this variable is zero, as is the default, this code will output
+informative error messages whenever it encounters an error.  Set this
+to a non-zero value if you want to suppress these messages.  In any
+case, the last error message can be found in C<$errmsg>.
+
+=cut
+
 $SNMP_Session::suppress_warnings = 0;
 
+=head1 METHODS in package SNMP_Session
+
+The abstract class C<SNMP_Session> defines objects that can be used to
+communicate with SNMP entities.  It has methods to send requests to
+and receive responses from an agent.
+
+Two instantiable subclasses are defined: C<SNMPv1_Session> implements
+SNMPv1 (RFC 1157) functionality C<SNMPv2c_Session> implements
+community-based SNMPv2 (RFC 3410-3417).
+
+=cut
+
 sub get_request      { 0 | context_flag () };
 sub getnext_request  { 1 | context_flag () };
 sub get_response     { 2 | context_flag () };
@@ -173,14 +196,70 @@ sub trap2_request    { 7 | context_flag () };
 
 sub standard_udp_port { 161 };
 
+=head2 open() - create an SNMP session object
+
+    $session = SNMP_Session->open
+      ($host, $community, $port,
+       $max_pdu_len, $local_port, $max_repetitions,
+       $local_host, $ipv4only);
+
+The calling and return conventions are identical to
+C<SNMPv1_Session::open()>.
+
+=cut
+
 sub open
 {
     return SNMPv1_Session::open (@_);
 }
 
+=head2 timeout() - return timeout value.
+
+Initial timeout, in seconds, to wait for a response PDU after a
+request is sent.  Note that when a request is retried, the timeout is
+increased by B<backoff> (see below).  The standard value is 2.0
+(seconds).
+
+=cut
+
 sub timeout { $_[0]->{timeout} }
+
+=head2 retries() - number of attempts to get a reply.
+
+Maximum number of attempts to get a reply for an SNMP request.  If no
+response is received after B<timeout> seconds, the request is resent
+and a new response awaited with a longer timeout, see the
+documentation on B<backoff> below.  The B<retries> value should be at
+least 1, because the first attempt counts, too (the name "retries" is
+confusing, sorry for that).
+
+=cut
+
 sub retries { $_[0]->{retries} }
+
+=head2 backoff() - backoff factor.for timeout on successive retries.
+
+Default backoff factor for C<SNMP_Session> objects.  This factor is
+used to increase the TIMEOUT every time an SNMP request is retried.
+The standard value is 1.0, which means the same timeout is used for
+all attempts.
+
+=cut
+
 sub backoff { $_[0]->{backoff} }
+
+=head2 set_timeout() - set initial timeout for session
+
+=head2 set_retries() - set maximum number of attempts for session
+
+=head2 set_backoff() - set backoff factor for session
+
+Example usage:
+
+    $session->set_backoff (1.5);
+
+=cut
+
 sub set_timeout {
     my ($session, $timeout) = @_;
     croak ("timeout ($timeout) must be a positive number") unless $timeout > 0.0;
@@ -282,38 +361,6 @@ sub decode_get_response {
     @{$this->{'unwrapped'}};
 }
 
-sub decode_trap_request ($$) {
-    my ($this, $trap) = @_;
-    my ($snmp_version, $community, $ent, $agent, $gen, $spec, $dt,
-	$request_id, $error_status, $error_index,
-	$bindings);
-    ($snmp_version, $community,
-     $ent, $agent,
-     $gen, $spec, $dt,
-     $bindings)
-	= decode_by_template ($trap, "%{%i%s%*{%O%A%i%i%u%{%@",
-			    trap_request);
-    if (!defined $snmp_version) {
-	($snmp_version, $community,
-	 $request_id, $error_status, $error_index,
-	 $bindings)
-	    = decode_by_template ($trap, "%{%i%s%*{%i%i%i%{%@",
-				  trap2_request);
-	if (!defined $snmp_version) {
-	    ($snmp_version, $community,$request_id, $error_status, $error_index, $bindings)
-		= decode_by_template ($trap, "%{%i%s%*{%i%i%i%{%@", inform_request);
-	}
-	return $this->error_return ("v2 trap/inform request contained errorStatus/errorIndex "
-				    .$error_status."/".$error_index)
-	    if defined $error_status && defined $error_index
-	    && ($error_status != 0 || $error_index != 0);
-    }
-    if (!defined $snmp_version) {
-	return $this->error_return ("BER error decoding trap:\n  ".$BER::errmsg);
-    }
-    return ($community, $ent, $agent, $gen, $spec, $dt, $bindings);
-}
-
 sub wait_for_response {
     my($this) = shift;
     my($timeout) = shift || 10.0;
@@ -323,24 +370,60 @@ sub wait_for_response {
     select($rout=$rin,$wout=$win,$eout=$ein,$timeout);
 }
 
+=head2 ..._request_response() - Send some request and receive response.
+
+Encodes a specific SNMP request, sends it to the destination address
+of the session, and waits for a matching response.  If such a response
+is received, this function will return the size of the response, which
+is necessarily greater than zero.
+
+An undefined value is returned if some error happens during encoding
+or sending, or if no matching response is received after the
+wait/retry schedule is exhausted.  See the documentation on the
+C<timeout()>, C<retries()>, and C<backoff()> methods on how the
+wait/retry logic works.
+
+=head2 get_request_response() - Send C<get> request and receive response.
+
+=head2 getnext_request_response() - Send C<get-next> request and receive response.
+
+    $result = $session->get_request_response (@encoded_oids);
+    $result = $session->getnext_request_response (@encoded_oids);
+
+=cut
+
 sub get_request_response ($@) {
     my($this, @oids) = @_;
     return $this->request_response_5 ($this->encode_get_request (@oids),
 				      get_response, \@oids, 1);
 }
 
-sub set_request_response ($@) {
-    my($this, @pairs) = @_;
-    return $this->request_response_5 ($this->encode_set_request (@pairs),
-				      get_response, \@pairs, 1);
-}
-
 sub getnext_request_response ($@) {
     my($this, at oids) = @_;
     return $this->request_response_5 ($this->encode_getnext_request (@oids),
 				      get_response, \@oids, 1);
 }
 
+=head2 set_request_response() - Send C<set> request and receive response.
+
+    $result = $session->set_request_response (@encoded_pair_list);
+
+This method takes its arguments in a different form; they are a list
+of pairs - references to two-element arrays - which respresent the
+variables to be set and the intended values, e.g.
+
+    ([$encoded_oid_0, $encoded_value_0],
+     [$encoded_oid_1, $encoded_value_1],
+     [$encoded_oid_2, $encoded_value_2], ...)
+
+=cut
+
+sub set_request_response ($@) {
+    my($this, @pairs) = @_;
+    return $this->request_response_5 ($this->encode_set_request (@pairs),
+				      get_response, \@pairs, 1);
+}
+
 sub getbulk_request_response ($$$@) {
     my($this,$non_repeaters,$max_repetitions, at oids) = @_;
     return $this->request_response_5
@@ -348,6 +431,12 @@ sub getbulk_request_response ($$$@) {
 	 get_response, \@oids, 1);
 }
 
+=head2 trap_request_send() - send SNMPv1 Trap.
+
+    $result = $session->trap_request_send ($ent, $gent, $gen, $spec, $dt, @pairs);
+
+=cut
+
 sub trap_request_send ($$$$$$@) {
     my($this, $ent, $agent, $gen, $spec, $dt, @pairs) = @_;
     my($req);
@@ -360,6 +449,12 @@ sub trap_request_send ($$$$$$@) {
     return 1;
 }
 
+=head2 v2_trap_request_send() - send SNMPv2 Trap.
+
+    $result = $session->v2_trap_request_send ($trap_oid, $dt, @pairs);
+
+=cut
+
 sub v2_trap_request_send ($$$@) {
     my($this, $trap_oid, $dt, @pairs) = @_;
     my @sysUptime_OID = ( 1,3,6,1,2,1,1,3 );
@@ -434,15 +529,49 @@ sub request_response_5 ($$$$$) {
 	if (defined $this->{'capture_buffer'}
 	    and ref $this->{'capture_buffer'} eq 'ARRAY');
     #
-    $this->error ("no response received");
+    return $this->error ("no response received");
 }
 
+=head2 map_table() - traverse an SNMP table.
+
+    $result = $session->map_table ([$col0, $col1, ...], $mapfn);
+
+This will call the provided function (C<&$mapfn>) once for each row of
+the table defined by the column OIDs C<$col0>, C<$col1>...  If the
+session can handle SNMPv2 operations, C<get-bulk> will be used to
+traverse the table.  Otherwise, C<get-next> will be used.
+
+If the first argument is a list of I<n> columns, the mapping function
+will be called with I<n+1> arguments.  The first argument will be the
+row index, i.e. the list of sub-IDs that was appended to the provided
+column OIDs for this row.  Note that the row index will be represented
+as a string, using dot-separated numerical OID notation.
+
+The remaining arguments to the mapping function will be the values of
+each column at the current index.  It is possible that the table has
+"holes", i.e. that for a given row index, not all columns have a
+value.  For columns with no value at the current row index, C<undef>
+will be passed to the mapping function.
+
+If an error is encountered at any point during the table traversal,
+this method will return undef and leave an error message in C<$errmsg>
+(which is also written out unless C<$suppress_warnings> is non-zero).
+
+Otherwise, the function will return the number of rows traversed,
+i.e. the number of times that the mapping function has been called.
+
+=cut
+
 sub map_table ($$$) {
     my ($session, $columns, $mapfn) = @_;
     return $session->map_table_4 ($columns, $mapfn,
 				  $session->default_max_repetitions ());
 }
 
+=head2 map_table_4() - traverse an SNMP table with more control.
+
+=cut
+
 sub map_table_4 ($$$$) {
     my ($session, $columns, $mapfn, $max_repetitions) = @_;
     return $session->map_table_start_end ($columns, $mapfn,
@@ -450,6 +579,16 @@ sub map_table_4 ($$$$) {
 					  $max_repetitions);
 }
 
+=head2 map_table_start_end() - traverse an SNMP table with lower/upper index limits.
+
+    $result = $session->map_table_start_end ($columns, $mapfn,
+        $start, $end, $max_repetition);
+
+Similar to C<map_table_4()>, except that the start and end index can
+be specified.
+
+=cut
+
 sub map_table_start_end ($$$$$$) {
     my ($session, $columns, $mapfn, $start, $end, $max_repetitions) = @_;
 
@@ -477,7 +616,7 @@ sub map_table_start_end ($$$$$$) {
 
 		my $out_index;
 
-		$out_index = &oid_diff ($base, $oid);
+		$out_index = oid_diff ($base, $oid);
 		my $cmp;
 		if (!defined $smallest_index
 		    || ($cmp = index_compare ($out_index,$smallest_index)) == -1) {
@@ -592,6 +731,112 @@ sub ber_error ($$) {
   return $this->error ("$type:\n$errmsg");
 }
 
+=head2 receive_trap_1() - receive message on trap socket.
+
+This method waits until a message is received on the trap socket.  If
+successful, it returns two values: the message that was received, and
+the address of the sender as a C<sockaddr> structure.  This address
+can be passed to C<getnameinfo()> to convert it to readable output.
+
+This method doesn't check whether the message actually encodes a trap
+or anything else - the caller should use C<decode_trap_request()> to
+find out.
+
+=cut
+
+sub receive_trap_1 ($ ) {
+    my ($this) = @_;
+    my ($remote_addr, $iaddr, $port, $trap, $af);
+    $remote_addr = recv ($this->sock,
+			 $this->{'pdu_buffer'},
+			 $this->max_pdu_len,0);
+    return undef unless $remote_addr;
+    $trap = $this->{'pdu_buffer'};
+    return ($trap, $remote_addr);
+}
+
+=head2 receive_trap() - receive message on trap socket (deprecated version).
+
+This function is identical to C<receive_trap_1()>, except that it
+returns the sender address as three (formerly two) separate values:
+The host IP address, the port, and (since version 1.14) the address
+family.  If you use this, please consider moving to
+C<receive_trap_1()>, because it is easier to process the sender
+address in sockaddr format, in particular in a world where IPv4 and
+IPv6 coexist.
+
+=cut
+
+sub receive_trap ($ ) {
+    my ($this) = @_;
+    my ($trap, $remote_addr) = $this->receive_trap_1 ();
+    return undef unless defined $trap;
+
+    my ($iaddr, $port, $af);
+    if ((defined $ipv6_addr_len) && (length $remote_addr == $ipv6_addr_len)) {
+	($port, $iaddr) = unpack_sockaddr_in6 ($remote_addr);
+	$af = AF_INET6;
+    } else {
+	($port, $iaddr) = unpack_sockaddr_in ($remote_addr);
+	$af = AF_INET;
+    }
+    return ($trap, $iaddr, $port, $af);
+}
+
+=head2 decode_trap_request()
+
+    ($community, $ent, $agent, $gen, $spec, $dt, $bindings)
+      = $session->decode_trap_request ($trap);
+
+Given a message such as one returned as the first return value from
+C<receive_trap_1()> or C<receive_trap()>, try to decode it as some
+notification PDU.  The code can handle SNMPv1 and SNMPv2 traps as well
+as SNMPv2 INFORMs, although it fails to distinguish traps from
+informs, which makes it hard to handle informs correctly (they should
+be acknowledged).
+
+The C<$ent>, C<$agent>, C<$gen>, C<$spec>, and C<$dt> values will only
+be defined for SNMPv1 traps.  For SNMPv2 traps and informs, some of
+this information will be encoded as bindings.
+
+=cut
+
+sub decode_trap_request ($$) {
+    my ($this, $trap) = @_;
+    my ($snmp_version, $community, $ent, $agent, $gen, $spec, $dt,
+	$request_id, $error_status, $error_index,
+	$bindings);
+    ($snmp_version, $community,
+     $ent, $agent,
+     $gen, $spec, $dt,
+     $bindings)
+	= decode_by_template ($trap, "%{%i%s%*{%O%A%i%i%u%{%@",
+			    trap_request);
+    if (!defined $snmp_version) {
+	($snmp_version, $community,
+	 $request_id, $error_status, $error_index,
+	 $bindings)
+	    = decode_by_template ($trap, "%{%i%s%*{%i%i%i%{%@",
+				  trap2_request);
+	if (!defined $snmp_version) {
+	    ($snmp_version, $community,$request_id, $error_status, $error_index, $bindings)
+		= decode_by_template ($trap, "%{%i%s%*{%i%i%i%{%@", inform_request);
+	}
+	return $this->error_return ("v2 trap/inform request contained errorStatus/errorIndex "
+				    .$error_status."/".$error_index)
+	    if defined $error_status && defined $error_index
+	    && ($error_status != 0 || $error_index != 0);
+    }
+    if (!defined $snmp_version) {
+	return $this->error_return ("BER error decoding trap:\n  ".$BER::errmsg);
+    }
+    return ($community, $ent, $agent, $gen, $spec, $dt, $bindings);
+}
+
+=head1 METHODS in package SNMPv1_Session
+
+=cut
+
 package SNMPv1_Session;
 
 use strict qw(vars subs);	# see above
@@ -613,8 +858,43 @@ BEGIN {
 
 sub snmp_version { 0 }
 
-# Supports both IPv4 and IPv6.
-# Numeric IPv6 addresses must be passed between square brackets []
+=head2 open() - create an SNMPv1 session object
+
+    $session = SNMPv1_Session->open
+      ($host, $community, $port,
+       $max_pdu_len, $local_port, $max_repetitions,
+       $local_host, $ipv4only);
+
+Note that all arguments except for C<$host> are optional.  The
+C<$host> can be specified either as a hostname or as a numeric
+address.  Numeric IPv6 addresses must be enclosed in square brackets
+[]
+
+C<$community> defaults to C<public>.
+
+C<$port> defaults to 161, the standard UDP port to send SNMP requests
+to.
+
+C<$max_pdu_len> defaults to 8000.
+
+C<$local_port> can be specified if a specific local port is desired,
+for example because of firewall rules for the response packets.  If
+none is specified, the operating system will choose a random port.
+
+C<$max_repetitions> is the maximum number of repetitions requested in
+C<get-bulk> requests.  It is only relevant in SNMPv2(c) and later.
+
+C<$local_host> can be used to specify a specific address/interface.
+It is useful on hosts that have multiple addresses if a specific
+address should be used, for example because of firewall rules.
+
+If C<$ipv4only> is either not present or non-zero, then an IPv4-only
+socket will be used.  This is also the case if the system only
+supports IPv4.  Otherwise, an IPv6 socket is created.  IPv6 sockets
+support both IPv6 and IPv4 requests and responses.
+
+=cut
+
 sub open {
     my($this,
        $remote_hostname,$community,$port,
@@ -633,37 +913,40 @@ sub open {
 
     if ($ipv4only || ! $SNMP_Session::ipv6available) {
 	# IPv4-only code, uses only Socket and INET calls
-    if (defined $remote_hostname) {
-	$remote_addr = inet_aton ($remote_hostname)
-	    or return $this->error_return ("can't resolve \"$remote_hostname\" to IP address");
-    }
-    if ($SNMP_Session::recycle_socket && exists $the_socket{$sockfamily}) {
-	$socket = $the_socket{$sockfamily};
-    } else {
-	$socket = IO::Socket::INET->new(Proto => 17,
-					Type => SOCK_DGRAM,
-					LocalAddr => $local_hostname,
-					LocalPort => $local_port)
-	    || return $this->error_return ("creating socket: $!");
-	$the_socket{$sockfamily} = $socket
-	    if $SNMP_Session::recycle_socket;
-    }
-    $remote_addr = pack_sockaddr_in ($port, $remote_addr)
-	if defined $remote_addr;
+	if (defined $remote_hostname) {
+	    $remote_addr = inet_aton ($remote_hostname)
+		or return $this->error_return ("can't resolve \"$remote_hostname\" to IP address");
+	}
+	if ($SNMP_Session::recycle_socket && exists $the_socket{$sockfamily}) {
+	    $socket = $the_socket{$sockfamily};
+	} else {
+	    $socket = IO::Socket::INET->new(Proto => 17,
+					    Type => SOCK_DGRAM,
+					    LocalAddr => $local_hostname,
+					    LocalPort => $local_port)
+		|| return $this->error_return ("creating socket: $!");
+	    $the_socket{$sockfamily} = $socket
+		if $SNMP_Session::recycle_socket;
+	}
+	$remote_addr = pack_sockaddr_in ($port, $remote_addr)
+	    if defined $remote_addr;
     } else {
 	# IPv6-capable code. Will use IPv6 or IPv4 depending on the address.
 	# Uses Socket6 and INET6 calls.
 
-	# If it's a numeric IPv6 addresses, remove square brackets
-	if ($remote_hostname =~ /^\[(.*)\]$/) {
-	    $remote_hostname = $1;
-	}
-
-	my (@res, $socktype_tmp, $proto_tmp, $canonname_tmp);
-	@res = getaddrinfo($remote_hostname, $port, AF_UNSPEC, SOCK_DGRAM);
-	($sockfamily, $socktype_tmp, $proto_tmp, $remote_addr, $canonname_tmp) = @res;
-	if (scalar(@res) < 5) {
-	    return $this->error_return ("can't resolve \"$remote_hostname\" to IPv6 address");
+	if (defined $remote_hostname) {
+	    # If it's a numeric IPv6 addresses, remove square brackets
+	    if ($remote_hostname =~ /^\[(.*)\]$/) {
+		$remote_hostname = $1;
+	    }
+	    my (@res, $socktype_tmp, $proto_tmp, $canonname_tmp);
+	    @res = getaddrinfo($remote_hostname, $port, AF_UNSPEC, SOCK_DGRAM);
+	    ($sockfamily, $socktype_tmp, $proto_tmp, $remote_addr, $canonname_tmp) = @res;
+	    if (scalar(@res) < 5) {
+		return $this->error_return ("can't resolve \"$remote_hostname\" to IPv6 address");
+	    }
+	} else {
+	    $sockfamily = AF_INET6;
 	}
 
 	if ($SNMP_Session::recycle_socket && exists $the_socket{$sockfamily}) {
@@ -675,7 +958,8 @@ sub open {
 					    LocalPort => $local_port)
 	         || return $this->error_return ("creating socket: $!");
 	} else {
-	    $socket = IO::Socket::INET6->new(Proto => 17,
+	    $socket = IO::Socket::INET6->new(Domain => AF_INET6,
+					     Proto => 17,
 					     Type => SOCK_DGRAM,
 					     LocalAddr => $local_hostname,
 					     LocalPort => $local_port)
@@ -711,10 +995,26 @@ sub open {
 	  };
 }
 
+=head2 open_trap_session() - create a session for receiving SNMP traps.
+
+    $session = open_trap_session ($port, $ipv4only);
+
+C<$port> defaults to 162, the standard UDP port that SNMP
+notifications are sent to.
+
+If C<$ipv4only> is either not present or non-zero, then an IPv4-only
+socket will be used.  This is also the case if the system only
+supports IPv4.  Otherwise, an IPv6 socket is created.  IPv6 sockets
+can receive messages over both IPv6 and IPv4.
+
+=cut
+
 sub open_trap_session (@) {
-    my ($this, $port) = @_;
+    my ($this, $port, $ipv4only) = @_;
     $port = 162 unless defined $port;
-    return $this->open (undef, "", 161, undef, $port);
+    $ipv4only = 1 unless defined $ipv4only;
+    return $this->open (undef, "", 161, undef, $port,
+	undef, undef, $ipv4only);
 }
 
 sub sock { $_[0]->{sock} }
@@ -892,22 +1192,6 @@ sub receive_response_3 {
     return length $this->pdu_buffer;
 }
 
-sub receive_trap {
-    my ($this) = @_;
-    my ($remote_addr, $iaddr, $port, $trap);
-    $remote_addr = recv ($this->sock,$this->{'pdu_buffer'},$this->max_pdu_len,0);
-    return undef unless $remote_addr;
-
-    if( (defined $ipv6_addr_len) && (length $remote_addr == $ipv6_addr_len)) {
-	($port,$iaddr) = unpack_sockaddr_in6($remote_addr);
-    } else {
-	($port,$iaddr) = unpack_sockaddr_in($remote_addr);
-    }
-
-    $trap = $this->{'pdu_buffer'};
-    return ($trap, $iaddr, $port);
-}
-
 sub describe {
     my($this) = shift;
     print $this->to_string (),"\n";
@@ -990,6 +1274,10 @@ sub decode_request {
     return undef;
 }
 
+=head1 METHODS in package SNMPv2c_Session
+
+=cut
+
 package SNMPv2c_Session;
 use strict qw(vars subs);	# see above
 use vars qw(@ISA);
@@ -1001,6 +1289,19 @@ use Carp;
 
 sub snmp_version { 1 }
 
+=head2 open() - create an SNMPv2(c) session object
+
+    $session = SNMPv2c_Session->open
+      ($host, $community, $port,
+       $max_pdu_len, $local_port, $max_repetitions,
+       $local_host, $ipv4only);
+
+The calling and return conventions are identical to
+C<SNMPv1_Session::open()>, except that this returns a session object
+that supports SNMPv2 operations.
+
+=cut
+
 sub open {
     my $session = SNMPv1_Session::open (@_);
     return undef unless defined $session;
@@ -1121,3 +1422,334 @@ sub map_table_start_end ($$$$$$) {
 }
 
 1;
+
+=head1 EXAMPLES
+
+The basic usage of these routines works like this:
+
+ use BER;
+ use SNMP_Session;
+ 
+ # Set $host to the name of the host whose SNMP agent you want
+ # to talk to.  Set $community to the community name under
+ # which you want to talk to the agent.	Set port to the UDP
+ # port on which the agent listens (usually 161).
+ 
+ $session = SNMP_Session->open ($host, $community, $port)
+     or die "couldn't open SNMP session to $host";
+ 
+ # Set $oid1, $oid2... to the BER-encoded OIDs of the MIB
+ # variables you want to get.
+ 
+ if ($session->get_request_response ($oid1, $oid2, ...)) {
+     ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
+ 
+     while ($bindings ne '') {
+ 	($binding,$bindings) = decode_sequence ($bindings);
+ 	($oid,$value) = decode_by_template ($binding, "%O%@");
+ 	print pretty_print ($oid)," => ", pretty_print ($value), "\n";
+     }
+ } else {
+     die "No response from agent on $host";
+ }
+
+=head2 Encoding OIDs
+
+In order to BER-encode OIDs, you can use the function
+B<BER::encode_oid>. It takes (a vector of) numeric subids as an
+argument. For example,
+
+ use BER;
+ encode_oid (1, 3, 6, 1, 2, 1, 1, 1, 0)
+
+will return the BER-encoded OID for the B<sysDescr.0>
+(1.3.6.1.2.1.1.1.0) instance of MIB-2.
+
+=head2 Decoding the results
+
+When C<get_request_response()> returns success, you must decode the
+response PDU from the remote agent. The function
+C<decode_get_response()> can be used to do this. It takes a
+C<get-response> PDU, checks its syntax and returns the I<bindings>
+part of the PDU. This is where the remote agent actually returns the
+values of the variables in your query.
+
+You should iterate over the individual bindings in this I<bindings>
+part and extract the value for each variable. In the example above,
+the returned bindings are simply printed using the
+C<BER::pretty_print()> function.
+
+For better readability of the OIDs, you can also use the following
+idiom, where the C<%pretty_oids> hash maps BER-encoded numerical OIDs
+to symbolic OIDs. Note that this simple-minded mapping only works for
+response OIDs that exactly match known OIDs, so it's unsuitable for
+table walking (where the response OIDs include an additional row
+index).
+
+ %ugly_oids = qw(sysDescr.0	1.3.6.1.2.1.1.1.0
+ 		sysContact.0	1.3.6.1.2.1.1.4.0);
+ foreach (keys %ugly_oids) {
+     $ugly_oids{$_} = encode_oid (split (/\./, $ugly_oids{$_}));
+     $pretty_oids{$ugly_oids{$_}} = $_;
+ }
+ ...
+ if ($session->get_request_response ($ugly_oids{'sysDescr.0'},
+ 				    $ugly_oids{'sysContact.0'})) {
+     ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
+     while ($bindings ne '') {
+ 	($binding,$bindings) = decode_sequence ($bindings);
+ 	($oid,$value) = decode_by_template ($binding, "%O%@");
+ 	print $pretty_oids{$oid}," => ",
+ 	      pretty_print ($value), "\n";
+     }
+ } ...
+
+=head2 Set Requests
+
+Set requests are generated much like C<get> or C<getNext> requests
+are, with the exception that you have to specify not just OIDs, but
+also the values the variables should be set to. Every binding is
+passed as a reference to a two-element array, the first element being
+the encoded OID and the second one the encoded value. See the
+C<test/set-test.pl> script for an example, in particular the
+subroutine C<snmpset>.
+
+=head2 Walking Tables
+
+Beginning with version 0.57 of C<SNMP_Session.pm>, there is API
+support for walking tables. The C<map_table()> method can be used for
+this as follows:
+
+ sub walk_function ($$$) {
+   my ($index, $val1, $val3) = @_;
+   ...
+ }
+ 
+ ...
+ $columns = [$base_oid1, $base_oid3];
+ $n_rows = $session->map_table ($columns, \&walk_function);
+
+The I<columns> argument must be a reference to a list of OIDs for table
+columns sharing the same index. The method will traverse the table and
+call the I<walk_function> for each row. The arguments for these calls
+will be:
+
+=over
+
+=item 1. the I<row index> as a partial OID in dotted notation, e.g.
+C<1.3>, or C<10.0.1.34>.
+
+=item 2. the values of the requested table columns in that row, in
+BER-encoded form. If you want to use the standard C<pretty_print()>
+subroutine to decode the values, you can use the following idiom:
+
+  grep (defined $_ && ($_=pretty_print $_), ($val1, $val3));
+
+=back
+
+=head2 Walking Tables With C<get-bulk>
+
+Since version 0.67, C<SNMP_Session> uses a different C<get_table>
+implementation for C<SNMPv2c_Session>s. This version uses the
+``powerful C<get-bulk> operator'' to retrieve many table rows with
+each request. In general, this will make table walking much faster
+under SNMPv2c, especially when round-trip times to the agent are long.
+
+There is one difficulty, however: With C<get-bulk>, a management
+application can specify the maximum number of rows to return in a
+single response. C<SNMP_Session.pm> provides a new function,
+C<map_table_4>, in which this C<maxRepetitions> value can be specified
+explicitly.
+
+For maximum efficiency, it should be set to a value that is one
+greater than the number of rows in the table. If it is smaller, then
+C<map_table()> will use more request/response cycles than necessary;
+if it is bigger, the agent will have to compute variable bindings
+beyond the end of the table (which C<map_table()> will throw away).
+
+Of course it is usually impossible to know the size of the table in
+advance. If you don't specify C<maxRepetitions> when walking a table,
+then C<map_table()> will use a per-session default
+(C<$session-E<gt>default_max_repetitions>). The default value for this
+default is 12.
+
+If you walk a table multiple times, and the size of the table is
+relatively stable, you should use the return value of C<map_table()>
+(which is the number of rows it has encountered) to compute the next
+value of C<maxRepetitions>. Remember to add one so that C<map_table()>
+notices when the table is finished!
+
+Note that for really big tables, this doesn't make a big difference,
+since the table won't fit in a single response packet anyway.
+
+=head2 Sending Traps
+
+To send a trap, you have to open an SNMP session to the trap receiver.
+Usually this is a process listening to UDP port 162 on a network
+management station. Then you can use the C<trap_request_send()> method
+to encode and send SNMPv1 traps. There is no way to find out whether
+the trap was actually received at the management station - SNMP traps
+are fundamentally unreliable.
+
+When constructing an SNMPv1 trap, you must provide
+
+=over
+
+=item * the "enterprise" Object Identifier for the entity that
+generates the trap
+
+=item * your IP address
+
+=item * the generic trap type
+
+=item * the specific trap type
+
+=item * the C<sysUpTime> at the time of trap generation
+
+=item * a sequence (may be empty) of variable bindings further
+describing the trap.
+
+=back
+
+For SNMPv2 traps, you need:
+
+=over
+
+=item * the trap's OID
+
+=item * the C<sysUpTime> at the time of trap generation
+
+=item * the bindings list as above
+
+=back
+
+For SNMPv2 traps, the uptime and trap OID are encoded as bindings which
+are added to the front of the other bindings you provide.
+
+Here is a short example:
+
+ my $trap_receiver = "netman.noc";
+ my $trap_community = "SNMP_Traps";
+ my $trap_session = $version eq '1'
+     ? SNMP_Session->open ($trap_receiver, $trap_community, 162)
+     : SNMPv2c_Session->open ($trap_receiver, $trap_community, 162);
+ my $myIpAddress = ...;
+ my $start_time = time;
+ 
+ ...
+ 
+ sub link_down_trap ($$) {
+   my ($if_index, $version) = @_;
+   my $genericTrap = 2;		# linkDown
+   my $specificTrap = 0;
+   my @ifIndexOID = ( 1,3,6,1,2,1,2,2,1,1 );
+   my $upTime = int ((time - $start_time) * 100.0);
+   my @myOID = ( 1,3,6,1,4,1,2946,0,8,15 );
+ 
+   warn "Sending trap failed"
+     unless ($version eq '1')
+ 	? $trap_session->trap_request_send (encode_oid (@myOID),
+ 					    encode_ip_address ($myIpAddress),
+ 					    encode_int ($genericTrap),
+ 					    encode_int ($specificTrap),
+ 					    encode_timeticks ($upTime),
+ 					    [encode_oid (@ifIndex_OID,$if_index),
+ 					     encode_int ($if_index)],
+ 					    [encode_oid (@ifDescr_OID,$if_index),
+ 					     encode_string ("foo")])
+ 	    : $trap_session->v2_trap_request_send (\@linkDown_OID, $upTime,
+ 						   [encode_oid (@ifIndex_OID,$if_index),
+ 						    encode_int ($if_index)],
+ 						   [encode_oid (@ifDescr_OID,$if_index),
+ 						    encode_string ("foo")]);
+ }
+
+=head2 Receiving Traps
+
+Since version 0.60, C<SNMP_Session.pm> supports the receipt and
+decoding of SNMPv1 trap requests. Since version 0.75, SNMPv2 Trap PDUs
+are also recognized.
+
+To receive traps, you have to create a special SNMP session that
+passively listens on the SNMP trap transport address, usually on UDP
+port 162.  Then you can receive traps - actually, SNMPv1 traps, SNMPv2
+traps, and SNMPv2 informs, using the C<receive_trap_1()> method and
+decode them using C<decode_trap_request()>. The I<enterprise>,
+I<agent>, I<generic>, I<specific> and I<sysUptime> return values are
+only defined for SNMPv1 traps. In SNMPv2 traps and informs, the
+equivalent information is contained in the bindings.
+
+ my $trap_session = SNMPv1_Session->open_trap_session (162, 0)
+   or die "cannot open trap session";
+ my ($trap, $sender_sockaddr) = $trap_session->receive_trap_1 ()
+   or die "cannot receive trap";
+ my ($community, $enterprise, $agent,
+     $generic, $specific, $sysUptime, $bindings)
+   = $trap_session->decode_trap_request ($trap)
+     or die "cannot decode trap received"
+ ...
+ my ($binding, $oid, $value);
+ while ($bindings ne '') {
+     ($binding,$bindings) = decode_sequence ($bindings);
+     ($oid, $value) = decode_by_template ($binding, "%O%@");
+     print BER::pretty_oid ($oid)," => ",pretty_print ($value),"\n";
+ }
+
+=head1 AUTHORS
+
+Created by:  Simon Leinen  E<lt>simon.leinen at switch.chE<gt>
+
+Contributions and fixes by:
+
+=over
+
+=item Matthew Trunnell E<lt>matter at media.mit.eduE<gt>
+
+=item Tobias Oetiker E<lt>tobi at oetiker.chE<gt>
+
+=item Heine Peters E<lt>peters at dkrz.deE<gt>
+
+=item Daniel L. Needles E<lt>dan_needles at INS.COME<gt>
+
+=item Mike Mitchell E<lt>mcm at unx.sas.comE<gt>
+
+=item Clinton Wong E<lt>clintdw at netcom.comE<gt>
+
+=item Alan Nichols E<lt>Alan.Nichols at Ebay.Sun.COME<gt>
+
+=item Mike McCauley E<lt>mikem at open.com.auE<gt>
+
+=item Andrew W. Elble E<lt>elble at icculus.nsg.nwu.eduE<gt>
+
+=item Brett T Warden E<lt>wardenb at eluminant.comE<gt>: pretty C<UInteger32>
+
+=item Michael Deegan E<lt>michael at cnspc18.murdoch.edu.auE<gt>
+
+=item Sergio Macedo E<lt>macedo at tmp.com.brE<gt>
+
+=item Jakob Ilves (/IlvJa) E<lt>jakob.ilves at oracle.comE<gt>: PDU capture
+
+=item Valerio Bontempi E<lt>v.bontempi at inwind.itE<gt>: IPv6 support
+
+=item Lorenzo Colitti E<lt>lorenzo at colitti.comE<gt>: IPv6 support
+
+=item Philippe Simonet E<lt>Philippe.Simonet at swisscom.comE<gt>: Export C<avoid...>
+
+=item Luc Pauwels E<lt>Luc.Pauwels at xalasys.comE<gt>: C<use_16bit_request_ids>
+
+=item Andrew Cornford-Matheson E<lt>andrew.matheson at corenetworks.comE<gt>: inform
+
+=item Gerry Dalton E<lt>gerry.dalton at consolidated.comE<gt>: C<strict subs> bug
+
+=item Mike Fischer E<lt>mlf2 at tampabay.rr.comE<gt>: pass MSG_DONTWAIT to C<recv()>
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright (c) 1995-2009, Simon Leinen.
+
+This program is free software; you can redistribute it under the
+"Artistic License 2.0" included in this distribution (file "Artistic").
+
+=cut
diff --git a/lib/SNMP_Table.pm b/lib/SNMP_Table.pm
new file mode 100644
index 0000000..033e4f6
--- /dev/null
+++ b/lib/SNMP_Table.pm
@@ -0,0 +1,132 @@
+### SNMP_Table.pm
+###
+### Convenience routines for dealing with SNMP tables
+###
+### Author:        Simon Leinen  <simon at switch.ch>
+### Date created:  24-Apr-2005
+###
+### Tables are a central concept to SNMP (or, more precisely, of the
+### SMI or Structure of Management Instrumentation).  SNMP tables are
+### conceptually similar to tables in relational database management
+### systems (RDBMS).  Each SNMP table has at least one conceptual
+### columns, of which at least one serve as an index columns.
+###
+### This little library implements a form of "object-relational
+### mapping", or rather, a "relational-object mapping" from SNMP
+### tables to Perl objects.
+###
+### In particular, they convert SNMP table rows into Perl objects, and
+### allow a user to store these objects into some composite structure
+### based on the index(es).
+
+package SNMP_Table;
+
+require 5.004;
+
+use strict;
+use vars qw(@ISA @EXPORT $VERSION);
+use Exporter;
+
+use SNMP_util;
+
+ at ISA = qw(Exporter);
+
+ at EXPORT = qw(snmp_rows_to_objects snmp_row_to_object snmp_map_row_objects);
+
+### snmp_rows_to_objects TARGET, CLASS, PREFIX, COLUMNS...
+###
+### Returns a reference to a hash that maps a table's index to objects
+### created from the set of COLUMNS.  The COLUMNS are partial OID
+### names, to each of which the PREFIX is prepended.  An object is
+### created for each row in the table, by creating a hash reference
+### with a slot for each column, named by the (partial) column name.
+### It is blessed to the CLASS.
+###
+### For example, if we have the following table at $TARGET:
+###
+### index fooBar fooBaz fooBlech
+###
+### 1000  asd    23498  vohdajae
+### 1001  fgh    45824  yaohetoo
+### 1002  jkl    89732  engahghi
+###
+### Then the call:
+###
+###  snmp_rows_to_objects ($TARGET, 'MyFoo', 'foo', 'bar', 'baz', 'blech') 
+###
+### will create a hash reference similar to this:
+###
+###     $result = {};
+###     $result{1000} = bless { 'bar' => 'asd',
+###                             'baz' => 23498,
+###                             'blech' => 'vohdajae' }, 'MyFoo';
+###     $result{1001} = bless { 'bar' => 'fgh',
+###                             'baz' => 45824,
+###                             'blech' => 'yaohetoo' }, 'MyFoo';
+###     $result{1002} = bless { 'bar' => 'jkl',
+###                             'baz' => 89732,
+###                             'blech' => 'engahghi' }, 'MyFoo';
+###
+sub snmp_rows_to_objects ($$$@) {
+    my ($target, $class, $prefix, @cols) = @_;
+    my $result = {};
+    snmp_map_row_objects
+      ($target, $class,
+       sub () {
+	   my ($index, $object) = @_;
+	   $result->{$index} = $object;
+       },
+       $prefix, @cols);
+    return $result;
+}
+
+### snmp_map_row_objects TARGET, CLASS, MAPFN, PREFIX, COLUMNS...
+###
+### This function traverses a table, creating an object for each row,
+### and applying the user-supplied MAPFN to each of these objects.
+###
+### The table is defined by PREFIX and COLUMNS, as described for
+### snmp_rows_to_objects above.  An object is created according to
+### CLASS and COLUMNS, as described above.  The difference is that,
+### rather than putting all objects in a hash, we simply apply the
+### user-supplied MAPFN to each row object.
+###
+sub snmp_map_row_objects ($$$$@) {
+    my ($target, $class, $mapfn, $prefix, @cols) = @_;
+    my @coloids = map ($prefix.ucfirst $_, at cols);
+    my @slotnames = @cols;
+    snmpmaptable ($target,
+		  sub () {
+		      my ($index, @colvals) = @_;
+		      my $object = bless { 'index' => $index }, $class;
+		      foreach my $slotname (@slotnames) {
+			  $object->{$slotname} = shift @colvals;
+		      }
+		      &$mapfn ($index, $object);
+		  },
+		  @coloids);
+}
+
+### snmp_row_to_object TARGET, CLASS, INDEX, PREFIX, COLUMNS...
+###
+### This can be used if one is only interested in a single row,
+### defined by INDEX.  This function uses a large SNMP get request to
+### retrieve all columns of interest, and assembles the result into a
+### hash blessed to CLASS.  This hash directly represents the row
+### object.  Note that this function returns just a single hash, as
+### opposed to snmp_rows_to_objects, which returns a hash that maps
+### index values to such hashes.
+###
+sub snmp_row_to_object ($$$$@ ) {
+    my ($target, $class, $index, $prefix, @cols) = @_;
+    my @coloids = map ($prefix.ucfirst $_.".".$index, at cols);
+    my @result = snmpget ($target, @coloids);
+    my %result = ();
+    foreach my $col (@cols) {
+	$result{$col} = shift @result;
+    }
+    bless \%result, $class;
+    return \%result;
+}
+
+1;
diff --git a/lib/SNMP_util.pm b/lib/SNMP_util.pm
index 20226f5..7ce96a6 100644
--- a/lib/SNMP_util.pm
+++ b/lib/SNMP_util.pm
@@ -2,7 +2,7 @@
 ######################################################################
 ### SNMP_util -- SNMP utilities using SNMP_Session.pm and BER.pm
 ######################################################################
-### Copyright (c) 1998-2008, Mike Mitchell.
+### Copyright (c) 1998-2010, Mike Mitchell.
 ###
 ### This program is free software; you can redistribute it under the
 ### "Artistic License 2.0" included in this distribution
@@ -29,6 +29,8 @@
 ### Joerg Kummer <JOERG.KUMMER at Roche.COM>: TimeTicks support in snmpset()
 ### Christopher J. Tengi <tengi at CS.Princeton.EDU>: Gauge32 support in snmpset()
 ### Nicolai Petri <nicolai at catpipe.net>: hashref passing for snmpwalkhash()
+### <jaccobs at online.nl>: parse NOTIFICATION-TYPE in MIB
+### Dan Thorson <Dan.Thorson at seagate.com>: handle quotes in MIB comments better
 ######################################################################
 
 package SNMP_util;
@@ -44,7 +46,7 @@ use BER "1.02";
 use SNMP_Session "1.00";
 use Socket;
 
-$VERSION = '1.13';
+$VERSION = '1.15';
 
 @ISA = qw(Exporter);
 
@@ -479,15 +481,18 @@ sub snmpget ($@) {
   my(@enoid, $var, $response, $bindings, $binding, $value, $oid, @retvals);
   my $session;
 
+  @retvals = ();
   $session = &snmpopen($host, 0, \@vars);
   if (!defined($session)) {
     carp "SNMPGET Problem for $host\n"
       unless ($SNMP_Session::suppress_warnings > 1);
-    return undef;
+    return wantarray ? @retvals : undef;
   }
 
   @enoid = &toOID(@vars);
-  return undef unless defined $enoid[0];
+  if ($#enoid < 0) {
+    return wantarray ? @retvals : undef;
+  }
 
   if ($session->get_request_response(@enoid)) {
     $response = $session->pdu_buffer;
@@ -503,7 +508,7 @@ sub snmpget ($@) {
   $var = join(' ', @vars);
   carp "SNMPGET Problem for $var on $host\n"
     unless ($SNMP_Session::suppress_warnings > 1);
-  return undef;
+  return wantarray ? @retvals : undef;
 }
 
 #
@@ -516,15 +521,18 @@ sub snmpgetnext ($@) {
   my($noid);
   my $session;
 
+  @retvals = ();
   $session = &snmpopen($host, 0, \@vars);
   if (!defined($session)) {
     carp "SNMPGETNEXT Problem for $host\n"
       unless ($SNMP_Session::suppress_warnings > 1);
-    return undef;
+    return wantarray ? @retvals : undef;
   }
 
   @enoid = &toOID(@vars);
-  return undef unless defined $enoid[0];
+  if ($#enoid < 0) {
+    return wantarray ? @retvals : undef;
+  }
 
   undef @vars;
   undef @retvals;
@@ -547,7 +555,7 @@ sub snmpgetnext ($@) {
     $var = join(' ', @vars);
     carp "SNMPGETNEXT Problem for $var on $host\n"
       unless ($SNMP_Session::suppress_warnings > 1);
-    return undef;
+    return wantarray ? @retvals : undef;
   }
 }
 
@@ -576,17 +584,31 @@ sub snmpwalk_flg ($$@) {
   my(%soid);
   my(%done, %rethash, $h_ref);
 
+  $h_ref = (ref $vars[$#vars] eq "HASH") ? pop(@vars) : \%rethash;
+
   $session = &snmpopen($host, 0, \@vars);
   if (!defined($session)) {
     carp "SNMPWALK Problem for $host\n"
       unless ($SNMP_Session::suppress_warnings > 1);
-    return undef;
+    if (defined($hash_sub)) {
+      return ($h_ref) if ($SNMP_util::Return_hash_refs);
+      return (%$h_ref);
+    } else {
+      @retvals = ();
+      return (@retvals);
+    }
   }
 
-  $h_ref = (ref $vars[$#vars] eq "HASH") ? pop(@vars) : \%rethash;
-
   @enoid = toOID(@vars);
-  return undef unless defined $enoid[0];
+  if ($#enoid < 0) {
+    if (defined($hash_sub)) {
+      return ($h_ref) if ($SNMP_util::Return_hash_refs);
+      return (%$h_ref);
+    } else {
+      @retvals = ();
+      return (@retvals);
+    }
+  }
 
   # GIL
   #
@@ -662,7 +684,15 @@ sub snmpwalk_flg ($$@) {
       }
       if ($ok) {
 	my $tmp = encode_oid_with_errmsg ($tempo);
-	return undef unless defined $tmp;
+	if (!defined $tmp) {
+	  if (defined($hash_sub)) {
+	    return ($h_ref) if ($SNMP_util::Return_hash_refs);
+	    return (%$h_ref);
+	  } else {
+	    @retvals = ();
+	    return (@retvals);
+	  }
+	}
 	if (exists($done{$tmp})) {	# GIL, Ilvja
 	  #
 	  # We've detected a loop for $nnoid[$ix], so mark it as finished.
@@ -743,18 +773,17 @@ sub snmpwalk_flg ($$@) {
 
     last if ($#nnoid < 0);   # @nnoid empty means we are done walking.
   }
-  if ($got) {
-    if (defined($hash_sub)) {
-	return ($h_ref) if ($SNMP_util::Return_hash_refs);
-    	return (%$h_ref);
-    } else {
-    	return (@retvals);
-    }
-  } else {
+  if (!$got) {
     $var = join(' ', @vars);
     carp "SNMPWALK Problem for $var on $host\n"
       unless ($SNMP_Session::suppress_warnings > 1);
-    return undef;
+    @retvals = ();
+  }
+  if (defined($hash_sub)) {
+      return ($h_ref) if ($SNMP_util::Return_hash_refs);
+      return (%$h_ref);
+  } else {
+      return (@retvals);
   }
 }
 
@@ -767,11 +796,12 @@ sub snmpset($@) {
   my($oid, @retvals, $type, $value, $val);
   my $session;
 
+  @retvals = ();
   $session = &snmpopen($host, 0, \@vars);
   if (!defined($session)) {
     carp "SNMPSET Problem for $host\n"
       unless ($SNMP_Session::suppress_warnings > 1);
-    return undef;
+    return wantarray ? @retvals : undef;
   }
 
   while(@vars) {
@@ -826,16 +856,18 @@ sub snmpset($@) {
     } else {
       carp "unknown SNMP type: $type\n"
 	unless ($SNMP_Session::suppress_warnings > 1);
-      return undef;
+      return wantarray ? @retvals : undef;
     }
     if (!defined($val)) {
       carp "SNMP type $type value $value didn't encode properly\n"
 	unless ($SNMP_Session::suppress_warnings > 1);
-      return undef;
+      return wantarray ? @retvals : undef;
     }
     push @enoid, [$oid,$val];
   }
-  return undef unless defined $enoid[0];
+  if ($#enoid < 0) {
+    return wantarray ? @retvals : undef;
+  }
   if ($session->set_request_response(@enoid)) {
     $response = $session->pdu_buffer;
     ($bindings) = $session->decode_get_response($response);
@@ -847,7 +879,7 @@ sub snmpset($@) {
     }
     return wantarray ? @retvals : $retvals[0];
   }
-  return undef;
+  return wantarray ? @retvals : undef;
 }
 
 #
@@ -912,18 +944,18 @@ sub snmpgetbulk ($$$@) {
   my($noid);
   my $session;
 
+  @retvals = ();
   $session = &snmpopen($host, 0, \@vars);
   if (!defined($session)) {
     carp "SNMPGETBULK Problem for $host\n"
       unless ($SNMP_Session::suppress_warnings > 1);
-    return undef;
+    return @retvals;
   }
 
   @enoid = &toOID(@vars);
-  return undef unless defined $enoid[0];
+  return @retvals if ($#enoid < 0);
 
   undef @vars;
-  undef @retvals;
   foreach $noid (@enoid) {
     $upoid = pretty_print($noid);
     push(@vars, $upoid);
@@ -938,12 +970,12 @@ sub snmpgetbulk ($$$@) {
       my $tempv = pretty_print($value);
       push @retvals, "$tempo:$tempv";
     }
-    return (@retvals);
+    return @retvals;
   } else {
     $var = join(' ', @vars);
     carp "SNMPGETBULK Problem for $var on $host\n"
       unless ($SNMP_Session::suppress_warnings > 1);
-    return undef;
+    return @retvals;
   }
 }
 
@@ -1037,7 +1069,10 @@ sub toOID(@) {
     }
     print "toOID: $var\n" if $SNMP_util::Debug;
     $tmp = encode_oid_with_errmsg($var);
-    return undef unless defined $tmp;
+    if (!defined($tmp)) {
+      my @empty = ();
+      return @empty;
+    }
     push(@retvar, $tmp);
   }
   return @retvar;
@@ -1086,7 +1121,7 @@ sub snmpLoad_OID_Cache ($) {
 
   while(<CACHE>) {
     s/#.*//;				# '#' starts a comment
-    s/--.*--//g;			# comment delimited by '--', like MIBs
+    s/--.*?--/ /g;			# comment delimited by '--', like MIBs
     s/--.*//;				# comment started by '--'
     next if (/^$/);
     next unless (/\s/);			# must have whitespace as separator
@@ -1129,7 +1164,8 @@ sub Check_OID ($) {
     if ($oid) {
       return ($oid, $tmp);
     } else {
-      return undef;
+      my @empty = ();
+      return @empty;
     }
   }
   return ($var, $var);
@@ -1172,22 +1208,20 @@ sub snmpMIB_to_OID ($) {
     if ($quote) {
       next unless /"/;
       $quote = 0;
-    } else {
-	s/--.*--//g;		# throw away comments (-- anything --)
-	s/^\s*--.*//;		# throw away comments at start of line
     }
     chomp;
-
     $buf .= ' ' . $_;
 
-    $buf =~ s/"[^"]*"//g;
-    if ($buf =~ /"/) {
+    $buf =~ s/"[^"]*"//g;	# throw away quoted strings
+    $buf =~ s/--.*?--/ /g;	# throw away comments (-- anything --)
+    $buf =~ s/--.*//;		# throw away comments (-- anything to EOL)
+    $buf =~ s/\s+/ /g;		# clean up multiple spaces
+
+    if ($buf =~ /"/) {		# look for quoted string
       $quote = 1;
       next;
     }
-    $buf =~ s/--.*--//g;	# throw away comments (-- anything --)
-    $buf =~ s/--.*//;		# throw away comments (-- anything EOL)
-    $buf =~ s/\s+/ /g;
+
     if ($buf =~ /DEFINITIONS *::= *BEGIN/) {
 	$cnt += MIB_fill_OID(\%tOIDs) if ($tgot);
 	$buf = '';
diff --git a/security.html b/security.html
new file mode 100644
index 0000000..dc11974
--- /dev/null
+++ b/security.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
+<html>
+ <head>
+  <title>SNMP support for Perl 5 - Notes on Security</title>
+ </head>
+ <body bgcolor="#ffffff">
+<div align=center>
+  <h1>SNMP support for Perl 5 - Notes on Security</h1>
+</div>
+
+<p> On February 12, 2002, the Computer Emergency Response Team issued
+<em><a
+href="http://www.cert.org/advisories/CA-2002-03.html">CERT™
+Advisory CA-2002-03 Multiple Vulnerabilities in Many Implementations
+of the Simple Network Management Protocol (SNMP)</a></em>.  The <a
+href="http://www.ee.oulu.fi/research/ouspg/index.html">OUSPG</a> at
+the University of Oulu in Finland had written an SNMPv1 test suite
+that uncovered difficulties in numerous SNMP implementations with
+respect to improperly encoded SNMP PDUs (Protocol Data Units).
+Possible effects of these vulnerabilities included program crashes as
+well as remote exploitabilities.�</p>
+
+<h2> Why <tt>SNMP_Session.pm</tt>/<tt>BER.pm</tt> Users Shouldn't Be
+Too Concerned </h2>
+
+<p> My SNMP support for Perl 5 is written entirely in Perl.  When it
+decodes BER-encoded SNMP PDUs, it parses them from left to right and
+splits them into sub-items as it goes, usually using
+<tt>substr()</tt> or <tt>unpack()</tt>. </p>
+
+<hr>
+<address>
+<!-- hhmts start -->
+2002/04/07 21:43:11
+<!-- hhmts end -->
+<a href="http://www.switch.ch/misc/leinen/">
+ Simon Leinen <simon.leinen at switch.ch></A>
+
+</address>
+
+</body>
+</html>
diff --git a/t/00ber.t b/t/00ber.t
new file mode 100644
index 0000000..cf2bf14
--- /dev/null
+++ b/t/00ber.t
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+###
+### Test BER.pm encoding and decoding routines
+
+use strict;
+use warnings;
+
+use Test::More tests => 15;
+use BER;
+
+### en_decode_test VALUE, ENCODER, TEMPLATE [, ENCODED]
+###
+### Test both encoding and decoding.
+###
+sub en_decode_test ($$$@) {
+    my ($value, $encoder, $template, $encoded) = @_;
+    if (defined $encoded) {
+	is(&$encoder ($value), $encoded);
+    } else {
+	$encoded = &$encoder ($value);
+    }
+    my ($x) = decode_by_template ($encoded, $template);
+    is($x, $value);
+}
+
+### tt PDU, TEMPLATE, EXPECTED, ARGS...
+###
+### Test decoding by template.  The PDU is decoded using TEMPLATE and
+### (optionally) ARGS.  The resulting values are then compared against
+### EXPECTED, which is a reference to a vector of expected values.
+###
+sub tt ($$$@) {
+    my ($pdu, $template, $desired_result, @args) = @_;
+    my @values = decode_by_template ($pdu, $template, @args);
+    print "# ".join ("; ", @values)."\n";
+    is_deeply (\@values, $desired_result);
+}
+
+en_decode_test ("foo", \&encode_string, "%s", "\x04\x03foo");
+en_decode_test (123, \&encode_int, "%i", "\x02\x01\x7b");
+is (encode_oid (1,3,6,1), "\x06\x03\x2b\x06\x01");
+tt ("\x02\x01\x03", "%i", [3]);
+tt ("\x02\x01\x03", "%u", [3]);
+tt ("\x02\x01\xff", "%i", [-1]);
+tt ("\x30\x03\x02\x01\xff", "%{%i", [-1]);
+tt ("\x30\x0b\x02\x01\x12\x02\x01\x02\x04\x03foo", "%{%i%i%s", [18, 2, "foo"]);
+tt ("\x30\x0b\x02\x01\x12\x02\x01\x02\x04\x03foo", "%{%i%2i%s", [18, "foo"]);
+tt ("\x30\x0b\x02\x01\x12\x02\x01\x02\x04\x03foo", "%{%i%2i%*s", [18], "foo");
+tt ("\x04\x03foo", "%s", ["foo"]);
+tt ("\x38\x03\x02\x01\xff", "%*{%i", [-1], 0x38);
+is (join (":",decode_sequence ("\x30\x05\x02\x00\x02\x01\x01\x30\x05\x02\x00\x02\x01\x01")),
+    "\x02\x00\x02\x01\x01:\x30\x05\x02\x00\x02\x01\x01");
diff --git a/test/asn1-test.pl b/test/asn1-test.pl
new file mode 100755
index 0000000..d632772
--- /dev/null
+++ b/test/asn1-test.pl
@@ -0,0 +1,32 @@
+#!/usr/local/bin/perl -w
+######################################################################
+### Name:	  asn1-test.pl
+### Date Created: Sat Feb  1 18:45:38 1997
+### Author:	  Simon Leinen  <simon at switch.ch>
+### RCS $Id: asn1-test.pl,v 1.3 1997-08-15 23:55:48 simon Exp $
+######################################################################
+
+require 5.002;
+use strict;
+use ASN_1;
+
+my ($result, $index);
+
+($result, $index) = &ASN_1::BER::decode_length ("\x81\x02", 0);
+die unless $result == (1 << 7) + 2;
+die unless $index == 2;
+($result, $index) = &ASN_1::BER::decode ("\x01\x01\x01", 0);
+die unless ref($result) eq 'ASN_1::Boolean';
+die unless $result->value eq 1;
+die unless $index == 3;
+($result, $index) = &ASN_1::BER::decode ("\x01\x01\x00", 0);
+die unless ref($result) eq 'ASN_1::Boolean';
+die unless $result->value eq 0;
+die unless $index == 3;
+($result, $index) = &ASN_1::BER::decode ("\x10\x03\x01\x01\x00", 0);
+die "$result" unless ref($result) eq 'ASN_1::Sequence';
+die unless length $result->members == 1;
+die unless ref(($result->members)[0]) eq 'ASN_1::Boolean';
+die unless ($result->members)[0]->value eq 0;
+die unless $index == 5;
+1;
diff --git a/test/atm-cfgmaker b/test/atm-cfgmaker
new file mode 100644
index 0000000..7126cad
--- /dev/null
+++ b/test/atm-cfgmaker
@@ -0,0 +1,128 @@
+#!/usr/local/bin/perl -w
+###
+### atm-cfgmaker HOST [COMMUNITY]
+###
+### Generate MRTG configuration for the PVCs and PVPs configured on a
+### Cisco LS1010 ATM switch.  Uses the RFC 1213 interfaces group and
+### Cisco's CISCO-ATM-CONN-MIB.
+###
+use strict;
+require 5.002;
+
+use SNMP_Session "0.57";
+use BER;
+
+my $ciscoAtmVclInCells = [1,3,6,1,4,1,9,10,13,1,2,1,1,13];
+my $ciscoAtmVclOutCells = [1,3,6,1,4,1,9,10,13,1,2,1,1,14];
+my $ciscoAtmVclCrossIfIndex = [1,3,6,1,4,1,9,10,13,1,2,1,1,15];
+my $ciscoAtmVclCrossVpi = [1,3,6,1,4,1,9,10,13,1,2,1,1,16];
+my $ciscoAtmVclCrossVci = [1,3,6,1,4,1,9,10,13,1,2,1,1,17];
+
+my $ciscoAtmVplInCells = [1,3,6,1,4,1,9,10,13,1,1,1,1,12];
+my $ciscoAtmVplOutCells = [1,3,6,1,4,1,9,10,13,1,1,1,1,13];
+my $ciscoAtmVplCrossIfIndex = [1,3,6,1,4,1,9,10,13,1,1,1,1,14];
+my $ciscoAtmVplCrossVpi = [1,3,6,1,4,1,9,10,13,1,1,1,1,15];
+
+my $router = shift @ARGV || usage (1);
+my $community = shift @ARGV || 'public';
+
+my $session = SNMP_Session->open ($router, $community, 161)
+    || die "Cannot open SNMP session to $router";
+my $if_table = $session->get_if_table ();
+$session->map_table ([$ciscoAtmVclCrossIfIndex,
+		      $ciscoAtmVclCrossVpi,
+		      $ciscoAtmVclCrossVci],
+		     sub ($$$$) {
+			 my ($index, $cross_if_index, $cross_vpi, $cross_vci)
+			     = @_;
+			 my ($if_index, $vpi, $vci) = split ('\.', $index);
+			 grep (defined $_ && ($_=pretty_print $_),
+			       ($cross_if_index, $cross_vpi, $cross_vci));
+			 out_link ($ciscoAtmVclInCells,
+				   $ciscoAtmVclOutCells,
+				   $index,
+				   $if_index,
+				   $cross_if_index,
+				   "VPI=$vpi VCI=$vci",
+				   "VPI=$cross_vpi VCI=$cross_vpi",
+				   $if_table,
+				   $router, $community)
+			     unless ($cross_vpi == 0 && $cross_vci == 5
+				     || $cross_vpi == 0 && $cross_vci == 16);
+		     });
+$session->map_table ([$ciscoAtmVplCrossIfIndex,
+		      $ciscoAtmVplCrossVpi],
+		     sub ($$$$) {
+			 my ($index, $cross_if_index, $cross_vpi)
+			     = @_;
+			 my ($if_index, $vpi) = split ('\.', $index);
+			 grep (defined $_ && ($_=pretty_print $_),
+			       ($cross_if_index, $cross_vpi));
+			 out_link ($ciscoAtmVplInCells,
+				   $ciscoAtmVplOutCells,
+				   $index,
+				   $if_index,
+				   $cross_if_index,
+				   "VPI=$vpi",
+				   "VPI=$cross_vpi",
+				   $if_table,
+				   $router, $community);
+		     });
+$session->close ()
+    || warn "Cannot close SNMP session to $router";
+1;
+
+sub usage ($) {
+    if ($_[0]) {
+	die "Usage: $0 switch-name [community]\n";
+    } else {
+	warn "Usage: $0 switch-name [community]\n";
+    }
+}
+
+sub out_link () {
+    my ($in, $out, $index, $if_index, $cross_if_index, $source_vxi, $dest_vxi, $if_table, $host, $community) = @_;
+    my $source_if_descr = $if_table->{$if_index}->{ifDescr} || $if_index;
+    my $dest_if_descr = $if_table->{$cross_if_index}->{ifDescr} || $cross_if_index;
+    my $source_speed = $if_table->{$if_index}->{ifSpeed};
+    my $dest_speed = $if_table->{$cross_if_index}->{ifSpeed};
+    my $min_speed = $source_speed < $dest_speed ? $source_speed : $dest_speed;
+    my $target_name = $host.'-'.$source_if_descr.'-'.$source_vxi;
+    $target_name =~ s/V[CP]I=//g;
+    $target_name =~ s/-ATM/-/;
+    $target_name =~ s@[-. /]@- at g;
+    my $source_oid = join ('.',@{$in}).".".$index;
+    my $dest_oid = join ('.',@{$out}).".".$index;
+    print STDOUT ("#$host Interface $source_if_descr $source_vxi\n");
+    print STDOUT "Target[$target_name]: $source_oid&$dest_oid:$community\@$host * 53\n";
+    print STDOUT "Options[$target_name]: growright,bits\n";
+    print STDOUT "Title[$target_name]: $host $source_vxi\n";
+    print STDOUT "PageTop[$target_name]: <hr><H3>ATM Traffic on $source_vxi</H3>\n";
+    print STDOUT "YLegend[$target_name]: bits per second\n";
+    print STDOUT "ShortLegend[$target_name]: bps\n";
+    print STDOUT "MaxBytes[$target_name]: ",$min_speed/8,"\n";
+    print STDOUT "AbsMax[$target_name]: ",$min_speed/8,"\n";
+    print STDOUT "\n";
+}
+
+package SNMP_Session;
+
+sub get_if_table ($) {
+    my ($session) = @_;
+
+    my $result = {};
+
+    my $ifDescr = [1,3,6,1,2,1,2,2,1,2];
+    my $ifSpeed = [1,3,6,1,2,1,2,2,1,5];
+    my $locIfDescr = [1,3,6,1,4,1,9,2,2,1,1,28];
+    $session->map_table ([$ifDescr,$locIfDescr,$ifSpeed],
+			 sub ($$$) {
+			     my ($index, $ifDescr, $locIfDescr, $ifSpeed) = @_;
+			     grep (defined $_ && ($_=pretty_print $_),
+				   ($ifDescr, $locIfDescr, $ifSpeed));
+			     $result->{$index} = {'ifDescr' => $ifDescr,
+						  'ifSpeed' => $ifSpeed,
+						  'locIfDescr' => $locIfDescr};
+			 });
+    $result;
+}
diff --git a/test/atol-test.c b/test/atol-test.c
new file mode 100644
index 0000000..ab94bec
--- /dev/null
+++ b/test/atol-test.c
@@ -0,0 +1,37 @@
+/*
+ atol-test.c
+
+ Date Created: Sun Jun 22 21:20:45 1997
+ Author:       Simon Leinen  <simon at switch.ch>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+void atol_test (const char *);
+
+int
+main (int argc, char **argv)
+{
+  unsigned k;
+
+  for (k = 1; k < argc; ++k)
+    {
+      atol_test (argv[k]);
+    }
+  return 0;
+}
+
+void
+atol_test (const char *string)
+{
+  long l, l2;
+  unsigned long ul;
+  long long ll;
+
+  l = atol (string);
+  l2 = strtol (string, 0, 10);
+  ul = strtoul (string, 0, 10);
+  ll = atoll (string);
+  printf ("%s => %ld(atol) %lld(atoll) %ld(strtol) %lu(strtoul)\n", string, l, ll, l2, ul);
+}
diff --git a/test/bad-trap.pl b/test/bad-trap.pl
new file mode 100644
index 0000000..dfebb4c
--- /dev/null
+++ b/test/bad-trap.pl
@@ -0,0 +1,40 @@
+#!/usr/local/bin/perl -w
+use strict;
+use BER;
+use SNMP_Session;
+use Socket;
+
+my $bad_trap
+    = "\x30\x82\x00\x3a"
+    ."\x02\x01\x00\x04\x06\x64\x63\x72\x32\x73\x62\xa4\x2d\x06\x07\x2b"
+    ."\x06\x01\x04\x01\x82\x3e\x40\x04\xce\xaf\x3b\x0e\x02\x01\x04\x02"
+    ."\x01\x00\x43\x04\x36\xfb\x79\x93\x30\x10\x30\x82\x00\x0c\x06\x08"
+    ."\x2b\x06\x01\x02\x01\x01\x01\x00\x05\x00";
+
+my $session = SNMP_Session->open ('localhost', 'public', 1162)
+    || die "open SNMP session: $SNMP_Session::errmsg";
+print_trap ($session, $bad_trap);
+$session->close ()
+    || warn "close SNMP session: $SNMP_Session::errmsg";
+1;
+
+sub print_trap ($$) {
+    my ($this, $trap) = @_;
+    my ($encoded_pair, $oid, $value);
+    my ($community, $ent, $agent, $gen, $spec, $dt, $bindings)
+	= $this->decode_trap_request ($trap);
+    my ($binding, $prefix);
+    print "    community: ".$community."\n";
+    print "   enterprise: ".BER::pretty_oid ($ent)."\n";
+    print "   agent addr: ".inet_ntoa ($agent)."\n";
+    print "   generic ID: $gen\n";
+    print "  specific ID: $spec\n";
+    print "       uptime: ".BER::pretty_uptime_value ($dt)."\n";
+    $prefix = "     bindings: ";
+    while ($bindings) {
+	($binding,$bindings) = decode_sequence ($bindings);
+	($oid,$value) = decode_by_template ($binding, "%O%@");
+	print $prefix.BER::pretty_oid ($oid)." => ".pretty_print ($value)."\n";
+	$prefix = "               ";
+    }
+}
diff --git a/test/bay-atm-test.pl b/test/bay-atm-test.pl
new file mode 100755
index 0000000..306f665
--- /dev/null
+++ b/test/bay-atm-test.pl
@@ -0,0 +1,54 @@
+#!/usr/local/bin/perl -w
+
+require 5;
+
+use SNMP_Session;
+use BER;
+
+$SNMP_Session::suppress_warnings = 1;
+
+$hostname = shift @ARGV || &usage;
+$community = shift @ARGV || 'public';
+&usage if $#ARGV >= 0;
+
+%ugly_oids = qw(u1	1.3.6.1.4.1.930.2.2.2.1.1.1.6.4.2
+		u2	1.3.6.1.4.1.930.2.2.2.1.1.1.8.4.2
+		);
+foreach (keys %ugly_oids) {
+    $ugly_oids{$_} = encode_oid (split (/\./, $ugly_oids{$_}));
+    $pretty_oids{$ugly_oids{$_}} = $_;
+}
+
+srand();
+die "Couldn't open SNMP session to $hostname: $SNMP_Session::errmsg"
+    unless ($session = SNMP_Session->open ($hostname, $community, 161));
+snmp_get ($session, qw(u1 u2));
+$session->close ();
+1;
+
+sub snmp_get
+{
+    my($session, @oids) = @_;
+    my($response, $bindings, $binding, $value, $oid);
+
+    grep ($_ = $ugly_oids{$_}, @oids);
+
+    if ($session->get_request_response (@oids)) {
+	$response = $session->pdu_buffer;
+	($bindings) = $session->decode_get_response ($response);
+
+	while ($bindings ne '') {
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    ($oid,$value) = decode_by_template ($binding, "%O%@");
+	    print $pretty_oids{$oid}," => ",
+	    pretty_print ($value), "\n";
+	}
+    } else {
+	warn "SNMP problem: $SNMP_Session::errmsg\n";
+    }
+}
+
+sub usage
+{
+    die "usage: $0 hostname [community]";
+}
diff --git a/test/bgpls b/test/bgpls
new file mode 100644
index 0000000..8072ad7
--- /dev/null
+++ b/test/bgpls
@@ -0,0 +1,209 @@
+#!/usr/bin/perl -w
+###
+### bgpls
+###
+### Simon Leinen  <simon.leinen at switch.ch>
+###
+### Show the table of the BGP-4 peers of a router.
+###
+use strict;
+
+use SNMP_util;
+
+## Prototypes
+sub bgp_table ($ );
+sub pretty_peer_state ($$);
+sub init_mibs ();
+
+## If $external_only is non-zero, internal BGP peers will be
+## suppressed from output. 
+##
+my $external_only = 1;
+
+## if $abnormal_only is non-zero, only peerings where the operational
+## state is inconsistent with the administrative state are printed.
+##
+my $abnormal_only = 1;
+
+init_mibs ();
+foreach my $target (@ARGV) {
+    bgp_table ($target);
+}
+1;
+
+sub bgp_table ($ ) {
+    my ($target) = @_;
+    my ($local_as, $bgp_id) = snmpget ($target, 'bgpLocalAs.0', 'bgpIdentifier.0');
+    snmpmaptable ($target,
+		  sub ()
+		  {
+		      my ($index, $state, $admin_status, $bgp_version, $remote_as,
+			  $est_time) = @_;
+		      return if $external_only and $remote_as == $local_as;
+		      return if $abnormal_only and
+			  (($admin_status == 1 && $state == 1)
+			   || ($admin_status == 2 && $state == 6));
+			  
+		      printf STDOUT ("%-15s AS%-5d v%1d %s",
+				     $index, $remote_as, $bgp_version,
+				     pretty_peer_state ($state, $admin_status));
+		      if ($state == 6) {
+			  printf STDOUT ", %s", pretty_time ($est_time);
+		      }
+		      print STDOUT "\n";
+		  },
+    'bgpPeerState',
+    'bgpPeerAdminStatus',
+    'bgpPeerNegotiatedVersion',
+    'bgpPeerRemoteAs',
+    'bgpPeerFsmEstablishedTime',
+    );
+}
+
+sub pretty_peer_state ($$) {
+    my ($state, $admin_status) = @_;
+    my @pretty_peer_state
+	= qw(idle connect active opensent openconfirm established);
+    return $state.'/'.$admin_status if $state < 1;
+    return $state.'/'.$admin_status if $state > @pretty_peer_state;
+    return 'shutdown' if $state == 1 and $admin_status == 1;
+    return $pretty_peer_state[$state-1]
+	.($admin_status == 2 ? "" : "/".($admin_status == 1 ? 'stop' : $admin_status));
+};
+
+sub pretty_time ($ ) {
+    my ($secs) = @_;
+    my $result = '';
+    if ($secs > 86400) {
+	$result = sprintf ("%3dd ", int ($secs/86400)), $secs %= 86400;
+    } else { $result = "     "; }
+    if ($secs > 3600) {
+	$result .= sprintf ("%02dh", int ($secs/3600)), $secs %= 3600;
+    } else { $result .= "   "; }
+    return $result.sprintf ("%02d:%02d", int ($secs/60), $secs % 60);
+}
+
+sub init_mibs () {
+    snmpmapOID
+	(qw(
+	    bgp                                     1.3.6.1.2.1.15
+	    bgpVersion                              1.3.6.1.2.1.15.1
+	    bgpLocalAs                              1.3.6.1.2.1.15.2
+	    bgpPeerTable                            1.3.6.1.2.1.15.3
+	    bgpPeerEntry                            1.3.6.1.2.1.15.3.1
+	    bgpPeerIdentifier                       1.3.6.1.2.1.15.3.1.1
+	    bgpPeerState                            1.3.6.1.2.1.15.3.1.2
+	    bgpPeerAdminStatus                      1.3.6.1.2.1.15.3.1.3
+	    bgpPeerNegotiatedVersion                1.3.6.1.2.1.15.3.1.4
+	    bgpPeerLocalAddr                        1.3.6.1.2.1.15.3.1.5
+	    bgpPeerLocalPort                        1.3.6.1.2.1.15.3.1.6
+	    bgpPeerRemoteAddr                       1.3.6.1.2.1.15.3.1.7
+	    bgpPeerRemotePort                       1.3.6.1.2.1.15.3.1.8
+	    bgpPeerRemoteAs                         1.3.6.1.2.1.15.3.1.9
+	    bgpPeerInUpdates                        1.3.6.1.2.1.15.3.1.10
+	    bgpPeerOutUpdates                       1.3.6.1.2.1.15.3.1.11
+	    bgpPeerInTotalMessages                  1.3.6.1.2.1.15.3.1.12
+	    bgpPeerOutTotalMessages                 1.3.6.1.2.1.15.3.1.13
+	    bgpPeerLastError                        1.3.6.1.2.1.15.3.1.14
+	    bgpPeerFsmEstablishedTransitions        1.3.6.1.2.1.15.3.1.15
+	    bgpPeerFsmEstablishedTime               1.3.6.1.2.1.15.3.1.16
+	    bgpPeerConnectRetryInterval             1.3.6.1.2.1.15.3.1.17
+	    bgpPeerHoldTime                         1.3.6.1.2.1.15.3.1.18
+	    bgpPeerKeepAlive                        1.3.6.1.2.1.15.3.1.19
+	    bgpPeerHoldTimeConfigured               1.3.6.1.2.1.15.3.1.20
+	    bgpPeerKeepAliveConfigured              1.3.6.1.2.1.15.3.1.21
+	    bgpPeerMinASOriginationInterval         1.3.6.1.2.1.15.3.1.22
+	    bgpPeerMinRouteAdvertisementInterval    1.3.6.1.2.1.15.3.1.23
+	    bgpPeerInUpdateElapsedTime              1.3.6.1.2.1.15.3.1.24
+	    bgpIdentifier                           1.3.6.1.2.1.15.4
+	    bgpRcvdPathAttrTable                    1.3.6.1.2.1.15.5
+	    bgpPathAttrEntry                        1.3.6.1.2.1.15.5.1
+	    bgpPathAttrPeer                         1.3.6.1.2.1.15.5.1.1
+	    bgpPathAttrDestNetwork                  1.3.6.1.2.1.15.5.1.2
+	    bgpPathAttrOrigin                       1.3.6.1.2.1.15.5.1.3
+	    bgpPathAttrASPath                       1.3.6.1.2.1.15.5.1.4
+	    bgpPathAttrNextHop                      1.3.6.1.2.1.15.5.1.5
+	    bgpPathAttrInterASMetric                1.3.6.1.2.1.15.5.1.6
+	    bgp4PathAttrTable                       1.3.6.1.2.1.15.6
+	    bgp4PathAttrEntry                       1.3.6.1.2.1.15.6.1
+	    bgp4PathAttrPeer                        1.3.6.1.2.1.15.6.1.1
+	    bgp4PathAttrIpAddrPrefixLen             1.3.6.1.2.1.15.6.1.2
+	    bgp4PathAttrIpAddrPrefix                1.3.6.1.2.1.15.6.1.3
+	    bgp4PathAttrOrigin                      1.3.6.1.2.1.15.6.1.4
+	    bgp4PathAttrASPathSegment               1.3.6.1.2.1.15.6.1.5
+	    bgp4PathAttrNextHop                     1.3.6.1.2.1.15.6.1.6
+	    bgp4PathAttrMultiExitDisc               1.3.6.1.2.1.15.6.1.7
+	    bgp4PathAttrLocalPref                   1.3.6.1.2.1.15.6.1.8
+	    bgp4PathAttrAtomicAggregate             1.3.6.1.2.1.15.6.1.9
+	    bgp4PathAttrAggregatorAS                1.3.6.1.2.1.15.6.1.10
+	    bgp4PathAttrAggregatorAddr              1.3.6.1.2.1.15.6.1.11
+	    bgp4PathAttrCalcLocalPref               1.3.6.1.2.1.15.6.1.12
+	    bgp4PathAttrBest                        1.3.6.1.2.1.15.6.1.13
+	    bgp4PathAttrUnknown                     1.3.6.1.2.1.15.6.1.14
+	    bgpTraps                                1.3.6.1.2.1.15.7
+
+	    ciscoBgp4MIB                            1.3.6.1.4.1.9.9.187
+	    ciscoBgp4NotifyPrefix                   1.3.6.1.4.1.9.9.187.0
+	    ciscoBgp4MIBObjects                     1.3.6.1.4.1.9.9.187.1
+	    cbgpRoute                               1.3.6.1.4.1.9.9.187.1.1
+	    cbgpRouteTable                          1.3.6.1.4.1.9.9.187.1.1.1
+	    cbgpRouteEntry                          1.3.6.1.4.1.9.9.187.1.1.1.1
+	    cbgpRouteAfi                            1.3.6.1.4.1.9.9.187.1.1.1.1.1
+	    cbgpRouteSafi                           1.3.6.1.4.1.9.9.187.1.1.1.1.2
+	    cbgpRoutePeerType                       1.3.6.1.4.1.9.9.187.1.1.1.1.3
+	    cbgpRoutePeer                           1.3.6.1.4.1.9.9.187.1.1.1.1.4
+	    cbgpRouteAddrPrefix                     1.3.6.1.4.1.9.9.187.1.1.1.1.5
+	    cbgpRouteAddrPrefixLen                  1.3.6.1.4.1.9.9.187.1.1.1.1.6
+	    cbgpRouteOrigin                         1.3.6.1.4.1.9.9.187.1.1.1.1.7
+	    cbgpRouteASPathSegment                  1.3.6.1.4.1.9.9.187.1.1.1.1.8
+	    cbgpRouteNextHop                        1.3.6.1.4.1.9.9.187.1.1.1.1.9
+	    cbgpRouteMedPresent                     1.3.6.1.4.1.9.9.187.1.1.1.1.10
+	    cbgpRouteMultiExitDisc                  1.3.6.1.4.1.9.9.187.1.1.1.1.11
+	    cbgpRouteLocalPrefPresent               1.3.6.1.4.1.9.9.187.1.1.1.1.12
+	    cbgpRouteLocalPref                      1.3.6.1.4.1.9.9.187.1.1.1.1.13
+	    cbgpRouteAtomicAggregate                1.3.6.1.4.1.9.9.187.1.1.1.1.14
+	    cbgpRouteAggregatorAS                   1.3.6.1.4.1.9.9.187.1.1.1.1.15
+	    cbgpRouteAggregatorAddrType             1.3.6.1.4.1.9.9.187.1.1.1.1.16
+	    cbgpRouteAggregatorAddr                 1.3.6.1.4.1.9.9.187.1.1.1.1.17
+	    cbgpRouteBest                           1.3.6.1.4.1.9.9.187.1.1.1.1.18
+	    cbgpRouteUnknownAttr                    1.3.6.1.4.1.9.9.187.1.1.1.1.19
+	    cbgpPeer                                1.3.6.1.4.1.9.9.187.1.2
+	    cbgpPeerTable                           1.3.6.1.4.1.9.9.187.1.2.1
+	    cbgpPeerEntry                           1.3.6.1.4.1.9.9.187.1.2.1.1
+	    cbgpPeerPrefixAccepted                  1.3.6.1.4.1.9.9.187.1.2.1.1.1
+	    cbgpPeerPrefixDenied                    1.3.6.1.4.1.9.9.187.1.2.1.1.2
+	    cbgpPeerPrefixLimit                     1.3.6.1.4.1.9.9.187.1.2.1.1.3
+	    cbgpPeerPrefixAdvertised                1.3.6.1.4.1.9.9.187.1.2.1.1.4
+	    cbgpPeerPrefixSuppressed                1.3.6.1.4.1.9.9.187.1.2.1.1.5
+	    cbgpPeerPrefixWithdrawn                 1.3.6.1.4.1.9.9.187.1.2.1.1.6
+	    cbgpPeerLastErrorTxt                    1.3.6.1.4.1.9.9.187.1.2.1.1.7
+	    cbgpPeerPrevState                       1.3.6.1.4.1.9.9.187.1.2.1.1.8
+	    cbgpPeerCapsTable                       1.3.6.1.4.1.9.9.187.1.2.2
+	    cbgpPeerCapsEntry                       1.3.6.1.4.1.9.9.187.1.2.2.1
+	    cbgpPeerCapCode                         1.3.6.1.4.1.9.9.187.1.2.2.1.1
+	    cbgpPeerCapIndex                        1.3.6.1.4.1.9.9.187.1.2.2.1.2
+	    cbgpPeerCapValue                        1.3.6.1.4.1.9.9.187.1.2.2.1.3
+	    cbgpPeerAddrFamilyTable                 1.3.6.1.4.1.9.9.187.1.2.3
+	    cbgpPeerAddrFamilyEntry                 1.3.6.1.4.1.9.9.187.1.2.3.1
+	    cbgpPeerAddrFamilyAfi                   1.3.6.1.4.1.9.9.187.1.2.3.1.1
+	    cbgpPeerAddrFamilySafi                  1.3.6.1.4.1.9.9.187.1.2.3.1.2
+	    cbgpPeerAddrFamilyName                  1.3.6.1.4.1.9.9.187.1.2.3.1.3
+	    cbgpPeerAddrFamilyPrefixTable           1.3.6.1.4.1.9.9.187.1.2.4
+	    cbgpPeerAddrFamilyPrefixEntry           1.3.6.1.4.1.9.9.187.1.2.4.1
+	    cbgpPeerAcceptedPrefixes                1.3.6.1.4.1.9.9.187.1.2.4.1.1
+	    cbgpPeerDeniedPrefixes                  1.3.6.1.4.1.9.9.187.1.2.4.1.2
+	    cbgpPeerPrefixAdminLimit                1.3.6.1.4.1.9.9.187.1.2.4.1.3
+	    cbgpPeerPrefixThreshold                 1.3.6.1.4.1.9.9.187.1.2.4.1.4
+	    cbgpPeerPrefixClearThreshold            1.3.6.1.4.1.9.9.187.1.2.4.1.5
+	    cbgpPeerAdvertisedPrefixes              1.3.6.1.4.1.9.9.187.1.2.4.1.6
+	    cbgpPeerSuppressedPrefixes              1.3.6.1.4.1.9.9.187.1.2.4.1.7
+	    cbgpPeerWithdrawnPrefixes               1.3.6.1.4.1.9.9.187.1.2.4.1.8
+	    ciscoBgp4NotificationPrefix             1.3.6.1.4.1.9.9.187.2
+	    ciscoBgp4MIBConformance                 1.3.6.1.4.1.9.9.187.3
+	    ciscoBgp4MIBCompliances                 1.3.6.1.4.1.9.9.187.3.1
+	    ciscoBgp4MIBGroups                      1.3.6.1.4.1.9.9.187.3.2
+	    ciscoBgp4RouteGroup                     1.3.6.1.4.1.9.9.187.3.2.1
+	    ciscoBgp4PeerGroup                      1.3.6.1.4.1.9.9.187.3.2.2
+	    ciscoBgp4PeerGroup1                     1.3.6.1.4.1.9.9.187.3.2.4
+	    ));
+}
diff --git a/test/bridge-list-fdb b/test/bridge-list-fdb
new file mode 100644
index 0000000..8546f27
--- /dev/null
+++ b/test/bridge-list-fdb
@@ -0,0 +1,104 @@
+#!/usr/local/bin/perl -w
+
+## Print dot1dTpFdbTable from RFC 1493
+
+use strict;
+use SNMP_Session;
+use BER;
+
+my $dot1dTpFdbAddress = [1,3,6,1,2,1,17,4,3,1,1];
+my $dot1dTpFdbPort    = [1,3,6,1,2,1,17,4,3,1,2];
+my $dot1dTpFdbStatus  = [1,3,6,1,2,1,17,4,3,1,3];
+
+my $host = shift @ARGV || die "Usage: $0 host [community]";
+my $community = shift @ARGV || 'public';
+
+my $session = SNMP_Session->open ($host, $community, 161)
+    || die "open SNMP session to $community\@$host: $!";
+$session->map_table ([$dot1dTpFdbPort, $dot1dTpFdbStatus],
+		     sub () {
+			 my ($addr, $port, $status) = @_;
+			 $addr = ether_hex (hex_string_aux (pack ("C6", split ('\.', $addr))));
+			 grep (defined $_ && ($_=pretty_print $_),
+			       ($port, $status));
+			 note_fdb ($addr, $port, $status);
+		     });
+$session->close
+    || warn "close SNMP session: $!";
+
+list_fdbs ();
+1;
+
+my %all_fdbs;
+
+sub fdb_addr ($) { defined $_[1] ? $_[0]->{addr} = $_[1] : $_[0]->{addr}; }
+sub fdb_port ($) { defined $_[1] ? $_[0]->{port} = $_[1] : $_[0]->{port}; }
+sub fdb_status ($) { defined $_[1] ? $_[0]->{status} = $_[1] : $_[0]->{status}; }
+
+sub make_fdb ($@) {
+    my ($addr, $port, $status) = @_;
+    {
+	addr => $addr, port => $port, status => $status,
+    };
+}
+
+sub note_fdb ($$@) {
+    my ($addr, @other_args) = @_;
+    my $fdb = make_fdb ($addr, @other_args);
+    $all_fdbs{$addr} = $fdb;
+    $fdb;
+}
+
+sub list_fdbs () {
+    print_fdbs_table_header ();
+    foreach my $fdb (sort { $a->{port} <=> $b->{port} || $a->{addr} cmp $b->{addr} }
+		      values %all_fdbs) {
+	my $addr = fdb_addr ($fdb);
+	my $port = fdb_port ($fdb);
+	my $status = fdb_status ($fdb);
+	printf STDOUT ("%4d %-20s %s\n", 
+		       $port, $addr, pretty_fdb_status ($status));
+    }
+}
+
+sub print_fdbs_table_header () {
+    printf STDOUT ("%-4s %-20s %s\n",
+		   "port",
+		   "MAC addr.",
+		   "status");
+    print STDOUT (("=" x 35),"\n");
+}
+
+sub pretty_fdb_status ($) {
+    my ($status) = @_;
+    if ($status == 1) {
+	return "other";
+    } elsif ($status == 2) {
+	return "invalid";
+    } elsif ($status == 3) {
+	return "learned";
+    } elsif ($status == 4) {
+	return "self";
+    } elsif ($status == 5) {
+	return "mgmt";
+    } else {
+	return "ILLEGAL".$status;
+    }
+}
+
+sub ether_hex ($) {
+  my ($string) = @_;
+  $string =~ s/([0-9a-f][0-9a-f])/$1:/g;
+  $string =~ s/:$//;
+  $string;
+}
+
+sub hex_string_aux ($) {
+    my ($binary_string) = @_;
+    my ($c, $result);
+    $result = '';
+    for $c (unpack "C*", $binary_string) {
+	$result .= sprintf "%02x", $c;
+    }
+    $result;
+}
diff --git a/test/cammer b/test/cammer
new file mode 100755
index 0000000..f0b8d07
--- /dev/null
+++ b/test/cammer
@@ -0,0 +1,245 @@
+#! /usr/sepp/bin/perl
+# -*- mode: Perl -*-
+##################################################################
+# Cammer 1.0
+##################################################################
+# Created by Tobias Oetiker <oetiker at ee.ethz.ch>
+#
+# Cammer needs the address of your local cisco switch and the address
+# of your router. With this it can produce a list of which machine
+# is currently active on which Switch interface
+##################################################################
+# Distributed under the GNU copyleft
+# Copyright 2000 by Tobias Oetiker
+##################################################################
+
+
+require 5.005;
+use strict;
+my $DEBUG = 0;
+BEGIN {
+    # Automatic OS detection ... do NOT touch
+    if ( $^O =~ /^(?:(ms)?(dos|win(32|nt)?))/i ) {
+        $main::OS = 'NT';
+        $main::SL = '\\';
+        $main::PS = ';';
+    } elsif ( $^O =~ /^VMS$/i ) {
+        $main::OS = 'VMS';
+        $main::SL = '.';
+        $main::PS = ':';
+    } else {
+        $main::OS = 'UNIX';
+        $main::SL = '/';
+        $main::PS = ':';
+    }
+}
+
+use FindBin;
+use lib "${FindBin::Bin}";
+use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2";
+
+use SNMP_Session "0.78";
+use BER "0.77";
+use SNMP_util "0.77";
+use Getopt::Long;
+use Pod::Usage;
+use Socket;
+
+
+my %OID = ('vlanIndex' =>             [1,3,6,1,4,1,9,5,1,9,2,1,1],
+           'vmVlan' =>                [1,3,6,1,4,1,9,9,68,1,2,2,1,2],
+	   'dot1dTpFdbPort' =>        [1,3,6,1,2,1,17,4,3,1,2],
+	   'dot1dBasePortIfIndex' =>  [1,3,6,1,2,1,17,1,4,1,2],
+	   'sysObjectID' =>           [1,3,6,1,2,1,1,2,0],
+           'CiscolocIfDescr' =>       [1,3,6,1,4,1,9,2,2,1,1,28],
+	   'ifAlias' =>               [1,3,6,1,2,1,31,1,1,1,18],
+           'ifName' =>                [1,3,6,1,2,1,31,1,1,1,1],
+           'ipNetToMediaPhysAddress' => [1,3,6,1,2,1,4,22,1,2],
+          );
+
+
+sub main {
+    my %opt;
+    options(\%opt);
+    # which vlans do exist on the device
+    my @vlans;
+    my $vlani;
+    my %vlan;
+    my $sws = SNMPv2c_Session->open ($opt{sw},$opt{swco},161)
+                || die "Opening SNMP_Session\n";
+
+    
+    warn "* Gather VLAN index Table from Switch\n";
+    my $sysdesc = (snmpget($opt{swco}.'@'.$opt{sw},'sysDescr'))[0];
+     
+    if ($sysdesc =~ /2900/){
+        warn "* Going into Cisco 2900 Mode\n";
+	$sws->map_table_4 ( [$OID{'vmVlan'}],
+           sub {    my($x,$value) = pretty(@_);
+        	    $vlan{$x} = $value; # catalyst 2900
+	            print "if: $x, vlan: $value\n" if $DEBUG;
+	            if (not scalar grep {$_ eq $value} @vlans) {
+		       push @vlans, $value;
+                       print "vlan: $value\n" if $DEBUG;
+	            }
+               }
+	,100);
+    } else {
+	$sws->map_table_4 ([$OID{'vlanIndex'}], 
+           sub {
+	       my($x,$value) = pretty(@_);
+	       push @vlans, $value;
+               print "vlan: $value\n" if $DEBUG;
+	   }
+        ,100 );
+    }
+    # which ifNames
+    my %name;
+    warn "* Gather Interface Name Table from Switch\n";
+    $sws->map_table_4 ([$OID{'ifName'}],
+        sub { my($if,$name) = pretty(@_);
+	      print "if: $if, name: $name\n" if $DEBUG;
+	      $name{$if}=$name;
+        }
+    ,100);
+    $sws->close();
+    # get mac to ip from router
+    my $ros = SNMPv2c_Session->open ($opt{ro},$opt{roco},161)
+                || die "Opening SNMP_Session\n";
+
+    my %ip;
+    warn "* Gather Arp Table from Router\n";
+    $ros->map_table_4 ([$OID{'ipNetToMediaPhysAddress'}],
+        sub {
+   	     my($ip,$mac) = pretty(@_);
+             $mac = unpack 'H*', pack 'a*',$mac;
+             $mac =~ s/../$&:/g;
+             $mac =~ s/.$//;
+             $ip =~ s/^.+?\.//;
+    	     push @{$ip{$mac}}, $ip;
+ 	     print "ip: $ip, mac: $mac\n" if $DEBUG;
+         }
+    ,100);
+    $ros->close();
+    # walk CAM table for each VLAN
+    my %if;
+    my %port;
+    warn "* Gather Mac 2 Port and Port 2 Interface table for all VLANS\n";
+    foreach my $vlan (@vlans){
+        # catalist 2900 does not use com at vlan hack
+        my $sws = SNMPv2c_Session->open ($opt{sw},$opt{swco}.'@'.$vlan,161)
+                || die "Opening SNMP_Session\n";
+    	$sws->map_table_4 ([$OID{'dot1dTpFdbPort'}],
+          sub {
+             my($mac,$port) = pretty(@_);
+   	     next if $port == 0;
+       	     $mac = sprintf "%02x:%02x:%02x:%02x:%02x:%02x", (split /\./, $mac);
+             print "mac: $mac,port: $port\n" if $DEBUG;
+	     $port{$vlan}{$mac}=$port;
+          }
+        ,100);
+	$sws->map_table_4 ( [$OID{'dot1dBasePortIfIndex'}],
+          sub {  my($port,$if) = pretty(@_);
+	         next if $port == 0;
+                 print "port: $port, if: $if\n" if $DEBUG;
+	         $if{$vlan}{$port} = $if;
+	  }
+        ,100);
+        $sws->close();
+    }
+    my %output;
+    foreach my $vlan (@vlans){
+        foreach my $mac (keys %{$port{$vlan}}){
+           my @ip = $ip{$mac} ? @{$ip{$mac}} : ();
+           my @host;
+           foreach my $ip (@ip) {
+                my $host = gethostbyaddr(pack('C4',split(/\./,$ip)),AF_INET);
+                $host =~ s/\.ethz\.ch//;
+                push @host, ($host or $ip);
+           }
+           my $name = $name{$if{$vlan}{$port{$vlan}{$mac}}};
+           my $truevlan = $vlan eq 'none' ? $vlan{$if{$vlan}{$port{$vlan}{$mac}}} : $vlan;
+           my $quest = scalar @ip > 1 ? "(Multi If Host)":"";
+	   push @{$output{$name}}, sprintf "%4s  %-17s  %-15s  %s %s",$truevlan,$mac,$ip[0],$host[0],$quest;
+        }
+    }
+    foreach my $name (sort keys %output){
+        foreach my $line (@{$output{$name}}) {
+                printf "%-4s  %s\n", $name , $line;
+        }
+    }
+}
+
+main;
+exit 0;
+
+                                 
+sub options () {
+   my $opt = shift;
+   GetOptions( $opt,
+   	'help|?',
+	'man') or pod2usage(2);
+   pod2usage(-verbose => 1) if $$opt{help} or scalar @ARGV != 2;
+   $opt->{sw} = shift @ARGV;
+   $opt->{ro} = shift @ARGV;
+   pod2usage(-exitstatus => 0, -verbose => 2) if $$opt{man};
+
+   $opt->{sw} =~ /^(.+)@(.+?)$/;
+   $opt->{sw} = $2;
+   $opt->{swco} = $1;
+   $opt->{ro} =~ /^(.+)@(.+?)$/;
+   $opt->{ro} = $2;
+   $opt->{roco} = $1;
+}
+
+sub pretty(@){
+  my $index = shift;
+  my @ret = ($index);
+  foreach my $x (@_){
+        push @ret, pretty_print($x);
+  };
+  return @ret;
+}
+
+__END__
+
+=head1 NAME
+
+cammer - list switch ports with associated IP-addresses
+
+=head1 SYNOPSIS
+
+cammer [--help|--man] community at switch community at router
+
+
+=head1 DESCRIPTION
+
+B<Cammer> is a script which polls a switch and a router in order to produce
+a list of machines attached (and currently online) at each port of the
+switch.
+
+=head1 COPYRIGHT
+
+Copyright (c) 2000 ETH Zurich, All rights reserved.
+
+=head1 LICENSE
+
+This script is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+=head1 AUTHOR
+
+Tobias Oetiker E<lt>oetiker at ee.ethz.chE<gt>
+
+=cut
diff --git a/test/cisco-config-history b/test/cisco-config-history
new file mode 100755
index 0000000..1bcb1e2
--- /dev/null
+++ b/test/cisco-config-history
@@ -0,0 +1,170 @@
+#!/usr/local/bin/perl -w
+###
+### Print history of configuration management actions on Cisco router
+### using CISCO-CONFIG-MAN-MIB.my.
+###
+use strict;
+
+use BER "0.72";
+use SNMP_Session "0.67";
+use Time::HiRes;
+
+use Getopt::Long;
+
+my $version = '1';
+
+while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
+    if ($ARGV[0] =~ /^-v/) {
+	if ($ARGV[0] eq '-v') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] eq '1') {
+	    $version = '1';
+	} elsif ($ARGV[0] eq '2c') {
+	    $version = '2c';
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] eq '-h') {
+	usage (0);
+	exit 0;
+    } else {
+	usage (1);
+    }
+    shift @ARGV;
+}
+my $host = shift @ARGV || usage (1);
+my $community = shift @ARGV || "public";
+usage (1) if $#ARGV >= $[;
+
+my $ccmHistoryEventTime = [1,3,6,1,4,1,9,9,43,1,1,6,1,2];
+my $ccmHistoryEventCommandSource = [1,3,6,1,4,1,9,9,43,1,1,6,1,3];
+my $ccmHistoryEventConfigSource = [1,3,6,1,4,1,9,9,43,1,1,6,1,4];
+my $ccmHistoryEventConfigDestination = [1,3,6,1,4,1,9,9,43,1,1,6,1,5];
+my $ccmHistoryEventTerminalType = [1,3,6,1,4,1,9,9,43,1,1,6,1,6];
+my $ccmHistoryEventTerminalNumber = [1,3,6,1,4,1,9,9,43,1,1,6,1,7];
+my $ccmHistoryEventTerminalUser = [1,3,6,1,4,1,9,9,43,1,1,6,1,8];
+my $ccmHistoryEventTerminalLocation = [1,3,6,1,4,1,9,9,43,1,1,6,1,9];
+my $ccmHistoryEventCommandSourceAddress = [1,3,6,1,4,1,9,9,43,1,1,6,1,10];
+my $ccmHistoryEventVirtualHostName = [1,3,6,1,4,1,9,9,43,1,1,6,1,11];
+my $ccmHistoryEventServerAddress = [1,3,6,1,4,1,9,9,43,1,1,6,1,12];
+my $ccmHistoryEventFile = [1,3,6,1,4,1,9,9,43,1,1,6,1,13];
+my $ccmHistoryEventRcpUser = [1,3,6,1,4,1,9,9,43,1,1,6,1,14];
+
+my $session =
+    ($version eq '1' ? SNMPv1_Session->open ($host, $community, 161)
+     : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, 161)
+     : die "Unknown SNMP version $version")
+  || die "Opening SNMP_Session";
+
+my $router_boottime = router_boottime ($session);
+
+$session->map_table ([$ccmHistoryEventTime,
+		      $ccmHistoryEventConfigSource,
+		      $ccmHistoryEventConfigDestination],
+		     sub () {
+			 my ($index, $time, $source, $dest) = @_;
+			 local $BER::pretty_print_timeticks = 0;
+			 grep (defined $_ && ($_=pretty_print $_),
+			       ($time, $source, $dest));
+			 my ($action);
+
+			 if ($source == 3 && $dest == 2) {
+			     $action = "show running-config";
+			 } elsif ($source == 4 && $dest == 2) {
+			     $action = "show configuration";
+			 } elsif ($source == 3 && $dest == 4) {
+			     $action = "write memory";
+			 } elsif ($source == 2 && $dest == 3) {
+			     $action = "configure terminal";
+			 } elsif ($source == 6 && $dest == 3) {
+			     $action = "configure network tftp:";
+			 } elsif ($source == 3 && $dest == 6) {
+			     $action = "write network tftp:";
+			 } else {
+			     $source = pretty_HistoryEventMedium ($source);
+			     $dest = pretty_HistoryEventMedium ($dest);
+			     $action = "$source -> $dest";
+			 }
+			 my $localtime = localtime ($time * 1e-2+$router_boottime);
+			 print $localtime," ",$action,"\n";
+			 
+		     });
+$session->close ()
+    || die "close SNMP session";
+1;
+
+sub pretty_HistoryEventMedium ($) {
+    my ($medium) = @_;
+    if ($medium == 1) {
+	return 'erase';
+    } elsif ($medium == 2) {
+	return 'commandSource';
+    } elsif ($medium == 3) {
+	return 'running';
+    } elsif ($medium == 4) {
+	return 'startup';
+    } elsif ($medium == 5) {
+	return 'local';
+    } elsif ($medium == 6) {
+	return 'networkTftp';
+    } elsif ($medium == 7) {
+	return 'networkRcp';
+    } else {
+	return "$medium???";
+    }
+}
+
+sub usage ($) {
+    warn <<EOM;
+Usage: $0 [-v (1|2c)] router [community]
+       $0 -h
+
+  -h           print this usage message and exit.
+
+  -v version   can be used to select the SNMP version.  The default
+   	       is SNMPv1, which is what most devices support.  If your box
+   	       supports SNMPv2c, you should enable this by passing "-v 2c"
+   	       to the script.  SNMPv2c is much more efficient for walking
+   	       tables, which is what this tool does.
+
+  router       hostname or IP address of a Cisco IOS device
+
+  community    SNMP community string to use.  Defaults to "public".
+EOM
+    exit (1) if $_[0];
+}
+
+### router_boottime SESSION
+###
+### Returns the boot time of the SNMP agent reachable through SESSION
+### in Unix seconds 
+###
+sub router_boottime ($) {
+    my ($session) = @_;
+
+    my $uptime = uptime_hundredths ($session);
+    my ($seconds,$microseconds) = Time::HiRes::gettimeofday ();
+    return $seconds + $microseconds * 1e-6 - $uptime * 1e-2;
+}
+
+sub uptime_hundredths ($) {
+    my ($session) = @_;
+    local $BER::pretty_print_timeticks = 0;
+    if ($session->get_request_response (encode_oid (1,3,6,1,2,1,1,3,0))) {
+	my $response = $session->pdu_buffer;
+	my ($bindings) = $session->decode_get_response ($response);
+	my $binding;
+	while ($bindings ne '') {
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    my ($oid,$value) = decode_by_template ($binding, "%O%@");
+	    my ($uptime) = pretty_print ($value);
+	    return $uptime;
+	}
+    } else {
+	die "cannot get sysUpTime.0 from $host";
+    }
+}
diff --git a/test/cisco-list-cards b/test/cisco-list-cards
new file mode 100755
index 0000000..be0f81d
--- /dev/null
+++ b/test/cisco-list-cards
@@ -0,0 +1,258 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use SNMP_Session;
+use BER;
+
+my %pretty_card_type =
+(
+    1 => 'unknown', 2 => 'csc1', 3 => 'csc2', 4 => 'csc3',
+    5 => 'csc4', 6 => 'rp', 7 => 'cpu-igs', 8 => 'cpu-2500',
+    9 => 'cpu-3000', 10 => 'cpu-3100', 11 => 'cpu-accessPro',
+    12 => 'cpu-4000', 13 => 'cpu-4000m', 14 => 'cpu-4500',
+    15 => 'rsp1', 16 => 'rsp2', 17 => 'cpu-4500m', 18 => 'cpu-1003',
+    19 => 'cpu-4700', 20 => 'csc-m', 21 => 'csc-mt', 22 => 'csc-mc',
+    23 => 'csc-mcplus', 24 => 'csc-envm', 25 => 'chassisInterface',
+    26 => 'cpu-4700S', 27 => 'cpu-7200-npe100', 28 => 'rsp7000',
+    29 => 'chassisInterface7000', 30 => 'rsp4', 31 => 'cpu-3600',
+    32 => 'cpu-as5200', 33 => 'c7200-io1fe', 34 => 'cpu-4700m',
+    35 => 'cpu-1600', 36 => 'c7200-io', 37 => 'cpu-1503',
+    38 => 'cpu-1502', 39 => 'cpu-as5300', 40 => 'csc-16',
+    41 => 'csc-p', 50 => 'csc-a', 51 => 'csc-e1', 52 => 'csc-e2',
+    53 => 'csc-y', 54 => 'csc-s', 55 => 'csc-t', 80 => 'csc-r',
+    81 => 'csc-r16', 82 => 'csc-r16m', 83 => 'csc-1r', 84 => 'csc-2r',
+    56 => 'sci4s', 57 => 'sci2s2t', 58 => 'sci4t', 59 => 'mci1t',
+    60 => 'mci2t', 61 => 'mci1s', 62 => 'mci1s1t', 63 => 'mci2s',
+    64 => 'mci1e', 65 => 'mci1e1t', 66 => 'mci1e2t', 67 => 'mci1e1s',
+    68 => 'mci1e1s1t', 69 => 'mci1e2s', 70 => 'mci2e',
+    71 => 'mci2e1t', 72 => 'mci2e2t', 73 => 'mci2e1s',
+    74 => 'mci2e1s1t', 75 => 'mci2e2s', 100 => 'csc-cctl1',
+    101 => 'csc-cctl2', 110 => 'csc-mec2', 111 => 'csc-mec4',
+    112 => 'csc-mec6', 113 => 'csc-fci', 114 => 'csc-fcit',
+    115 => 'csc-hsci', 116 => 'csc-ctr', 121 => 'cpu-7200-npe150',
+    122 => 'cpu-7200-npe200', 123 => 'cpu-wsx5302', 124 => 'gsr-rp',
+    126 => 'cpu-3810', 127 => 'cpu-2600', 150 => 'sp', 151 => 'eip',
+    152 => 'fip', 153 => 'hip', 154 => 'sip', 155 => 'trip',
+    156 => 'fsip', 157 => 'aip', 158 => 'mip', 159 => 'ssp',
+    160 => 'cip', 161 => 'srs-fip', 162 => 'srs-trip', 163 => 'feip',
+    164 => 'vip', 165 => 'vip2', 166 => 'ssip', 167 => 'smip',
+    168 => 'posip', 169 => 'feip-tx', 170 => 'feip-fx',
+    178 => 'cbrt1', 179 => 'cbr120e1', 180 => 'cbr75e',
+    181 => 'vip2-50', 182 => 'feip2', 183 => 'acip',
+    200 => 'npm-4000-fddi-sas', 201 => 'npm-4000-fddi-das',
+    202 => 'npm-4000-1e', 203 => 'npm-4000-1r', 204 => 'npm-4000-2s',
+    205 => 'npm-4000-2e1', 206 => 'npm-4000-2e',
+    207 => 'npm-4000-2r1', 208 => 'npm-4000-2r', 209 => 'npm-4000-4t',
+    210 => 'npm-4000-4b', 211 => 'npm-4000-8b', 212 => 'npm-4000-ct1',
+    213 => 'npm-4000-ce1', 214 => 'npm-4000-1a',
+    215 => 'npm-4000-6e-pci', 217 => 'npm-4000-1fe',
+    218 => 'npm-4000-1hssi', 230 => 'pa-1fe', 231 => 'pa-8e',
+    232 => 'pa-4e', 233 => 'pa-5e', 234 => 'pa-4t', 235 => 'pa-4r',
+    236 => 'pa-fddi', 237 => 'sa-encryption', 238 => 'pa-ah1t',
+    239 => 'pa-ah2t', 241 => 'pa-a8t-v35', 242 => 'pa-1fe-tx-isl',
+    243 => 'pa-1fe-fx-isl', 244 => 'pa-1fe-tx-nisl',
+    245 => 'sa-compression', 246 => 'pa-atm-lite-1', 247 => 'pa-ct3',
+    248 => 'pa-oc3sm-mux-cbrt1', 249 => 'pa-oc3sm-mux-cbr120e1',
+    254 => 'pa-ds3-mux-cbrt1', 255 => 'pa-e3-mux-cbr120e1',
+    257 => 'pa-8b-st', 258 => 'pa-4b-u', 259 => 'pa-fddi-fd',
+    260 => 'pm-cpm-1e2w', 261 => 'pm-cpm-2e2w',
+    262 => 'pm-cpm-1e1r2w', 263 => 'pm-ct1-csu', 264 => 'pm-2ct1-csu',
+    265 => 'pm-ct1-dsx1', 266 => 'pm-2ct1-dsx1',
+    267 => 'pm-ce1-balanced', 268 => 'pm-2ce1-balanced',
+    269 => 'pm-ce1-unbalanced', 270 => 'pm-2ce1-unbalanced',
+    271 => 'pm-4b-u', 272 => 'pm-4b-st', 273 => 'pm-8b-u',
+    274 => 'pm-8b-st', 275 => 'pm-4as', 276 => 'pm-8as',
+    277 => 'pm-4e', 278 => 'pm-1e', 280 => 'pm-m4t', 281 => 'pm-16a',
+    282 => 'pm-32a', 283 => 'pm-c3600-1fe-tx',
+    284 => 'pm-c3600-compression', 285 => 'pm-dmodem',
+    288 => 'pm-c3600-1fe-fx', 290 => 'as5200-carrier',
+    291 => 'as5200-2ct1', 292 => 'as5200-2ce1',
+    310 => 'pm-as5xxx-12m', 330 => 'wm-c2500-5in1',
+    331 => 'wm-c2500-t1-csudsu', 332 => 'wm-c2500-sw56-2wire-csudsu',
+    333 => 'wm-c2500-sw56-4wire-csudsu', 334 => 'wm-c2500-bri',
+    335 => 'wm-c2500-bri-nt1', 360 => 'wic-serial-1t',
+    364 => 'wic-s-t-3420', 365 => 'wic-s-t-2186', 366 => 'wic-u-3420',
+    367 => 'wic-u-2091', 368 => 'wic-u-2091-2081', 400 => 'pa-jt2',
+    401 => 'pa-posdw', 402 => 'pa-4me1-bal', 406 => 'pa-atmdx-ds3',
+    407 => 'pa-atmdx-e3', 408 => 'pa-atmdx-sml-oc3',
+    409 => 'pa-atmdx-smi-oc3', 410 => 'pa-atmdx-mm-oc3',
+    414 => 'pa-a8t-x21', 415 => 'pa-a8t-rs232',
+    416 => 'pa-4me1-unbal', 417 => 'pa-4r-fdx',
+    424 => 'pa-1fe-fx-nisl', 435 => 'mc3810-dcm',
+    436 => 'mc3810-mfm-e1balanced-bri',
+    437 => 'mc3810-mfm-e1unbalanced-bri',
+    438 => 'mc3810-mfm-e1-unbalanced', 439 => 'mc3810-mfm-dsx1-bri',
+    440 => 'mc3810-mfm-dsx1-csu', 441 => 'mc3810-vcm',
+    442 => 'mc3810-avm', 443 => 'mc3810-avm-fxs',
+    444 => 'mc3810-avm-fxo', 445 => 'mc3810-avm-em',
+    480 => 'as5300-4ct1', 481 => 'as5300-4ce1',
+    482 => 'as5300-carrier', 500 => 'vic-em', 501 => 'vic-fxo',
+    502 => 'vic-fxs', 503 => 'vpm-2v', 504 => 'vpm-4v',
+    530 => 'pos-qoc3-mm', 531 => 'pos-qoc3-sm', 532 => 'pos-oc12-mm',
+    533 => 'pos-oc12-sm', 534 => 'atm-oc12-mm', 535 => 'atm-oc12-sm',
+    536 => 'pos-oc48-mm-l', 537 => 'pos-oc48-sm-l', 538 => 'gsr-sfc',
+    539 => 'gsr-csc', 540 => 'gsr-csc4', 541 => 'gsr-csc8',
+    542 => 'gsr-sfc8', 545 => 'gsr-oc12chds3-mm',
+    546 => 'gsr-oc12chds3-sm', 605 => 'pm-atm25',
+);
+
+my $ciscoLS1010 = [1,3,6,1,4,1,9,1,107];
+
+my $sysObjectID_0 = [1,3,6,1,2,1,1,2,0];
+
+my $cardType = [1,3,6,1,4,1,9,3,6,11,1,2];
+my $cardDescr = [1,3,6,1,4,1,9,3,6,11,1,3];
+my $cardSerial = [1,3,6,1,4,1,9,3,6,11,1,4];
+my $cardHwVersion = [1,3,6,1,4,1,9,3,6,11,1,5];
+my $cardSwVersion = [1,3,6,1,4,1,9,3,6,11,1,6];
+my $cardSlotNumber = [1,3,6,1,4,1,9,3,6,11,1,7];
+my $cardContainedByIndex = [1,3,6,1,4,1,9,3,6,11,1,8];
+my $cardOperStatus = [1,3,6,1,4,1,9,3,6,11,1,9];
+my $cardSlots = [1,3,6,1,4,1,9,3,6,11,1,10];
+
+my $host = shift @ARGV || die "Usage: $0 host [community]";
+my $community = shift @ARGV || 'public';
+
+my $session = SNMP_Session->open ($host, $community, 161)
+    || die "open SNMP session to $community\@$host: $!";
+$session->map_table ([$cardContainedByIndex,$cardType,$cardDescr,
+		      $cardSerial,$cardHwVersion,$cardSwVersion,
+		      $cardSlotNumber,$cardOperStatus,$cardSlots],
+		     sub () {
+			 my ($index, $contained_by,
+			     $type, $descr, $serial,
+			     $hw_version, $sw_version,
+			     $slot_number, $oper_status, $slots) = @_;
+			 grep (defined $_ && ($_=pretty_print $_),
+			       ($contained_by, $type, $descr, $serial,
+				$hw_version, $sw_version,
+				$slot_number, $oper_status, $slots));
+			 note_card ($index, $contained_by, $type,
+				    $descr, $serial,
+				    $hw_version, $sw_version,
+				    $slot_number, $oper_status, $slots);
+		     });
+$session->close
+    || warn "close SNMP session: $!";
+
+list_cards ();
+1;
+
+my %all_cards;
+my @top_level_cards;
+
+sub card_index ($) { defined $_[1] ? $_[0]->{idx} = $_[1] : $_[0]->{idx}; }
+sub card_type ($) { defined $_[1] ? $_[0]->{type} = $_[1] : $_[0]->{type}; }
+sub card_parent ($) { defined $_[1] ? $_[0]->{parent} = $_[1] : $_[0]->{parent}; }
+sub card_descr ($) { defined $_[1] ? $_[0]->{descr} = $_[1] : $_[0]->{descr}; }
+sub card_children ($) { defined $_[1] ? $_[0]->{children} = $_[1] : $_[0]->{children}; }
+
+sub make_card ($$@) {
+    my ($index, $parent,
+	$type, $descr,
+	$serial, $hw_version,
+	$sw_version, $slot_number,
+	$oper_status, $slots) = @_;
+    {
+	idx => $index,
+	parent => $parent,
+	type => $type,
+	descr => $descr,
+	serial => $serial,
+	hw_version => $hw_version,
+	sw_version => $sw_version,
+	slot_number => $slot_number,
+	oper_status => $oper_status,
+	slots => $slots,
+	children => [],
+    };
+}
+
+sub note_card ($$@) {
+    my ($index, $parent, @other_args) = @_;
+    my $card = make_card ($index, $parent, @other_args);
+    $all_cards{$index} = $card;
+    if ($parent) {
+	my $parent_card = $all_cards{$parent};
+	die "Parent card $parent not found"
+	    unless defined $parent_card;
+	push @{(card_children ($parent_card))}, $card;
+    } else {
+	push @top_level_cards, $card;
+    }
+    $card;
+}
+
+sub list_cards () {
+    print_cards_table_header ();
+    list_cards_with_children ('', @top_level_cards);
+}
+
+sub list_cards_with_children ($@) {
+    my ($indent, @cards) = @_;
+    foreach my $card (sort { $a->{slot_number} cmp $b->{slot_number} }
+		      @cards) {
+	my $index = card_index ($card);
+	my $type = card_type ($card);
+##	  my $pretty_type = ($pretty_card_type{$type}
+##			     || $type);
+	printf STDOUT ("%-48s %-4s %8d %4s %5s %2s %2s\n",
+		       $indent.card_descr ($card),
+		       pretty_card_oper_status ($card->{oper_status}),
+		       $card->{serial},
+		       $card->{hw_version},
+		       ($card->{sw_version} eq 'not available' 
+			? 'n/a' : $card->{sw_version}),
+		       pretty_card_slot_number ($card->{slot_number}),
+		       pretty_card_nslots ($card->{slots}));
+	list_cards_with_children ($indent.'  ',@{card_children $card});
+    }
+}
+
+sub print_cards_table_header () {
+    printf STDOUT ("%-48s %-4s %8s %4s %5s %2s %2s\n",
+		   "description",
+		   "stat",
+		   "serial",
+		   "hw",
+		   "sw",
+		   "sl",
+		   "#s");
+    print STDOUT (("=" x 79),"\n");
+}
+
+sub pretty_card_slot_number ($) {
+    my ($slot_number) = @_;
+    if ($slot_number == -1) {
+	return "";
+    } else {
+	return $slot_number;
+    }
+}
+
+sub pretty_card_nslots ($) {
+    my ($nslots) = @_;
+    if ($nslots == -1) {
+	return "?";
+    } elsif ($nslots == 0) {
+	return "";
+    } else {
+	return $nslots;
+    }
+}
+
+sub pretty_card_oper_status ($) {
+    my ($oper_status) = @_;
+    if ($oper_status == 1) {
+	return "-";
+    } elsif ($oper_status == 2) {
+	return "up";
+    } elsif ($oper_status == 3) {
+	return "down";
+    } elsif ($oper_status == 4) {
+	return "stby";
+    } else {
+	return "ILLEGAL".$oper_status;
+    }
+}
diff --git a/test/cisco-tftp.pl b/test/cisco-tftp.pl
new file mode 100755
index 0000000..64c9b1b
--- /dev/null
+++ b/test/cisco-tftp.pl
@@ -0,0 +1,50 @@
+#!/usr/local/bin/perl -w
+
+require 5.002;
+use strict;
+use SNMP_Session;
+use BER;
+
+my %OIDS = (
+	    'netConfigSet' => '1.3.6.1.4.1.9.2.1.50',
+	    'WriteNet'	   => '1.3.6.1.4.1.9.2.1.55',
+	    'WriteMem'	   => '1.3.6.1.4.1.9.2.1.54.0',
+	 );
+
+my $key;
+foreach $key (keys %OIDS) {
+    my @oid;
+
+    @oid = split (/\./,$OIDS{$key});
+    $OIDS{$key} = \@oid;
+}
+
+my ($router,$community) = ($ARGV[0] || 'popo', $ARGV[1] || "asdjkfhagk");
+
+my $tftphost = "130.59.1.30";
+my $filename = "snmp-test";
+
+sub write_net ($ $ $ ) {
+    my ($session, $tftphost, $filename) = @_;
+
+    my $write_net_oid = encode_oid (@{$OIDS{WriteNet}}, split (/\./,$tftphost));
+    my @enoid = ([$write_net_oid, encode_string ($filename)]);
+
+    #print (join(".",$write_net_oid), "\n");
+    if ($session->set_request_response(@enoid)) {
+	my $response = $session->pdu_buffer;
+	my ($bindings) = $session->decode_get_response ($response);
+	$session->close ();
+	while ($bindings) {
+	    my ($binding, $oid, $value);
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    ($oid,$value) = decode_by_template ($binding, "%O%@");
+	}
+    } else {
+	return (-1,-1);
+    }
+}
+
+my $session = SNMP_Session->open ($router , $community, 161);
+&write_net ($session, $tftphost, $filename);
+1;
diff --git a/test/counter64-test.pl b/test/counter64-test.pl
new file mode 100644
index 0000000..955c3ef
--- /dev/null
+++ b/test/counter64-test.pl
@@ -0,0 +1,201 @@
+#!/usr/local/bin/perl -w
+###
+### Author:       Simon Leinen  <simon at switch.ch>
+### Date Created: 03-Mar-1999
+###
+### Try to work with Counter64 values
+###
+require 5.003;
+
+use strict;
+
+### Forward declarations
+sub usage ($);
+
+use BER;
+use SNMP_Session "0.67";	# requires map_table_4
+use POSIX;			# for exact time
+use Curses;
+use Math::BigInt;
+
+my $version = '1';
+
+my $desired_interval = 5.0;
+
+while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
+    if ($ARGV[0] =~ /^-v/) {
+	if ($ARGV[0] eq '-v') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] eq '1') {
+	    $version = '1';
+	} elsif ($ARGV[0] eq '2c') {
+	    $version = '2c';
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-t/) {
+	if ($ARGV[0] eq '-t') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+(\.[0-9]+)?$/) {
+	    $desired_interval = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] eq '-h') {
+	usage (0);
+	exit 0;
+    } else {
+	usage (1);
+    }
+    shift @ARGV;
+}
+my $host = shift @ARGV || usage (1);
+my $community = shift @ARGV || "public";
+usage (1) if $#ARGV >= $[;
+
+my $ifDescr = [1,3,6,1,2,1,2,2,1,2];
+my $ifAdminStatus = [1,3,6,1,2,1,2,2,1,7];
+my $ifOperStatus = [1,3,6,1,2,1,2,2,1,8];
+my $ifInOctets = [1,3,6,1,2,1,2,2,1,10];
+my $ifOutOctets = [1,3,6,1,2,1,2,2,1,16];
+my $ifHCInOctets = [1,3,6,1,2,1,31,1,1,1,6];
+my $ifHCOutOctets = [1,3,6,1,2,1,31,1,1,1,10];
+my $ifInUcastPkts = [1,3,6,1,2,1,2,2,1,11];
+my $ifOutUcastPkts = [1,3,6,1,2,1,2,2,1,17];
+
+my $clock_ticks = POSIX::sysconf( &POSIX::_SC_CLK_TCK );
+
+my $win = new Curses;
+
+my %old;
+my $sleep_interval = $desired_interval + 0.0;
+my $interval;
+my $linecount;
+
+sub out_interface {
+    my ($index, $descr, $admin, $oper, $in, $out) = @_;
+    my ($clock) = POSIX::times();
+    my $alarm = 0;
+
+    grep (defined $_ && ($_=pretty_print $_),
+	  ($descr, $admin, $oper, $in, $out));
+    $win->clrtoeol ();
+    return unless defined $oper && $oper == 1;	# up
+    return unless defined $in && defined $out;
+    if (!defined $old{$index}) {
+	$win->addstr ($linecount, 0,
+		      sprintf ("%2d  %-24s %10s %10s\n",
+			       $index,
+			       defined $descr ? $descr : '',
+			       defined $in ? $in : '-',
+			       defined $out ? $out : '-'));
+    } else {
+	my $old = $old{$index};
+
+	$interval = ($clock-$old->{'clock'}) * 1.0 / $clock_ticks;
+	my $d_in = $in ? ("".$in-$old->{'in'})*8000
+	    /int ($interval*1000)
+	    : 0;
+	my $d_out = $out ? ("".$out-$old->{'out'})*8000
+	    /int ($interval*1000)
+	    : 0;
+	warn "in: $in out: $out d_in: $d_in d_out: $d_out old->{in}: ",$old->{in}," old->{out}: ",$old->{out};
+	$alarm = ($d_out > 0 && $d_in == 0);
+	print STDERR "\007" if $alarm && !$old->{'alarm'};
+	print STDERR "\007" if !$alarm && $old->{'alarm'};
+	$win->standout() if $alarm;
+	$win->addstr ($linecount, 0,
+		      sprintf ("%2d  %-24s %10.1f %10.1f\n",
+			       $index,
+			       defined $descr ? $descr : '',
+			       defined $in ? $d_in : 0,
+			       defined $out ? $d_out : 0));
+	$win->standend() if $alarm;
+    }
+    $old{$index} = {'in' => $in,
+		    'out' => $out,
+		    'clock' => $clock,
+		    'alarm' => $alarm};
+    ++$linecount;
+    $win->refresh ();
+}
+
+$win->erase ();
+my $session =
+    ($version eq '1' ? SNMPv1_Session->open ($host, $community, 161)
+     : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, 161)
+     : die "Unknown SNMP version $version")
+  || die "Opening SNMP_Session";
+
+### max_repetitions:
+###
+### We try to be smart about the value of $max_repetitions.  Starting
+### with the session default, we use the number of rows in the table
+### (returned from map_table_4) to compute the next value.  It should
+### be one more than the number of rows in the table, because
+### map_table needs an extra set of bindings to detect the end of the
+### table.
+###
+my $max_repetitions = $session->default_max_repetitions;
+while (1) {
+    $win->addstr (0, 0, sprintf ("%-20s interval %4.1fs %d reps",
+				 $host,
+				 $interval || $desired_interval,
+				 $max_repetitions));
+    $win->standout();
+    $win->addstr (1, 0,
+		  sprintf ("%2s  %-24s %10s %10s\n",
+			   "ix", "name",
+			   "bits/s", "bits/s"));
+    $win->addstr (2, 0,
+		  sprintf ("%2s  %-24s %10s %10s\n",
+			   "", "",
+			   "in", "out"));
+    $win->clrtoeol ();
+    $win->standend();
+    $linecount = 3;
+    my $calls = $session->map_table_4
+	([$ifDescr,
+	  $ifAdminStatus,
+	  $ifOperStatus,
+	  $version ne '1' ? $ifHCInOctets : $ifInOctets,
+	  $version ne '1' ? $ifHCOutOctets : $ifOutOctets],
+	 \&out_interface,
+	 $max_repetitions);
+    $max_repetitions = $calls + 1
+	if $calls > 0;
+    $sleep_interval -= ($interval - $desired_interval)
+	if defined $interval;
+    select (undef, undef, undef, $sleep_interval);
+}
+1;
+
+sub usage ($) {
+    warn <<EOM;
+Usage: $0 [-t secs] [-v (1|2c)] switch [community]
+       $0 -h
+
+  -h           print this usage message and exit.
+
+  -t secs      specifies the sampling interval.  Defaults to 5 seconds.
+
+  -v version   can be used to select the SNMP version.  The default
+   	       is SNMPv1, which is what most devices support.  If your box
+   	       supports SNMPv2c, you should enable this by passing "-v 2c"
+   	       to the script.  SNMPv2c is much more efficient for walking
+   	       tables, which is what this tool does.
+
+  switch       hostname or IP address of an LS1010 switch
+
+  community    SNMP community string to use.  Defaults to "public".
+EOM
+    exit (1) if $_[0];
+}
diff --git a/test/cricket-genconf-sensor b/test/cricket-genconf-sensor
new file mode 100755
index 0000000..a3d226d
--- /dev/null
+++ b/test/cricket-genconf-sensor
@@ -0,0 +1,232 @@
+#!/usr/local/bin/perl -w
+##
+## cricket-genconf-sensor
+##
+## Generate Cricket configuration for sensor monitoring
+##
+## Author:        Simon Leinen  <simon at limmat.switch.ch>
+## Date created:  21-Dec-2006
+##
+## This script generates Cricket configuration files for
+## SNMP-monitorable sensors in a set of routers.  It does this on the
+## basis of a RANCID configuration file repository.  For each router
+## in that directory that seems to have monitorable sensors, the
+## script calls the `entls' script to generate Cricket configuration.
+##
+## The script puts newly generated configuration files into a
+## temporary directory, and then installs some "safe" configuration
+## changes by itself.  For other configuration changes, the user is
+## presented with "diff" output and has to decide how to apply them.
+
+use strict;
+use warnings;
+
+use Cisco::Abbrev;
+
+my $testing = 0;
+
+### Prototypes
+sub read_router_configurations ($ );
+sub has_sensors_p ($ );
+sub postprocess_router_config ($$);
+sub maybe_install_new_configuration ($ );
+sub install_new_configuration ($ );
+
+my $rancid_directory = '/usr/local/rancid/backbone/configs';
+
+my @routers = read_router_configurations ($rancid_directory);
+
+my $cricket_config_dir = '/home/cricket/cricket-config';
+my $old_config_dir = $cricket_config_dir.'/'.'transceiver-monitoring';
+
+-d $old_config_dir or die "cannot find existing configuration $old_config_dir";
+
+my $new_config_dir = '/tmp'.'/foo/';
+-d $new_config_dir
+    or mkdir $new_config_dir
+    or die "Cannot create $new_config_dir: $!";
+
+my (@unchanged, @installed, @unresolved);
+foreach my $router (@routers) {
+    my $routername = $router->{name};
+    ## For testing, only look at one router
+    next if $testing and $routername ne 'swiix2';
+    next unless has_sensors_p ($router);
+    my $rdir = $new_config_dir.'/'.$routername;
+    -d $rdir or mkdir $rdir or die "cannot create directory $rdir: $!";
+    my $retval = system ('perl -Ilib test/entls -t hctiws@'
+			 .$routername.':::::2:v4only > '
+			 .$rdir.'/'.$routername);
+    if ($retval) {
+	warn "failed to generate configuration for $routername";
+    } else {
+	postprocess_router_config ($rdir.'/'.$routername, $router);
+	maybe_install_new_configuration ($router);
+    }
+}
+print "Unchanged: ",join (", ", map { $_->{name} } @unchanged),"\n";
+print "Installed: ",join (", ", map { $_->{name} } @installed),"\n";
+print "Unresolved: ",join (", ", map { $_->{name} } @unresolved),"\n";
+1;
+
+sub postprocess_router_config ($$) {
+    my ($file, $router) = @_;
+    my ($pre, $sd, $post, $long, $desc);
+    open IN, $file or die "Cannot open configuration file $file: $!";
+    open OUT, ">$file.post" or die "Cannot open configuration file $file.post: $!";
+    while (<IN>) {
+	if (/^(\s*display-name\s*=\s*")(.*)("\s*)$/) {
+	} elsif (/^(\s*long-desc\s*=\s*")(.*)("\s*)$/) {
+	} elsif (/^(\s*short-desc\s*=\s*")(.*)("\s*)$/) {
+	    ($pre, $sd, $post) = ($1, $2, $3);
+	    $sd =~ s/^transceiver //i;
+	    $long = cisco_long_int ($sd);
+	    $sd = $long if defined $long;
+	    if (defined $long) {
+		$desc = $long;
+		$sd = $desc = $router->{ifdesc}->{$long}
+		if exists $router->{ifdesc}->{$long};
+	    } else {
+		$long = $desc = $sd;
+	    }
+	    chomp $post;
+	    print OUT "$pre$sd$post\n";
+	    print OUT "\tlong-desc\t = \"<h3>$long - $desc</h3>\"\n";
+	    print OUT "\tdisplay-name\t = \"%router% - $long\"\n";
+	} else {
+	    print OUT $_;
+	}
+    }
+    close IN or die "Cannot close configuration file $file: $!";
+    close OUT or die "Cannot close configuration file $file.post: $!";
+    rename $file, "$file.pre" or unlink $file;
+    rename "$file.post", $file or rename "$file.pre", $file;
+    unlink "$file.pre";
+    return 1;
+}
+
+sub read_router_configurations ($ ) {
+    my ($dir) = @_;
+    my @routers = ();
+    opendir CONFIG, $dir
+	or die "open directory $dir: $!";
+    foreach my $file (readdir CONFIG) {
+	next if $testing and $file ne 'swiix2';
+	next unless -f $dir.'/'.$file;
+	push @routers, { name => $file };
+    }
+    closedir CONFIG
+	or die "close directory $dir: $!";
+    @routers;
+}
+
+sub has_sensors_p ($ ) {
+    my ($router) = @_;
+    my $routername = $router->{name};
+    my $have_sensor_p = 0;
+    my ($ifname, %ifdesc);
+    open (CONFIG, $rancid_directory.'/'.$routername)
+	or die "open configuration file for $routername: $!";
+    while (<CONFIG>) {
+	if (/Receive Power Sensor/) {
+	    $have_sensor_p = 1;
+	} elsif (/^interface (.*)/) {
+	    $ifname = $1;
+	} elsif (/^ description (.*)$/
+		 and defined $ifname) {
+	    $ifdesc{$ifname} = $1;
+	} elsif (/^ /) {
+	} else {
+	    $ifname = undef;
+	}
+    }
+    close CONFIG or die "close configuration file for $routername: $!";
+    ## foreach my $ifname (sort keys %ifdesc) {
+    ## 	printf "%-20s %s\n", $ifname, $ifdesc{$ifname};
+    ## }
+    $router->{ifdesc} = \%ifdesc;
+    return $have_sensor_p;
+}
+
+## maybe_install_new_configuration ROUTER
+##
+## Check whether the newly generated Cricket configuration file for
+## router ROUTER has to/can be installed.
+##
+## If the file doesn't exist in the current configuration, we can
+## safely install the new one.
+##
+## If the newly generated file is identical to the old one, we don't
+## have to do anything.
+##
+## The the newly generated file differs from the old one, we output
+## the diff and don't install anything.
+##
+## TODO: Apply the diffs.  This is not totally trivial, however.  As
+## long as the diff consists in only added lines, the new file can be
+## safely installed over the current one.  But when configuration is
+## lost (i.e. sensors are removed), we should deactivate the lost
+## targets using "collect = 0", rather than removing them entirely, to
+## make sure that history is kept.
+##
+## There is an additional case, namely that a file exists in the
+## current configuration that was not generated in the new run.  We
+## don't handle this situation currently.
+##
+## TODO: Check the existing configuration for files that were lost in
+## the new generation run, and deactivate collection in these files.
+##
+## Actual installation of configurations is performed by
+## install_new_configuration().
+##
+sub maybe_install_new_configuration ($ ) {
+    my ($router) = @_;
+    my $routername = $router->{name};
+    my $old_file = $old_config_dir.'/'.$routername.'/'.$routername;
+    my $new_file = $new_config_dir.'/'.$routername.'/'.$routername;
+
+    if (! -f $old_file) {
+	print "NEW: $routername\n";
+	if (install_new_configuration ($router)) {
+	    push @installed, $router;
+	} else {
+	    push @unresolved, $router;
+	}
+    } else {
+	my $retval = system ("diff", "-uw", $old_file, $new_file);
+	if ($retval) {
+	    ##warn "TESTING:\n";
+	    ##$retval = system "diff -w $old_file $new_file | egrep -v '\^>'";
+	    ##warn "TESTING END: $retval\n";
+	    print "DIFFER: $routername\n";
+	    push @unresolved, $router;
+	} else {
+	    push @unchanged, $router;
+	    unlink $new_file;
+	}
+    }
+}
+
+## install_new_configuration ROUTER
+##
+## Install the newly generated configuration file for router ROUTER in
+## the active directory tree.  The containing directory is created if
+## needed.
+##
+sub install_new_configuration ($ ) {
+    my ($router) = @_;
+    my $routername = $router->{name};
+    my $old_file = $old_config_dir.'/'.$routername.'/'.$routername;
+    my $new_file = $new_config_dir.'/'.$routername.'/'.$routername;
+    my $old_dir = $old_config_dir.'/'.$routername;
+
+    unless (-d $old_dir || mkdir $old_dir) {
+	warn "Cannot create $old_dir: $!";
+	return undef;
+    }
+    if (system ("mv", $new_file, $old_file)) {
+	warn "Failed to move $new_file to $old_file";
+	return undef;
+    }
+    return 1;
+}
diff --git a/test/d.pl b/test/d.pl
new file mode 100755
index 0000000..aaab3a5
--- /dev/null
+++ b/test/d.pl
@@ -0,0 +1,62 @@
+#!/usr/local/bin/perl -w
+# Minimal useful application of the SNMP package.
+# Author: Simon Leinen  <simon at lia.di.epfl.ch>
+# RCS $Header: /home/leinen/CVS/SNMP_Session/test/d.pl,v 1.1.1.1 2003-09-02 20:12:36 leinen Exp $
+######################################################################
+# This application sends a get request for three fixed MIB-2 variable
+# instances (sysDescr.0, sysContact.0 and ipForwarding.0) to a given
+# host.  The hostname and community string can be given as
+# command-line arguments.
+######################################################################
+
+require 5;
+
+use SNMP_SessionD;
+use BERD;
+
+$hostname = shift @ARGV || &usage;
+$community = shift @ARGV || 'public';
+&usage if $#ARGV >= 0;
+
+%ugly_oids = qw(sysDescr.0	1.3.6.1.2.1.1.1.0
+		sysContact.0	1.3.6.1.2.1.1.4.0
+		ipForwarding.0	1.3.6.1.2.1.4.1.0
+		);
+foreach (keys %ugly_oids) {
+    $ugly_oids{$_} = encode_oid (split (/\./, $ugly_oids{$_}));
+    $pretty_oids{$ugly_oids{$_}} = $_;
+}
+
+srand();
+die "Couldn't open SNMP session to $hostname"
+    unless ($session = SNMP_SessionD->open ($hostname, $community, 161));
+snmp_get ($session, qw(sysDescr.0 sysContact.0 ipForwarding.0));
+$session->close ();
+1;
+
+sub snmp_get
+{
+    my($session, @oids) = @_;
+    my($response, $bindings, $binding, $value, $oid);
+
+    grep ($_ = $ugly_oids{$_}, @oids);
+
+    if ($session->get_request_response (@oids)) {
+	$response = $session->pdu_buffer;
+	($bindings) = $session->decode_get_response ($response);
+
+	while ($bindings ne '') {
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    ($oid,$value) = decode_by_template ($binding, "%O%@");
+	    print $pretty_oids{$oid}," => ",
+	    pretty_print ($value), "\n";
+	}
+    } else {
+	warn "Response not received.\n";
+    }
+}
+
+sub usage
+{
+    die "usage: $0 hostname [community]";
+}
diff --git a/test/digital-bug b/test/digital-bug
new file mode 100755
index 0000000..9cca4f1
--- /dev/null
+++ b/test/digital-bug
@@ -0,0 +1,32 @@
+#!/usr/local/bin/perl
+
+#Check for SNMP Values
+
+use BER;					
+require 'SNMP_Session.pm';
+
+#Variables
+$host=@ARGV[0] || die "usage";
+$community=@ARGV[1] || 'public';
+$port='161';
+ 
+$session = SNMP_Session->open ($host, $community, $port)
+        || die "Couldn't open SNMP session to $host";
+
+
+#Oid's
+$oid1=encode_oid (1,3,6,1,2,1,1,5,0);
+
+if ($session->get_request_response ($oid1)){
+	($bindings) = $session->decode_get_response
+($session->{pdu_buffer});
+
+        while ($bindings ne '') {
+              ($binding,$bindings) = &decode_sequence ($bindings);
+              ($oid,$value) = &decode_by_template ($binding, "%O%@");
+              print $pretty_oids{$oid}," => ",
+                    &pretty_print ($value), "\n";
+        }
+} else {
+        die "No response from agent on $host";
+}
diff --git a/test/entls b/test/entls
new file mode 100644
index 0000000..fbdf673
--- /dev/null
+++ b/test/entls
@@ -0,0 +1,269 @@
+#!/usr/bin/perl -w
+###
+### entls - list ENTITY-MIB
+###
+### Author:       Simon Leinen <simon at switch.ch>
+### Date created: 03-May-2005
+
+use strict;
+use SNMP_util;
+use SNMP_Table;
+
+## Prototypes
+sub init_mibs ();
+sub collect_entity_information ($ );
+sub print_physical ($$);
+sub print_phys_tree ($$);
+sub print_phys_tree_1 ($$@);
+sub get_physical ($ );
+sub decode_truth_value ($ );
+sub snmp_decode_value ($@);
+sub node_is_transceiver_sensor ($ );
+sub router_pretty_name ($ );
+sub router_pretty_name_1 ($ );
+sub usage ();
+
+my @targets = ();
+
+my $phys_tree;
+
+## Set to 1 if you want entPhysicalVendorType to be printed.
+##
+my $print_vendor_type = 0;
+
+## Set to 1 if you want the entPhysical index to be printed.
+##
+my $print_ent_physical_index = 0;
+
+## Set to 1 if you want transceiver sensors listed.
+##
+my $transceiver_sensors = 0;
+
+## Directory in which to find router configurations.
+## Currently, this is only used to find the "canonical" names
+## of routers, using the `hostname' command from the router
+## configuration file.
+##
+my $rancid_base = '/usr/local/rancid/backbone/configs';
+
+my $print_relative_index = 1;
+my $print_hierarchy_indentation = 1;
+
+my $current_router;
+my $current_transceiver;
+
+while (@ARGV) {
+    if ($ARGV[0] eq '-V') {
+	$print_vendor_type = 1;
+    } elsif ($ARGV[0] eq '-i') {
+	$print_ent_physical_index = 1;
+    } elsif ($ARGV[0] eq '-t') {
+	$transceiver_sensors = 1;
+	$print_ent_physical_index = 1;
+	$print_relative_index = 0;
+	$print_hierarchy_indentation = 0;
+    } elsif ($ARGV[0] eq '-h') {
+	usage ();
+	exit (0);
+    } elsif ($ARGV[0] =~ /^-/) {
+	warn ("Unknown option ",$ARGV[0]);
+	usage ();
+	exit (1);
+    } else {
+	push @targets, $ARGV[0];
+    }
+    shift @ARGV;
+}
+usage (), exit 1 unless @targets;
+
+init_mibs ();
+foreach my $target (@targets) {
+    $current_router = $target;
+    $current_router =~ s/.*@//;
+    $current_router =~ s/:.*//;
+    collect_entity_information ($target);
+    print_physical ($current_router, $phys_tree);
+}
+1;
+
+sub collect_entity_information ($ ) {
+    my ($target) = @_;
+    $phys_tree = get_physical ($target);
+}
+
+sub print_physical ($$) {
+    my ($current_router, $phys_tree) = @_;
+    if ($transceiver_sensors) {
+	print "target --default--\n\trouter\t= ",
+	router_pretty_name ($current_router), "\n";
+	print "\tdirectory-desc\t= \"Router %router% transceiver monitoring\"\n";
+	print "\tdisplay-name\t= \"%router% - %short-desc%\"\n";
+	print "\tlong-desc\t= \"<H3>%short-desc%</H3>\"\n";
+    } else {
+	print "Physical Entities\n\n";
+    }
+    print_phys_tree ($phys_tree, "");
+}
+
+sub print_phys_tree ($$) {
+    print_phys_tree_1 ($_[0], $_[1], ());
+}
+
+sub print_phys_tree_1 ($$@) {
+    my ($node, $prefix, @stack) = @_;
+    $node->{parent_stack} = \@stack;
+    printf STDOUT ("%s%s\n", $prefix, $node->tostring ())
+	if !$transceiver_sensors or node_is_transceiver_sensor ($node);
+    foreach my $class (sort keys %{$node->{_children}}) {
+	my $class_children = $node->{_children}->{$class};
+	foreach my $child_index (sort { $a <=> $b } keys %{$class_children}) {
+	    print_phys_tree_1 ($class_children->{$child_index},
+			       ($print_hierarchy_indentation
+				? (' 'x length $prefix)
+				: '')
+			       .($print_relative_index
+				 ? sprintf ("%d. ", $child_index)
+				 : ''),
+			       ($node, @stack));
+	}
+    }
+}
+
+sub node_is_transceiver_sensor ($ ) {
+    my ($node) = @_;
+    ($node->{vendorType} =~ /1\.3\.6\.1\.4\.1\.9\.12\.3\.1\.8\.(4[6789]|50)$/)
+	? 1 : 0;
+}
+### get_if_entries TARGET
+###
+### Read the MIB-II interface table, and construct a hash mapping the
+### interface indices to hashes containing important slots.
+### Currently, only ifDescr and ifAlias are recorded.
+###
+sub get_if_entries ($ ) {
+    my ($target) = @_;
+    return snmp_rows_to_objects
+      ($target, 'MIBII::Interface', 'if', qw(descr alias));
+}
+
+sub get_physical ($ ) {
+    my ($target) = @_;
+    my ($phys, $root);
+    $phys = snmp_rows_to_objects
+	($target, 'Entity::PhysicalEntry', 'entPhysical',
+	 qw(descr vendorType containedIn class parentRelPos name
+	    hardwareRev firmwareRev softwareRev serialNum mfgName
+	    modelName alias assetID isFRU));
+    foreach my $i1 (keys %{$phys}) {
+	my $e1 = $phys->{$i1};
+	if ($e1->{containedIn} == 0) {
+	    die "multiple roots" if defined $root;
+	    $root = $e1;
+	}
+	foreach my $i2 (keys %{$phys}) {
+	    my $e2 = $phys->{$i2};
+	    next if $e2->{_visited};
+	    if ($i1 == $e2->{containedIn}) {
+		$e1->{_children}->{$e2->{class}}->{$e2->{parentRelPos}} = $e2;
+		$e2->{parent} = $e1;
+	    }
+	}
+    }
+    return $root;
+}
+
+sub decode_truth_value ($ ) {return snmp_decode_value ($_[0], qw(1 0));}
+
+sub snmp_decode_value ($@) {
+    my ($index, @mapvec) = @_;
+    return $index if $index < 1 or $index > $#mapvec+1;
+    return $mapvec[$index-1];
+}
+
+sub init_mibs () {
+    snmpmapOID
+	(qw(
+entPhysicalDescr	1.3.6.1.2.1.47.1.1.1.1.2
+entPhysicalVendorType	1.3.6.1.2.1.47.1.1.1.1.3
+entPhysicalContainedIn	1.3.6.1.2.1.47.1.1.1.1.4
+entPhysicalClass	1.3.6.1.2.1.47.1.1.1.1.5
+entPhysicalParentRelPos	1.3.6.1.2.1.47.1.1.1.1.6
+entPhysicalName		1.3.6.1.2.1.47.1.1.1.1.7
+entPhysicalHardwareRev	1.3.6.1.2.1.47.1.1.1.1.8
+entPhysicalFirmwareRev	1.3.6.1.2.1.47.1.1.1.1.9
+entPhysicalSoftwareRev	1.3.6.1.2.1.47.1.1.1.1.10
+entPhysicalSerialNum	1.3.6.1.2.1.47.1.1.1.1.11
+entPhysicalMfgName	1.3.6.1.2.1.47.1.1.1.1.12
+entPhysicalModelName	1.3.6.1.2.1.47.1.1.1.1.13
+entPhysicalAlias	1.3.6.1.2.1.47.1.1.1.1.14
+entPhysicalAssetID	1.3.6.1.2.1.47.1.1.1.1.15
+entPhysicalIsFRU	1.3.6.1.2.1.47.1.1.1.1.16
+));
+}
+
+sub usage () {
+    print "Usage: $0 -h\n";
+    print "Usage: $0 [-V] [-i] [-t] TARGET...\n";
+    print "  -V      print entities' vendorTypes\n";
+    print "  -i      print indexes from entPhysicalTable\n";
+    print "  -t      list transceiver sensors\n";
+}
+
+my %router_pretty_name = ();
+
+sub router_pretty_name ($ ) {
+    my ($router_name) = @_;
+    return $router_pretty_name{$router_name}
+	if exists $router_pretty_name{$router_name};
+    return ($router_pretty_name{$router_name}
+	    = router_pretty_name_1 ($router_name));
+}
+
+sub router_pretty_name_1 ($ ) {
+    my ($router_name) = @_;
+    open (CONFIG, $rancid_base.'/'.$router_name)
+	or return $router_name;
+    while (<CONFIG>) {
+	return $1 if /^hostname (.*)$/;
+    }
+    close CONFIG;
+}
+
+package MIBII::Interface;
+package Entity::PhysicalEntry;
+
+my $last_transceiver;
+
+sub tostring ($ ) {
+    my ($node) = @_;
+    my $result = '';
+    my $parent = $node->{parent_stack}->[0];
+    my $current_transceiver = $parent->{name};
+    if (defined $current_transceiver
+	&& (!defined $last_transceiver || $current_transceiver ne $last_transceiver)) {
+	my $target = "$current_transceiver";
+	$target = lc $target;
+	$target =~ s/^transceiver //;
+	$target =~ s/[ \/.]/_/g;
+	$target =~ s/^gi/gigabitethernet/g;
+	$target =~ s/^te/tengigabitethernet/g;
+	print "\ntarget $target\n";
+	print "\tshort-desc\t= \"",$current_transceiver,"\"\n";
+	print "\ttarget-type\t= \"monitored-transceiver\"\n";
+	$last_transceiver = $current_transceiver;
+    }
+    if ($transceiver_sensors) {
+	my ($interface, $sensor_type) = split (/ /, $node->{name}, 2);
+	$sensor_type =~ s/[ \/]/-/g;
+	$result = "\t".lc $sensor_type;
+	$result .= sprintf (" = \"%d\"", $node->{index});
+    } else {
+	$result .= sprintf ("%d:", $node->{index})
+	    if $print_ent_physical_index;
+	$result .= sprintf ("%s", $node->{name});
+	$result .= " (".$node->{alias}.")" if $node->{alias};
+	$result .= " (".$node->{vendorType}.")"
+	    if $print_vendor_type and $node->{vendorType};
+    }
+    return $result;
+}
diff --git a/test/fore-test.pl b/test/fore-test.pl
new file mode 100755
index 0000000..95bcf09
--- /dev/null
+++ b/test/fore-test.pl
@@ -0,0 +1,50 @@
+#!/usr/local/bin/perl -w
+###
+### Small test program that uses GetNext requests to walk a table.
+###
+
+use strict;
+use BER;
+use SNMP_Session;
+
+my $hostname = $ARGV[0] || '193.246.0.134';
+my $community = $ARGV[1] || 'public';
+
+my $session;
+
+die unless ($session = SNMP_Session->open ($hostname, $community, 161));
+
+my @nsapTopoLinkDestPort = split ('\.', '1.3.6.1.4.1.326.2.2.2.1.9.3.1.7');
+
+my $destPortIndex = encode_oid (@nsapTopoLinkDestPort);
+
+my @oids = ($destPortIndex);
+my @next_oids;
+
+my $oid;
+my $i;
+for (;;) {
+    if ($session->getnext_request_response (@oids)) {
+	my $response = $session->pdu_buffer;
+	my ($bindings, $binding, $oid, $value);
+
+	($bindings) = $session->decode_get_response ($response);
+	@next_oids = ();
+
+	## IP address
+	($binding,$bindings) = decode_sequence ($bindings);
+	($oid,$value) = decode_by_template ($binding, "%O%@");
+	last
+	    unless BER::encoded_oid_prefix_p ($destPortIndex, $oid);
+	push @next_oids, $oid;
+	print pretty_print ($value), ' [',pretty_print ($oid), "]\n";
+
+    } else {
+	die "No response received.\n";
+    }
+    @oids = @next_oids;
+}
+
+$session->close ();
+
+1;
diff --git a/test/hex-test.pl b/test/hex-test.pl
new file mode 100755
index 0000000..254181e
--- /dev/null
+++ b/test/hex-test.pl
@@ -0,0 +1,61 @@
+#!/usr/local/bin/perl -w
+######################################################################
+### Name:	  hex-test.pl
+### Date Created: Mon Sep 22 21:15:06 1997
+### Author:	  Simon Leinen  <simon at switch.ch>
+### RCS $Id: hex-test.pl,v 1.1 1997-09-22 19:25:19 simon Exp $
+######################################################################
+### Test the new `hex_string' subroutine.
+######################################################################
+
+require 5;
+
+require 'SNMP_Session.pm';
+use BER;
+
+$hostname = shift @ARGV || &usage;
+$community = shift @ARGV || 'public';
+&usage if $#ARGV >= 0;
+
+%ugly_oids = qw(sysDescr.0	1.3.6.1.2.1.1.1.0
+		sysContact.0	1.3.6.1.2.1.1.4.0
+		ipForwarding.0	1.3.6.1.2.1.4.1.0
+		);
+foreach (keys %ugly_oids) {
+    $ugly_oids{$_} = encode_oid (split (/\./, $ugly_oids{$_}));
+    $pretty_oids{$ugly_oids{$_}} = $_;
+}
+
+srand();
+die "Couldn't open SNMP session to $hostname"
+    unless ($session = SNMP_Session->open ($hostname, $community, 161));
+snmp_get ($session, qw(sysDescr.0 sysContact.0 ipForwarding.0));
+$session->close ();
+1;
+
+sub snmp_get
+{
+    my($session, @oids) = @_;
+    my($response, $bindings, $binding, $value, $oid);
+
+    grep ($_ = $ugly_oids{$_}, @oids);
+
+    if ($session->get_request_response (@oids)) {
+	$response = $session->pdu_buffer;
+	($bindings) = $session->decode_get_response ($response);
+
+	while ($bindings ne '') {
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    ($oid,$value) = decode_by_template ($binding, "%O%@");
+	    print $pretty_oids{$oid}," => ",
+	          &hex_string ($value), "\n";
+	}
+    } else {
+	warn "Response not received.\n";
+    }
+}
+
+sub usage
+{
+    die "usage: $0 hostname [community]";
+}
diff --git a/test/inexist.pl b/test/inexist.pl
new file mode 100755
index 0000000..5e26f1d
--- /dev/null
+++ b/test/inexist.pl
@@ -0,0 +1,62 @@
+#!/usr/local/bin/perl -w
+# Minimal useful application of the SNMP package.
+# Author: Simon Leinen  <simon at lia.di.epfl.ch>
+# RCS $Header: /home/leinen/CVS/SNMP_Session/test/inexist.pl,v 1.1.1.1 2003-09-02 20:12:36 leinen Exp $
+######################################################################
+# This application sends a get request for three fixed MIB-2 variable
+# instances (sysDescr.0, sysContact.0 and ipForwarding.0) to a given
+# host.  The hostname and community string can be given as
+# command-line arguments.
+######################################################################
+
+require 5;
+
+require 'SNMP_Session.pm';
+use BER;
+
+$SNMP_Session::suppress_warnings = 1;
+
+$hostname = shift @ARGV || &usage;
+$community = shift @ARGV || 'public';
+&usage if $#ARGV >= 0;
+
+%ugly_oids = qw(foo.0	1.3.6.1.2.1.1.0.0
+		);
+foreach (keys %ugly_oids) {
+    $ugly_oids{$_} = encode_oid (split (/\./, $ugly_oids{$_}));
+    $pretty_oids{$ugly_oids{$_}} = $_;
+}
+
+srand();
+die "Couldn't open SNMP session to $hostname"
+    unless ($session = SNMP_Session->open ($hostname, $community, 161));
+snmp_get ($session, qw(foo.0));
+$session->close ();
+1;
+
+sub snmp_get
+{
+    my($session, @oids) = @_;
+    my($response, $bindings, $binding, $value, $oid);
+
+    grep ($_ = $ugly_oids{$_}, @oids);
+
+    if ($session->get_request_response (@oids)) {
+	$response = $session->pdu_buffer;
+	($bindings) = $session->decode_get_response ($response);
+
+	while ($bindings ne '') {
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    ($oid,$value) = decode_by_template ($binding, "%O%@");
+	    print $pretty_oids{$oid}," => ",
+	    pretty_print ($value), "\n";
+	}
+    } else {
+	warn "SNMP problem: $SNMP_Session::errmsg\n";
+    }
+}
+
+sub usage
+{
+    die "usage: $0 hostname [community]";
+}
diff --git a/test/ip-addr-to-if-index b/test/ip-addr-to-if-index
new file mode 100755
index 0000000..59c984c
--- /dev/null
+++ b/test/ip-addr-to-if-index
@@ -0,0 +1,141 @@
+#!/usr/local/bin/perl -w
+## Name:	test/ip-addr-to-if-index
+## Author:	Simon Leinen  <simon at switch.ch>
+## Description:	Find ifIndex for given IP address
+######################################################################
+## Usage: test/ip-addr-to-if-index [-q] community at hostname ipaddr...
+##
+## This script finds the interface indices corresponding to the given
+## IP addresses on the node.
+##
+## Example: 
+##
+## $ ./test/ip-addr-to-if-index public at babar 127.0.0.1 130.59.4.2
+## 127.0.0.1       lo0        1
+## 130.59.4.2      hme0       2
+##
+## The `-q' form suppresses the IP addresses and interface
+## description:
+##
+## $ ./test/ip-addr-to-if-index -q public at babar 127.0.0.1 130.59.4.2
+## 1
+## 2
+######################################################################
+
+require 5.002;
+use strict;
+use SNMP_Session;
+use BER;
+
+sub usage();
+sub ether_hex($ );
+sub ifDescr($ $ );
+
+my ($hostname,$community,$quiet);
+$quiet = 0;
+while ($ARGV[0] =~ /^-/) {
+    if ($ARGV[0] eq '-q') {
+	$quiet = 1;
+	shift @ARGV;
+    } else {
+	usage ();
+    }
+}
+$hostname = shift @ARGV || usage();
+($community,$hostname) = ($hostname =~ /^(.*)@(.*)$/);
+
+my $session;
+
+die "Couldn't open SNMP session to $hostname"
+    unless ($session = SNMP_Session->open ($hostname, $community, 161));
+
+my $ip_address;
+while ($ip_address = shift @ARGV) {
+    my ($ip1,$ip2,$ip3,$ip4);
+    usage()
+	unless (($ip1,$ip2,$ip3,$ip4)
+		= $ip_address =~ m/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/);
+
+    my @ifIndex_OID = qw(1 3 6 1 2 1 4 20 1 2);
+    push @ifIndex_OID, $ip1;
+    push @ifIndex_OID, $ip2;
+    push @ifIndex_OID, $ip3;
+    push @ifIndex_OID, $ip4;
+
+    my @oids = (encode_oid (@ifIndex_OID));
+
+    if ($session->get_request_response (@oids)) {
+	my $response = $session->pdu_buffer;
+	my ($bindings, $binding, $oid, $value);
+	my ($ifIndex);
+
+	($bindings) = $session->decode_get_response ($response);
+
+	## IfIndex
+	($binding,$bindings) = decode_sequence ($bindings);
+	($oid,$value) = decode_by_template ($binding, "%O%@");
+	$ifIndex = pretty_print ($value);
+
+	if (!$quiet) {
+	    printf STDOUT ("%-15s %-10s ", $ip_address,
+			   &ifDescr ($ifIndex, $session));
+	}
+	printf STDOUT ("%d\n", $ifIndex);
+
+    } else {
+	die "No response received.\n";
+    }
+}
+
+$session->close ();
+
+1;
+
+sub usage ()
+{
+    die "usage: $0 community\@hostname ipaddr...";
+}
+
+## ether_hex (HEX_STRING)
+##
+## Converts a raw hex representation into the common form used in
+## Ethernet addresses, e.g. "080020830069" becomes
+## "08:00:20:83:00:69".
+##
+sub ether_hex ($ )
+{
+    my ($string) = @_;
+    $string =~ s/([0-9a-f][0-9a-f])/$1:/g;
+    $string =~ s/:$//;
+    $string;
+}
+
+my %ifDescrCache;
+
+## ifDescr (IFINDEX, SESSION)
+##
+## Return the interface description associated with the given
+## IFINDEX.  Uses SESSION as the destination for SNMP request.
+## Results are cached in %ifDescrCache to avoid sending the same SNMP
+## request more than once.
+##
+sub ifDescr($ $ )
+{
+    my @ifDescr = split ('\.','1.3.6.1.2.1.2.2.1.2');
+    my ($ifIndex, $session) = @_;
+
+    return $ifDescrCache{$ifIndex,$session}
+    if defined ($ifDescrCache{$ifIndex,$session});
+    push @ifDescr,$ifIndex;
+    if ($session->get_request_response (encode_oid (@ifDescr))) {
+	my $response = $session->pdu_buffer;
+	my ($bindings, $binding, $oid, $value);
+
+	($bindings) = $session->decode_get_response ($response);
+	($binding,$bindings) = decode_sequence ($bindings);
+	($oid,$value) = decode_by_template ($binding, "%O%@");
+	return $ifDescrCache{$ifIndex,$session} = pretty_print ($value);
+    } else {
+	return "if#".$ifIndex;
+    }
+}
diff --git a/test/lambda-monitor.pl b/test/lambda-monitor.pl
new file mode 100644
index 0000000..90d5778
--- /dev/null
+++ b/test/lambda-monitor.pl
@@ -0,0 +1,169 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use SNMP_Session;
+use SNMP_util;
+
+sub show_light_trail (@);
+sub show_bitmap ($$);
+sub print_html_header ();
+sub print_html_trailer ();
+
+my $html_file = "/opt/www/htdocs/lan/switchlambda/status.html";
+
+my $inverse_video = 0;
+
+snmpmapOID (qw(amplifierOperStatus 1.3.6.1.4.1.2522.1.5.1.1.0
+	       amplifierLastChange 1.3.6.1.4.1.2522.1.5.1.2.0
+	       amplifierGain-dB 1.3.6.1.4.1.2522.1.5.1.3.0
+	       inputPowerStatus 1.3.6.1.4.1.2522.1.5.1.4.0
+	       inputPowerLevel 1.3.6.1.4.1.2522.1.5.1.5.0
+	       outputPowerStatus 1.3.6.1.4.1.2522.1.5.1.6.0
+	       outputPowerLevel 1.3.6.1.4.1.2522.1.5.1.7.0));
+
+my @eastbound_amps = qw(public at muxCE1-A8  
+	      public at muxLS1-A1  
+	      public at muxLS1-A8  
+	      public at muxBE1-A1
+	      public at muxBE1-A8
+	      public at muxBA1-A1  
+	      public at muxBA1-A8
+	      public at muxEZ1-A1);
+
+
+my @westbound_amps = qw(
+	      public at muxEZ1-A2
+	      public at muxBA1-A7  
+	      public at muxBA1-A2  
+	      public at muxBE1-A7
+	      public at muxBE1-A2
+	      public at muxLS1-A7  
+	      public at muxLS1-A2  
+			);
+
+my @amps = (@eastbound_amps, @westbound_amps);
+
+for (;;) {
+    open (HTML, ">$html_file.new");
+    print_html_header ();
+    my $localtime = localtime();
+    print "",$localtime,"\n";
+
+    print HTML "<p> Last updated: $localtime </p>\n";
+
+    print "\nEastbound:\n";
+    print HTML "<table width=\"100%\"><tr><td align=\"left\" valign=\"top\" width=\"45%\"><h2> Eastbound </h2>\n";
+    show_light_trail (@eastbound_amps);
+
+    print HTML "</td><td align=\"right\" valign=\"top\" width=\"45%\"><h2> Westbound </h2>";
+    print "\nWestbound:\n";
+    show_light_trail (@westbound_amps);
+
+    print HTML "</td></tr></table>\n";
+    print "-"x 75,"\n";
+    print_html_trailer ();
+    close (HTML);
+    rename ($html_file.".new",$html_file);
+    sleep (292);
+}
+1;
+
+sub show_light_trail (@) {
+    my @amps = @_;
+    printf "%-16s%-8s%-8s\n", "node", "  in pwr", " out pwr";
+    printf "%-16s%-8s%-8s\n", "name", "   dBm", "   dBm";
+
+    print HTML "<table>\n <tr>\n  <th>Amplifier</th>\n  <th>Input<br>Power<br>(dBm)</th>\n  <th>Output<br>Power<br>(dBm)</th>\n </tr>\n";
+    foreach my $amp (@amps) {
+	my ($community,$nodename) = split (/@/,$amp);
+	my ($amp_status,
+	    $in_status, $in,
+	    $out_status, $out)
+	    = snmpget ($amp, qw(amplifierOperStatus
+					   inputPowerStatus inputPowerLevel
+					   outputPowerStatus outputPowerLevel));
+	printf "%-16s%8.2f%8.2f\t%-8s%-8s%-8s\n",
+	$nodename, $in, $out,
+	show_bitmap ($amp_status, 5),
+	pretty_power_status ($in_status),
+	pretty_power_status ($out_status);
+	my ($amp_class, $in_class, $out_class)
+	    = (class_for_amp_status ($amp_status),
+	       class_for_level_status ($in_status),
+	       class_for_level_status ($out_status));
+	print HTML "<tr><td class=\"$amp_class\">$nodename</td><td class=\"$in_class\">$in</td><td class=\"$out_class\">$out</td></tr>\n";
+    }
+    print HTML "</table>\n";
+}
+
+sub show_bitmap ($$) {
+    my ($bits,$n) = @_;
+    my ($k,$result);
+    for ($k = 0, $result = ''; $k < $n; ++$k) {
+	$result .= (($bits & (1<<$k)) ? '*' : ' ');
+    }
+    return $result;
+}
+
+sub pretty_power_status ($) {
+    return (qw(???? ---- minr majr CRIT))[$_[0]];
+}
+
+sub class_for_level_status ($) {
+    return (qw(weird normal minor major critical))[$_[0]];
+}
+
+sub class_for_amp_status ($) {
+    return 'failed' if $_[0] & 1<<4;
+    return 'critical' if $_[0] & 1<<3;
+    return 'major' if $_[0] & 1<<2;
+    return 'minor' if $_[0] & 1<<1;
+    return 'normal' if $_[0] & 1<<0;
+    return 'normal';
+}
+
+sub print_html_header () {
+    my $expires = time + 300;
+    my $expire_string = http_date_string ($expires);
+    my ($c_bg, $c_normal, $c_minor, $c_major, $c_critical, $c_failed)
+	= $inverse_video
+	    ? qw(#000000 #ffffff #ff8080 #ff4040 #ff0000 #ff8000)
+		 : qw(#ffffff #000000 #804040 #ff4040 #ff0000 #ff8000);
+    print HTML <<EOM;
+<html><head>
+<title>SWITCHlambda Amplifier Status</title>
+<meta http-equiv="Refresh" content="300">
+<meta http-equiv="Expires" content="$expire_string">
+<style type="text/css">
+.normal {color: $c_normal}
+.minor {color: $c_minor}
+.major {color: $c_major}
+.critical {color: $c_critical; font-weight: bold}
+.failed {color: $c_failed; font-weight: bold; font-style: italic}
+</style>
+</head><body bgcolor="$c_bg" text="$c_normal">\n<h1>SWITCHlambda Amplifier Status</h1>
+EOM
+}
+
+sub print_html_trailer () {
+    print HTML <<EOM;
+<h3> Color Legend </h3>
+<p><span class="normal">normal</span>
+- <span class="minor">minor</span>
+- <span class="major">major</span>
+- <span class="critical">critical</span>
+- <span class="failed">failed</span></p>
+</body></html>
+EOM
+}
+
+sub http_date_string ($) {
+    my ($time) = @_;
+    my @gmtime = gmtime $time;
+    my ($wday) = (qw(Sun Mon Tue Wed Thu Fri Sat))[$gmtime[6]];
+    my ($month) = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$gmtime[4]];
+    my ($mday, $year, $hour, $min, $sec) = @gmtime[3,5,2,1,0];
+    return sprintf ("%s, %02d %s %04d %02d:%02d:%02d GMT",
+		    $wday, $mday, $month, $year+1900, $hour, $min, $sec);
+}
diff --git a/test/lambda-webmon.pl b/test/lambda-webmon.pl
new file mode 100755
index 0000000..d9cd0fe
--- /dev/null
+++ b/test/lambda-webmon.pl
@@ -0,0 +1,208 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use SNMP_Session;
+use SNMP_util;
+
+sub make_amp_html_page ($$);
+sub show_light_trail ($@);
+sub print_html_header ();
+sub print_html_trailer ();
+
+my $html_file = "/opt/www/htdocs/lan/switchlambda/status-new.html";
+my $html_file_iv = "/opt/www/htdocs/lan/switchlambda/status-iv.html";
+
+my $inverse_video = 0;
+
+snmpmapOID (qw(amplifierOperStatus 1.3.6.1.4.1.2522.1.5.1.1.0
+	       amplifierLastChange 1.3.6.1.4.1.2522.1.5.1.2.0
+	       amplifierGain-dB 1.3.6.1.4.1.2522.1.5.1.3.0
+	       inputPowerStatus 1.3.6.1.4.1.2522.1.5.1.4.0
+	       inputPowerLevel 1.3.6.1.4.1.2522.1.5.1.5.0
+	       outputPowerStatus 1.3.6.1.4.1.2522.1.5.1.6.0
+	       outputPowerLevel 1.3.6.1.4.1.2522.1.5.1.7.0
+
+	       aType 1.3.6.1.4.1.2522.1.2.1.1.0
+	       aTypeValue 1.3.6.1.4.1.2522.1.2.1.2.0
+	       aChannel 1.3.6.1.4.1.2522.1.2.1.3.0
+	       aTxLaserOn 1.3.6.1.4.1.2522.1.2.1.4.0
+	       aTxLaserOutput 1.3.6.1.4.1.2522.1.2.1.5.0
+	       aTxLaserTemp 1.3.6.1.4.1.2522.1.2.1.6.0
+	       aTxLossOfSignal 1.3.6.1.4.1.2522.1.2.1.7.0
+	       aRxOpticalPower 1.3.6.1.4.1.2522.1.2.1.8.0
+	       aRxLossOfSignal 1.3.6.1.4.1.2522.1.2.1.9.0
+	       aRxAPDBias 1.3.6.1.4.1.2522.1.2.1.10.0
+	       bType 1.3.6.1.4.1.2522.1.2.2.1.0
+	       bTypeValue 1.3.6.1.4.1.2522.1.2.2.2.0
+	       bChannel 1.3.6.1.4.1.2522.1.2.2.3.0
+	       bTxLaserOn 1.3.6.1.4.1.2522.1.2.2.4.0
+	       bTxLaserOutput 1.3.6.1.4.1.2522.1.2.2.5.0
+	       bTxLaserTemp 1.3.6.1.4.1.2522.1.2.2.6.0
+	       bTxLossOfSignal 1.3.6.1.4.1.2522.1.2.2.7.0
+	       bRxOpticalPower 1.3.6.1.4.1.2522.1.2.2.8.0
+	       bRxLossOfSignal 1.3.6.1.4.1.2522.1.2.2.9.0
+	       bRxAPDBias 1.3.6.1.4.1.2522.1.2.2.10.0
+));
+
+my @eastbound_amps = qw(public at mCE11-A8  
+	      public at mLS11-A1  
+	      public at mLS11-A8  
+	      public at mBE11-A1
+	      public at mBE11-A8
+	      public at mBA11-A1  
+	      public at mBA11-A8
+	      public at mEZ11-A1);
+
+my @westbound_amps = qw(public at mEZ11-A2
+	      public at mBA11-A7  
+	      public at mBA11-A2  
+	      public at mBE11-A7
+	      public at mBE11-A2
+	      public at mLS11-A7  
+	      public at mLS11-A2);
+
+my @amps = (@eastbound_amps, @westbound_amps);
+
+for (;;) {
+    my $amp_status = get_amp_status (@amps);
+    $inverse_video = 0;
+    make_amp_html_page ($html_file, $amp_status);
+    $inverse_video = 1;
+    make_amp_html_page ($html_file_iv, $amp_status);
+    sleep (292);
+}
+1;
+
+sub get_amp_status (@) {
+    my (@amps) = @_;
+    my %status = ();
+    foreach my $amp (@amps) {
+	my ($amp_status,
+	    $in_status, $in,
+	    $out_status, $out)
+	    = snmpget ($amp, qw(amplifierOperStatus
+				inputPowerStatus inputPowerLevel
+				outputPowerStatus outputPowerLevel));
+	$status{$amp} = {amp_status => $amp_status,
+			 in_status => $in_status,
+			 in => $in,
+			 out_status => $out_status,
+			 out => $out};
+    }
+    return \%status;
+}
+
+sub make_amp_html_page ($$) {
+    my ($out_file, $status) = @_;
+    open (HTML, ">$out_file.new");
+    print_html_header ();
+    my $localtime = localtime();
+
+    print HTML "<table width=\"100%\"><tr><td align=\"left\" valign=\"top\" width=\"45%\"><h2> Eastbound </h2>\n";
+    show_light_trail ($status, @eastbound_amps);
+
+    print HTML "</td><td align=\"right\" valign=\"top\" width=\"45%\"><h2> Westbound </h2>";
+    show_light_trail ($status, @westbound_amps);
+
+    print HTML "</td></tr></table>\n";
+    print HTML "<p> Last updated: $localtime </p>\n";
+    print_html_trailer ();
+    close (HTML);
+    rename ($out_file.".new",$out_file);
+}
+
+sub show_light_trail ($@) {
+    my ($status, @amps) = @_;
+
+    print HTML "<table width=\"90%\">\n <tr>\n  <th>Amplifier</th>\n  <th>Input<br>Power<br>(dBm)</th>\n  <th>Output<br>Power<br>(dBm)</th>\n </tr>\n";
+    foreach my $amp (@amps) {
+	my ($community,$nodename) = split (/@/,$amp);
+	my $values = $status->{$amp};
+	my ($amp_status,
+	    $in_status, $in,
+	    $out_status, $out)
+	    = ($values->{amp_status},
+	       $values->{in_status},$values->{in},
+	       $values->{out_status},$values->{out});
+	my ($amp_class, $in_class, $out_class)
+	    = (class_for_amp_status ($amp_status),
+	       class_for_level_status ($in_status),
+	       class_for_level_status ($out_status));
+	print HTML "<tr><td class=\"$amp_class\">$nodename</td><td class=\"$in_class\">$in</td><td class=\"$out_class\">$out</td></tr>\n";
+    }
+    print HTML "</table>\n";
+}
+
+sub class_for_level_status ($) {
+    return (qw(weird normal minor major critical))[$_[0]];
+}
+
+sub class_for_amp_status ($) {
+    return 'failed' if $_[0] & 1<<4;
+    return 'critical' if $_[0] & 1<<3;
+    return 'major' if $_[0] & 1<<2;
+    return 'minor' if $_[0] & 1<<1;
+    return 'normal' if $_[0] & 1<<0;
+    return 'offline';
+}
+
+sub print_html_header () {
+    my $expires = time + 300;
+    my $expire_string = http_date_string ($expires);
+    my ($c_bg, $c_normal, $c_offline, $c_minor, $c_major, $c_critical, $c_failed)
+	= $inverse_video
+	    ? ('#000000', '#ffffff', '#606060', '#ff7070', '#ff3030', '#ff0000', '#ff8000')
+		 : ('#ffffff', '#000000', '#a0a0a0', '#c06060', '#ff4040', '#ff0000', '#ff8000');
+    print HTML <<EOM;
+<html><head>
+<title>SWITCHlambda Amplifier Status</title>
+<meta http-equiv="Refresh" content="300">
+<meta http-equiv="Expires" content="$expire_string">
+<style type="text/css">
+p {font-family: helvetica,arial}
+h1 {font-family: helvetica,arial}
+h2 {font-family: helvetica,arial}
+th {font-family: helvetica,arial}
+td {font-family: lucidatypewriter,courier,helvetica,arial}
+.normal {color: $c_normal; }
+.offline {color: $c_offline; }
+.minor {color: $c_minor; }
+.major {color: $c_major; }
+.critical {color: $c_critical; font-weight: bold; }
+.failed {color: $c_failed; font-weight: bold; font-style: italic; }
+.trailer {font-size: 70%; }
+</style>
+</head><body bgcolor="$c_bg" text="$c_normal">\n<h1>SWITCHlambda Amplifier Status</h1>
+EOM
+}
+
+sub print_html_trailer () {
+    print HTML <<EOM;
+
+<p> <b>Color Legend:</b>
+  <span class="normal">normal</span>
+- <span class="offline">offline</span>
+- <span class="minor">minor</span>/<span class="major">major</span>/<span class="critical">critical
+alarm condition</span>
+- <span class="failed">failed</span></p>
+
+<p class="trailer"> Generated by a script using the <a
+href="http://www.switch.ch/misc/leinen/snmp/perl/">SNMP_Session.pl</a>.<br>
+
+Script by <a href="http://www.switch.ch/misc/leinen/">Simon
+Leinen</a>, <a href="http://www.switch.ch/">SWITCH</a>, 2001. </p>
+
+</body></html>
+EOM
+}
+
+sub http_date_string ($) {
+    my ($time) = @_;
+    my @gmtime = gmtime $time;
+    my ($wday) = (qw(Sun Mon Tue Wed Thu Fri Sat))[$gmtime[6]];
+    my ($month) = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$gmtime[4]];
+    my ($mday, $year, $hour, $min, $sec) = @gmtime[3,5,2,1,0];
+    return sprintf ("%s, %02d %s %04d %02d:%02d:%02d GMT",
+		    $wday, $mday, $month, $year+1900, $hour, $min, $sec);
+}
diff --git a/test/list-bgp4-neighbors b/test/list-bgp4-neighbors
new file mode 100755
index 0000000..e677699
--- /dev/null
+++ b/test/list-bgp4-neighbors
@@ -0,0 +1,53 @@
+#!/usr/local/bin/perl -w
+
+require 5.002;
+use strict;
+use SNMP_Session "0.59";
+use BER;
+use Socket;
+
+sub usage ();
+
+my $bgpPeerState = [1,3,6,1,2,1,15,3,1,2];
+my $bgpPeerRemoteAs = [1,3,6,1,2,1,15,3,1,9];
+
+my $hostname = $ARGV[0] || usage ();
+my $community = $ARGV[1] || "public";
+
+my $session;
+
+die "Couldn't open SNMP session to $hostname"
+    unless ($session = SNMP_Session->open ($hostname, $community, 161));
+$session->map_table ([$bgpPeerState, $bgpPeerRemoteAs],
+		     sub () {
+			 my ($index, $state, $as) = @_;
+			 grep (defined $_ && ($_=pretty_print $_),
+			       ($state, $as));
+			 my $pretty_state = (qw(? idle connect active
+						opensent openconfirm
+						established))[$state];
+			 printf "%-15s %-12s %32s AS%-5s\n",
+				 $index, $pretty_state,
+				 hostname ($index), $as;
+		     });
+$session->close ();
+
+1;
+
+sub pretty_addr ($ ) {
+    my ($addr) = @_;
+    my ($hostname,$aliases,$addrtype,$length, at addrs)
+	= gethostbyaddr (inet_aton ($addr), AF_INET);
+    $hostname ? $hostname." [".$addr."]" : $addr;
+}
+
+sub hostname ($ ) {
+    my ($addr) = @_;
+    my ($hostname,$aliases,$addrtype,$length, at addrs)
+	= gethostbyaddr (inet_aton ($addr), AF_INET);
+    $hostname || "[".$addr."]";
+}
+
+sub usage () {
+  die "usage: $0 host [community]";
+}
diff --git a/test/list-bgp4-table b/test/list-bgp4-table
new file mode 100644
index 0000000..8dc5d14
--- /dev/null
+++ b/test/list-bgp4-table
@@ -0,0 +1,84 @@
+#!/usr/local/bin/perl -w
+
+require 5.002;
+use strict;
+use SNMP_Session "0.59";
+use BER;
+use Socket;
+use Getopt::Long;
+
+sub usage ();
+
+my $snmp_version = '2';
+
+GetOptions ("version=i" => \$snmp_version);
+
+my $bgp4PathAttrASPathSegment = [1,3,6,1,2,1,15,6,1,5];
+
+my $hostname = $ARGV[0] || usage ();
+my $community = $ARGV[1] || "public";
+
+my $session;
+
+die "Couldn't open SNMP session to $hostname"
+    unless ($session =
+	    ($snmp_version eq '1' ? SNMP_Session->open ($hostname, $community, 161)
+	     : SNMPv2c_Session->open ($hostname, $community, 161)));
+$session->map_table ([$bgp4PathAttrASPathSegment],
+		     sub () {
+			 my ($index, $as_path_segment) = @_;
+			 my ($dest_net, $preflen, $peer)
+			     = ($index =~ /([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\.([0-9]+)\.([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/);
+			 grep (defined $_ && ($_=pretty_print $_),
+			       ($as_path_segment));
+			 printf "%-18s %-15s %s\n",
+			 $dest_net."/".$preflen, $peer, pretty_as_path ($as_path_segment);
+		     });
+$session->close ();
+
+1;
+
+sub pretty_addr ($ ) {
+    my ($addr) = @_;
+    my ($hostname,$aliases,$addrtype,$length, at addrs)
+	= gethostbyaddr (inet_aton ($addr), AF_INET);
+    $hostname ? $hostname." [".$addr."]" : $addr;
+}
+
+sub hostname ($ ) {
+    my ($addr) = @_;
+    my ($hostname,$aliases,$addrtype,$length, at addrs)
+	= gethostbyaddr (inet_aton ($addr), AF_INET);
+    $hostname || "[".$addr."]";
+}
+
+sub pretty_as_path ($ ) {
+    my ($aps) = @_;
+    my $start = 0;
+    my $result = '';
+    while (length ($aps) > $start) {
+	my ($type,$length) = unpack ("CC", substr ($aps, $start, 2));
+	$start += 2;
+	my ($pretty_ases, $next_start) = pretty_ases ($length, $aps, $start);
+	$result .= ($type == 1 ? "SET " : $type == 2 ? "" : "type $type??")
+	    .$pretty_ases;
+	$start = $next_start;
+    }
+    $result;
+}
+
+sub pretty_ases ($$$ ) {
+    my ($length, $aps, $start) = @_;
+    my $result = undef;
+    return ('',0) if $length == 0;
+    while ($length-- > 0) {
+	my $as = unpack ("S", substr ($aps, $start, 2));
+	$start += 2;
+	$result = defined $result ? $result." ".$as : $as;
+    }
+    ($result, $start);
+}
+
+sub usage () {
+  die "usage: $0 host [community]";
+}
diff --git a/test/list-ospf-neighbors b/test/list-ospf-neighbors
new file mode 100755
index 0000000..6637704
--- /dev/null
+++ b/test/list-ospf-neighbors
@@ -0,0 +1,47 @@
+#!/usr/local/bin/perl -w
+
+require 5.002;
+use strict;
+use SNMP_Session "0.59";
+use BER;
+use Socket;
+
+sub usage ();
+
+my $ospfNbrIpAddr = [1,3,6,1,2,1,14,10,1,1];
+my $ospfNbrRtrId = [1,3,6,1,2,1,14,10,1,3];
+my $ospfNbrState = [1,3,6,1,2,1,14,10,1,6];
+
+my $hostname = $ARGV[0] || usage ();
+my $community = $ARGV[1] || "public";
+
+my $session;
+
+die "Couldn't open SNMP session to $hostname"
+    unless ($session = SNMP_Session->open ($hostname, $community, 161));
+$session->map_table ([$ospfNbrIpAddr, $ospfNbrRtrId, $ospfNbrState],
+		     sub () {
+			 my ($index, $addr, $router_id, $state) = @_;
+			 grep (defined $_ && ($_=pretty_print $_),
+			       ($addr, $router_id, $state));
+			 my $pretty_state = (qw(? down attempt init twoWay
+						exchangeStart exchange
+						loading full))[$state];
+			 print STDOUT (pretty_addr ($addr)," ",
+				       pretty_addr ($router_id),
+				       " (",$pretty_state,")\n");
+		     });
+$session->close ();
+
+1;
+
+sub pretty_addr ($ ) {
+    my ($addr) = @_;
+    my ($hostname,$aliases,$addrtype,$length, at addrs)
+	= gethostbyaddr (inet_aton ($addr), AF_INET);
+    $hostname ? $hostname." [".$addr."]" : $addr;
+}
+
+sub usage () {
+  die "usage: $0 host [community]";
+}
diff --git a/test/look-at-counters.pl b/test/look-at-counters.pl
new file mode 100755
index 0000000..2cf04eb
--- /dev/null
+++ b/test/look-at-counters.pl
@@ -0,0 +1,105 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use SNMP_Session;
+use BER;
+use Time::HiRes qw(gettimeofday tv_interval);
+
+sub usage();
+
+$SNMP_Session::suppress_warnings = 1;
+
+my ($host, $community, $interval, $port, $factor, @OIDS);
+
+$interval = 5;
+$port = 161;
+$factor = 1;
+
+while ($#ARGV >= 0) {
+    $_ = shift @ARGV;
+    if (/^-t$/) {
+	$interval = shift @ARGV;
+    } elsif (/^-p$/) {
+	$port = shift @ARGV;
+    } elsif (/^-b$/) {
+	$factor = 8;
+    } elsif (! defined ($host)) {
+	$host = $_;
+    } elsif (! defined ($community)) {
+	$community = $_;
+    } else {
+	push @OIDS, $_;
+    }
+}
+usage() if !defined $host || !defined $community || !@OIDS;
+
+my @encoded_oids = @OIDS;
+
+grep (($_ = encode_oid (split ('\.',$_)) || die "cannot encode $_"),
+      @encoded_oids);
+
+my $session = SNMP_Session->open ($host, $community, $port)
+    || die "couldn't open SNMP session to $host";
+
+my %last_values;
+my $last_time;
+
+get_initial_values ($session, @encoded_oids)
+    || die "Couldn't get initial values";
+while (1) {
+    sleep $interval;
+    print_value_changes ($session, @encoded_oids);
+}
+$session->close ();
+1;
+
+sub usage() {
+    die "Usage: $0 [-t interval] [-p port] host community OID...";
+}
+
+sub get_initial_values ($@) {
+    my ($session, @encoded_oids) = @_;
+
+    if (!$session->get_request_response (@encoded_oids)) {
+	print STDERR "Request to $host failed: $SNMP_Session::errmsg\n";
+    } else {
+	my $response = $session->pdu_buffer;
+	my ($bindings) = $session->decode_get_response ($response);
+
+	$last_time = [gettimeofday()];
+	while ($bindings ne '') {
+	    my $binding;
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    my ($oid,$value) = decode_by_template ($binding, "%O%@");
+	    grep ($_=pretty_print $_, $oid, $value);
+	    $last_values{$oid} = $value;
+	}
+    }
+    1;
+}
+
+sub print_value_changes ($@) {
+    my ($session, @encoded_oids) = @_;
+    if (!$session->get_request_response (@encoded_oids)) {
+	print STDERR "Request to $host failed: $SNMP_Session::errmsg\n";
+    } else {
+	my $this_time = [gettimeofday()];
+	my $response = $session->pdu_buffer;
+	my ($bindings) = $session->decode_get_response ($response);
+	my $real_interval = tv_interval ($last_time, $this_time);
+	$last_time = $this_time;
+
+	while ($bindings ne '') {
+	    my $binding;
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    my ($oid,$value) = decode_by_template ($binding, "%O%@");
+	    grep ($_=pretty_print $_, $oid, $value);
+	    my $diff = $value - $last_values{$oid};
+	    printf "%12.2f",$diff/$real_interval*$factor,"\n";
+	    $last_values{$oid} = $value;
+	}
+	print "\n";
+    }
+    1;
+}
diff --git a/test/ls1010-list-vcls b/test/ls1010-list-vcls
new file mode 100755
index 0000000..197b207
--- /dev/null
+++ b/test/ls1010-list-vcls
@@ -0,0 +1,258 @@
+#!/usr/local/bin/perl -w
+###
+### ls1010-list-vcls
+###
+### Author:       Simon Leinen  <simon at switch.ch>
+### Date Created: 24-Feb-1999
+###
+### Real-time full-screen display of the cell counts on VCLs (Virtual
+### Channel Links) and VPLs (Virtual Path Links) on a Cisco LS1010 ATM
+### switch.
+###
+### Description: 
+###
+### Call this script with "-h" to learn about command usage.
+###
+### The script will poll an LS1010's ciscoAtmVplTable and
+### ciscoAtmVclTable (from CISCO-ATM-CONN-MIB) at specified intervals
+### (default is every five seconds).
+###
+### For each VCL except for the standard signaling and ILMI VCs (0/5
+### and 0/16, respectively), a line is written to the terminal which
+### lists the VCL's connection info, as well as the input and output
+### transfer rates, as computed from the deltas of the respective cell
+### counts since the last sample.
+###
+### When a VCL is found to have had only output, but no input traffic
+### in the last sampling interval, it is shown in inverse video.  In
+### addition, when a VCL changes state (from normal to inverse or vice
+### versa), a bell character is sent to the terminal.
+###
+### Note that on the very first display, the actual SNMP counter
+### values are displayed.  THOSE ABSOLUTE COUNTER VALUES HAVE NO
+### DEFINED SEMANTICS WHATSOEVER.  However, in some versions of
+### Cisco's software, the values seem to correspond to the total
+### number of counted items since system boot (modulo 2^32).  This can
+### be useful for certain kinds of slowly advancing counters (such as
+### CRC errors, hopefully).
+###
+### The topmost screen line shows the name of the managed node, as
+### well as a few hard-to-explain items I found useful while debugging
+### the script.
+###
+### Please send any patches and suggestions for improvement to the
+### author (see e-mail address above).  Hope you find this useful!
+###
+require 5.003;
+
+use strict;
+
+use BER;
+use SNMP_Session "0.67";	# requires map_table_4
+use POSIX;			# for exact time
+use Curses;
+
+my $version = '1';
+
+my $desired_interval = 5.0;
+
+while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
+    if ($ARGV[0] =~ /^-v/) {
+	if ($ARGV[0] eq '-v') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] eq '1') {
+	    $version = '1';
+	} elsif ($ARGV[0] eq '2c') {
+	    $version = '2c';
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-t/) {
+	if ($ARGV[0] eq '-t') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+(\.[0-9]+)?$/) {
+	    $desired_interval = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] eq '-h') {
+	usage (0);
+	exit 0;
+    } else {
+	usage (1);
+    }
+    shift @ARGV;
+}
+my $host = shift @ARGV || usage (1);
+my $community = shift @ARGV || "public";
+usage (1) if $#ARGV >= $[;
+
+my $ciscoAtmVclInCells = [1,3,6,1,4,1,9,10,13,1,2,1,1,13];
+my $ciscoAtmVclOutCells = [1,3,6,1,4,1,9,10,13,1,2,1,1,14];
+my $ciscoAtmVclCrossIfIndex = [1,3,6,1,4,1,9,10,13,1,2,1,1,15];
+my $ciscoAtmVclCrossVpi = [1,3,6,1,4,1,9,10,13,1,2,1,1,16];
+my $ciscoAtmVclCrossVci = [1,3,6,1,4,1,9,10,13,1,2,1,1,17];
+
+my $ciscoAtmVplInCells = [1,3,6,1,4,1,9,10,13,1,1,1,1,12];
+my $ciscoAtmVplOutCells = [1,3,6,1,4,1,9,10,13,1,1,1,1,13];
+my $ciscoAtmVplCrossIfIndex = [1,3,6,1,4,1,9,10,13,1,1,1,1,14];
+my $ciscoAtmVplCrossVpi = [1,3,6,1,4,1,9,10,13,1,1,1,1,15];
+
+my $clock_ticks = POSIX::sysconf( &POSIX::_SC_CLK_TCK );
+
+my $win = new Curses;
+
+my %old;
+my $sleep_interval = $desired_interval + 0.0;
+my $interval;
+my $linecount;
+
+sub out_vpl {
+    my ($index, $xif, $xvpi, $incells, $outcells) = @_;
+    my ($if, $vpi) = split (/\./, $index);
+    grep (defined $_ && ($_=pretty_print $_),
+	  ($xif, $xvpi, $incells, $outcells));
+    return out_vxl ($if, $vpi, undef,
+		    $xif, $xvpi, undef,
+		    $incells, $outcells);
+}
+sub out_vcl {
+    my ($index, $xif, $xvpi, $xvci, $incells, $outcells) = @_;
+    my ($if, $vpi, $vci) = split (/\./, $index);
+    grep (defined $_ && ($_=pretty_print $_),
+	  ($xif, $xvpi, $xvci, $incells, $outcells));
+    return out_vxl ($if, $vpi, $vci,
+		    $xif, $xvpi, $xvci,
+		    $incells, $outcells);
+}
+
+sub out_vxl {
+    my ($if, $vpi, $vci, $xif, $xvpi, $xvci, $incells, $outcells) = @_;
+    my $index = join ('.', $if, $vpi, defined $vci ? $vci : '-');
+
+    my ($clock) = POSIX::times();
+    my $alarm = 0;
+    $win->clrtoeol ();
+    ## Ignore signaling VCs
+    return if defined $vpi && $vpi == 0 && defined $vci && ($vci == 5 || $vci == 16);
+    return if defined $xvpi && $xvpi == 0 && defined $xvci && ($xvci == 5 || $xvci == 16);
+
+    return unless defined $incells && defined $outcells;
+    if (!defined $old{$index}) {
+	$win->addstr ($linecount, 0,
+		      sprintf ("%2d %3d/%-3s %2d %3d/%-3s %10s %10s\n",
+			       $if, $vpi, defined $vci ? $vci : '-',
+			       $xif, $xvpi, defined $xvci ? $xvci : '-',
+			       $incells, $outcells));
+    } else {
+	my $old = $old{$index};
+
+	$interval = ($clock-$old->{'clock'}) * 1.0 / $clock_ticks;
+	my $d_in = ($incells-$old->{'incells'})*8*53/$interval;
+	my $d_out = ($outcells-$old->{'outcells'})*8*53/$interval;
+	$alarm = (($d_out > 0) && ($d_in == 0));
+	print STDERR "\007" if $alarm && !$old->{'alarm'};
+	print STDERR "\007" if !$alarm && $old->{'alarm'};
+	$win->standout() if $alarm;
+	$win->addstr ($linecount, 0,
+		      sprintf ("%2d %3d/%-3s %2d %3d/%-3s %10.0f %10.0f\n\n",
+			       $if, $vpi, defined $vci ? $vci : '-',
+			       $xif, $xvpi, defined $xvci ? $xvci : '-',
+			       $d_in, $d_out));
+	$win->standend() if $alarm;
+    }
+    $old{$index} = {'incells' => $incells,
+		    'outcells' => $outcells,
+		    'clock' => $clock,
+		    'alarm' => $alarm};
+    ++$linecount;
+    $win->refresh ();
+}
+
+$win->erase ();
+my $session =
+    ($version eq '1' ? SNMPv1_Session->open ($host, $community, 161)
+     : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, 161)
+     : die "Unknown SNMP version $version")
+  || die "Opening SNMP_Session";
+
+### max_vcl_repetitions, max_vpl_repetitions:
+###
+### We try to be smart about the value of maxRepetitions.  Starting
+### with the session default, we use the number of rows in the table
+### (returned from map_table_4) to compute the next value.  It should
+### be one more than the number of rows in the table, because
+### map_table needs an extra set of bindings to detect the end of the
+### table.
+###
+my $max_vcl_repetitions = $session->default_max_repetitions;
+my $max_vpl_repetitions = $session->default_max_repetitions;
+while (1) {
+    $win->addstr (0, 0, sprintf ("%-20s interval %4.1fs %d VCLs %d VPLs",
+				 $host,
+				 $interval || $desired_interval,
+				 $max_vcl_repetitions,
+				 $max_vpl_repetitions));
+    $win->standout();
+    $win->addstr (1, 0,
+		  sprintf ("%2s %3s/%-3s %2s %3s/%-3s %10s %10s",
+			   "if", "VPI", "VCI",
+			   "if", "VPI", "VCI",
+			   "bits/s", "bits/s"));
+    $win->addstr (2, 0,
+		  sprintf ("%2s %3s %-3s %2s %3s %-3s %10s %10s\n",
+			   "", "", "", "", "", "",
+			   "in", "out",
+			   ""));
+    $win->clrtoeol ();
+    $win->standend();
+    $linecount = 3;
+    my $vcls = $session->map_table_4
+	([$ciscoAtmVclCrossIfIndex, $ciscoAtmVclCrossVpi, $ciscoAtmVclCrossVci,
+	  $ciscoAtmVclInCells, $ciscoAtmVclOutCells],
+	 \&out_vcl,
+	 $max_vcl_repetitions);
+    $max_vcl_repetitions = $vcls + 1
+	if $vcls > 0;
+    my $vpls = $session->map_table_4
+	([$ciscoAtmVplCrossIfIndex, $ciscoAtmVplCrossVpi,
+	  $ciscoAtmVplInCells, $ciscoAtmVplOutCells],
+	 \&out_vpl,
+	 $max_vpl_repetitions);
+    $max_vpl_repetitions = $vpls + 1
+	if $vpls > 0;
+    $sleep_interval -= ($interval - $desired_interval)
+	if defined $interval;
+    select (undef, undef, undef, $sleep_interval);
+}
+1;
+
+sub usage ($) {
+    warn <<EOM;
+Usage: $0 [-t secs] [-v (1|2c)] switch [community]
+       $0 -h
+
+  -h           print this usage message and exit.
+
+  -t secs      specifies the sampling interval.  Defaults to 5 seconds.
+
+  -v version   can be used to select the SNMP version.  The default
+   	       is SNMPv1, which is what most devices support.  If your box
+   	       supports SNMPv2c, you should enable this by passing "-v 2c"
+   	       to the script.  SNMPv2c is much more efficient for walking
+   	       tables, which is what this tool does.
+
+  switch       hostname or IP address of an LS1010 switch
+
+  community    SNMP community string to use.  Defaults to "public".
+EOM
+    exit (1) if $_[0];
+}
diff --git a/test/make-cisco-products.pl b/test/make-cisco-products.pl
new file mode 100644
index 0000000..d4aee16
--- /dev/null
+++ b/test/make-cisco-products.pl
@@ -0,0 +1,21 @@
+#!/usr/local/bin/perl -w
+### Script to convert CISCO-PRODUCTS-MIB.my to a Perl variable
+### definition for use in e.g. etna:/home/noc/bin/get-cisco-versions.
+### Should be run from time to time when a new MIB arrives on
+### ftp.cisco.com/pub/mibs/v2.
+###
+use strict;
+
+my $source = "/home/leinen/snmp/mibs/cisco/v2/CISCO-PRODUCTS-MIB.my";
+
+open (SRC, $source) || die "open $source: $!";
+print "my %cisco_product_name = (\n";
+while (<SRC>) {
+    my ($product, $octet);
+    next unless ($product, $octet)
+	= /^(.*)\s+OBJECT\s+IDENTIFIER\s*::=\s*{\s*ciscoProducts\s*([0-9]+)\s*}/;
+    print "  $octet => \"$product\",\n";
+}
+close (SRC) || warn "close $source: $!";
+print ");\n";
+1;
diff --git a/test/max-list-sessions b/test/max-list-sessions
new file mode 100755
index 0000000..12fe5cf
--- /dev/null
+++ b/test/max-list-sessions
@@ -0,0 +1,130 @@
+#!/usr/local/bin/perl -w
+#
+# max-list-sessions
+#
+# Use ASCEND-SESSION-MIB to print a list of active connections on an
+# Ascend dialin router such as a MAX.
+#
+# Date Created: 1998/06/29
+# Author:       Simon Leinen  <simon at switch.ch>
+# RCS $Header: /home/leinen/CVS/SNMP_Session/test/max-list-sessions,v 1.5 2000-09-24 18:54:03 leinen Exp $
+#
+use strict;
+
+use SNMP_Session "0.59";
+use BER;
+use Socket;
+
+my $host = shift @ARGV || die;
+my $community = shift @ARGV || die;
+
+my $ssnStatusIndex = [1,3,6,1,4,1,529,12,2,1,1];
+my $ssnStatusValidFlag = [1,3,6,1,4,1,529,12,2,1,2];
+my $ssnStatusUserName = [1,3,6,1,4,1,529,12,2,1,3];
+my $ssnStatusUserIPAddress = [1,3,6,1,4,1,529,12,2,1,4];
+my $ssnStatusUserSubnetMask = [1,3,6,1,4,1,529,12,2,1,5];
+my $ssnStatusCurrentService = [1,3,6,1,4,1,529,12,2,1,6];
+my $ssnStatusCallReferenceNum = [1,3,6,1,4,1,529,12,2,1,7];
+
+my $callStatusIndex = [1,3,6,1,4,1,529,11,2,1,1];
+my $callStatusValidFlag = [1,3,6,1,4,1,529,11,2,1,2];
+my $callStatusStartingTimeStamp = [1,3,6,1,4,1,529,11,2,1,3];
+my $callStatusCallReferenceNum = [1,3,6,1,4,1,529,11,2,1,4];
+my $callStatusDataRate = [1,3,6,1,4,1,529,11,2,1,5];
+my $callStatusSlotNumber = [1,3,6,1,4,1,529,11,2,1,6];
+my $callStatusSlotLineNumber = [1,3,6,1,4,1,529,11,2,1,7];
+my $callStatusSlotChannelNumber = [1,3,6,1,4,1,529,11,2,1,8];
+my $callStatusModemSlotNumber = [1,3,6,1,4,1,529,11,2,1,9];
+my $callStatusModemOnSlot = [1,3,6,1,4,1,529,11,2,1,10];
+my $callStatusIfIndex = [1,3,6,1,4,1,529,11,2,1,11];
+my $callSessionIndex = [1,3,6,1,4,1,529,11,2,1,12];
+my $callStatusType = [1,3,6,1,4,1,529,11,2,1,13];
+my $callStatusXmitRate = [1,3,6,1,4,1,529,11,2,1,14];
+my $callStatusPortType = [1,3,6,1,4,1,529,11,2,1,15];
+
+my %call_to_session = ();
+
+my $session = SNMP_Session->open ($host, $community, 161)
+  || die "Opening SNMP_Session";
+read_sessions ($session);
+read_calls ($session);
+foreach my $call_ref (sort keys %call_to_session) {
+    pretty_session ($call_to_session{$call_ref});
+}
+1;
+
+sub read_calls ($) {
+    my ($session) = @_;
+    $session->map_table ([$callStatusValidFlag,
+			  $callStatusCallReferenceNum,
+			  $callStatusDataRate],
+			 sub {
+			     my ($index, $valid, $refno, $rate) = @_;
+			     map { defined ($_) && ($_=pretty_print $_) }
+			     ($valid, $refno, $rate);
+			     return if $valid == 1;
+			     my $session = $call_to_session{$refno};
+			     return unless defined $session;
+			     $session->{'data_rate'} = $rate;
+			 });
+}
+
+sub read_sessions ($) {
+    my ($session) = @_;
+    $session->map_table ([$ssnStatusValidFlag,
+			  $ssnStatusUserName,
+			  $ssnStatusUserIPAddress, $ssnStatusUserSubnetMask,
+			  $ssnStatusCurrentService, $ssnStatusCallReferenceNum],
+			 sub {
+			     my ($index, $valid, $user, $addr, $mask,
+				 $service, $call_ref) = @_;
+			     
+			     map (defined $_ && ($_=pretty_print $_),
+				   ($valid, $user, $addr, $mask,
+				    $service, $call_ref));
+			     return if $valid == 1;	# invalid
+			     $call_to_session{$call_ref}
+			     = { 'index' => $index,
+				 'user' => $user,
+				 'addr' => $addr,
+				 'mask' => $mask,
+				 'service' => $service,
+				 'call_ref' => $call_ref,
+			     };
+			 });
+}
+
+sub pretty_session ($ ) {
+    my ($session) = @_;
+    printf STDOUT ("%2d  %-24s %-15s %-15s %6d %s\n",
+		   $session->{'index'},
+		   defined $session->{user} ? $session->{user} : '',
+		   inet_ntoa (pack ("C4",split ('\.',$session->{addr}))),
+		   inet_ntoa (pack ("C4",split ('\.',$session->{mask}))),
+		   $session->{'data_rate'},
+		   pretty_service ($session->{service}));
+}
+
+sub pretty_service ($) {
+    my ($service) = @_;
+    return
+	$service == 1 ? 'none'
+	: $service == 2 ? 'other'
+	: $service == 3 ? 'ppp'
+	: $service == 4 ? 'slip'
+	: $service == 5 ? 'mpp'
+	: $service == 6 ? 'x25'
+	: $service == 7 ? 'combinet'
+	: $service == 8 ? 'frameRelay'
+	: $service == 9 ? 'euraw'
+	: $service == 10 ? 'euui'
+	: $service == 11 ? 'telnet'
+	: $service == 12 ? 'telnetBinary'
+	: $service == 13 ? 'rawTcp'
+	: $service == 14 ? 'terminalServer'
+	: $service == 15 ? 'mp'
+	: $service == 16 ? 'virtualConnect'
+	: $service == 17 ? 'dchannelX25'
+	: $service == 18 ? 'dtpt'
+	    : 'unknown('.$service.')';
+}
diff --git a/test/mcount.pl b/test/mcount.pl
new file mode 100755
index 0000000..7c63f11
--- /dev/null
+++ b/test/mcount.pl
@@ -0,0 +1,218 @@
+#!/usr/local/bin/perl -w
+###
+### Author:       Simon Leinen  <simon at switch.ch>
+### Date Created: 28-Jul-1999
+###
+### Track ipMRouteInterfaceInMcastOctets and
+### ipMRouteInterfaceOutMcastOctets for all multicast interfaces of a
+### router.  The router has to support IPMROUTE-MIB according to
+### draft-ietf-idmr-multicast-routmib-10.txt (or an earlier version,
+### but not all versions may include these counters.
+###
+require 5.003;
+
+use strict;
+
+use BER;
+use SNMP_Session "0.67";	# requires map_table_4
+use POSIX;			# for exact time
+use Curses;
+
+my $version = '1';
+
+my $desired_interval = 5.0;
+
+my $all_p = 0;
+
+while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
+    if ($ARGV[0] =~ /^-v/) {
+	if ($ARGV[0] eq '-v') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] eq '1') {
+	    $version = '1';
+	} elsif ($ARGV[0] eq '2c') {
+	    $version = '2c';
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-t/) {
+	if ($ARGV[0] eq '-t') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+(\.[0-9]+)?$/) {
+	    $desired_interval = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] eq '-a') {
+	$all_p = 1;
+    } elsif ($ARGV[0] eq '-h') {
+	usage (0);
+	exit 0;
+    } else {
+	usage (1);
+    }
+    shift @ARGV;
+}
+my $host = shift @ARGV || usage (1);
+my $community = shift @ARGV || "public";
+usage (1) if $#ARGV >= $[;
+
+my $ifDescr = [1,3,6,1,2,1,2,2,1,2];
+my $ifAdminStatus = [1,3,6,1,2,1,2,2,1,7];
+my $ifOperStatus = [1,3,6,1,2,1,2,2,1,8];
+my $ipMRouteInterfaceTtl = [1,3,6,1,3,60,1,1,4,1,2];
+my $ipMRouteProtocol = [1,3,6,1,3,60,1,1,4,1,3];
+my $ipMRouteRateLimit = [1,3,6,1,3,60,1,1,4,1,4];
+my $ipMRouteInMcastOctets = [1,3,6,1,3,60,1,1,4,1,5];
+my $ipMRouteOutMcastOctets = [1,3,6,1,3,60,1,1,4,1,6];
+my $ipMRouteHCInMcastOctets = [1,3,6,1,3,60,1,1,4,1,7];
+my $ipMRouteHCOutMcastOctets = [1,3,6,1,3,60,1,1,4,1,8];
+
+my $locIfDescr = [1,3,6,1,4,1,9,2,2,1,1,28];
+
+my $clock_ticks = POSIX::sysconf( &POSIX::_SC_CLK_TCK );
+
+my $win = new Curses;
+
+my %old;
+my $sleep_interval = $desired_interval + 0.0;
+my $interval;
+my $linecount;
+
+sub out_multicast_interface {
+    my ($index, $descr, $admin, $oper, $in, $out, $comment) = @_;
+    my ($clock) = POSIX::times();
+    my $alarm = 0;
+
+    grep (defined $_ && ($_=pretty_print $_),
+	  ($descr, $admin, $oper, $in, $out, $comment));
+    $win->clrtoeol ();
+    return unless $all_p || defined $oper && $oper == 1;	# up
+    return unless defined $in && defined $out;
+
+    if (!defined $old{$index}) {
+	$win->addstr ($linecount, 0,
+		      sprintf ("%2d  %-24s %10s %10s %s\n",
+			       $index,
+			       defined $descr ? $descr : '',
+			       defined $in ? $in : '-',
+			       defined $out ? $out : '-',
+			       defined $comment ? $comment : ''));
+    } else {
+	my $old = $old{$index};
+
+	$interval = ($clock-$old->{'clock'}) * 1.0 / $clock_ticks;
+	my $d_in = $in ? ($in-$old->{'in'})*8/$interval : 0;
+	my $d_out = $out ? ($out-$old->{'out'})*8/$interval : 0;
+	print STDERR "\007" if $alarm && !$old->{'alarm'};
+	print STDERR "\007" if !$alarm && $old->{'alarm'};
+	$win->standout() if $alarm;
+	$win->addstr ($linecount, 0,
+		      sprintf ("%2d  %-24s %s %s %s\n",
+			       $index,
+			       defined $descr ? $descr : '',
+			       pretty_bps ($in, $d_in),
+			       pretty_bps ($out, $d_out),
+			       defined $comment ? $comment : ''));
+	$win->standend() if $alarm;
+    }
+    $old{$index} = {'in' => $in,
+		    'out' => $out,
+		    'clock' => $clock,
+		    'alarm' => $alarm};
+    ++$linecount;
+    $win->refresh ();
+}
+
+sub pretty_bps ($$) {
+    my ($count, $bps) = @_;
+    if (! defined $count) {
+	return '      -   ';
+    } elsif ($bps > 1000000) {
+	return sprintf ("%8.4f M", $bps/1000000);
+    } elsif ($bps > 1000) {
+	return sprintf ("%9.1fk", $bps/1000);
+    } else {
+	return sprintf ("%10.0f", $bps);
+    }
+}
+
+$win->erase ();
+my $session =
+    ($version eq '1' ? SNMPv1_Session->open ($host, $community, 161)
+     : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, 161)
+     : die "Unknown SNMP version $version")
+  || die "Opening SNMP_Session";
+
+### max_repetitions:
+###
+### We try to be smart about the value of $max_repetitions.  Starting
+### with the session default, we use the number of rows in the table
+### (returned from map_table_4) to compute the next value.  It should
+### be one more than the number of rows in the table, because
+### map_table needs an extra set of bindings to detect the end of the
+### table.
+###
+my $max_repetitions = $session->default_max_repetitions;
+while (1) {
+    $win->addstr (0, 0, sprintf ("%-20s interval %4.1fs %d reps",
+				 $host,
+				 $interval || $desired_interval,
+				 $max_repetitions));
+    $win->standout();
+    $win->addstr (1, 0,
+		  sprintf ("%2s  %-24s %10s %10s %10s %s\n",
+			   "ix", "name",
+			   "bits/s", "bits/s",
+			   "description"));
+    $win->addstr (2, 0,
+		  sprintf ("%2s  %-24s %10s %10s %10s %s\n",
+			   "", "",
+			   "in", "out",
+			   ""));
+    $win->clrtoeol ();
+    $win->standend();
+    $linecount = 3;
+    my $calls = $session->map_table_4
+	([$ifDescr,$ifAdminStatus,$ifOperStatus,
+	  $ipMRouteInMcastOctets, $ipMRouteOutMcastOctets,
+	  $locIfDescr],
+	 \&out_multicast_interface,
+	 $max_repetitions);
+    $max_repetitions = $calls + 1
+	if $calls > 0;
+    $sleep_interval -= ($interval - $desired_interval)
+	if defined $interval;
+    select (undef, undef, undef, $sleep_interval);
+}
+1;
+
+sub usage ($) {
+    warn <<EOM;
+Usage: $0 [-t secs] [-v (1|2c)] switch [community]
+       $0 -h
+
+  -h           print this usage message and exit.
+
+  -t secs      specifies the sampling interval.  Defaults to 5 seconds.
+
+  -v version   can be used to select the SNMP version.  The default
+   	       is SNMPv1, which is what most devices support.  If your box
+   	       supports SNMPv2c, you should enable this by passing "-v 2c"
+   	       to the script.  SNMPv2c is much more efficient for walking
+   	       tables, which is what this tool does.
+
+  switch       hostname or IP address of an LS1010 switch
+
+  community    SNMP community string to use.  Defaults to "public".
+EOM
+    exit (1) if $_[0];
+}
diff --git a/test/mdebug b/test/mdebug
new file mode 100755
index 0000000..6a6a08d
--- /dev/null
+++ b/test/mdebug
@@ -0,0 +1,142 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use SNMP_Session;
+use BER;
+use Socket;
+
+sub print_mroutes ($$);
+sub pretty_next_hop_state ($);
+
+my $version = '2c';
+my $port = 161;
+my $debug = 0;
+
+while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
+    if ($ARGV[0] =~ /^-v/) {
+	if ($ARGV[0] eq '-v') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] eq '1') {
+	    $version = '1';
+	} elsif ($ARGV[0] eq '2c') {
+	    $version = '2c';
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-p/) {
+	if ($ARGV[0] eq '-p') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+$/) {
+	    $port = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] eq '-h') {
+	usage (0);
+	exit 0;
+    } else {
+	usage (1);
+    }
+    shift @ARGV;
+}
+my $host = shift @ARGV || usage (1);
+my $community = shift @ARGV || "public";
+my ($group_name, $group, $group_quad);
+my ($source_name, $source, $source_quad);
+$group_name = shift @ARGV || die ("no source/group given");
+if ($#ARGV >= $[) {
+    $source_name = $group_name;
+    $group_name = shift @ARGV;
+}
+usage (1) if $#ARGV >= $[;
+if (! defined ($group = inet_aton ($group_name))) {
+    die ("Cannot parse group $group_name");
+}
+$group_quad = inet_ntoa ($group);
+if (! defined ($source = inet_aton ($source_name))) {
+    die ("Cannot parse source $source_name");
+}
+$source_quad = inet_ntoa ($source);
+
+my $session =
+    ($version eq '1' ? SNMPv1_Session->open ($host, $community, $port)
+     : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, $port)
+     : die "Unknown SNMP version $version")
+  || die "Opening SNMP_Session";
+print_mroutes ($session, $group);
+1;
+
+sub print_mroutes ($$) {
+    my $ipMRouteUpstreamNeighbor = [1,3,6,1,3,60,1,1,2,1,4];
+    my $ipMRouteInIfIndex = [1,3,6,1,3,60,1,1,2,1,5];
+    my $ipMRouteUpTime = [1,3,6,1,3,60,1,1,2,1,6];
+    my $ipMRouteExpiryTime = [1,3,6,1,3,60,1,1,2,1,7];
+    my $ipMRoutePkts = [1,3,6,1,3,60,1,1,2,1,8];
+    my $ipMRouteDifferentInIfPackets = [1,3,6,1,3,60,1,1,2,1,9];
+    my $ipMRouteOctets = [1,3,6,1,3,60,1,1,2,1,10];
+    my $ipMRouteProtocol = [1,3,6,1,3,60,1,1,2,1,11];
+
+    my $ipMRouteNextHopState = [1,3,6,1,3,60,1,1,3,1,6];
+    my $ipMRouteNextHopProtocol = [1,3,6,1,3,60,1,1,3,1,10];
+    my ($session, $group) = @_;
+    my @group_subids = split (/\./, inet_ntoa ($group), 4);
+    my @oids = ([@{$ipMRouteUpstreamNeighbor}, at group_subids],
+		[@{$ipMRouteProtocol}, at group_subids]);
+    $session->map_table
+	(\@oids,
+	 sub () {
+	     my ($index, $nbr, $protocol) = @_;
+	     my ($source, $mask) = 
+		 ($index =~ m/^(\d+\.\d+\.\d+\.\d+)\.(\d+\.\d+\.\d+\.\d+)$/);
+	     map { $_ = pretty_print $_ if defined $_ }
+	     ($nbr, $protocol);
+	     print STDOUT ("$source ($mask) ",inet_ntoa ($group),": ",
+			   pretty_next_hop_protocol ($protocol),
+			   "\n");
+	 });
+
+    @oids = ([@{$ipMRouteNextHopState}, at group_subids],
+		[@{$ipMRouteNextHopProtocol}, at group_subids]);
+    $session->map_table
+	(\@oids,
+	 sub () {
+	     my ($index, $state, $protocol) = @_;
+	     my ($source, $mask, $if_index, $nexthop) = 
+		 ($index =~ m/^(\d+\.\d+\.\d+\.\d+)\.(\d+\.\d+\.\d+\.\d+)\.(\d+)\.(\d+\.\d+\.\d+\.\d+)$/);
+	     map { $_ = pretty_print $_ if defined $_ }
+	     ($state, $protocol);
+	     print STDOUT ("$source ($mask) ",inet_ntoa ($group),": ",
+			   pretty_next_hop_state ($state)," ",
+			   pretty_next_hop_protocol ($protocol),
+			   "\n");
+	 });
+}
+
+sub pretty_next_hop_state ($ ) {
+    return "forwarding(1)" if $_[0] eq 1;
+    return "pruned(2)" if $_[0] eq 2;
+    return "???($_[0])";
+}
+
+sub pretty_next_hop_protocol ($ ) {
+    return "other(1)" if $_[0] eq 1;
+    return "local(2)" if $_[0] eq 2;
+    return "netmgmt(3)" if $_[0] eq 3;
+    return "dvmrp(4)" if $_[0] eq 4;
+    return "mospf(5)" if $_[0] eq 5;
+    return "pimSparseDense(6)" if $_[0] eq 6;
+    return "cbt(7)" if $_[0] eq 7;
+    return "pimSparseMode(8)" if $_[0] eq 8;
+    return "pimDenseMode(9)" if $_[0] eq 9;
+    return "igmpOnly(10)" if $_[0] eq 10;
+    return "???($_[0])";
+}
diff --git a/test/mrouted-genconf b/test/mrouted-genconf
new file mode 100755
index 0000000..1981d20
--- /dev/null
+++ b/test/mrouted-genconf
@@ -0,0 +1,114 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+use SNMP_Session "0.58";
+use BER;
+use Socket;
+
+my $mrouters =
+[
+ 'public at it-ws.ten-34.net:9161',
+ 'public at de-ws.ten-34.net',
+ 'public at ch-ws.ten-34.net',
+ 'public at at-ws.ten-34.net',
+ 'public at uk-ws.ten-34.net',
+ ];
+
+## Define this if you want WorkDir set in the generated configuration
+## file.
+##
+#my $work_dir = '/var/tmp/mrtg';
+
+## Define this if you want IconDir set in the generated configuration
+## file.
+##
+#my $icon_dir = '/lan/stat';
+
+## Define this if you want Directory[] set in each target.
+##
+#my $directory = 'ten-34/ipmcast';
+
+## An absolute maximum for traffic rates over tunnels, in Bytes per
+## second.  You probably don't need to change this.
+##
+my $abs_max = '100000000';
+
+my $dvmrpInterfaceType = [1,3,6,1,3,62,1,1,3,1,2];
+my $dvmrpInterfaceRemoteAddress = [1,3,6,1,3,62,1,1,3,1,5];
+my $dvmrpInterfaceMetric = [1,3,6,1,3,62,1,1,3,1,7];
+my $dvmrpInterfaceRateLimit = [1,3,6,1,3,62,1,1,3,1,8];
+my $dvmrpInterfaceInOctets = [1,3,6,1,3,62,1,1,3,1,11];
+my $dvmrpInterfaceOutOctets = [1,3,6,1,3,62,1,1,3,1,12];
+
+## Print head of configuration file
+print "WorkDir: $work_dir\n" if defined $work_dir;
+print "IconDir: $icon_dir\n" if defined $icon_dir;
+print "WriteExpires: Yes\nWeekformat[^]: V\nWithPeak[_]: wmy\n";
+
+foreach my $target (@{$mrouters}) {
+    my $session;
+    my $abs_max_bytes = $abs_max >> 3;
+    my ($community, $mrouter, $port);
+
+    if ($target =~ /^(.*)@(.*):([0-9]+)$/) {
+	$community = $1; $mrouter = $2; $port = $3;
+    } elsif ($target =~ /^(.*)@(.*)$/) {
+	$community = $1; $mrouter = $2; $port = 161;
+    } else {
+	warn "Malformed target $target\n";
+	next;
+    }
+    $session = SNMP_Session->open ($mrouter, $community, $port)
+	|| (warn ("Opening SNMP session to $mrouter\n"), next);
+
+    my $snmp_target = $community.'@'.$mrouter;
+    $snmp_target .= ":".$port
+	unless $port == 161;
+    print "\n\nTarget[$mrouter-mroutes]:
+    1.3.6.1.3.62.1.1.9.0&1.3.6.1.3.62.1.1.10.0:$snmp_target\n";
+    print "Directory[$mrouter-mroutes]: $directory\n"
+	if defined $directory;
+    print <<EOM
+MaxBytes[$mrouter-mroutes]: 8000
+AbsMax[$mrouter-mroutes]: 100000
+Options[$mrouter-mroutes]: growright,gauge,nopercent
+ShortLegend[$mrouter-mroutes]: routes
+YLegend[$mrouter-mroutes]: # of routes
+Title[$mrouter-mroutes]: Multicast routes on $mrouter
+PageTop[$mrouter-mroutes]: <hr><H3>Multicast routes on $mrouter</H3>
+Legend1[$mrouter-mroutes]: # reachable DVMRP routes
+Legend2[$mrouter-mroutes]: # DVMRP routing table entries
+Legend3[$mrouter-mroutes]: max # reachable routes
+Legend4[$mrouter-mroutes]: max # routing table entries
+LegendI[$mrouter-mroutes]:  reachable
+LegendO[$mrouter-mroutes]:  total
+EOM
+    eval {
+	$session->map_table
+	([$dvmrpInterfaceType, $dvmrpInterfaceRemoteAddress,
+	  $dvmrpInterfaceMetric, $dvmrpInterfaceRateLimit], sub
+	 { 
+	     my ($index, $type, $peer_addr, $metric, $rate_limit) = @_;
+	     grep (defined $_ && ($_=pretty_print $_),
+		   ($type, $peer_addr, $metric, $rate_limit));
+	     my $rate_limit_bytes = $rate_limit * 1000 >> 3;
+	     ## ignore interfaces other than tunnels for now
+	     return unless $type == 1;
+	     my $peer_name = gethostbyaddr(pack ("C4",split ('\.',$peer_addr)),
+					   AF_INET)
+		 || $peer_addr;
+	     my $graph_name = $mrouter.'-'.$peer_name;
+		 print <<EOM;
+
+Target[$graph_name]: 1.3.6.1.3.62.1.1.3.1.11.$index&1.3.6.1.3.62.1.1.3.1.12.$index:$snmp_target
+PageTop[$graph_name]: <hr><H3>Tunnel $mrouter -> $peer_name (metric $metric)</H3>
+Options[$graph_name]: growright,bits
+MaxBytes[$graph_name]: $rate_limit_bytes
+AbsMax[$graph_name]: $abs_max_bytes
+Title[$graph_name]: Mbone Tunnel from $mrouter to $peer_name
+EOM
+    print "Directory[$graph_name]: $directory\n"
+	if $directory;
+	 }) };
+    $session->close ();
+}
diff --git a/test/mrtg-ipmcast b/test/mrtg-ipmcast
new file mode 100644
index 0000000..1e79d33
--- /dev/null
+++ b/test/mrtg-ipmcast
@@ -0,0 +1,319 @@
+#!/usr/local/bin/perl -w
+##############################################################################
+### File Name:	  mrtg-ipmcast
+### Description:  Generate MRTG configuration for multicast statistics
+### Author:	  Simon Leinen  <simon at switch.ch>
+### Date Created: 20-Jun-2000
+### RCS $Header: /home/leinen/CVS/SNMP_Session/test/mrtg-ipmcast,v 1.6 2001-03-07 15:38:11 leinen Exp $
+##############################################################################
+### This script can be used to generate a piece of MRTG[1]
+### configuration file for plotting per-interface multicast traffic
+### statistics using IPMROUTE-MIB[2].
+###
+### Usage: mrtg-ipmcast [-d DIR] [-w WORKDIR] [-i ICONDIR]
+###          [-c COMMUNITY] [-v ( 1 | 2c )] [-p port] ROUTER1 ROUTER2 ...
+###
+### This will contact all ROUTERs under the specified COMMUNITY and
+### look at some columns of the ipMRouteInterfaceTable.  For each rows
+### for which those columns have defined values, an MRTG target
+### definition will be written.  Such a target definition might look
+### like this:
+###
+###     Target[swice1-multicast-atm4-0-0.6]: 1.3.6.1.3.60.1.1.4.1.5.66&1.3.6.1.3.60.1.1.4.1.6.66:secret at swiCE1.switch.ch
+###     MaxBytes[swice1-multicast-atm4-0-0.6]: 19375000
+###     AbsMax[swice1-multicast-atm4-0-0.6]: 19375000
+###     Options[swice1-multicast-atm4-0-0.6]: growright,bits
+###     Title[swice1-multicast-atm4-0-0.6]: Multicast Traffic on swiCE1.switch.ch:ATM4/0/0.6-aal5 layer (ATM PVC to swiZHX)
+###     PageTop[swice1-multicast-atm4-0-0.6]: <hr><H3>Multicast Traffic on swiCE1.switch.ch:ATM4/0/0.6-aal5 layer (ATM PVC to swiZHX)</H3>
+###     Directory[swice1-multicast-atm4-0-0.6]: multicast
+###
+### The OIDs are ipMRouteInterfaceInMcastOctets and
+### ipMRouteInterfaceOutMcastOctets indexed for the interface.  In the
+### example, the interface has index 66, and the ifName is
+### "ATM4/0/0.6".
+###
+### "AbsMax" is taken from the ifSpeed value for the interface.
+###
+### "MaxBytes" is set to the multicast rate limit as per
+### ipMRouteInterfaceRateLimit.  If no rate-limit is specified, the
+### same value as for AbsMax will be used.
+###
+### "Directory" is only defined if the "-d DIR" option has been passed
+### to the script.  In the example, the script has been called with
+### "-d multicast".
+###
+### The "-w" and "-i" options can be used to cause WorkDir and IconDir
+### definitions to be generated, respectively.
+##############################################################################
+
+use strict;
+use SNMP_Session "0.58";
+use BER;
+use Socket;
+
+## Forward declarations
+sub usage ($ );
+
+### If set, a Directory[] attribute pointing to this directory will be
+### included for every target in the generated configuration.
+my $directory;
+
+## Define this if you want WorkDir set in the generated configuration
+## file.
+##
+my $work_dir;
+
+## Define this if you want IconDir set in the generated configuration
+## file.
+##
+my $icon_dir;
+
+## An absolute maximum for traffic rates over tunnels, in Bytes per
+## second.  You probably don't need to change this.
+##
+my $abs_max = '100000000';
+
+my $mrouters = [];
+
+my $version = '1';
+
+my $community = 'public';
+
+my $port = 161;
+
+while (@ARGV) {
+    if ($ARGV[0] =~ /^-h/) {
+	usage (0);
+	exit (0);
+    } elsif ($ARGV[0] =~ /^-p/) {
+	if ($ARGV[0] eq '-p') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+$/) {
+	    $port = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-v/) {
+	if ($ARGV[0] eq '-v') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	    $version = '2c' if ($ARGV[0] eq '2');
+	}
+	if ($ARGV[0] eq '1') {
+	    $version = '1';
+	} elsif ($ARGV[0] eq '2c') {
+	    $version = '2c';
+	} elsif ($ARGV[0] eq '2') {
+	    $version = '2c';
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] eq '-c') {
+	shift @ARGV;
+	usage (1) unless @ARGV;
+	$community = $ARGV[0];
+    } elsif ($ARGV[0] eq '-d') {
+	shift @ARGV;
+	usage (1) unless @ARGV;
+	$directory = $ARGV[0];
+    } elsif ($ARGV[0] eq '-w') {
+	shift @ARGV;
+	usage (1) unless @ARGV;
+	$work_dir = $ARGV[0];
+    } elsif ($ARGV[0] eq '-i') {
+	shift @ARGV;
+	usage (1) unless @ARGV;
+	$icon_dir = $ARGV[0];
+    } else {
+	push @{$mrouters}, "$community\@$ARGV[0]:$port";
+    }
+    shift @ARGV;
+}
+
+my %pretty_protocol_name =
+(
+  1 => "other",
+  2 => "local",
+  3 => "netmgmt",
+  4 => "dvmrp",
+  5 => "mospf",
+  6 => "pimSparseDense",
+  7 => "cbt",
+  8 => "pimSparseMode",
+  9 => "pimDenseMode",
+  10 => "igmpOnly",
+  11 => "bgmp",
+  12 => "msdp",
+);
+
+my $ipMRouteInterfaceTtl = [1,3,6,1,3,60,1,1,4,1,2];
+my $ipMRouteInterfaceProtocol = [1,3,6,1,3,60,1,1,4,1,3];
+my $ipMRouteInterfaceRateLimit = [1,3,6,1,3,60,1,1,4,1,4];
+my $ipMRouteInterfaceInMcastOctets = [1,3,6,1,3,60,1,1,4,1,5];
+my $ipMRouteInterfaceOutMcastOctets = [1,3,6,1,3,60,1,1,4,1,6];
+
+## Print head of configuration file
+print "WorkDir: $work_dir\n" if defined $work_dir;
+print "IconDir: $icon_dir\n" if defined $icon_dir;
+print "WriteExpires: Yes\nWeekformat[^]: V\nWithPeak[_]: wmy\n";
+
+foreach my $target (@{$mrouters}) {
+    my $session;
+    my ($community, $mrouter, $port);
+
+    if ($target =~ /^(.*)@(.*):([0-9]+)$/) {
+	$community = $1; $mrouter = $2; $port = $3;
+    } elsif ($target =~ /^(.*)@(.*)$/) {
+	$community = $1; $mrouter = $2; $port = 161;
+    } else {
+	warn "Malformed target $target\n";
+	next;
+    }
+    $session =
+	($version eq '1' ? SNMPv1_Session->open ($mrouter, $community, $port)
+	 : $version eq '2c' ? SNMPv2c_Session->open ($mrouter, $community, $port)
+	 : die "Unknown SNMP version $version")
+	    || die "Opening SNMP_Session to router $mrouter";
+
+    my $if_table = $session->get_if_table;
+
+    my $snmp_target = $community.'@'.$mrouter;
+    $snmp_target .= ":".$port
+	unless $port == 161;
+## eval
+    {
+	$session->map_table
+	([$ipMRouteInterfaceTtl,
+	  $ipMRouteInterfaceProtocol,
+	  $ipMRouteInterfaceRateLimit], sub
+	 { 
+	     my ($index, $ttl, $protocol, $rate_limit) = @_;
+	     grep (defined $_ && ($_=pretty_print $_),
+		   ($protocol, $ttl, $rate_limit));
+	     my ($if_entry, $abs_max_bytes, $rate_limit_bytes,
+		 $interface, $mr, $graph_name);
+	     die unless defined ($if_entry = $if_table->{$index});
+	     if (defined $if_entry->{ifSpeed}) {
+		 if ($rate_limit == 0 || $if_entry->{ifSpeed} < $rate_limit) {
+		     $rate_limit = $if_entry->{ifSpeed} / 1000;
+		 }
+		 $abs_max_bytes = $if_entry->{ifSpeed} >> 3
+		     if defined $if_entry->{ifSpeed};
+	     } else {
+	     }
+	     $abs_max_bytes = $abs_max >> 3
+		 unless defined $abs_max_bytes;
+	     $rate_limit_bytes = $rate_limit * 1000 >> 3;
+
+	     $protocol = $pretty_protocol_name{$protocol}
+	     if exists $pretty_protocol_name{$protocol};
+##	       my $peer_name = gethostbyaddr(pack ("C4",split ('\.',$peer_addr)),
+##					     AF_INET)
+##		   || $peer_addr;
+	     my $peer_name = "?";
+	     $interface = $index;
+	     if (defined ($if_entry->{ifDescr})) {
+		 $interface = $if_entry->{ifDescr};
+	     }
+	     print STDERR "IF $interface TTL $ttl $protocol\n";
+	     $mr = $mrouter;
+	     $mr =~ s/\..*//;
+	     $graph_name = lc ($mr.'-multicast-'.cleanup ($interface));
+	      if (defined ($if_entry->{ifAlias}) && $if_entry->{ifAlias} ne '') {
+		  $interface .= " (".$if_entry->{ifAlias}.")";
+	      } elsif (defined ($if_entry->{locIfDescr}) && $if_entry->{locIfDescr} ne '') {
+		  $interface .= " (".$if_entry->{locIfDescr}.")";
+	      }
+		 print <<EOM;
+
+Target[$graph_name]: 1.3.6.1.3.60.1.1.4.1.5.$index&1.3.6.1.3.60.1.1.4.1.6.$index:$snmp_target
+MaxBytes[$graph_name]: $rate_limit_bytes
+AbsMax[$graph_name]: $abs_max_bytes
+Options[$graph_name]: growright,bits
+Title[$graph_name]: Multicast Traffic on $mrouter:$interface
+PageTop[$graph_name]: <hr><H3>Multicast Traffic on $mrouter:$interface</H3>
+EOM
+    print "Directory[$graph_name]: $directory\n"
+	if $directory;
+	 })
+};
+    $session->close ();
+}
+
+sub cleanup ($ ) {
+    local ($_) = @_;
+    s@/@- at g;
+    s at -(aal5|cef) layer$@@;
+    $_;
+}
+
+sub usage ($) {
+    warn <<EOM;
+Usage: $0 [-w workdir] [-i icondir] [-v (1|2c)] [-p port] [-c community] hostname...
+       $0 -h
+
+  -h           print this usage message and exit.
+
+  -w workdir   specifies the WorkDir parameter for the generated MRTG
+               configuration file.
+
+  -i icondir   specifies the IconDir parameter for the generated MRTG
+               configuration file.
+
+  -v version   can be used to select the SNMP version.  The default
+   	       is SNMPv1, which is what most devices support.  If your box
+   	       supports SNMPv2c, you should enable this by passing "-v 2c"
+   	       to the script.  SNMPv2c is much more efficient for walking
+   	       tables, which is what this tool does.
+
+  -p port      can be used to specify a non-standard UDP port of the SNMP
+               agent (the default is UDP port 161).
+
+  -c community SNMP community string to use.  Defaults to "public".
+
+  hostname...  hostnames or IP addresses of multicast routers
+EOM
+    exit (1) if $_[0];
+}
+
+package SNMP_Session;
+
+sub get_if_table ($) {
+    my ($session) = @_;
+
+    my $result = {};
+
+    my $ifDescr = [1,3,6,1,2,1,2,2,1,2];
+    my $ifSpeed = [1,3,6,1,2,1,2,2,1,5];
+    my $locIfDescr = [1,3,6,1,4,1,9,2,2,1,1,28];
+    my $ifAlias = [1,3,6,1,2,1,31,1,1,1,18];
+    $session->map_table ([$ifDescr,$ifSpeed],
+			 sub ($$$) {
+			     my ($index, $ifDescr, $ifSpeed) = @_;
+			     grep (defined $_ && ($_=pretty_print $_),
+				   ($ifDescr, $ifSpeed));
+			     $result->{$index} = {'ifDescr' => $ifDescr,
+						  'ifSpeed' => $ifSpeed};
+			 });
+    $session->map_table ([$locIfDescr],
+			 sub ($$$) {
+			     my ($index, $locIfDescr) = @_;
+			     grep (defined $_ && ($_=pretty_print $_),
+				   ($locIfDescr));
+			     $result->{$index}->{'locIfDescr'} = $locIfDescr;
+			 });
+    $session->map_table ([$ifAlias],
+			 sub ($$$) {
+			     my ($index, $ifAlias) = @_;
+			     grep (defined $_ && ($_=pretty_print $_),
+				   ($ifAlias));
+			     $result->{$index}->{'ifAlias'} = $ifAlias;
+			 });
+    $result;
+}
diff --git a/test/msdpls b/test/msdpls
new file mode 100755
index 0000000..ac5f338
--- /dev/null
+++ b/test/msdpls
@@ -0,0 +1,303 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use SNMP_Session;
+use BER;
+use Socket;
+
+my $version = '1';
+my $port = 161;
+my $debug = 0;
+my $group;
+my $numericp = 0;
+
+### Prototypes
+sub msdp_list_duplicate_sas ($ );
+sub msdp_collect_sas ($ );
+sub msdp_fill_in_duplicates ($$);
+sub msdp_report_duplicate_sas ($$$$);
+sub msdp_duplicate_report_header ($$$$);
+sub msdp_duplicate_report_trailer ($ );
+sub msdp_map_group ($$$$);
+sub msdp_map_sg ($$$$$ );
+sub msdp_list_group ($$);
+sub pretty_ip_html ($ );
+sub pretty_ip ($ );
+sub usage ($ );
+
+while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
+    if ($ARGV[0] =~ /^-v/) {
+	if ($ARGV[0] eq '-v') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] eq '1') {
+	    $version = '1';
+	} elsif ($ARGV[0] eq '2c') {
+	    $version = '2c';
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-p/) {
+	if ($ARGV[0] eq '-p') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+$/) {
+	    $port = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] eq '-d') {
+	++$debug;
+    } elsif ($ARGV[0] eq '-n') {
+	++$numericp;
+    } elsif ($ARGV[0] eq '-h') {
+	usage (0);
+	exit 0;
+    } elsif ($ARGV[0] eq '-g') {
+	shift @ARGV;
+	$group = $ARGV[0] or usage (1);
+    } else {
+	usage (1);
+    }
+    shift @ARGV;
+}
+my $host = shift @ARGV || usage (1);
+my $community = shift @ARGV || "public";
+usage (1) if $#ARGV >= $[;
+my $session =
+    ($version eq '1' ? SNMPv1_Session->open ($host, $community, $port)
+     : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, $port)
+     : die "Unknown SNMP version $version")
+  || die "Opening SNMP_Session";
+$session->debug (1) if $debug;
+$session->{max_repetitions} = 100;
+
+my $msdpSACachePeerLearnedFrom = [1,3,6,1,3,92,1,1,6,1,4];
+my $msdpSACacheRPFPeer = [1,3,6,1,3,92,1,1,6,1,5];
+my $msdpSACacheInSAs = [1,3,6,1,3,92,1,1,6,1,6];
+my $msdpSACacheInDataPackets = [1,3,6,1,3,92,1,1,6,1,7];
+my $msdpSACacheUpTime = [1,3,6,1,3,92,1,1,6,1,8];
+my $msdpSACacheExpiryTime = [1,3,6,1,3,92,1,1,6,1,9];
+my $msdpSACacheStatus = [1,3,6,1,3,92,1,1,6,1,10];
+
+if (defined $group) {
+    msdp_list_group ($session, inet_aton ($group));
+} else {
+    msdp_list_duplicate_sas ($session);
+}
+1;
+
+sub msdp_list_duplicate_sas ($ ) {
+    my ($session) = @_;
+    my ($announcements, $nsas, $nsgs, $ndups);
+    ($announcements, $nsas) = msdp_collect_sas ($session);
+    $nsgs = keys %{$announcements};
+    ($announcements, $ndups) = msdp_fill_in_duplicates ($session, $announcements);
+    msdp_report_duplicate_sas ($announcements, $nsas, $nsgs, $ndups);
+}
+
+sub msdp_collect_sas ($ ) {
+    my ($session) = @_;
+    my @oids = ($msdpSACacheStatus);
+    my $nsa = 0;
+    my %announcements;
+    $session->map_table
+	(\@oids,
+	 sub () {
+	     my ($index, $sa_status) = @_;
+	     die "index: $index"
+		 unless $index =~ /^(\d+\.\d+\.\d+\.\d+)\.(\d+\.\d+\.\d+\.\d+)\.(\d+\.\d+\.\d+\.\d+)$/;
+	     my ($group, $source, $rp) = ($1, $2, $3);
+	     warn ("S/G/RP entry (status): $group $source $rp ("
+		   .pretty_print ($sa_status).")\n")
+		 if $debug;
+	     return 0 unless pretty_print ($sa_status) == 1; # active(1)
+	     ++$nsa;
+	     push @{$announcements{$source,$group}}, {rp => $rp};
+	 });
+    (\%announcements, $nsa);
+}
+
+sub msdp_fill_in_duplicates ($$) {
+    my ($session, $announcements) = @_;
+    my %result = ();
+    my ($oldreps, $key, $anns, $dupannouncements, $nrps);
+    $oldreps = $session->{max_repetitions};
+    $session->{max_repetitions} = 5;
+    $dupannouncements = 0;
+    foreach $key (keys %{$announcements}) {
+	my ($source, $group) = split ($;,$key);
+	$anns = $announcements->{$key};
+	if ($#{$anns} > 0) {
+	    $nrps = 0;
+	    my @newanns = ();
+	    msdp_map_sg ($session, $group, $source,
+			 [$msdpSACachePeerLearnedFrom,
+			  $msdpSACacheRPFPeer,
+			  $msdpSACacheInSAs,
+			  $msdpSACacheInDataPackets,
+			  $msdpSACacheUpTime,
+			  $msdpSACacheExpiryTime,
+			  $msdpSACacheStatus],
+			 sub () {
+			     my ($rp,
+				 $peer_learned_from,$rpf_peer,
+				 $in_sas,$in_data_packets,
+				 $up_time,$expiry_time,$status) = @_;
+			     return 1 unless $status == 1; # active(1)
+			     push @newanns, {rp => $rp,
+					     ## peer_learned_from => $peer_learned_from,
+					     ## rpf_peer => $rpf_peer,
+					     in_sas => $in_sas,
+					     in_data_packets => $in_data_packets,
+					     up_time => $up_time,
+					     expiry_time => $expiry_time,
+					 };
+			     ++$nrps;
+			 });
+	    if ($nrps > 1) {
+		++$dupannouncements;
+		$result{$key} = \@newanns;
+	    }
+	}
+    }
+    $session->{max_repetitions} = $oldreps;
+    (\%result, $dupannouncements);
+}
+
+sub msdp_report_duplicate_sas ($$$$) {
+    my ($announcements, $nsas, $nsgs, $ndups) = @_;
+    msdp_duplicate_report_header ($announcements, $nsas, $nsgs, $ndups);
+    foreach my $key (sort keys %{$announcements}) {
+	my ($source, $group) = split ($;,$key);
+	my $announcements = $announcements->{$key};
+	if ($#{$announcements} > 0) {
+	    printf STDOUT ("<tr><th colspan=\"3\">(%s,%s)</th></tr>\n",
+			   pretty_ip_html ($source),
+			   pretty_ip_html ($group));
+	    foreach my $announcement (@{$announcements}) {
+		printf STDOUT ("<tr><td>%s</td><td align=\"right\">%d</td><td align=\"right\">%d</td></tr>\n",
+			       pretty_ip_html ($announcement->{rp}),
+			       $announcement->{in_sas},
+			       $announcement->{in_data_packets});
+	    }
+	}
+    }
+    msdp_duplicate_report_trailer ($announcements);
+}
+
+sub msdp_duplicate_report_header ($$$$) {
+    my ($announcements, $nsas, $nsgs, $ndups) = @_;
+    print STDOUT ("<html><head><title>MSDP Duplicate SA Report</title></head>\n");
+    print STDOUT <<EOM;
+<style type="text/css">    <!--
+ body{background-color:#ffffff; color:black; font-family:helvetica }
+ tt{font-family:courier,lucidatypewriter }
+ th{font-family:helvetica,arial }
+ td{font-family:helvetica,arial }
+ pre{font-family:courier,lucidatypewriter,monaco,monospaced }
+    -->
+</style>
+EOM
+    print STDOUT ("<body><h1>MSDP Duplicate SA Report</h1>\n");
+    printf STDOUT ("<p> %d (S,G) pairs found in %d SAs in <tt>%s</tt>'s cache. \n",
+		   $nsgs, $nsas, $host);
+    printf STDOUT ("Total number of (S,G) pairs with multiple RPs: %d </p>\n",
+		   $ndups);
+    printf STDOUT ("<table border=\"0\">\n<tr><th>RP</th><th>#SAs</th><th>#data pkts</th></tr>\n");
+}
+
+sub msdp_duplicate_report_trailer ($ ) {
+    my ($announcements) = @_;
+    print STDOUT "</table>\n</body></html>\n";
+}
+
+sub msdp_map_group ($$$$) {
+    my ($session, $group, $cols, $mapfn) = @_;
+    my @group_subids = split (/\./, inet_ntoa ($group), 4);
+    my @oids = map { $_ = [@{$_}, at group_subids] } @{$cols};
+    $session->map_table
+	(\@oids,
+	 sub () {
+	     my ($index, @colvals) = @_;
+	     map { $_ = pretty_print $_ if defined $_ } (@colvals);
+	     my ($source,$rp);
+	     (($source,$rp) = ($index =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\.([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/))
+		 || die "?";
+	     &$mapfn ($source,$rp, at colvals);
+});
+}
+
+sub msdp_map_sg ($$$$$ ) {
+    my ($session, $group, $source, $cols, $mapfn) = @_;
+    my @group_subids = split (/\./, $group, 4);
+    my @source_subids = split (/\./, $source, 4);
+    my @oids = map { $_ = [@{$_}, at group_subids, at source_subids] } @{$cols};
+    $session->map_table
+	(\@oids,
+	 sub () {
+	     my ($index, @colvals) = @_;
+	     map { $_ = pretty_print $_ if defined $_ } (@colvals);
+	     my ($rp);
+	     (($rp) = ($index =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/))
+		 || die "?";
+	     &$mapfn ($rp, at colvals);
+});
+}
+
+sub msdp_list_group ($$) {
+    my ($session, $group) = @_;
+    msdp_map_group ($session,$group,
+		    [$msdpSACachePeerLearnedFrom,
+		     $msdpSACacheRPFPeer,
+		     $msdpSACacheInSAs,
+		     $msdpSACacheInDataPackets,
+		     $msdpSACacheUpTime,
+		     $msdpSACacheExpiryTime,
+		     $msdpSACacheStatus],
+		    sub () {
+			my ($source,$rp,
+			    $peer_learned_from,$rpf_peer,
+			    $in_sas,$in_data_packets,
+			    $up_time,$expiry_time,$status) = @_;
+			#return unless $in_data_packets;
+			print "  ",pretty_ip ($source)," $in_data_packets pkts\n";
+			print " $peer_learned_from (learned-from) != $rpf_peer (RPF peer)\n"
+			    if $peer_learned_from ne $rpf_peer;
+		    });
+}
+
+sub pretty_ip_html ($ ) {
+    return "<tt>".pretty_ip ($_[0])."</tt>";
+}
+
+sub pretty_ip ($ ) {
+    my ($ip) = @_;
+    my ($name);
+
+    !$numericp && ($name = gethostbyaddr (inet_aton ($ip),AF_INET))
+	? "$name [$ip]"
+	: "$ip";
+}
+
+sub usage ($ ) {
+    print STDERR
+	("Usage: $0 [OPTIONS...] ROUTER [COMMUNITY]\n\n",
+	 "  OPTIONS:\n\n",
+	 "    -v 1|2c    select SNMPv1 or SNMPv2c (community-based SNMPv2)\n",
+	 "    -p PORT    specify an alternate UDP port to contact SNMP agent\n",
+	 "    -g GROUP   list sources for a specific group\n",
+	 "    -d         print debugging output\n",
+	 "    -n         don't resolve hostnames\n",
+	 "\n",
+	 "  ROUTER     which agent to contact - must implement the MSDP MIB\n",
+	 "  COMMUNITY  specifies the SNMP community string, defaults to \"public\"\n");
+    exit $_[0];
+}
diff --git a/test/mtf.pl b/test/mtf.pl
new file mode 100755
index 0000000..2b4574f
--- /dev/null
+++ b/test/mtf.pl
@@ -0,0 +1,51 @@
+#!/usr/local/bin/perl -w
+#
+# @@@ Modified to use an illegal OID
+# @@@ (to check whether a correct error message is generated)
+#
+# Demonstration code for table walking
+#
+# This script should serve as an example of how to "correctly"
+# traverse the rows of a table.  This functionality is implemented in
+# the map_table() subroutine.  The example script displays a few
+# columns of the RFC 1213 interface table and Cisco's locIfTable.  The
+# tables share the same index, so they can be handled by a single
+# invocation of map_table().
+
+require 5.003;
+
+use strict;
+
+use BER;
+use SNMP_Session;
+
+my $host = shift @ARGV || die;
+my $community = shift @ARGV || die;
+
+my $ifDescr = [1,3,6,1,2,1,2,2,1,2];
+my $ifInOctets = [1,3,6,1,2,1,2,2,1,10];
+my $ifOutOctets = [1,3,6,1,2,1,2,2,1,16];
+my $locIfInBitsSec = [1,3,6,1,4,1,9,2,2,1,1,6];
+# @@@
+my $locIfOutBitsSec = [9,2,2,1,1,8];
+# @@@
+my $locIfDescr = [1,3,6,1,4,1,9,2,2,1,1,28];
+
+sub out_interface {
+  my ($index, $descr, $in, $out, $comment) = @_;
+
+  grep (defined $_ && ($_=pretty_print $_),
+	($descr, $in, $out, $comment));
+  printf "%2d  %-24s %10s %10s %s\n",
+  $index,
+  defined $descr ? $descr : '',
+  defined $in ? $in/1000.0 : '-',
+  defined $out ? $out/1000.0 : '-',
+  defined $comment ? $comment : '';
+}
+
+my $session = SNMP_Session->open ($host, $community, 161)
+  || die "Opening SNMP_Session";
+$session->map_table ([$ifDescr,$locIfInBitsSec,$locIfOutBitsSec,$locIfDescr],
+		     \&out_interface);
+1;
diff --git a/test/negative-counter.pl b/test/negative-counter.pl
new file mode 100644
index 0000000..79eb8b4
--- /dev/null
+++ b/test/negative-counter.pl
@@ -0,0 +1,8 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use BER;
+
+print pretty_print ("\x41\x01\xff"),"\n";
+1;
diff --git a/test/party-test.pl b/test/party-test.pl
new file mode 100755
index 0000000..0b782fe
--- /dev/null
+++ b/test/party-test.pl
@@ -0,0 +1,15 @@
+#!/usr/local/bin/perl
+######################################################################
+# Check that we can read the CMU SNMPv2 party definition file in
+# /etc/party.conf.  Describe the party named "zeusmd5".  This is
+# basically intended as a regression test for the party-parsing code.
+######################################################################
+
+require 5;
+
+require 'Party.pm';
+
+Party::read_cmu_party_database('/etc/party.conf');
+Party->find ('zeusmsmd5')->describe (STDERR);
+
+1;
diff --git a/test/pnni-find-ilmi-neighbors.pl b/test/pnni-find-ilmi-neighbors.pl
new file mode 100755
index 0000000..9141908
--- /dev/null
+++ b/test/pnni-find-ilmi-neighbors.pl
@@ -0,0 +1,41 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use BER;
+use SNMP_Session "0.57";
+
+sub usage();
+
+my $host = shift @ARGV || usage();
+my $community = shift @ARGV || 'public';
+
+my $pnniRouteAddrProto = [1,3,6,1,4,1,353,5,4,1,1,19,4,1,8];
+
+my $ilmionly = 1;
+my $hostonly = 1;
+
+my $session = SNMP_Session->open ($host, $community, 161)
+    || die "couldn't open SNMP session";
+$session->map_table ([$pnniRouteAddrProto],
+  sub { 
+      my ($index, $proto) = @_;
+      grep (defined $_ && ($_=pretty_print $_),
+	    ($proto));
+      ## we are only interested in routes whose proto is local(2).
+      return if $ilmionly && $proto != 2;
+      my @index = split ('\.',$index);
+      my $nsap = join (".", grep ($_=sprintf ("%02x",$_), at index[1..19])); 
+      my $prefix_length = $index[20];
+
+      return if $hostonly && $prefix_length != 152;
+      print $nsap;
+      print "/",$prefix_length unless $hostonly;
+      print "\n";
+  });
+$session->close;
+1;
+
+sub usage () {
+    die "Usage: $0 host [community]";
+}
diff --git a/test/qosls b/test/qosls
new file mode 100755
index 0000000..c9c96cd
--- /dev/null
+++ b/test/qosls
@@ -0,0 +1,553 @@
+#!/usr/bin/perl -w
+###
+### qosls - list QoS configuration on a Cisco router
+###
+### Author:       Simon Leinen <simon at switch.ch>
+### Date created: 26-Mar-2005
+###
+### This script reads QoS configuration information from the
+### CISCO-CLASS-BASED-QOS-MIB, and constructs an internal
+### representation for it.
+
+use strict;
+use SNMP_util;
+
+## Prototypes
+sub init_mibs ();
+sub collect_qos_information ($ );
+sub print_service_policies ($$);
+sub print_qos_objects ($ );
+sub print_qos_config ($ );
+sub get_if_entries ($ );
+sub get_service_policies ($ );
+sub get_qos_objects ($ );
+sub fixup_parents ($ );
+sub get_qos_object_configs ($ );
+sub get_qos_config ($$$$@);
+sub pretty_traffic_direction ($ );
+sub pretty_interface_type ($ );
+sub pretty_config_type ($ );
+sub pretty_class_info ($ );
+sub pretty_match_info ($ );
+sub pretty_queueing_bandwidth_units ($ );
+sub pretty_queueing_unit_type ($ );
+sub pretty_red_mechanism ($ );
+sub pretty_police_action ($ );
+sub pretty_traffic_shaping_limit ($ );
+sub get_police_action_configs ($$);
+sub decode_truth_value ($ );
+sub snmp_decode_value ($@);
+sub snmp_rows_to_objects ($$$@ );
+sub snmp_map_row_objects ($$$$@ );
+
+my $target = shift @ARGV || die "Usage: $0 target\n";
+
+init_mibs ();
+collect_qos_information ($target);
+1;
+
+sub collect_qos_information ($ ) {
+    my $if_entry = get_if_entries ($target);
+    my $service_policies = get_service_policies ($target);
+    my $qos_objects = get_qos_objects ($target);
+    my $configs = get_qos_object_configs ($target);
+
+    print_service_policies ($service_policies, $if_entry);
+    print_qos_objects ($qos_objects);
+    print_qos_config ($configs);
+}
+
+sub print_service_policies ($$) {
+    my ($service_policies, $if_entry) = @_;
+    print "Service Policies\n\n";
+    foreach my $installed_index (sort keys %{$service_policies}) {
+	my $service_policy = $service_policies->{$installed_index};
+	printf STDOUT
+	  ("%4d %-10s %s\n",
+	   $installed_index,
+	   $if_entry->{$service_policy->{ifIndex}}->{descr},
+	   pretty_traffic_direction ($service_policy->{policyDirection}));
+    }
+    print "\n";
+}
+
+sub print_qos_objects ($ ) {
+    my ($qos_objects) = @_;
+    print "QoS objects\n\n";
+    foreach my $policy_index (sort keys %{$qos_objects}) {
+	foreach my $object_index (sort keys %{$qos_objects->{$policy_index}}) {
+	    my $qos_object = $qos_objects->{$policy_index}->{$object_index};
+	    printf STDOUT
+		("%4d %4d [%4d] %d %-10s\n",
+		 $policy_index,
+		 $object_index,
+		 $qos_object->{parentObjectsIndex},
+		 $qos_object->{configIndex},
+		 pretty_config_type $qos_object->{objectsType});
+	}
+    }
+    print "\n";
+}
+
+sub print_qos_config ($ ) {
+    my ($configs) = @_;
+    print "QoS Configuration\n\n";
+    foreach my $config_index (sort keys %{$configs}) {
+	my $config = $configs->{$config_index};
+	printf STDOUT
+	  ("%4d %-14s %s\n",
+	   $config_index,
+	   ref $config,
+	   $config->tostring ());
+    }
+    print "\n";
+}
+
+### get_if_entries TARGET
+###
+### Read the MIB-II interface table, and construct a hash mapping the
+### interface indices to hashes containing important slots.
+### Currently, only ifDescr and ifAlias are recorded.
+###
+sub get_if_entries ($ ) {
+    my ($target) = @_;
+    return snmp_rows_to_objects
+      ($target, 'MIBII::Interface', 'if', qw(descr alias));
+}
+
+sub get_service_policies ($ ) {
+    return snmp_rows_to_objects
+      ($target, 'CBQM::ServicePolicy',
+       'cbQos', qw(ifType policyDirection ifIndex frDLCI atmVCI));
+}
+
+sub get_qos_objects ($ ) {
+    my ($target) = @_;
+    my $qos_objects = {};
+    snmp_map_row_objects
+      ($target, 'CBQM::QosObject',
+       sub () {
+	   my ($index, $object) = @_;
+	   my ($policy_index, $object_index) = split ('\.', $index);
+	   $qos_objects->{$policy_index}->{$object_index} = $object;
+       },
+       'cbQos',
+       qw(configIndex objectsType parentObjectsIndex));
+    fixup_parents ($qos_objects);
+    return $qos_objects;
+}
+
+sub fixup_parents ($ ) {
+    my ($qos_objects) = @_;
+    foreach my $policy_index (keys %{$qos_objects}) {
+	my $policy = $qos_objects->{$policy_index};
+	foreach my $object_index (keys %$policy) {
+	    my $object = $policy->{$object_index};
+	    my $parent_index = $object->{'parentObjectsIndex'};
+	    if ($parent_index != 0) {
+		die ("missing parent ",$parent_index)
+		    unless $policy->{$parent_index};
+		$object->{'parent'} = $policy->{$parent_index};
+		push @{$policy->{$parent_index}->{'children'}}, $object;
+	    }
+	}
+    }
+}
+
+sub get_qos_object_configs ($ ) {
+    my ($target) = @_;
+    my $configs = {};
+    get_qos_config ($target, 'CBQM::PolicyMapCfg', $configs,
+		    'cbQosPolicyMap', qw(name desc));
+    get_qos_config ($target, 'CBQM::ClassMapCfg', $configs,
+		    'cbQosCM', qw(name desc info));
+    get_qos_config ($target, 'CBQM::MatchStmtCfg', $configs,
+		    'cbQosMatchStmt', qw(name info));
+    get_qos_config ($target, 'CBQM::QueueingCfg', $configs,
+		    'cbQosQueueingCfg',
+		    qw(bandwidth bandwidthUnits flowEnabled priorityEnabled
+		       aggregateQSize individualQSize dynamicQNumber
+		       prioBurstSize qLimitUnits aggregateQLimit));
+    get_qos_config ($target, 'CBQM::REDCfg', $configs,
+		    'cbQosREDCfg',
+		    qw(exponWeight meanQsize dscpPrec eCNEnabled));
+    get_qos_config ($target, 'CBQM::REDClassCfg', $configs,
+		    'cbQosRED',
+		    qw(cfgPktDropProb classCfgThresholdUnit
+		       classCfgMinThreshold classCfgMaxThreshold));
+    get_qos_config ($target, 'CBQM::PoliceCfg', $configs,
+		    'cbQosPoliceCfg',
+		    qw(rate burstSize extBurstSize
+		       conformAction conformSetValue
+		       exceedAction exceedSetValue
+		       violateAction violateSetValue
+		       pir rate64));
+    get_qos_config ($target, 'CBQM::TrafficShaperCfg', $configs,
+		    'cbQosTSCfg',
+		    qw(rate burstSize extBurstSize
+		       adaptiveEnabled adaptiveRate limitType));
+    get_qos_config ($target, 'CBQM::SetCfg', $configs,
+		    'cbQosSetCfg',
+		    qw(feature ipDSCPValue ipPrecedenceValue qosGroupValue
+		       l2CosValue mplsExpValue discardClassValue));
+    get_police_action_configs ($target, $configs);
+    return $configs;
+}
+
+sub get_qos_config ($$$$@) {
+    my ($target, $class, $configs, $prefix, @cols) = @_;
+    snmp_map_row_objects
+      ($target, $class,
+       sub () { my ($index, $object) = @_;
+		$configs->{$index} = $object; },
+       $prefix, @cols);
+    return $configs;
+}
+
+sub get_police_action_configs ($$) {
+    my ($target, $configs) = @_;
+    snmp_map_row_objects
+      ($target, 'CBQM::PoliceActionCfg',
+       sub () {
+	   my ($index, $object) = @_;
+	   my ($config_index, $action_index)
+	     = split ('\.', $index);
+	   $configs->{$config_index}->{'police_action'}->{$action_index}
+	     = $object;
+       },
+       'cbQosPoliceActionCfg',
+       qw(conform conformSetValue exceed exceedSetValue
+	  violate violateSetValue));
+    return $configs;
+}
+
+sub pretty_traffic_direction ($ ) {
+    return snmp_decode_value ($_[0], qw(input output));} 
+sub pretty_interface_type ($ ) {
+    return snmp_decode_value
+      ($_[0], qw(mainInterface subInterface frDLCI atmPVC));}
+sub pretty_config_type ($ ) {
+    return snmp_decode_value
+      ($_[0], qw(policymap classmap matchStatement queueing
+		 randomDetect trafficShaping police set));}
+sub pretty_class_info ($ ) {
+    return snmp_decode_value ($_[0], qw(none matchAll matchAny));} 
+sub pretty_match_info ($ ) {
+    return snmp_decode_value ($_[0], qw(none matchNot));}
+sub pretty_queueing_bandwidth_units ($ ) {
+    return snmp_decode_value ($_[0], qw(kbps percentage percentageRemaining));}
+sub pretty_queueing_unit_type ($ ) {
+    return snmp_decode_value ($_[0], qw(packets cells bytes));}
+sub pretty_red_mechanism ($ ) {
+    return snmp_decode_value ($_[0], qw(precedence dscp));}
+sub pretty_police_action ($ ) {
+    return snmp_decode_value
+      ($_[0], qw(transmit setIpDSCP setIpPrecedence setQosGroup
+		 drop setMplsExp setAtmClp setFrDe setL2Cos setDiscardClass));}
+sub pretty_traffic_shaping_limit ($ ) {
+    return snmp_decode_value ($_[0], qw(average peak));}
+sub pretty_set_feature_type ($ ) {
+    return snmp_decode_value
+      ($_[0], qw(ipDscp ipPrecedence qosGroupNumber
+		 frDeBit atmClpBit l2Cos mplsExp discardClass));}
+
+sub decode_truth_value ($ ) {return snmp_decode_value ($_[0], qw(1 0));}
+
+sub snmp_decode_value ($@) {
+    my ($index, @mapvec) = @_;
+    return $index if $index < 1 or $index > $#mapvec+1;
+    return $mapvec[$index-1];
+}
+
+### snmp_rows_to_objects TARGET, CLASS, PREFIX, COLUMNS...
+###
+### Returns a reference to a hash that maps a table's index to objects
+### created from the set of COLUMNS.  The COLUMNS are partial OID
+### names, to each of which the PREFIX is prepended.  An object is
+### created for each row in the table, by creating a hash reference
+### with a slot for each column, named by the (partial) column name.
+### It is blessed to the CLASS.
+###
+### For example, if we have the following table at $TARGET:
+###
+### index fooBar fooBaz fooBlech
+###
+### 1000  asd    23498  vohdajae
+### 1001  fgh    45824  yaohetoo
+### 1002  jkl    89732  engahghi
+###
+### Then the call:
+###
+###  snmp_rows_to_objects ($TARGET, 'MyFoo', 'foo', 'bar', 'baz', 'blech') 
+###
+### will create a hash reference similar to this:
+###
+###     $result = {};
+###     $result{1000} = bless { 'bar' => 'asd',
+###                             'baz' => 23498,
+###                             'blech' => 'vohdajae' }, 'MyFoo';
+###     $result{1001} = bless { 'bar' => 'fgh',
+###                             'baz' => 45824,
+###                             'blech' => 'yaohetoo' }, 'MyFoo';
+###     $result{1002} = bless { 'bar' => 'jkl',
+###                             'baz' => 89732,
+###                             'blech' => 'engahghi' }, 'MyFoo';
+###
+sub snmp_rows_to_objects ($$$@) {
+    my ($target, $class, $prefix, @cols) = @_;
+    my $result = {};
+    snmp_map_row_objects
+      ($target, $class,
+       sub () {
+	   my ($index, $object) = @_;
+	   $result->{$index} = $object;
+       },
+       $prefix, @cols);
+    return $result;
+}
+
+### snmp_map_row_objects TARGET, CLASS, MAPFN, PREFIX, COLUMNS...
+###
+### This function traverses a table, creating an object for each row,
+### and applying the user-supplied MAPFN to each of these objects.
+###
+### The table is defined by PREFIX and COLUMNS, as described for
+### snmp_rows_to_objects above.  An object is created according to
+### CLASS and COLUMNS, as described above.  The difference is that,
+### rather than putting all objects in a hash, we simply apply the
+### user-supplied MAPFN to each row object.
+###
+sub snmp_map_row_objects ($$$$@) {
+    my ($target, $class, $mapfn, $prefix, @cols) = @_;
+    snmpmaptable ($target,
+		  sub () {
+		      my ($index, @colvals) = @_;
+		      my $object = bless {}, $class;
+		      foreach my $col (@cols) {
+			  $object->{$col} = shift @colvals;
+		      }
+		      &$mapfn ($index, $object);
+		  },
+       map ($prefix.ucfirst $_, at cols));
+}
+
+sub init_mibs () {
+    snmpmapOID
+	(qw(
+cbQosIfType				1.3.6.1.4.1.9.9.166.1.1.1.1.2
+cbQosPolicyDirection			1.3.6.1.4.1.9.9.166.1.1.1.1.3
+cbQosIfIndex				1.3.6.1.4.1.9.9.166.1.1.1.1.4
+cbQosFrDLCI				1.3.6.1.4.1.9.9.166.1.1.1.1.5
+cbQosAtmVPI				1.3.6.1.4.1.9.9.166.1.1.1.1.6
+cbQosAtmVCI				1.3.6.1.4.1.9.9.166.1.1.1.1.7
+cbQosConfigIndex			1.3.6.1.4.1.9.9.166.1.5.1.1.2
+cbQosObjectsType			1.3.6.1.4.1.9.9.166.1.5.1.1.3
+cbQosParentObjectsIndex			1.3.6.1.4.1.9.9.166.1.5.1.1.4
+cbQosPolicyMapName			1.3.6.1.4.1.9.9.166.1.6.1.1.1
+cbQosPolicyMapDesc			1.3.6.1.4.1.9.9.166.1.6.1.1.2
+cbQosCMName				1.3.6.1.4.1.9.9.166.1.7.1.1.1
+cbQosCMDesc				1.3.6.1.4.1.9.9.166.1.7.1.1.2
+cbQosCMInfo				1.3.6.1.4.1.9.9.166.1.7.1.1.3
+cbQosMatchStmtName			1.3.6.1.4.1.9.9.166.1.8.1.1.1
+cbQosMatchStmtInfo			1.3.6.1.4.1.9.9.166.1.8.1.1.2
+));
+    ## configuration
+    snmpmapOID (qw(
+cbQosQueueingCfgBandwidth		1.3.6.1.4.1.9.9.166.1.9.1.1.1
+cbQosQueueingCfgBandwidthUnits		1.3.6.1.4.1.9.9.166.1.9.1.1.2
+cbQosQueueingCfgFlowEnabled		1.3.6.1.4.1.9.9.166.1.9.1.1.3
+cbQosQueueingCfgPriorityEnabled		1.3.6.1.4.1.9.9.166.1.9.1.1.4
+cbQosQueueingCfgAggregateQSize		1.3.6.1.4.1.9.9.166.1.9.1.1.5
+cbQosQueueingCfgIndividualQSize		1.3.6.1.4.1.9.9.166.1.9.1.1.6
+cbQosQueueingCfgDynamicQNumber		1.3.6.1.4.1.9.9.166.1.9.1.1.7
+cbQosQueueingCfgPrioBurstSize		1.3.6.1.4.1.9.9.166.1.9.1.1.8
+cbQosQueueingCfgQLimitUnits		1.3.6.1.4.1.9.9.166.1.9.1.1.9
+cbQosQueueingCfgAggregateQLimit		1.3.6.1.4.1.9.9.166.1.9.1.1.10
+cbQosREDCfgExponWeight			1.3.6.1.4.1.9.9.166.1.10.1.1.1
+cbQosREDCfgMeanQsize			1.3.6.1.4.1.9.9.166.1.10.1.1.2
+cbQosREDCfgDscpPrec			1.3.6.1.4.1.9.9.166.1.10.1.1.3
+cbQosREDCfgECNEnabled			1.3.6.1.4.1.9.9.166.1.10.1.1.4
+cbQosREDValue				1.3.6.1.4.1.9.9.166.1.11.1.1.1
+cbQosREDCfgMinThreshold			1.3.6.1.4.1.9.9.166.1.11.1.1.2
+cbQosREDCfgMaxThreshold			1.3.6.1.4.1.9.9.166.1.11.1.1.3
+cbQosREDCfgPktDropProb			1.3.6.1.4.1.9.9.166.1.11.1.1.4
+cbQosREDClassCfgThresholdUnit		1.3.6.1.4.1.9.9.166.1.11.1.1.5
+cbQosREDClassCfgMinThreshold		1.3.6.1.4.1.9.9.166.1.11.1.1.6
+cbQosREDClassCfgMaxThreshold		1.3.6.1.4.1.9.9.166.1.11.1.1.7
+cbQosPoliceCfgRate			1.3.6.1.4.1.9.9.166.1.12.1.1.1
+cbQosPoliceCfgBurstSize			1.3.6.1.4.1.9.9.166.1.12.1.1.2
+cbQosPoliceCfgExtBurstSize		1.3.6.1.4.1.9.9.166.1.12.1.1.3
+cbQosPoliceCfgConformAction		1.3.6.1.4.1.9.9.166.1.12.1.1.4
+cbQosPoliceCfgConformSetValue		1.3.6.1.4.1.9.9.166.1.12.1.1.5
+cbQosPoliceCfgExceedAction		1.3.6.1.4.1.9.9.166.1.12.1.1.6
+cbQosPoliceCfgExceedSetValue		1.3.6.1.4.1.9.9.166.1.12.1.1.7
+cbQosPoliceCfgViolateAction		1.3.6.1.4.1.9.9.166.1.12.1.1.8
+cbQosPoliceCfgViolateSetValue		1.3.6.1.4.1.9.9.166.1.12.1.1.9
+cbQosPoliceCfgPir			1.3.6.1.4.1.9.9.166.1.12.1.1.10
+cbQosPoliceCfgRate64			1.3.6.1.4.1.9.9.166.1.12.1.1.11
+cbQosTSCfgRate				1.3.6.1.4.1.9.9.166.1.13.1.1.1
+cbQosTSCfgBurstSize			1.3.6.1.4.1.9.9.166.1.13.1.1.2
+cbQosTSCfgExtBurstSize			1.3.6.1.4.1.9.9.166.1.13.1.1.3
+cbQosTSCfgAdaptiveEnabled		1.3.6.1.4.1.9.9.166.1.13.1.1.4
+cbQosTSCfgAdaptiveRate			1.3.6.1.4.1.9.9.166.1.13.1.1.5
+cbQosTSCfgLimitType			1.3.6.1.4.1.9.9.166.1.13.1.1.6
+cbQosSetCfgFeature			1.3.6.1.4.1.9.9.166.1.14.1.1.1
+cbQosSetCfgIpDSCPValue			1.3.6.1.4.1.9.9.166.1.14.1.1.2
+cbQosSetCfgIpPrecedenceValue		1.3.6.1.4.1.9.9.166.1.14.1.1.3
+cbQosSetCfgQosGroupValue		1.3.6.1.4.1.9.9.166.1.14.1.1.4
+cbQosSetCfgL2CosValue			1.3.6.1.4.1.9.9.166.1.14.1.1.5
+cbQosSetCfgMplsExpValue			1.3.6.1.4.1.9.9.166.1.14.1.1.6
+cbQosSetCfgDiscardClassValue		1.3.6.1.4.1.9.9.166.1.14.1.1.7
+cbQosPoliceActionCfgIndex		1.3.6.1.4.1.9.9.166.1.21.1.1.1
+cbQosPoliceActionCfgConform		1.3.6.1.4.1.9.9.166.1.21.1.1.2
+cbQosPoliceActionCfgConformSetValue	1.3.6.1.4.1.9.9.166.1.21.1.1.3
+cbQosPoliceActionCfgExceed		1.3.6.1.4.1.9.9.166.1.21.1.1.4
+cbQosPoliceActionCfgExceedSetValue	1.3.6.1.4.1.9.9.166.1.21.1.1.5
+cbQosPoliceActionCfgViolate		1.3.6.1.4.1.9.9.166.1.21.1.1.6
+cbQosPoliceActionCfgViolateSetValue	1.3.6.1.4.1.9.9.166.1.21.1.1.7
+));
+    ## statistics
+    snmpmapOID (qw(
+cbQosCMPrePolicyPktOverflow		1.3.6.1.4.1.9.9.166.1.15.1.1.1
+cbQosCMPrePolicyPkt			1.3.6.1.4.1.9.9.166.1.15.1.1.2
+cbQosCMPrePolicyPkt64			1.3.6.1.4.1.9.9.166.1.15.1.1.3
+cbQosCMPrePolicyByteOverflow		1.3.6.1.4.1.9.9.166.1.15.1.1.4
+cbQosCMPrePolicyByte			1.3.6.1.4.1.9.9.166.1.15.1.1.5
+cbQosCMPrePolicyByte64			1.3.6.1.4.1.9.9.166.1.15.1.1.6
+cbQosCMPrePolicyBitRate			1.3.6.1.4.1.9.9.166.1.15.1.1.7
+cbQosCMPostPolicyByteOverflow		1.3.6.1.4.1.9.9.166.1.15.1.1.8
+cbQosCMPostPolicyByte			1.3.6.1.4.1.9.9.166.1.15.1.1.9
+cbQosCMPostPolicyByte64			1.3.6.1.4.1.9.9.166.1.15.1.1.10
+cbQosCMPostPolicyBitRate		1.3.6.1.4.1.9.9.166.1.15.1.1.11
+cbQosCMDropPktOverflow			1.3.6.1.4.1.9.9.166.1.15.1.1.12
+cbQosCMDropPkt				1.3.6.1.4.1.9.9.166.1.15.1.1.13
+cbQosCMDropPkt64			1.3.6.1.4.1.9.9.166.1.15.1.1.14
+cbQosCMDropByteOverflow			1.3.6.1.4.1.9.9.166.1.15.1.1.15
+cbQosCMDropByte				1.3.6.1.4.1.9.9.166.1.15.1.1.16
+cbQosCMDropByte64			1.3.6.1.4.1.9.9.166.1.15.1.1.17
+cbQosCMDropBitRate			1.3.6.1.4.1.9.9.166.1.15.1.1.18
+cbQosCMNoBufDropPktOverflow		1.3.6.1.4.1.9.9.166.1.15.1.1.19
+cbQosCMNoBufDropPkt			1.3.6.1.4.1.9.9.166.1.15.1.1.20
+cbQosCMNoBufDropPkt64			1.3.6.1.4.1.9.9.166.1.15.1.1.21
+cbQosMatchPrePolicyPktOverflow		1.3.6.1.4.1.9.9.166.1.16.1.1.1
+cbQosMatchPrePolicyPkt			1.3.6.1.4.1.9.9.166.1.16.1.1.2
+cbQosMatchPrePolicyPkt64		1.3.6.1.4.1.9.9.166.1.16.1.1.3
+cbQosMatchPrePolicyByteOverflow		1.3.6.1.4.1.9.9.166.1.16.1.1.4
+cbQosMatchPrePolicyByte			1.3.6.1.4.1.9.9.166.1.16.1.1.5
+cbQosMatchPrePolicyByte64		1.3.6.1.4.1.9.9.166.1.16.1.1.6
+cbQosMatchPrePolicyBitRate		1.3.6.1.4.1.9.9.166.1.16.1.1.7
+cbQosPoliceConformedPktOverflow		1.3.6.1.4.1.9.9.166.1.17.1.1.1
+cbQosPoliceConformedPkt			1.3.6.1.4.1.9.9.166.1.17.1.1.2
+cbQosPoliceConformedPkt64		1.3.6.1.4.1.9.9.166.1.17.1.1.3
+cbQosPoliceConformedByteOverflow	1.3.6.1.4.1.9.9.166.1.17.1.1.4
+cbQosPoliceConformedByte		1.3.6.1.4.1.9.9.166.1.17.1.1.5
+cbQosPoliceConformedByte64		1.3.6.1.4.1.9.9.166.1.17.1.1.6
+cbQosPoliceConformedBitRate		1.3.6.1.4.1.9.9.166.1.17.1.1.7
+cbQosPoliceExceededPktOverflow		1.3.6.1.4.1.9.9.166.1.17.1.1.8
+cbQosPoliceExceededPkt			1.3.6.1.4.1.9.9.166.1.17.1.1.9
+cbQosPoliceExceededPkt64		1.3.6.1.4.1.9.9.166.1.17.1.1.10
+cbQosPoliceExceededByteOverflow		1.3.6.1.4.1.9.9.166.1.17.1.1.11
+cbQosPoliceExceededByte			1.3.6.1.4.1.9.9.166.1.17.1.1.12
+cbQosPoliceExceededByte64		1.3.6.1.4.1.9.9.166.1.17.1.1.13
+cbQosPoliceExceededBitRate		1.3.6.1.4.1.9.9.166.1.17.1.1.14
+cbQosPoliceViolatedPktOverflow		1.3.6.1.4.1.9.9.166.1.17.1.1.15
+cbQosPoliceViolatedPkt			1.3.6.1.4.1.9.9.166.1.17.1.1.16
+cbQosPoliceViolatedPkt64		1.3.6.1.4.1.9.9.166.1.17.1.1.17
+cbQosPoliceViolatedByteOverflow		1.3.6.1.4.1.9.9.166.1.17.1.1.18
+cbQosPoliceViolatedByte			1.3.6.1.4.1.9.9.166.1.17.1.1.19
+cbQosPoliceViolatedByte64		1.3.6.1.4.1.9.9.166.1.17.1.1.20
+cbQosPoliceViolatedBitRate		1.3.6.1.4.1.9.9.166.1.17.1.1.21
+cbQosQueueingCurrentQDepth		1.3.6.1.4.1.9.9.166.1.18.1.1.1
+cbQosQueueingMaxQDepth			1.3.6.1.4.1.9.9.166.1.18.1.1.2
+cbQosQueueingDiscardByteOverflow	1.3.6.1.4.1.9.9.166.1.18.1.1.3
+cbQosQueueingDiscardByte		1.3.6.1.4.1.9.9.166.1.18.1.1.4
+cbQosQueueingDiscardByte64		1.3.6.1.4.1.9.9.166.1.18.1.1.5
+cbQosQueueingDiscardPktOverflow		1.3.6.1.4.1.9.9.166.1.18.1.1.6
+cbQosQueueingDiscardPkt			1.3.6.1.4.1.9.9.166.1.18.1.1.7
+cbQosQueueingDiscardPkt64		1.3.6.1.4.1.9.9.166.1.18.1.1.8
+cbQosTSStatsDelayedByteOverflow		1.3.6.1.4.1.9.9.166.1.19.1.1.1
+cbQosTSStatsDelayedByte			1.3.6.1.4.1.9.9.166.1.19.1.1.2
+cbQosTSStatsDelayedByte64		1.3.6.1.4.1.9.9.166.1.19.1.1.3
+cbQosTSStatsDelayedPktOverflow		1.3.6.1.4.1.9.9.166.1.19.1.1.4
+cbQosTSStatsDelayedPkt			1.3.6.1.4.1.9.9.166.1.19.1.1.5
+cbQosTSStatsDelayedPkt64		1.3.6.1.4.1.9.9.166.1.19.1.1.6
+cbQosTSStatsDropByteOverflow		1.3.6.1.4.1.9.9.166.1.19.1.1.7
+cbQosTSStatsDropByte			1.3.6.1.4.1.9.9.166.1.19.1.1.8
+cbQosTSStatsDropByte64			1.3.6.1.4.1.9.9.166.1.19.1.1.9
+cbQosTSStatsDropPktOverflow		1.3.6.1.4.1.9.9.166.1.19.1.1.10
+cbQosTSStatsDropPkt			1.3.6.1.4.1.9.9.166.1.19.1.1.11
+cbQosTSStatsDropPkt64			1.3.6.1.4.1.9.9.166.1.19.1.1.12
+cbQosTSStatsActive			1.3.6.1.4.1.9.9.166.1.19.1.1.13
+cbQosTSStatsCurrentQSize		1.3.6.1.4.1.9.9.166.1.19.1.1.14
+cbQosREDRandomDropPktOverflow		1.3.6.1.4.1.9.9.166.1.20.1.1.1
+cbQosREDRandomDropPkt			1.3.6.1.4.1.9.9.166.1.20.1.1.2
+cbQosREDRandomDropPkt64			1.3.6.1.4.1.9.9.166.1.20.1.1.3
+cbQosREDRandomDropByteOverflow		1.3.6.1.4.1.9.9.166.1.20.1.1.4
+cbQosREDRandomDropByte			1.3.6.1.4.1.9.9.166.1.20.1.1.5
+cbQosREDRandomDropByte64		1.3.6.1.4.1.9.9.166.1.20.1.1.6
+cbQosREDTailDropPktOverflow		1.3.6.1.4.1.9.9.166.1.20.1.1.7
+cbQosREDTailDropPkt			1.3.6.1.4.1.9.9.166.1.20.1.1.8
+cbQosREDTailDropPkt64			1.3.6.1.4.1.9.9.166.1.20.1.1.9
+cbQosREDTailDropByteOverflow		1.3.6.1.4.1.9.9.166.1.20.1.1.10
+cbQosREDTailDropByte			1.3.6.1.4.1.9.9.166.1.20.1.1.11
+cbQosREDTailDropByte64			1.3.6.1.4.1.9.9.166.1.20.1.1.12
+cbQosREDTransmitPktOverflow		1.3.6.1.4.1.9.9.166.1.20.1.1.13
+cbQosREDTransmitPkt			1.3.6.1.4.1.9.9.166.1.20.1.1.14
+cbQosREDTransmitPkt64			1.3.6.1.4.1.9.9.166.1.20.1.1.15
+cbQosREDTransmitByteOverflow		1.3.6.1.4.1.9.9.166.1.20.1.1.16
+cbQosREDTransmitByte			1.3.6.1.4.1.9.9.166.1.20.1.1.17
+cbQosREDTransmitByte64			1.3.6.1.4.1.9.9.166.1.20.1.1.18
+cbQosREDECNMarkPktOverflow		1.3.6.1.4.1.9.9.166.1.20.1.1.19
+cbQosREDECNMarkPkt			1.3.6.1.4.1.9.9.166.1.20.1.1.20
+cbQosREDECNMarkPkt64			1.3.6.1.4.1.9.9.166.1.20.1.1.21
+cbQosREDECNMarkByteOverflow		1.3.6.1.4.1.9.9.166.1.20.1.1.22
+cbQosREDECNMarkByte			1.3.6.1.4.1.9.9.166.1.20.1.1.23
+cbQosREDECNMarkByte64			1.3.6.1.4.1.9.9.166.1.20.1.1.24
+cbQosREDMeanQSizeUnits			1.3.6.1.4.1.9.9.166.1.20.1.1.25
+cbQosREDMeanQSize			1.3.6.1.4.1.9.9.166.1.20.1.1.26
+	    ));
+}
+
+package MIBII::Interface;
+package CBQM::ServicePolicy;
+package CBQM::QosObject;
+package CBQM::PolicyMapCfg;
+
+sub tostring ($ ) {
+    my $result = $_[0]->{name};
+    $result .= ' ('.$_[0]->{desc}.')'
+	if $_[0]->{desc};
+    return $result;
+}
+
+package CBQM::ClassMapCfg;
+
+sub tostring ($ ) {
+    my $result = $_[0]->{name};
+    $result .= ' ('.$_[0]->{desc}.')'
+	if $_[0]->{desc};
+    return $result;
+}
+
+package CBQM::MatchStmtCfg;
+
+sub tostring ($ ) {
+    my $result = $_[0]->{name};
+    $result .= ' ('.$_[0]->{desc}.')'
+	if $_[0]->{desc};
+    return $result;
+}
+
+package CBQM::QueueingCfg;
+package CBQM::REDCfg;
+package CBQM::REDClassCfg;
+package CBQM::PoliceCfg;
+
+sub tostring ($ ) {
+    my $result = "rate: ".($_[0]->{rate64} || $_[0]->{rate});
+    return $result;
+}
+
+package CBQM::TrafficShaperCfg;
+package CBQM::SetCfg;
+package CBQM::PoliceActionCfg;
diff --git a/test/router-stats.pl b/test/router-stats.pl
new file mode 100755
index 0000000..c2471f5
--- /dev/null
+++ b/test/router-stats.pl
@@ -0,0 +1,161 @@
+#!/usr/local/bin/perl
+
+use BER;
+require 'SNMP_Session.pm';
+
+# Set $host to the name of the host whose SNMP agent you want
+# to talk to.  Set $community to the community name under
+# which you want to talk to the agent.	Set port to the UDP
+# port on which the agent listens (usually 161).
+
+my $routerfile = 'test/routers';
+my @routers = qw(swiEG1.switch.ch swiEZ1.switch.ch swiEZ2.switch.ch swiCS1.switch.ch swiCS2.switch.ch);
+my $redline=10;
+my $yellowline=5;
+
+my $redball = "<table bgcolor=red><tr><td> </td></tr></table>";
+my $yelball = "<table bgcolor=yellow><tr><td> </td></tr></table>";
+my $greenball = "<table bgcolor=green><tr><td> </td></tr></table>";
+
+print <<"TEXT";    
+ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+ <html>
+ <head>
+ <meta http-equiv="Content-Type"
+ content="text/html; charset=iso-8859-1">
+ <title>WAN Routers at a glance</title>
+ </head>
+
+ <body background="/images/background.gif">
+<table border="0" cellpadding="0" cellspacing="1" width="80%">
+    <tr>
+        <td align="right" rowspan="2">
+<!-- <img src="../images/pushme2.gif" width="150" height="159"></td> -->
+        <td colspan="2"><p align="center"><font size="5"
+        face="Palatino"><strong>WAN Routers at a Glance<br>
+        </strong></font><font size="4" face="Palatino"><strong>Technical
+        Team Only</strong></font></p>
+        </td>
+    </tr>
+</table>
+<br>
+TEXT
+ at results2=();
+for ($currouter=0; $currouter < $#routers; $currouter++) {
+
+$host=@routers[$currouter];
+$community = "public";
+$port = "161";
+$path = 'test/stats/';
+
+$session = SNMP_Session->open ($host, $community, $port)
+    || die "couldn't open SNMP session to $host";
+
+# Set $oid1, $oid2... to the BER-encoded OIDs of the MIB
+# variables you want to get.
+
+$oid1 = encode_oid (1, 3, 6, 1, 2, 1, 2, 1, 0);
+$oid2 = encode_oid (1, 3, 6, 1, 2, 1, 1, 5, 0);
+# Cisco CPU OID
+$oid3 = encode_oid (1, 3, 6, 1, 4,1,9,2,1,58,0);
+if ($session->get_request_response ($oid1,$oid2,$oid3)) {
+    ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
+    while ($bindings ne '') {
+	($binding,$bindings) = &decode_sequence ($bindings);
+	($oid,$value) = &decode_by_template ($binding, "%O%@");
+	$interfaces=pretty_print ($value);
+	($binding,$bindings) = &decode_sequence ($bindings);
+	($oid,$value) = &decode_by_template ($binding, "%O%@");
+	$sysname=pretty_print ($value);
+	($binding,$bindings) = &decode_sequence ($bindings);
+	($oid,$value) = &decode_by_template ($binding, "%O%@");
+	$cpupercent=pretty_print ($value);
+    }
+} else {
+    die "No response from agent on $host";
+}
+print <<TEXT;
+
+ <div align="left"><left>
+
+ <table border="1" cellpadding="0" cellspacing="1" width=80%>
+<TR> <td colspan=
+TEXT
+ at results = ();
+ at outhead=();
+ at outvalue=();
+ at outhead[0]="CPU";
+if ($cpupercent>$redline){
+	    $graphic=$redball;
+	  } elsif ($cpupercent>$yellowline) {
+	    $graphic=$yelball;
+	  } else {
+	    $graphic=$greenball;
+	  }
+ at outvalue[0]=$graphic;
+$a=1;
+for ($i=1; $i <= $interfaces; $i++) {
+$oid1=encode_oid(1,3, 6, 1, 2, 1, 2, 2, 1 ,2, $i);
+$oid2=encode_oid(1,3, 6, 1, 2, 1, 2, 2, 1 ,8, $i);
+$oid3=encode_oid(1,3, 6, 1, 2, 1, 2, 2, 1 ,5, $i);
+if ($session->get_request_response ($oid1,$oid2,$oid3)) {
+    ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
+    while ($bindings ne '') {
+	($binding,$bindings) = &decode_sequence ($bindings);
+	($oid,$value) = &decode_by_template ($binding, "%O%@");
+	$name=pretty_print ($value);
+	($binding,$bindings) = &decode_sequence ($bindings);
+	($oid,$value) = &decode_by_template ($binding, "%O%@");
+	$status=pretty_print ($value);
+	($binding,$bindings) = &decode_sequence ($bindings);
+	($oid,$value) = &decode_by_template ($binding, "%O%@");
+	$maxspeed=(pretty_print ($value)/8);
+	
+	if ($status=="1") {
+	  $file = $path.$host.".".$i.".log";
+	  @temp=split(/\n/,$file);
+	  $file=@temp[0]. at temp[1];
+	  #print $file,"\n"; 
+	  open(INFO, $file);
+	  @lines = <INFO>;
+	  @elements=split(/ /, at lines[1]);
+	  $curtot=@elements[1]+ at elements[2];
+	  if ($maxspeed == 0) {
+	    $graphic="";
+	  } else {
+	    $percentage=($curtot/$maxspeed)*100;
+	    if ($percentage>$redline){
+	      $graphic=$redball;
+	    } elsif ($percentage>$yellowline) {
+	      $graphic=$yelball;
+	    } else {
+	      $graphic=$greenball;
+	    }
+	  }
+	  @outhead[$a]=$name;
+	  @outvalue[$a]=$graphic;
+	  $a++;
+	}} 
+} else {
+    die "No response from agent on $host";
+}
+	
+}
+print $#outhead+1,">Utilisation statistics for ",$sysname," </TD></tr>";
+for ($x=0; $x <= $#outhead; $x++) {
+print "<td>", at outhead[$x],"</td>\n";
+}
+print "<tr>\n";
+for ($x=0; $x <= $#outvalue; $x++) {
+print "<td>", at outvalue[$x],"</td>\n";
+}
+ at outhead=();
+ at outvalue=();
+print <<"TEXT";
+</tr>
+</table><br>
+</left></div>
+TEXT
+     
+}
+print "</body></html>";
diff --git a/test/sequence-bug.pl b/test/sequence-bug.pl
new file mode 100644
index 0000000..b7fe7c6
--- /dev/null
+++ b/test/sequence-bug.pl
@@ -0,0 +1,22 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use SNMP_Session;
+use SNMP_util;
+
+my $snmphost = $ARGV[0];
+
+my (%IN, %OUT);
+my @ret = &snmpwalk($snmphost, "ipAdEntIfIndex");
+foreach my $desc (@ret) {
+    my ($ipad, $ifType);
+    ($ipad, $desc) = split(':', $desc, 2);
+    next if $ipad=~/127.0.0.1/;
+
+    ($ifType,$IN{$ipad},$OUT{$ipad})=&snmpget($snmphost,"ifType.$desc","ifInOctets.$desc","ifOutOctets.$desc");
+}
+
+foreach my $ipad (sort keys %IN) {
+    printf "%-15s %12d %12d\n", $ipad, $IN{$ipad}, $OUT{$ipad};
+}
diff --git a/test/shipmr b/test/shipmr
new file mode 100755
index 0000000..6b37058
--- /dev/null
+++ b/test/shipmr
@@ -0,0 +1,119 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+
+use BER;
+use SNMP_Session;
+use Socket;
+
+sub get_table_entry ($$$ );
+
+my $version = '2c';
+my $port = 161;
+my $debug = 0;
+
+while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
+    if ($ARGV[0] =~ /^-v/) {
+	if ($ARGV[0] eq '-v') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] eq '1') {
+	    $version = '1';
+	} elsif ($ARGV[0] eq '2c') {
+	    $version = '2c';
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-p/) {
+	if ($ARGV[0] eq '-p') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+$/) {
+	    $port = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] eq '-h') {
+	usage (0);
+	exit 0;
+    } else {
+	usage (1);
+    }
+    shift @ARGV;
+}
+my $host = shift @ARGV || usage (1);
+my $community = shift @ARGV || "public";
+
+my $source = '130.59.4.2';
+my $group = '233.2.47.1';
+my $source_mask = '255.255.255.255';
+my $index = $group.".".$source.".".$source_mask;
+usage (1) if $#ARGV >= $[;
+
+
+my @ipMRouteTableOIDs = ([1,3,6,1,3,60,1,1,2,1,4],
+			 [1,3,6,1,3,60,1,1,2,1,5],
+			 [1,3,6,1,3,60,1,1,2,1,6],
+			 [1,3,6,1,3,60,1,1,2,1,7],
+			 [1,3,6,1,3,60,1,1,2,1,8],
+			 [1,3,6,1,3,60,1,1,2,1,9],
+			 [1,3,6,1,3,60,1,1,2,1,10],
+			 [1,3,6,1,3,60,1,1,2,1,11]);
+
+&print_route_at_router ($host, $community);
+
+my %router_seen = ();
+
+sub print_route_at_router {
+    my ($host, $community) = @_;
+    return if $router_seen{$host};
+    ++$router_seen{$host};
+    my $session =
+	($version eq '1' ? SNMPv1_Session->open ($host, $community, $port)
+	 : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, $port)
+	 : die "Unknown SNMP version $version")
+	    || die "Opening SNMP_Session";
+    $session->debug (1) if $debug;
+    my ($upstream_neighbor, $in_if_index, $up_time, $expiry_time, $pkts, $different_in_if_packets, $octets, $protocol)
+	= get_table_entry ($session, \@ipMRouteTableOIDs, $index);
+    return undef unless defined $upstream_neighbor;
+    my $upstream_name = gethostbyaddr(pack ("C4",split ('\.',$upstream_neighbor)),
+				      AF_INET) || $upstream_neighbor;
+    print "Router: $host\n";
+    print "  upstream neighbor: $upstream_neighbor ($upstream_name)\n";
+    print "  in-interface: $in_if_index\n";
+    print_route_at_router ($upstream_name, $community)
+	unless $upstream_neighbor eq '0.0.0.0';
+}
+
+sub get_table_entry ($$$ ) {
+    my ($session, $columns, $index) = @_;
+    my @result;
+
+    if ($session->get_request_response (map { encode_oid (@{$_},split ('\.',$index)) } (@{$columns}))) {
+	my $response = $session->pdu_buffer;
+	my ($bindings) = $session->decode_get_response ($response);
+	my ($binding, $oid, $value);
+
+	while ($bindings ne '') {
+	    ($binding,$bindings) = decode_sequence ($bindings);
+	    ($oid,$value) = decode_by_template ($binding, "%O%@");
+	    push @result, pretty_print ($value);
+	}
+    } else {
+	warn "SNMP problem: $SNMP_Session::errmsg\n";
+    }
+    @result;
+}
+
+#foreach my $oid (@ipMRouteTableOIDs) {
+#    warn "OID: $oid";
+#}
+#system "snmpget $host ipMRouteTable.ipMRouteEntry.ipMRouteInIfIndex.$index";
+1;
diff --git a/test/snmpmap_table-test.pl b/test/snmpmap_table-test.pl
new file mode 100644
index 0000000..aa5eb3d
--- /dev/null
+++ b/test/snmpmap_table-test.pl
@@ -0,0 +1,28 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use BER;
+use SNMP_Session;
+use SNMP_util "0.86";
+
+snmpmapOID (qw(locIfInBitsSec	1.3.6.1.4.1.9.2.2.1.1.6
+	       locIfOutBitsSec	1.3.6.1.4.1.9.2.2.1.1.8
+	       locIfDescr	1.3.6.1.4.1.9.2.2.1.1.28));
+
+sub usage () { die "Usage: $0 community\@host\n"; }
+
+my $host = shift @ARGV || usage ();
+
+snmpmaptable ($host,
+	      sub {
+		  my ($index, $descr, $in, $out, $comment) = @_;
+
+		  printf "%2d  %-24s %10s %10s %s\n",
+		  $index,
+		  defined $descr ? $descr : '',
+		  defined $in ? $in/1000.0 : '-',
+		  defined $out ? $out/1000.0 : '-',
+		  defined $comment ? $comment : '';
+	      },
+	      qw(ifDescr locIfInBitsSec locIfOutBitsSec locIfDescr));
diff --git a/test/snmpspeed.pl b/test/snmpspeed.pl
new file mode 100644
index 0000000..dc369bb
--- /dev/null
+++ b/test/snmpspeed.pl
@@ -0,0 +1,213 @@
+#!/usr/local/bin/perl
+# -*- mode: Perl -*-
+##################################################################
+# Config file creator
+##################################################################
+# Created by Tobias Oetiker <oetiker at ee.ethz.ch>
+# this produces a config file for one router, by bulling info
+# off the router via snmp
+#################################################################
+#
+# Distributed under the GNU copyleft
+#
+# $Id: snmpspeed.pl,v 1.3 2001-11-14 13:24:32 leinen Exp $
+#
+use SNMP_Session "0.54";
+$SNMP_Session::default_timeout = 0.2;
+$SNMP_Session::default_backoff = 1.5;
+  
+use BER "0.51";
+
+%snmpget::OIDS = (  'sysDescr' => '1.3.6.1.2.1.1.1.0',
+		    'sysContact' => '1.3.6.1.2.1.1.4.0',
+		    'sysName' => '1.3.6.1.2.1.1.5.0',
+		    'sysLocation' => '1.3.6.1.2.1.1.6.0',
+		    'sysUptime' => '1.3.6.1.2.1.1.3.0',
+		    'ifNumber' =>  '1.3.6.1.2.1.2.1.0',
+		    ###################################
+		    # add the ifNumber ....
+   # add the ifNumber ....
+   'ifDescr' => '1.3.6.1.2.1.2.2.1.2',
+   'ifType' => '1.3.6.1.2.1.2.2.1.3',
+   'ifIndex' => '1.3.6.1.2.1.2.2.1.1',
+   'ifInErrors' => '1.3.6.1.2.1.2.2.1.14',
+   'ifOutErrors' => '1.3.6.1.2.1.2.2.1.20',
+   'ifInOctets' => '1.3.6.1.2.1.2.2.1.10',
+   'ifOutOctets' => '1.3.6.1.2.1.2.2.1.16',
+   'ifInDiscards' => '1.3.6.1.2.1.2.2.1.13',
+   'ifOutDiscards' => '1.3.6.1.2.1.2.2.1.19',
+   'ifInUcastPkts' => '1.3.6.1.2.1.2.2.1.11',
+   'ifOutUcastPkts' => '1.3.6.1.2.1.2.2.1.17',
+   'ifInNUcastPkts' => '1.3.6.1.2.1.2.2.1.12',
+   'ifOutNUcastPkts' => '1.3.6.1.2.1.2.2.1.18',
+   'ifInUnknownProtos' => '1.3.6.1.2.1.2.2.1.15',
+   'ifOutQLen' => '1.3.6.1.2.1.2.2.1.21',
+   'ifSpeed' => '1.3.6.1.2.1.2.2.1.5',
+ 		    'ifDescr' => '1.3.6.1.2.1.2.2.1.2',
+		    'ifType' => '1.3.6.1.2.1.2.2.1.3',
+		    'ifIndex' => '1.3.6.1.2.1.2.2.1.1',
+		    'ifSpeed' => '1.3.6.1.2.1.2.2.1.5', 
+		    'ifOperStatus' => '1.3.6.1.2.1.2.2.1.8',		 
+		    'ifAdminStatus' => '1.3.6.1.2.1.2.2.1.7',		 
+		    # up 1, down 2, testing 3
+		    'ipAdEntAddr' => '1.3.6.1.2.1.4.20.1.1',
+		    'ipAdEntIfIndex' => '1.3.6.1.2.1.4.20.1.2',
+		    'sysObjectID' => '1.3.6.1.2.1.1.2.0',
+		    'CiscolocIfDescr' => '1.3.6.1.4.1.9.2.2.1.1.28',
+		    'CiscoportIndex' => '1.3.6.1.4.1.9.5.1.4.1.1.2',
+		    'CiscoportName' => '1.3.6.1.4.1.9.5.1.4.1.1.4',
+		    'CiscoportIfIndex' => '1.3.6.1.4.1.9.5.1.4.1.1.11',
+		    'CiscoswPortName' => '1.3.6.1.4.1.437.1.1.3.3.1.1.3',
+
+		 );
+
+
+
+
+sub main {
+
+    my $session = SNMP_Session->open ('ezci1.ethz.ch', 'public', 161)
+	|| die "open SNMP session: $SNMP_Session::errmsg";
+    $|=1;
+for (my $i=0;$ i < 100; $i++){
+    print "$i, ";# if $i % 10 ==0; 
+    my($ifinoct) = snmpget($session,'ifInOctets.1');
+    $ifinoct = snmpget($session,'ifInOctets.2');
+}
+    $session->close ()
+	|| die "close SNMP session: $SNMP_Session::errmsg";
+}  
+main;
+exit(0);
+
+sub snmpget {
+  my($session, at vars) = @_;
+  my(@enoid, $var,$response, $bindings, $binding, $value, $inoid,$outoid,
+     $upoid,$oid, at retvals);
+  foreach $var (@vars) {
+    if ($var =~ /^([a-z]+)/i) {
+      my $oid = $snmpget::OIDS{$1};
+      if ($oid) {
+        $var =~ s/$1/$oid/;
+      } else {
+        die "Unknown SNMP var $var\n"
+      }
+    }
+    print "SNMPGET OID: $var\n" if $main::DEBUG >5;
+    push @enoid,  encode_oid((split /\./, $var));
+  }
+  srand();
+  if ($session->get_request_response(@enoid)) {
+    $response = $session->pdu_buffer;
+    ($bindings) = $session->decode_get_response ($response);
+    while ($bindings) {
+      ($binding,$bindings) = decode_sequence ($bindings);
+      ($oid,$value) = decode_by_template ($binding, "%O%@");
+      my $tempo = pretty_print($value);
+      $tempo=~s/\t/ /g;
+      $tempo=~s/\n/ /g;
+      $tempo=~s/^\s+//;
+      $tempo=~s/\s+$//;
+      push @retvals,  $tempo;
+    }
+    return (@retvals);
+  } else {
+    return (-1,-1);
+  }
+}                    
+
+
+sub snmpgettable{
+  my($host,$community,$var) = @_;
+  my($next_oid,$enoid,$orig_oid, 
+     $response, $bindings, $binding, $value, $inoid,$outoid,
+     $upoid,$oid, at table,$tempo,$tempoO);
+  die "Unknown SNMP var $var\n" 
+    unless $snmpget::OIDS{$var};
+  
+  $orig_oid = encode_oid(split /\./, $snmpget::OIDS{$var});
+  $enoid=$orig_oid;
+  srand();
+  my $session = SNMP_Session->open ($host ,
+                                 $community, 
+                                 161);
+  for(;;)  {
+    if ($session->getnext_request_response(($enoid))) {
+      $response = $session->pdu_buffer;
+      ($bindings) = $session->decode_get_response ($response);
+      ($binding,$bindings) = decode_sequence ($bindings);
+      ($next_oid,$value) = decode_by_template ($binding, "%O%@");
+      # quit once we are outside the table
+      last unless BER::encoded_oid_prefix_p($orig_oid,$next_oid);
+      $tempo = pretty_print($value);
+      #print "$var: '$tempo'\n";
+      $tempo=~s/\t/ /g;
+      $tempo=~s/\n/ /g;
+      $tempo=~s/^\s+//;
+      $tempo=~s/\s+$//;
+      push @table, $tempo;
+     
+    } else {
+      die "No answer from $ARGV[0]\n";
+    }
+    $enoid=$next_oid;
+  }
+  $session->close ();    
+  return (@table);
+}
+
+sub snmpgettable2{
+  my($host,$community,$var) = @_;
+  my($next_oid,$enoid,$orig_oid, 
+     $response, $bindings, $binding, $value, $inoid,$outoid,
+     $upoid,$oid, at table,$tempo,$tempoO);
+  die "Unknown SNMP var $var\n" 
+    unless $snmpget::OIDS{$var};
+  
+  $orig_oid = encode_oid(split /\./, $snmpget::OIDS{$var});
+  $enoid=$orig_oid;
+  $tempoO = pretty_print($orig_oid);
+  $tempoO=~s/\t/ /g;
+  $tempoO=~s/\n/ /g;
+  $tempoO=~s/^\s+//;
+  $tempoO=~s/\s+$//;
+  srand();
+  my $session = SNMP_Session->open ($host ,
+                                 $community, 
+                                 161);
+  for(;;)  {
+    if ($session->getnext_request_response(($enoid))) {
+      $response = $session->pdu_buffer;
+      ($bindings) = $session->decode_get_response ($response);
+      ($binding,$bindings) = decode_sequence ($bindings);
+      ($next_oid,$value) = decode_by_template ($binding, "%O%@");
+      # quit once we are outside the table
+      last unless BER::encoded_oid_prefix_p($orig_oid,$next_oid);
+      $tempo = pretty_print($next_oid);
+      $tempo=~s/\t/ /g;
+      $tempo=~s/\n/ /g;
+      $tempo=~s/^\s+//;
+      $tempo=~s/\s+$//;
+      $tempo=substr($tempo,length($tempoO)+1);
+      #print "$var: '$tempo'\n";
+      push @table, $tempo;
+     
+    } else {
+      die "No answer from $ARGV[0]\n";
+    }
+    $enoid=$next_oid;
+  }
+  $session->close ();    
+  return (@table);
+}
+
+sub fmi {
+  my($number) = $_[0];
+  my(@short);
+  @short = ("Bytes/s","kBytes/s","MBytes/s","GBytes/s");
+  my $digits=length("".$number);
+  my $divm=0;
+  while ($digits-$divm*3 > 4) { $divm++; }
+  my $divnum = $number/10**($divm*3);
+  return sprintf("%1.1f %s",$divnum,$short[$divm]);
+}
diff --git a/test/snmptrap.note b/test/snmptrap.note
new file mode 100644
index 0000000..a511146
--- /dev/null
+++ b/test/snmptrap.note
@@ -0,0 +1,4 @@
+The first two arguments to snmptrap are the trap destination and the
+community string.  The next argument is the Trap OID, the source's
+hostname or IP address as a dotted-quad, the generic trap ID, the
+specific trap ID, then the variable bindings (OID and value pairs).
diff --git a/test/snmptrap.pl b/test/snmptrap.pl
new file mode 100644
index 0000000..3b926b1
--- /dev/null
+++ b/test/snmptrap.pl
@@ -0,0 +1,83 @@
+#! /usr/local/bin/perl5
+# -*- mode: Perl -*-
+BEGIN{
+$main::OS = 'UNIX';
+#$main::OS = 'NT';
+#$main::OS = 'VMS';
+##################################################################
+   # The path separator is a slash, backslash or semicolon, depending
+   # on the platform.
+   $main::SL = {
+     UNIX=>'/',
+     WINDOWS=>'\\',
+     NT=>'\\',
+     VMS=>''
+     }->{$main::OS};
+
+   # The search path separator is a colon or semicolon depending on the
+   # operating system.
+   $main::PS = {
+     UNIX=>':',
+     WINDOWS=>';',
+     NT=>';',
+     VMS=>':'
+     }->{$main::OS};
+
+  # We need to find the place where this is installed, and
+  # then take the .pm programms from there.
+  $main::binpath ="";
+  if ($0 =~ /^(.+\Q${main::SL}\E)/) {
+    $main::binpath="$1";
+  } else {
+    foreach $pathname ( split ${main::PS}, $ENV{'PATH'}) {
+      if ((($main::OS eq 'NT') &&
+           (-e "$pathname${main::SL}$0")) ||
+           (-x "$pathname${main::SL}$0")) {
+	$main::binpath=$pathname;
+  	last;
+      }
+    }
+  }
+  die "ERROR: Can\'t find location of mrtg executable\n" 
+    unless $main::binpath; 
+  unshift (@INC,$main::binpath);
+}
+
+# The older perls tend to behave peculiar with
+# large integers ... 
+require 5.003;
+
+if ($main::OS eq 'UNIX' || $main::OS eq 'NT') {
+    use SNMP_util "0.54";
+    $main::SNMPDEBUG =0;
+}
+
+use strict;
+
+$main::DEBUG=0;
+
+sub main {
+  
+  my($trapid, $sev, $message);
+  my($machine, $ret);
+  # unbuffer stdout to see everything immediately
+  $|=1 if $main::DEBUG;   
+
+  $trapid = 1100;
+  $sev = "Major";
+  $message = "MCM -- I'm testing, please ignore";
+  $machine = `hostname` ;
+  chop($machine);
+
+  $ret = &snmptrap("dizzy.unx.sas.com", "",
+		"1.3.6.1.4.1.11.2.17.1", $machine, 6, $trapid, 
+		"1.3.6.1.4.1.11.2.17.2.1.0", "Integer", 14,
+		"1.3.6.1.4.1.11.2.17.2.2.0", "OctetString", $machine,
+		"1.3.6.1.4.1.11.2.17.2.4.0", "OctetString", $message,
+		"1.3.6.1.4.1.11.2.17.2.5.0", "OctetString", $sev);
+
+  print "ret = <$ret>\n";
+}
+
+main;
+exit(0);
diff --git a/test/sorrento-nest-list b/test/sorrento-nest-list
new file mode 100644
index 0000000..ec8dfcd
--- /dev/null
+++ b/test/sorrento-nest-list
@@ -0,0 +1,85 @@
+#!/usr/local/bin/perl -w
+
+use SNMP_util;
+
+sub process_nest ($$);
+
+snmpmapOID ("slotCardName", "1.3.6.1.4.1.2522.1.1.2.1.1.2",
+	    "slotCardType", "1.3.6.1.4.1.2522.1.1.2.1.1.3",
+	    "slotCardStatus", "1.3.6.1.4.1.2522.1.1.2.1.1.4",
+	    "slotIpAddress", "1.3.6.1.4.1.2522.1.1.3.1.1.2",
+	    "cardName", "1.3.6.1.4.1.2522.1.1.3.1.1.3");
+
+my @nestmasters =
+    (['mCE11','public at 130.59.48.16'],
+     ['mCE13','public at 130.59.48.17'],
+     ['mLS11','public at 130.59.48.80'],
+     ['mLS13','public at 130.59.48.81'],
+     ['mBE11','public at 130.59.48.144'],
+     ['mBE13','public at 130.59.48.145'],
+     ['mBA11','public at 130.59.48.208'],
+     ['mEZ11','public at 130.59.49.16'],
+    );
+
+## Override here if you just want to re-generate the names for one nest.
+# @nestmasters = (['mCE11','public at 130.59.48.16']);
+
+my %short_types =
+    qw(GMI-1GSX      c
+       GMI-1GLX      c
+       GMOA-1A       A
+       GMTR-15       c
+       GMTR-25       c
+       GMOX-06       c
+       GMOX-15       c
+       GMOX-25       c
+       GMOX-ER       c
+       GM-GE2        c
+       GM-GE2-2.5G-A c
+       GMGE2-2.5G-M c
+       GM-GE4-2.5G-A c
+       GMCR-10GL-LR  c);
+
+foreach (@nestmasters) {
+    process_nest ($_->[0], $_->[1]);
+}
+1;
+
+sub process_nest ($$) {
+    my ($name, $dest) = @_;
+    my %slot_name = ();
+    my %slot_type = ();
+
+    my ($nest_ip_address) = ($dest =~ /.*@(.*)$/);
+    (out_ip ($name.'-M0', $nest_ip_address, undef),
+     print "$name\t\tIN\tCNAME\t$name-M0\n")
+	if defined $nest_ip_address;
+    snmpmaptable ($dest, sub () {
+	my ($slotCardSlot, $slotCardName, $slotCardType, $slotCardStatus) = @_;
+	return if $slotCardStatus == 3;	# empty
+	$slot_name{$slotCardSlot} = $slotCardName;
+	$slot_type{$slotCardSlot} = $slotCardType;
+    },
+		  qw(slotCardName slotCardType slotCardStatus));
+    snmpmaptable ($dest, sub () {
+	my ($slotIndex, $slotIpAddress, $cardName) = @_;
+	return unless exists $slot_name{$slotIndex};
+	return if $slot_type{$slotIndex} eq 'N.A.';
+
+	my $short_type = $short_types{$slot_type{$slotIndex}};
+	if (!defined $short_type) {
+	    warn "unknown type $slot_type{$slotIndex}";
+	    $short_type = 'other';
+	}
+	out_ip ($name.'-'.$short_type.$slotIndex, $slotIpAddress, $slot_type{$slotIndex});
+	#print "$name: slotIndex $slotIndex (name $slot_name{$slotIndex} type $slot_type{$slotIndex}) ip $slotIpAddress type $cardName\n";
+    },
+		  qw(slotIpAddress cardName));
+    print ";;\n";
+}
+
+sub out_ip ($$$) {
+    my ($name, $ip, $type) = @_;
+    my $comment = $type ? "\t;$type" : '';
+    print "$name\tIN\tA\t$ip$comment\n",
+}
diff --git a/test/trap-listener b/test/trap-listener
index 2bc6112..23c176f 100755
--- a/test/trap-listener
+++ b/test/trap-listener
@@ -10,10 +10,11 @@ package main;
 
 use strict;
 
-use SNMP_Session;
+use SNMP_Session "1.14";	# requires receive_trap_1()
 use SNMP_util;
 use BER;
 use Socket;
+use Socket6;
 
 ### Forward declarations
 sub print_trap ($$);
@@ -23,13 +24,14 @@ sub pretty_addr ($ );
 sub hostname ($ );
 sub fromOID ($ );
 sub fromOID_aux ($$);
-sub really_pretty_oid ($);
+sub really_pretty_oid ($ );
 
 my $port = 162;
 
 my $print_community = 0;
 my $print_port = 0;
 my $print_hostname = 1;
+my $ipv4only = 0;
 
 register_pretty_printer {BER::object_id_tag(), \&really_pretty_oid};
 
@@ -42,6 +44,9 @@ while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
     } elsif ($ARGV[0] eq '-h') {
 	usage (0);
 	exit 0;
+    } elsif ($ARGV[0] eq '-4') {
+	shift @ARGV;
+	$ipv4only = 1;
     } else {
 	usage (1);
     }
@@ -49,16 +54,15 @@ while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
 snmpLoad_OID_Cache($SNMP_util::CacheFile);
 die unless SNMP_util::toOID("mib-2") eq SNMP_util::toOID ("1.3.6.1.2.1");
 %SNMP_util::revOIDS = reverse %SNMP_util::OIDS unless %SNMP_util::revOIDS;
-my $session = SNMPv2c_Session->open_trap_session ($port)
+my $session = SNMPv2c_Session->open_trap_session ($port, $ipv4only)
     or die "couldn't open trap session";
 $SNMP_Session::suppress_warnings = 1; # We print all error messages ourselves.
-my ($trap, $sender, $sender_port);
+my ($trap, $sender);
 
-while (($trap, $sender, $sender_port) = $session->receive_trap ()) {
+while (($trap, $sender) = $session->receive_trap_1 ()) {
     my $now_string = localtime time;
     print "$now_string ";
-    print pretty_addr (inet_ntoa ($sender));
-    print ".$sender_port" if $print_port;
+    print pretty_addr ($sender);
     print "\n";
     print_trap ($session, $trap);
 }
@@ -106,27 +110,71 @@ sub usage ($) {
 Usage: $0 [-p port]
        $0 -h
 
-  -h           print this usage message and exit.
-
-  -p port      Select the UDP port on which the program will listen
-  	       for SNMP traps.  The default port is 162.
+  -h       Print this usage message and exit.
+  -p port  Listen for traps on a specific UDP port.  The default is 162.
+  -4       Listen for IPv4 packets only.
 
 EOM
     exit (1) if $_[0];
 }
 
-sub pretty_addr ($ ) {
-    my ($addr) = @_;
-    my ($hostname,$aliases,$addrtype,$length, at addrs)
-	= gethostbyaddr (inet_aton ($addr), AF_INET);
-    $hostname ? $hostname." [".$addr."]" : $addr;
+sub make_sockaddr ($$) {
+    my ($af, $addr) = @_;
+    if ($af == AF_INET) {
+	return pack_sockaddr_in 0, $addr;
+    } elsif ($af == AF_INET6) {
+	return pack_sockaddr_in6 0, $addr;
+    } else {
+	die "Unsupported address family $af";
+    }
 }
 
-sub hostname ($ ) {
-    my ($addr) = @_;
-    my ($hostname,$aliases,$addrtype,$length, at addrs)
-	= gethostbyaddr (inet_aton ($addr), AF_INET);
-    $hostname || "[".$addr."]";
+### pretty_addr SOCKADDR
+###
+### Return a pretty representation of the given socket address.
+### If $print_hostname is non-zero, try to resolve the host part
+### of the address to a hostname.  The numeric form of the address
+### will be included in any case.  If $print_port is non-zero, the
+### port is also included in numerical form.
+###
+### For example, assuming that addresses 192.0.2.1 and 2001:db8::1
+### both map to hostname "foo.example", this function would return the
+### following results:
+###
+### pretty_addr (sockaddr_in (1234, inet_aton ("192.0.2.1")))
+###                                 if $print_hostname $print_port
+### => '192.0.2.1'                           ==0          ==0
+### => 'foo.example [192.0.2.1]'             !=0          ==0
+### => '192.0.2.1.1234'                      ==0          !=0
+### => 'foo.example [192.0.2.1].1234'        !=0          !=0
+###
+### pretty_addr (sockaddr_in6 (1234, inet_pton (AF_INET6, "2001:db8::1")))
+###                                 if $print_hostname $print_port
+### => '2001:db8::1'                         ==0          ==0
+### => 'foo.example [2001:db8::1]'           !=0          ==0
+### => '2001:db8::1.1234'                    ==0          !=0
+### => 'foo.example [2001:db8::1].1234'      !=0          !=0
+###
+### Handling of IPv4-mapped IPv6 addresses
+###
+### When receiving an IPv4-mapped IPv6 address, this routine will
+### print the embedded IPv4 address in the numeric part.  The mapping
+### to a hostname should also work, because that's what getnameinfo()
+### is specified to do.  Therefore,
+###
+### pretty_addr (sockaddr_in6 (1234, inet_pton (AF_INET6, "::ffff:192.0.2.1")))
+### should always return the same string as
+### pretty_addr (sockaddr_in (1234, inet_aton ("192.0.2.1")))
+###
+sub pretty_addr ($ ) {
+    my ($sockaddr) = @_;
+    my ($hostname, $port, $result);
+    ($result, $port) = getnameinfo ($sockaddr, NI_NUMERICHOST | NI_NUMERICSERV);
+    $result = $1 if $result =~ /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i;
+    ($hostname) = getnameinfo ($sockaddr) if $print_hostname;
+    $result = $hostname." [".$result."]" if $hostname;
+    $result .= '.'.$port if $print_port;
+    return $result;
 }
 
 sub fromOID ($ ) {
diff --git a/test/v6-list-prefixes b/test/v6-list-prefixes
new file mode 100644
index 0000000..531e62f
--- /dev/null
+++ b/test/v6-list-prefixes
@@ -0,0 +1,13 @@
+#!/usr/local/bin/perl -w
+
+use SNMP_util;
+
+snmpmapOID (
+	    "cIpv6InterfaceEffectiveMtu", "1.3.6.1.4.1.9.10.86.1.2.3.1.2",
+	    "cIpv6InterfaceReasmMaxSize", "1.3.6.1.4.1.9.10.86.1.2.3.1.3",
+	    "cIpv6InterfaceIdentifier", "1.3.6.1.4.1.9.10.86.1.2.3.1.4",
+	    "cIpv6InterfaceIdentifierLength", "1.3.6.1.4.1.9.10.86.1.2.3.1.5",
+	    "cIpv6InterfacePhysicalAddress", "1.3.6.1.4.1.9.10.86.1.2.3.1.6",
+	    );
+
+1;
diff --git a/test/vc-counters.pl b/test/vc-counters.pl
new file mode 100644
index 0000000..8b27e91
--- /dev/null
+++ b/test/vc-counters.pl
@@ -0,0 +1,446 @@
+#!/usr/local/bin/perl -w
+######################################################################
+### Observe interface counters in real time.
+######################################################################
+### Copyright (c) 1995-2000, Simon Leinen.
+###
+### This program is free software; you can redistribute it under the
+### "Artistic License" included in this distribution (file "Artistic").
+######################################################################
+### Author:       Simon Leinen  <simon at switch.ch>
+### Date Created: 21-Feb-1999
+###
+### Real-time full-screen display of the octet and (Cisco-specific)
+### CRC error counters on interfaces of an SNMP-capable node
+###
+### Description: 
+###
+### Call this script with "-h" to learn about command usage.
+###
+### The script will poll the RFC 1213 ifTable at specified intervals
+### (default is every five seconds).
+###
+### For each interface except for those that are down, a line is
+### written to the terminal which lists the interfaces name (ifDescr),
+### well as the input and output transfer rates, as computed from the
+### deltas of the respective octet counts since the last sample.
+###
+### "Alarms"
+###
+### When an interface is found to have had CRC errors in the last
+### sampling interval, or only output, but no input traffic, it is
+### shown in inverse video.  In addition, when a link changes state
+### (from normal to inverse or vice versa), a bell character is sent
+### to the terminal.
+###
+### Miscellaneous
+###
+### Note that on the very first display, the actual SNMP counter
+### values are displayed.  THOSE ABSOLUTE COUNTER VALUES HAVE NO
+### DEFINED SEMANTICS WHATSOEVER.  However, in some versions of
+### Cisco's software, the values seem to correspond to the total
+### number of counted items since system boot (modulo 2^32).  This can
+### be useful for certain kinds of slowly advancing counters (such as
+### CRC errors, hopefully).
+###
+### The topmost screen line shows the name of the managed node, as
+### well as a few hard-to-explain items I found useful while debugging
+### the script.
+###
+### Please send any patches and suggestions for improvement to the
+### author (see e-mail address above).  Hope you find this useful!
+###
+### Original Purpose:
+###
+### This script should serve as an example of how to "correctly"
+### traverse the rows of a table.  This functionality is implemented in
+### the map_table() subroutine.  The example script displays a few
+### columns of the RFC 1213 interface table and Cisco's locIfTable.  The
+### tables share the same index, so they can be handled by a single
+### invocation of map_table().
+###
+require 5.003;
+
+use strict;
+
+use BER;
+use SNMP_Session "0.67";	# requires map_table_4
+use POSIX;			# for exact time
+use Curses;
+use Math::BigInt;
+
+### Forward declarations
+sub out_interface ($$$$$$@);
+sub pretty_bps ($$);
+sub usage ($ );
+
+my $version = '1';
+
+my $desired_interval = 5.0;
+
+my $all_p = 0;
+
+my $port = 161;
+
+my $max_repetitions = 0;
+
+my $suppress_output = 0;
+
+my $debug = 0;
+
+my $show_out_discards = 0;
+
+my $cisco_p = 0;
+
+my $counter64_p = 0;
+
+while (defined $ARGV[0] && $ARGV[0] =~ /^-/) {
+    if ($ARGV[0] =~ /^-v/) {
+	if ($ARGV[0] eq '-v') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] eq '1') {
+	    $version = '1';
+	} elsif ($ARGV[0] eq '2c') {
+	    $version = '2c';
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-m/) {
+	if ($ARGV[0] eq '-m') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+$/) {
+	    $max_repetitions = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-p/) {
+	if ($ARGV[0] eq '-p') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+$/) {
+	    $port = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] =~ /^-t/) {
+	if ($ARGV[0] eq '-t') {
+	    shift @ARGV;
+	    usage (1) unless defined $ARGV[0];
+	} else {
+	    $ARGV[0] = substr($ARGV[0], 2);
+	}
+	if ($ARGV[0] =~ /^[0-9]+(\.[0-9]+)?$/) {
+	    $desired_interval = $ARGV[0];
+	} else {
+	    usage (1);
+	}
+    } elsif ($ARGV[0] eq '-a') {
+	$all_p = 1;
+    } elsif ($ARGV[0] eq '-c') {
+	$cisco_p = 1;
+    } elsif ($ARGV[0] eq '-l') {
+	$counter64_p = 1;
+    } elsif ($ARGV[0] eq '-n') {
+	$suppress_output = 1;
+    } elsif ($ARGV[0] eq '-d') {
+	$suppress_output = 1;
+	$debug = 1;
+    } elsif ($ARGV[0] eq '-D') {
+	$show_out_discards = 1;
+    } elsif ($ARGV[0] eq '-h') {
+	usage (0);
+	exit 0;
+    } else {
+	usage (1);
+    }
+    shift @ARGV;
+}
+my $host = shift @ARGV || usage (1);
+my $community = shift @ARGV || "public";
+usage (1) if $#ARGV >= $[;
+
+my $ifDescr = [1,3,6,1,2,1,2,2,1,2];
+my $ifAdminStatus = [1,3,6,1,2,1,2,2,1,7];
+my $ifOperStatus = [1,3,6,1,2,1,2,2,1,8];
+my $ifInOctets = [1,3,6,1,2,1,2,2,1,10];
+my $ifOutOctets = [1,3,6,1,2,1,2,2,1,16];
+my $ifInUcastPkts = [1,3,6,1,2,1,2,2,1,11];
+my $ifOutUcastPkts = [1,3,6,1,2,1,2,2,1,17];
+my $ifOutDiscards = [1,3,6,1,2,1,2,2,1,19];
+my $ifAlias = [1,3,6,1,2,1,31,1,1,1,18];
+## Counter64 variants
+my $ifHCInOctets = [1,3,6,1,2,1,31,1,1,1,6];
+my $ifHCOutOctets = [1,3,6,1,2,1,31,1,1,1,10];
+## Cisco-specific variables enabled by `-c' option
+my $locIfInCRC = [1,3,6,1,4,1,9,2,2,1,1,12];
+my $locIfOutCRC = [1,3,6,1,4,1,9,2,2,1,1,12];
+
+my $clock_ticks = POSIX::sysconf( &POSIX::_SC_CLK_TCK );
+
+my $win = new Curses
+    unless $suppress_output;
+
+my %old;
+my $sleep_interval = $desired_interval + 0.0;
+my $interval;
+my $linecount;
+
+sub rate_32 ($$$@) {
+    my ($old, $new, $interval, $multiplier) = @_;
+    $multiplier = 1 unless defined $multiplier;
+    my $diff = $new-$old;
+    if ($diff < 0) {
+	$diff += (2**32);
+    }
+    return $diff / $interval * $multiplier;
+}
+
+sub rate_64 ($$$@) {
+    my ($old, $new, $interval, $multiplier) = @_;
+    $multiplier = 1 unless defined $multiplier;
+    return 0 if $old == $new;
+    my $diff = Math::BigInt->new ($new-$old);
+    if ($diff < 0) {
+	$diff = $diff->add (2**64);
+    }
+    ## hrm.  Why is this so complicated?
+    ## I want a real programming language (such as Lisp).
+    my $result = $diff->bnorm () / $interval * $multiplier;
+    return $result;
+}
+
+sub rate ($$$$@) {
+    my ($old, $new, $interval, $counter64_p, $multiplier) = @_;
+    $multiplier = 1 unless defined $multiplier;
+    return $counter64_p
+	? rate_64 ($old, $new, $interval, $multiplier)
+	: rate_32 ($old, $new, $interval, $multiplier);
+}
+
+sub rate_or_0 ($$$$$) {
+    my ($old, $new, $interval, $counter64_p, $multiplier) = @_;
+    return defined $new
+	? rate ($old, $new, $interval, $counter64_p, $multiplier)
+	: 0;
+}
+
+sub out_interface ($$$$$$@) {
+    my ($index, $descr, $admin, $oper, $in, $out);
+    my ($crc, $comment);
+    my ($drops);
+    my ($clock) = POSIX::times();
+    my $alarm = 0;
+
+    ($index, $descr, $admin, $oper, $in, $out, $comment, @_) = @_;
+    ($crc, @_) = @_ if $cisco_p;
+    ($drops, @_) = @_ if $show_out_discards;
+
+    grep (defined $_ && ($_=pretty_print $_),
+	  ($descr, $admin, $oper, $in, $out, $crc, $comment, $drops));
+    $win->clrtoeol ()
+	unless $suppress_output;
+    return unless $all_p || defined $oper && $oper == 1;	# up
+    return unless defined $in && defined $out;
+
+    if (!defined $old{$index}) {
+	$win->addstr ($linecount, 0,
+		      sprintf ("%2d  %-24s %10s %10s",
+			       $index,
+			       defined $descr ? $descr : '',
+			       defined $in ? $in : '-',
+			       defined $out ? $out : '-'))
+	    unless $suppress_output;
+	if ($show_out_discards) {
+	    $win->addstr (sprintf (" %8s",
+				   defined $drops ? $drops : '-'))
+		unless $suppress_output;
+	}
+	if ($cisco_p) {
+	    $win->addstr (sprintf (" %10s",
+				   defined $crc ? $crc : '-'))
+		unless $suppress_output;
+	}
+	$win->addstr (sprintf (" %s",
+			       defined $comment ? $comment : ''))
+	    unless $suppress_output;
+    } else {
+	my $old = $old{$index};
+
+	$interval = ($clock-$old->{'clock'}) * 1.0 / $clock_ticks;
+	my $d_in = rate_or_0 ($old->{'in'}, $in, $interval, $counter64_p, 8);
+	my $d_out = rate_or_0 ($old->{'out'}, $out, $interval, $counter64_p, 8);
+	my $d_drops = rate_or_0 ($old->{'drops'}, $drops, $interval, 0, 1);
+	my $d_crc = rate_or_0 ($old->{'crc'}, $crc, $interval, 0, 1);
+	$alarm = ($d_crc != 0)
+	    || 0 && ($d_out > 0 && $d_in == 0);
+	print STDERR "\007" if $alarm && !$old->{'alarm'};
+	print STDERR "\007" if !$alarm && $old->{'alarm'};
+	$win->standout() if $alarm && !$suppress_output;
+	$win->addstr ($linecount, 0,
+		      sprintf ("%2d  %-24s %s %s",
+			       $index,
+			       defined $descr ? $descr : '',
+			       pretty_bps ($in, $d_in),
+			       pretty_bps ($out, $d_out)))
+	    unless $suppress_output;
+	if ($show_out_discards) {
+	    $win->addstr (sprintf (" %8.1f %s",
+				   defined $drops ? $d_drops : 0))
+		unless $suppress_output;
+	}
+	if ($cisco_p) {
+	    $win->addstr (sprintf (" %10.1f",
+				   defined $crc ? $d_crc : 0))
+		unless $suppress_output;
+	}
+	$win->addstr (sprintf (" %s",
+			       defined $comment ? $comment : ''))
+	    unless $suppress_output;
+	$win->standend() if $alarm && !$suppress_output;
+    }
+    $old{$index} = {'in' => $in,
+		    'out' => $out,
+		    'crc' => $crc,
+		    'drops' => $drops,
+		    'clock' => $clock,
+		    'alarm' => $alarm};
+    ++$linecount;
+    $win->refresh ()
+	unless $suppress_output;
+}
+
+sub pretty_bps ($$) {
+    my ($count, $bps) = @_;
+    if (! defined $count) {
+	return '      -   ';
+    } elsif ($bps > 1000000) {
+	return sprintf ("%8.4f M", $bps/1000000);
+    } elsif ($bps > 1000) {
+	return sprintf ("%9.1fk", $bps/1000);
+    } else {
+	return sprintf ("%10.0f", $bps);
+    }
+}
+
+$win->erase ()
+    unless $suppress_output;
+my $session =
+    ($version eq '1' ? SNMPv1_Session->open ($host, $community, $port)
+     : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, $port)
+     : die "Unknown SNMP version $version")
+  || die "Opening SNMP_Session";
+$session->debug (1) if $debug;
+
+### max_repetitions:
+###
+### We try to be smart about the value of $max_repetitions.  Starting
+### with the session default, we use the number of rows in the table
+### (returned from map_table_4) to compute the next value.  It should
+### be one more than the number of rows in the table, because
+### map_table needs an extra set of bindings to detect the end of the
+### table.
+###
+$max_repetitions = $session->default_max_repetitions
+    unless $max_repetitions;
+while (1) {
+    unless ($suppress_output) {
+	$win->addstr (0, 0, sprintf ("%-20s interval %4.1fs %d reps",
+				     $host,
+				     $interval || $desired_interval,
+				     $max_repetitions));
+	$win->standout();
+	$win->addstr (1, 0,
+		      sprintf (("%2s  %-24s %10s %10s"),
+			       "ix", "name",
+			       "bits/s", "bits/s"));
+	if ($show_out_discards) {
+	    $win->addstr (sprintf ((" %8s"),
+				   "drops/s"));
+	}
+	if ($cisco_p) {
+	    $win->addstr (sprintf ((" %10s"), "pkts/s"));
+	}
+	$win->addstr (sprintf ((" %s"), "description"));
+	$win->addstr (2, 0,
+		      sprintf (("%2s  %-24s %10s %10s"),
+			       "", "",
+			       "in", "out"));
+	if ($show_out_discards) {
+	    $win->addstr (sprintf ((" %8s"),
+				   ""));
+	}
+	if ($cisco_p) {
+	    $win->addstr (2, 0,
+			  sprintf ((" %10s %s"),
+				   "CRC",
+				   ""));
+	}
+	$win->clrtoeol ();
+	$win->standend();
+    }
+    $linecount = 3;
+    my @oids = ($ifDescr,$ifAdminStatus,$ifOperStatus);
+    if ($counter64_p) {
+	@oids = (@oids,$ifHCInOctets,$ifHCOutOctets);
+    } else {
+	@oids = (@oids,$ifInOctets,$ifOutOctets);
+    }
+    @oids = (@oids,$ifAlias);
+    if ($cisco_p) {
+	push @oids, $locIfInCRC;
+    }
+    if ($show_out_discards) {
+	push @oids, $ifOutDiscards;
+    }
+    my $calls = $session->map_table_4
+	(\@oids, \&out_interface, $max_repetitions);
+    $max_repetitions = $calls + 1
+	if $calls > 0;
+    $sleep_interval -= ($interval - $desired_interval)
+	if defined $interval;
+    select (undef, undef, undef, $sleep_interval);
+}
+1;
+
+sub usage ($) {
+    warn <<EOM;
+Usage: $0 [-t secs] [-v (1|2c)] [-c] [-l] [-m max] [-p port] host [community]
+       $0 -h
+
+  -h           print this usage message and exit.
+
+  -c           also use Cisco-specific variables (locIfInCrc)
+
+  -l           use 64-bit counters (requires SNMPv2 or higher)
+
+  -t secs      specifies the sampling interval.  Defaults to 5 seconds.
+
+  -v version   can be used to select the SNMP version.  The default
+   	       is SNMPv1, which is what most devices support.  If your box
+   	       supports SNMPv2c, you should enable this by passing "-v 2c"
+   	       to the script.  SNMPv2c is much more efficient for walking
+   	       tables, which is what this tool does.
+
+  -m max       specifies the maxRepetitions value to use in getBulk requests
+               (only relevant for SNMPv2c).
+
+  -m port      can be used to specify a non-standard UDP port of the SNMP
+               agent (the default is UDP port 161).
+
+  host         hostname or IP address of a router
+
+  community    SNMP community string to use.  Defaults to "public".
+EOM
+    exit (1) if $_[0];
+}
diff --git a/test/verio-problem.pl b/test/verio-problem.pl
new file mode 100644
index 0000000..0116ff3
--- /dev/null
+++ b/test/verio-problem.pl
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+use BER;
+require 'SNMP_Session.pm';
+
+# Set $host to the name of the host whose SNMP agent you want
+# to talk to.  Set $community to the community name under
+# which you want to talk to the agent.  Set port to the UDP
+# port on which the agent listens (usually 161).
+
+$host = "vcp-nt.cp.verio.net";
+$community = "public";
+$port = "161";
+
+$session = SNMP_Session->open ($host, $community, $port)
+    || die "couldn't open SNMP session to $host";
+
+# Set $oid1, $oid2... to the BER-encoded OIDs of the MIB
+# variables you want to get.
+$oid = "1.3.6.1.2.1.1.1.0";
+
+%pretty_oids = ( encode_oid(1,3,6,1,2,1,1,1,0), "sysDescr.0" );
+
+if ($session->get_request_response (encode_oid (split '\.',$oid))) {
+    ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
+
+    while ($bindings ne '') {
+        ($binding,$bindings) = &decode_sequence ($bindings);
+        ($oid,$value) = &decode_by_template ($binding, "%O%@");
+        print $pretty_oids{$oid}," => ",
+              &pretty_print ($value), "\n";
+    }
+} else {
+    die "No response from agent on $host";
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-perl/packages/libsnmp-session-perl.git



More information about the Pkg-perl-cvs-commits mailing list