Cisco::Management - Interface for Cisco Management


Cisco-Management documentation Contained in the Cisco-Management distribution.

Index


Code Index:

NAME

Top

Cisco::Management - Interface for Cisco Management

SYNOPSIS

Top

  use Cisco::Management;

DESCRIPTION

Top

Cisco::Management is a class implementing several management functions for Cisco devices - mostly via SNMP. Cisco::Management uses the Net::SNMP module to do the SNMP calls.

METHODS

Top

new() - create a new Cisco::Management object

  my $cm = new Cisco::Management([OPTIONS]);

or

  my $cm = Cisco::Management->new([OPTIONS]);

Create a new Cisco::Management object with OPTIONS as optional parameters. Valid options are:

  Option     Description                            Default
  ------     -----------                            -------
  -hostname  Remote device to connect to            localhost
  -port      Port to connect to                     161
  -community SNMP read/write community string       private
  -timeout   Timeout to wait for request in seconds 10

session() - return Net::SNMP session object

  $session = $cm->session;

Return the Net::SNMP session object created by the Cisco::Management new() method. This is useful to call Net::SNMP methods directly without having to create a new Net::SNMP object. For example:

  my $cm = new Cisco::Management(
                                 -host      => 'router1',
                                 -community => 'snmpRW'
                                );
  my $session = $cm->session();
  # get_request() is a Net::SNMP method
  $session->get_request('1.3.6.1.2.1.1.4.0');

In this case, the get_request call is a method provided by the Net::SNMP module that can be accessed directly via the $session object returned by the $cm->session() method.

close() - close session

  $cm->close;

Close the Cisco::Management session.

error() - print last error

  printf "Error: %s\n", Cisco::Management->error;

Return last error.

Configuration Management Options

The following methods are for configuration file management. These methods implement the CISCO-CONFIG-COPY-MIB for configuration file management. If these operations fail, the older method in OLD-CISCO-SYS-MIB is tried. All Catalyst OS operations are performed against the CISCO-STACK-MIB.

config_copy() - configuration file management

  my $cc = $cm->config_copy([OPTIONS]);

Manage configuration files. Options allow for TFTP upload or download of running-config or startup-config and a copy running-config to startup-config or vice versa. Valid options are:

  Option     Description                            Default
  ------     -----------                            -------
  -tftp      TFTP server address                    localhost
  -source    'startup-config', 'running-config'     'running-config'
             or filename on TFTP server
  -dest      'startup-config', 'running-config'     'startup-config'
             or filename for TFTP server
  -catos     Catalyst OS boolean flag.  Enable if   0
             device runs Catalyst OS.

The default behavior with no options is copy running-config startup-config.

NOTE: Use care when performing TFTP upload to startup-config. This MUST be a FULL configuration file as the config file is NOT merged, but instead OVERWRITES the startup-config.

Allows the following methods to be called.

config_copy_starttime() - return config copy start time

  $cc->config_copy_starttime();

Return the start time of the configuration copy operation relative to system uptime.

config_copy_endtime() - return config copy end time

  $cc->config_copy_endtime();

Return the end time of the configuration copy operation relative to system uptime.

CPU Info

The following methods are for CPU utilization. These methods implement the CISCO-PROCESS-MIB and OLD-CISCO-SYS-MIB.

cpu_info() - return CPU utilization info

  my $cpuinfo = $cm->cpu_info();

Populate a data structure with CPU information. If successful, returns pointer to an array containing CPU information.

  $cpuinfo->[0]->{'Name', '5sec', '1min', ...}
  $cpuinfo->[1]->{'Name', '5sec', '1min', ...}
  ...
  $cpuinfo->[n]->{'Name', '5sec', '1min', ...}

Interface Options

The following methods are for interface management. These methods implement the IF-MIB.

interface_getbyindex() - get interface name by ifIndex

  my $line = $cm->interface_getbyindex([OPTIONS]);

Resolve an ifIndex to the full interface name. Called with one argument, interpreted as the interface ifIndex to resolve.

  Option     Description                            Default
  ------     -----------                            -------
  -index     The ifIndex to resolve                 -REQUIRED-

Returns the full interface name string.

interface_getbyname() - get interface name/ifIndex by string

  my $name = $cm->interface_getbyname([OPTIONS]);

Get the full interface name or ifIndex number by the Cisco 'shortcut' name. For example, 'gig0/1' or 's0/1' resolves to 'GigabitEthernet0/1' and 'Serial0/1' respectively. Called with one argument, interpreted as the interface string to resolve.

  Option     Description                            Default
  ------     -----------                            -------
  -interface String to resolve                      -REQUIRED-
  -index     Return ifIndex instead (boolean)       0

Returns a string with the full interface name or ifIndex - if -index boolean flag is set.

interface_info() - return interface info

  my $ifs = $cm->interface_info([OPTIONS]);

Populate a data structure with interface information. Called with no arguments, populates data structure for all interfaces. Called with one argument, interpreted as the interface(s) to retrieve information for.

  Option     Description                            Default
  ------     -----------                            -------
  -interface ifIndex or range of ifIndex (, and -)  (all)

Interface information consists of the following MIB entries (exludes counter-type interface metrics):

  Index
  Description
  Type
  MTU
  Speed
  Duplex *
  PhysAddress
  AdminStatus
  OperStatus
  LastChange

NOTE: Duplex is found in the EtherLike-MIB and thus will not be populated for non-Ethernet interface types.

If successful, returns a pointer to a hash containing interface information.

  $ifs->{1}->{'Index', 'Description', ...}
  $ifs->{2}->{'Index', 'Description', ...}
  ...
  $ifs->{n}->{'Index', 'Description', ...}

interface_ip() - return IP info for interfaces

  my $ips = $cm->interface_ip([1]);

Populate a data structure with the IP information per interface. If successful, returns a pointer to a hash containing interface IP information. For /xx instead of dotted-octet format for mask, use the optional boolean argument.

  $ips->{1}->[0]->{'IPAddress', 'IPMask'}
             [1]->{'IPAddress', 'IPMask'}
             ...
  ...
  $ips->{n}->[0]->{'IPAddress', 'IPMask'}

First hash value is the interface ifIndex, next array is the list of current IP information per the interface ifIndex.

interface_metrics() - return interface metrics

  my $ifs = $cm->interface_metrics([OPTIONS]);

Populate a data structure with interface metrics.

NOTE: This method only provides the counter values - do NOT confuse this with utilization. This is the raw number of "metric" types seen since the counter was last reset.

Called with no arguments, populates data structure for all interfaces. Called with one argument, interpreted as the interface(s) to retrieve metrics for.

  Option     Description                            Default
  ------     -----------                            -------
  -interface ifIndex or range of ifIndex (, and -)  (all)
  -metrics   Metric or array of metrics to return   (all)
             eg:    -metrics => 'octets'
             eg:    -metrics => [octets, ...]
               (or) -metrics => \@mets

Interface metrics consist of the following MIB entries:

  Multicasts   (count of packets in/out)
  Broadcasts   (count of packets in/out)
  Octets       (count of octets in/out)
  Unicasts     (count of packets in/out)
  Discards     (count of packets in/out)
  Errors       (count of packets in/out)
  Unknowns *   (count of packets in)

NOTE: Providing an above value for -metrics returns the In and Out counter for the metric; except for Unknowns, which does not have an Out counter.

If successful, returns a pointer to a hash containing interface metrics.

  $ifs->{1}->{'InMulticasts', 'OutMulticasts', 'InOctets', ...}
  $ifs->{2}->{'InMulticasts', 'OutMulticasts', 'InOctets', ...}
  ...
  $ifs->{n}->{'InMulticasts', 'OutMulticasts', 'InOctets', ...}

interface_utilization() - return interface utilization

  my $ifs = $cm->interface_utilization([OPTIONS]);

or

  my ($ifs, $recur);
  ($ifs, $recur) = $cm->interface_utilization(
                                              [OPTIONS]
                                              -recursive => $recur
                                             );

Populate a data structure with interface utilizations.

NOTE: This method processes the counter values described in the interface_metrics method and returns utilizations in packets or octets per second. This is done by retrieving the metrics, waiting for a 'polling interval' of time, retrieving the metrics again and finally processing the utilizations, populating and returning the data structure.

Called with no arguments, populates data structure for all interfaces. Called with one argument, interpreted as the interface(s) to retrieve metrics for.

  Option     Description                            Default
  ------     -----------                            -------
  -interface ifIndex or range of ifIndex (, and -)  (all)
  -metrics   Metric or array of metrics to return   (all)
             eg:    -metrics => 'octets'
             eg:    -metrics => [octets, ...]
               (or) -metrics => \@mets
  -polling   The polling interval in seconds        10
  -recursive Variable with previous results         -none-

Interface utilizations consist of the following MIB entries:

  Multicasts   (packets/second in/out)
  Broadcasts   (packets/second in/out)
  Octets       (bits/second in/out)
  Unicasts     (packets/second in/out)
  Discards     (packets/second in/out)
  Errors       (packets/second in/out)
  Unknowns *   (packets/second in)

NOTE: Providing an above value for -metrics returns the In and Out utilization for the metric; except for Unknowns, which does not have an Out counter.

If successful, returns a pointer to a hash containing interface utilizations.

  $ifs->{1}->{'InMulticasts', 'OutMulticasts', 'InOctets', ...}
  $ifs->{2}->{'InMulticasts', 'OutMulticasts', 'InOctets', ...}
  ...
  $ifs->{n}->{'InMulticasts', 'OutMulticasts', 'InOctets', ...}

Notes on Interface Utilization

As previously mentioned, interface utilization is computed by retrieving interface metrics, waiting for a 'polling interval' of time, retrieving interface metrics again and calculating the difference (and other math in the case of octets). To accomplish this, the following is executed:

  User calls 'interface_utilization'

    'interface_utilization' method calls 'interface_metrics' method
    'interface_utilization' method waits for 'polling' seconds
    'interface_utilization' method calls 'interface_metrics' method
    'interface_utilization' method performs calculations and returns

  User program continues

This works well to get the interface utilization over a single polling interval. However, if the user program were to repeatedly obtain interface utilization statistics (for example, using a while() loop), this method can be improved.

Consider for example:

  my ($ifs, $recur);
  while (1) {
      ($ifs, $recur) = $cm->interface_utilization(
                                                  -recursive => $recur
                                                 );
      printf "%i\n", $ifs->{'1'}->{InOctets}
  }

The -recursive option along with an array return value ($ifs, $recur) allows the user to specify 2 return values: the first is the interface utilization statistics, the second is the interface metrics retrieved in the interface_utilization method's second call to the interface_metrics method. Upon first execution, this value is empty and the interface_utilization method calls interface_metrics twice. However, on subsequent calls to the interface_utilization method, it skips the first call to the interface_metrics method and just uses the previously obtained metrics found in $recur. This streamlines the utilization calculations by saving time, bandwidth and processing power on both the device running this script and the device under test.

To illustrate, assume we poll a device at 'T' polling intervals. We retrieve the metrics (M) at each interval and calculate the utilization (U) for each interval.

  |---- T ---|---- T ---|---- T ---|
  M1         M2         M3         M4

  Utilization 1 = M2 - M1
  Utilization 2 = M3 - M2
  Utilization 3 = M4 - M3

WITHOUT the -recursive option, the following less efficient (but still effective) operation occurs:

   |---- T ---||---- T ---||---- T ---|
  M1         M2M3        M4M5        M6

  Utilization 1 = M2 - M1
  Utilization 2 = M4 - M3
  Utilization 3 = M6 - M5

interface_updown() - admin up/down interface

  my $line = $cm->interface_updown([OPTIONS]);

Admin up or down the interface. Called with no arguments, admin up all interfaces. Called with one argument, interpreted as the interface(s) to admin up.

  Option     Description                            Default
  ------     -----------                            -------
  -operation 'up' or 'down'                         'up'
  -interface ifIndex or range of ifIndex (, and -)  (all)

To specify individual interfaces, provide their number:

  my $line = $cm->interface_updown(2);

Admin up ifIndex 2. To specify a range of interfaces, provide a range:

  my $line = $cm->interface_updown(
                                   -operation => 'down',
                                   -interface => '2-4,6,9-11'
                                  );

Admin down ifIndex 2 3 4 6 9 10 11.

If successful, returns a pointer to an array containing the interfaces admin up/down.

Line Options

The following methods are for line management. Lines on Cisco devices refer to console, auxillary and terminal lines for user interaction. These methods implement the OLD-CISCO-TS-MIB which is not available on some newer forms of IOS.

line_clear() - clear connection to line

  my $line = $cm->line_clear([OPTIONS]);

Clear the line (disconnect interactive session). Called with no arguments, clear all lines. Called with one argument, interpreted as the lines to clear.

  Option     Description                            Default
  ------     -----------                            -------
  -lines     Line or range of lines (, and -)       (all)

To specify individual lines, provide their number:

  my $line = $cm->line_clear(2);

Clear line 2. To specify a range of lines, provide a range:

  my $line = $cm->line_clear('2-4,6,9-11');

Clear lines 2 3 4 6 9 10 11.

If successful, returns a pointer to an array containing the lines cleared.

line_info() - return line info

  my $line = $cm->line_info();

Populate a data structure with line information. If successful, returns a pointer to a hash containing line information.

  $line->{0}->{'Number', 'TimeActive', ...}
  $line->{1}->{'Number', 'TimeActive', ...}
  ...
  $line->{n}->{'Number', 'TimeActive', ...}

line_sessions() - return session info for lines

  my $session = $cm->line_sessions();

Populate a data structure with the session information per line. If successful, returns a pointer to a hash containing session information.

  $sessions->{1}->[0]->{'Session', 'Type', 'Dir' ...}
                  [1]->{'Session', 'Type', 'Dir' ...}
                  ...
  ...
  $sessions->{n}->[0]->{'Session', 'Type', 'Dir' ...}

First hash value is the line number, next array is the list of current sessions per the line number.

line_message() - send message to line

  my $line = $cm->line_message([OPTIONS]);

Send a message to the line. With no arguments, a "Test Message" is sent to all lines. If 1 argument is provided, interpreted as the message to send to all lines. Valid options are:

  Option     Description                            Default
  ------     -----------                            -------
  -lines     Line or range of lines (, and -)       (all)
  -message   Double-quote delimited string          "Test Message"

If successful, returns a pointer to an array containing the lines messaged.

line_numberof() - return number of lines

  my $line = $cm->line_numberof();

If successful, returns the number of lines on the device.

Memory Info

The following methods are for memory utilization. These methods implement the CISCO-MEMORY-POOL-MIB.

memory_info() - return memory utilization info

  my $meminfo = $cm->memory_info();

Populate a data structure with memory information. If successful, returns a pointer to an array containing memory information.

  $meminfo->[0]->{'Name', 'Used', 'Free', ...}
  $meminfo->[1]->{'Name', 'Used', 'Free', ...}
  ...
  $meminfo->[n]->{'Name', 'Used', 'Free', ...}

Proxy Ping

The following methods are for proxy ping. These methods implement the CISCO-PING-MIB.

proxy_ping() - execute proxy ping

  my $ping = $cm->proxy_ping([OPTIONS]);

Send proxy ping from the object defined in $cm to the provided destination. Called with no options, sends the proxy ping to the localhost. Called with one argument, interpreted as the destination to proxy ping. Valid options are:

  Option     Description                            Default
  ------     -----------                            -------
  -host      Destination to send proxy ping to      (localhost)
  -count     Number of pings to send                1
  -size      Size of the ping packets in bytes      64
  -wait      Time to wait for replies in seconds    1
  -vrf       VRF name to source pings from          [none]

Allows the following methods to be called.

proxy_ping_sent() - return number of pings sent

  $ping->config_copy_sent();

Return the number of pings sent in the current proxy ping execution.

proxy_ping_received() - return number of pings received

  $ping->config_copy_received();

Return the number of pings received in the current proxy ping execution.

proxy_ping_minimum() - return minimum round trip time

  $ping->config_copy_minimum();

Return the minimum round trip time in milliseconds of pings sent and received in the current proxy ping execution.

proxy_ping_average() - return average round trip time

  $ping->config_copy_average();

Return the average round trip time in milliseconds of pings sent and received in the current proxy ping execution.

proxy_ping_maximum() - return maximum round trip time

  $ping->config_copy_maximum();

Return the maximum round trip time in milliseconds of pings sent and received in the current proxy ping execution.

System Info

The following methods implement the System MIB defined in SNMPv2-MIB.

system_info() - populate system info data structure.

  my $sysinfo = $cm->system_info();

Retrieve the system MIB information from the object defined in $cm.

Allows the following methods to be called.

system_info_description() - return system description

  $sysinfo->system_info_description();

Return the system description from the system info data structure.

system_info_objectID() - return system object ID

  $sysinfo->system_info_objectID();

Return the system object ID from the system info data structure.

system_info_uptime() - return system uptime

  $sysinfo->system_info_uptime();

Return the system uptime from the system info data structure.

system_info_contact() - return system contact

  $sysinfo->system_info_contact();

Return the system contact from the system info data structure.

system_info_name() - return system name

  $sysinfo->system_info_name();

Return the system name from the system info data structure.

system_info_location() - return system location

  $sysinfo->system_info_location();

Return the system location from the system info data structure.

system_info_services() - return system services

  $sysinfo->system_info_services([1]);

Return a pointer to an array containing the names of the system services from the system info data structure. For the raw number, use the optional boolean argument.

system_info_osversion() - return system OS version

  $sysinfo->system_info_osversion();

Return the system OS version as parsed from the sysDescr OID.

SUBROUTINES

Top

Password subroutines are for decrypting and encrypting Cisco type 7 passwords. The algorithm is freely available on the Internet on several sites; thus, I can/will NOT take credit or ANY liability for its use.

password_decrypt() - decrypt a Cisco type 7 password

  my $passwd = Cisco::Management->password_decrypt('00071A150754');

Where 00071A150754 is the encrypted Cisco password in this example.

password_encrypt() - encrypt a Cisco type 7 password

  my $passwd = Cisco::Management->password_encrypt('cleartext'[,# | *]);
  print "$_\n" for (@{$passwd});

Where cleartext is the clear text string to encrypt. The second optional argument is a number in the range of 0 - 52 inclusive or random text.

Returns a pointer to an array constructed based on the second argument to password_encrypt.

  Option  Description            Action
  ------  -----------            -------
          No argument provided   Return all 53 possible encryptions.
  #       Number 0-52 inclusive  Return password encrypted with # index.
  (other) Random text            Return a random password.

NOTE: Cisco routers by default only seem to use the first 16 indexes (0 - 15) to encrypt passwords. You notice this by looking at the first two characters of any type 7 encrypted password in a Cisco router configuration. However, testing on IOS 12.x and later shows that manually entering a password encrypted with a higher index (generated from this script) to a Cisco configuration will not only be allowed, but will function normally for authentication. This may be a form of "security through obscurity" given that some older Cisco password decrypters don't use the entire translation index and limit 'valid' passwords to those starting with the fist 16 indexes (0 - 15). Using passwords with an encryption index of 16 - 52 inclusive may render older Cisco password decrypters useless.

Additionally, the Cisco router command prompt seems to be limited to 254 characters, making the largest password 250 characters (254 - 4 characters for the pas (followed by space) command to enter the password).

EXPORT

Top

None by default.

EXAMPLES

Top

This distribution comes with several scripts (installed to the default bin install directory) that not only demonstrate example uses but also provide functional execution.

LICENSE

Top

This software is released under the same terms as Perl itself. If you don't know what that means visit http://perl.com/.

AUTHOR

Top

Copyright (C) Michael Vincent 2010

http://www.VinsWorld.com

All rights reserved


Cisco-Management documentation Contained in the Cisco-Management distribution.

package Cisco::Management;

########################################################
#
# AUTHOR = Michael Vincent
# www.VinsWorld.com
#
########################################################

require 5.005;

use strict;
use Exporter;

use Sys::Hostname;
use IO::Socket;
use Net::SNMP qw(:asn1 :snmp DEBUG_ALL);

our $VERSION      = '0.04';
our @ISA          = qw(Exporter);
our @EXPORT       = qw();
our %EXPORT_TAGS  = (
                     'password' => [qw(password_decrypt password_encrypt)],
                     'hashkeys' => [qw(@IPKEYS @IFKEYS @LINEKEYS @SESSIONKEYS @IFMETRICKEYS @IFMETRICRETKEYS)]
                    );
our @EXPORT_OK    = map {@{$EXPORT_TAGS{$_}}} keys(%EXPORT_TAGS);
$EXPORT_TAGS{ALL} = [ @EXPORT_OK ];

########################################################
# Start Variables
########################################################
# Cisco's XOR key
my @xlat = ( 0x64, 0x73, 0x66, 0x64, 0x3B, 0x6B, 0x66, 0x6F, 0x41, 0x2C, 
             0x2E, 0x69, 0x79, 0x65, 0x77, 0x72, 0x6B, 0x6C, 0x64, 0x4A, 
             0x4B, 0x44, 0x48, 0x53, 0x55, 0x42, 0x73, 0x67, 0x76, 0x63, 
             0x61, 0x36, 0x39, 0x38, 0x33, 0x34, 0x6E, 0x63, 0x78, 0x76, 
             0x39, 0x38, 0x37, 0x33, 0x32, 0x35, 0x34, 0x6B, 0x3B, 0x66, 
             0x67, 0x38, 0x37
           );

our @IFKEYS      = qw(Index Description Type MTU Speed PhysAddress AdminStatus OperStatus LastChange Duplex);
our @IPKEYS      = qw(IPAddress IPMask);

our @LINEKEYS    = qw(Active Type Autobaud SpeedIn SpeedOut Flow Modem Location Term ScrLen ScrWid Esc Tmo Sestmo Rotary Uses Nses User Noise Number TimeActive);
our @SESSIONKEYS = qw(Type Direction Address Name Current Idle Line);

our @IFMETRICKEYS    = qw(Multicasts Broadcasts Octets Unicasts Discards Errors Unknowns);
our @IFMETRICRETKEYS = qw(InMulticasts OutMulticasts InBroadcasts OutBroadcasts InOctets OutOctets InUnicasts OutUnicasts InDiscards OutDiscards InErrors OutErrors InUnknowns);

our $LASTERROR;
########################################################
# End Variables
########################################################

########################################################
# Start Public Module
########################################################

sub new {
    my $self = shift;
    my $class = ref($self) || $self;

    my %params = (
        community => 'private',
        port      => 161,
        timeout   => 10
    );

    my %args;
    if (@_ == 1) {
        ($params{'hostname'}) = @_
    } else {
        %args = @_;
        for (keys(%args)) {
            if (/^-?port$/i) {
                $params{'port'} = $args{$_}
            } elsif (/^-?community$/i) {
                $params{'community'} = $args{$_}
            } elsif ((/^-?hostname$/i) || (/^-?(?:de?st|peer)?addr$/i)) {
                $params{'hostname'} = $args{$_}
            } elsif (/^-?timeout$/i) {
                $params{'timeout'} = $args{$_}
            }
        }
    }

    my ($session, $error) = Net::SNMP->session(%params);

    if (!defined($session)) {
        $LASTERROR = "Error creating Net::SNMP object: $error";
        return(undef)
    }

    return bless {
                  %params,       # merge user parameters
                  '_SESSION_' => $session
                 }, $class
}

sub session {
    my $self = shift;
    return $self->{'_SESSION_'}
}

sub config_copy {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my $cc;
    foreach my $key (keys(%{$self})) {
        # everything but '_xxx_'
        $key =~ /^\_.+\_$/ and next;
        $cc->{$key} = $self->{$key}
    }

    my %params = (
        op         => 'wr',
        catos      => 0,
        source     => 4,
        dest       => 3,
        tftpserver => inet_ntoa((gethostbyname(hostname))[4])
    );

    my %args;
    if (@_ == 1) {
        $LASTERROR = "Insufficient number of args: @_";
        return(undef)
    } else {
        %args = @_;
        for (keys(%args)) {
            if ((/^-?(?:tftp)?server$/i) || (/^-?tftp$/)) {
                $params{'tftpserver'} = $args{$_}
            } elsif (/^-?catos$/i) {
                if ($args{$_} == 1) {
                    $params{'catos'} = 1
                }
            } elsif (/^-?source$/i) {
                if ($args{$_} =~ /^run(?:ning)?(?:-config)?$/i) {
                    $params{'source'} = 4
                } elsif ($args{$_} =~ /^start(?:up)?(?:-config)?$/i) {
                    $params{'source'} = 3
                } else {
                    $params{'source'} = 1;
                    $params{'op'}     = 'put';
                    $params{'file'}   = $args{$_}
                }
            } elsif (/^-?dest(?:ination)?$/i) {
                if ($args{$_} =~ /^run(?:ning)?(?:-config)?$/i) {
                    $params{'dest'} = 4
                } elsif ($args{$_} =~ /^start(?:up)?(?:-config)?$/i) {
                    $params{'dest'} = 3
                } else {
                    $params{'dest'} = 1;
                    $params{'op'}   = 'get';
                    $params{'file'} = $args{$_}
                }
            }
        }
    }
    $cc->{'_CONFIGCOPY_'}{'_params_'} = \%params;

    if ($params{'source'} == $params{'dest'}) {
        $LASTERROR = "Source and destination cannot be same";
        return(undef)
    }

    my $response;
    my $instance = int(rand(1024)+1024);
    my %err = (
        1 => "Unknown",
        2 => "Bad file name",
        3 => "Timeout",
        4 => "No memory",
        5 => "No config",
        6 => "Unsupported protocol",
        7 => "Config apply fail",
        8 => "System not ready",
        9 => "Request abort"
    );

    # wr mem
    if ($params{'op'} eq 'wr') {
        if ($params{'catos'}) {
            $LASTERROR = "Copy run start not allowed on CatOS";
            return(undef)
        }
        # ccCopyEntryRowStatus (5 = createAndWait, 6 = destroy)
        $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.14.' . $instance, INTEGER, 6);

        if (!defined($response)) {
            $LASTERROR = "[wr mem] NOT SUPPORTED - Trying old way";
            $response = $session->set_request('1.3.6.1.4.1.9.2.1.54.0', INTEGER, 1);
            if (defined($response)) {
                return bless $cc, $class
            } else {
                $LASTERROR = "[wr mem] FAILED (new/old)";
                return(undef)
            }
        }

          # ccCopySourceFileType (1 = networkFile, 3 = startupConfig, 4 = runningConfig)
        $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.3.' . $instance, INTEGER, $params{'source'});
          # ccCopyDestFileType (1 = networkFile, 3 = startupConfig, 4 = runningConfig)
        $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.4.' . $instance, INTEGER, $params{'dest'})

    # TFTP PUT (to device)
    } elsif ($params{'op'} eq 'put') {
        # CatOS
        if ($params{'catos'}) {
            $response = $session->set_request('1.3.6.1.4.1.9.5.1.5.1.0', OCTET_STRING, $params{'tftpserver'});
            $response = $session->set_request('1.3.6.1.4.1.9.5.1.5.2.0', OCTET_STRING, $params{'file'});
            $response = $session->set_request('1.3.6.1.4.1.9.5.1.5.3.0', INTEGER, 1);
            $response = $session->set_request('1.3.6.1.4.1.9.5.1.5.4.0', INTEGER, 2);
            if (defined($response)) {
                return bless $cc, $class
            } else {
                $LASTERROR = "[CatOS TFTP put] FAILED";
                return(undef)
            }

        # IOS
        } else {
            # ccCopyEntryRowStatus (5 = createAndWait, 6 = destroy)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.14.' . $instance, INTEGER, 6);
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.14.' . $instance, INTEGER, 5);

              # ccCopyProtocol (1 = TFTP)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.2.' . $instance, INTEGER, 1);

            if (!defined($response)) {
                $LASTERROR = "[IOS TFTP put] NOT SUPPORTED - Trying old way";
                $response = $session->set_request('1.3.6.1.4.1.9.2.1.50.' . $params{'tftpserver'}, OCTET_STRING, $params{'file'});
                if (defined($response)) {
                    return bless $cc, $class
                } else {
                    $LASTERROR = "[IOS TFTP put] FAILED (new/old)";
                    return(undef)
                }
            }
              # ccCopySourceFileType (1 = networkFile, 3 = startupConfig, 4 = runningConfig)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.3.' . $instance, INTEGER, 1);
              # ccCopyDestFileType (1 = networkFile, 3 = startupConfig, 4 = runningConfig)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.4.' . $instance, INTEGER, $params{'dest'});
              # New way
              # ccCopyServerAddressType (1 = IPv4, 2 = IPv6)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.15.' . $instance, INTEGER, 1);

            if (defined($response)) {
                  # ccCopyServerAddressRev1
                $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.16.' . $instance, OCTET_STRING, $params{'tftpserver'})
            } else {
                  # Deprecated
                  # ccCopyServerAddress
                $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.5.' . $instance, IPADDRESS, $params{'tftpserver'})
            }
              # ccCopyFileName
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.6.' . $instance, OCTET_STRING, $params{'file'})
        }

    # TFTP GET (from device)
    } elsif ($params{'op'} eq 'get') {
        # CatOS
        if ($params{'catos'}) {
            $response = $session->set_request('1.3.6.1.4.1.9.5.1.5.1.0', OCTET_STRING, $params{'tftpserver'});
            $response = $session->set_request('1.3.6.1.4.1.9.5.1.5.2.0', OCTET_STRING, $params{'file'});
            $response = $session->set_request('1.3.6.1.4.1.9.5.1.5.3.0', INTEGER, 1);
            $response = $session->set_request('1.3.6.1.4.1.9.5.1.5.4.0', INTEGER, 3);
            if (defined($response)) {
                return bless $cc, $class
            } else {
                $LASTERROR = "[CatOS TFTP get] FAILED";
                return(undef)
            }

        # IOS
        } else {
            # ccCopyEntryRowStatus (5 = createAndWait, 6 = destroy)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.14.' . $instance, INTEGER, 6);
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.14.' . $instance, INTEGER, 5);

              # ccCopyProtocol (1 = TFTP)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.2.' . $instance, INTEGER, 1);

            if (!defined($response)) {
                $LASTERROR = "[IOS TFTP get] NOT SUPPORTED - Trying old way";
                $response = $session->set_request('1.3.6.1.4.1.9.2.1.55.' . $params{'tftpserver'}, OCTET_STRING, $params{'file'});
                if (defined($response)) {
                    return bless $cc, $class
                } else {
                    $LASTERROR = "[IOS TFTP get] FAILED (new/old)";
                    return(undef)
                }
            }
              # ccCopySourceFileType (1 = networkFile, 3 = startupConfig, 4 = runningConfig)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.3.' . $instance, INTEGER, $params{'source'});
              # ccCopyDestFileType (1 = networkFile, 3 = startupConfig, 4 = runningConfig)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.4.' . $instance, INTEGER, 1);
              # New way
              # ccCopyServerAddressType (1 = IPv4, 2 = IPv6)
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.15.' . $instance, INTEGER, 1);

            if (defined($response)) {
                  # ccCopyServerAddressRev1
                $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.16.' . $instance, OCTET_STRING, $params{'tftpserver'})
            } else {
                  # Deprecated
                  # ccCopyServerAddress
                $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.5.' . $instance, IPADDRESS, $params{'tftpserver'})
            }
              # ccCopyFileName
            $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.6.' . $instance, OCTET_STRING, $params{'file'})
        }
    }
    # ccCopyEntryRowStatus (4 = createAndGo, 6 = destroy)
    $response = $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.14.' . $instance, INTEGER, 1);

    # Check status, wait done
    $response = $session->get_request('1.3.6.1.4.1.9.9.96.1.1.1.1.10.' . $instance);
    if (!defined($response)) {
        $LASTERROR = "NOT SUPPORTED (after setup)";
        return(undef)
    }
    while ($response->{'1.3.6.1.4.1.9.9.96.1.1.1.1.10.' . $instance} <= 2) {
        $response = $session->get_request('1.3.6.1.4.1.9.9.96.1.1.1.1.10.' . $instance)
    }
    # Success
    if ($response->{'1.3.6.1.4.1.9.9.96.1.1.1.1.10.' . $instance} == 3) {
        $response = $session->get_request('1.3.6.1.4.1.9.9.96.1.1.1.1.11.' . $instance);
        $cc->{'_CONFIGCOPY_'}{'StartTime'} = $response->{'1.3.6.1.4.1.9.9.96.1.1.1.1.11.' . $instance};
        $response = $session->get_request('1.3.6.1.4.1.9.9.96.1.1.1.1.12.' . $instance);
        $cc->{'_CONFIGCOPY_'}{'EndTime'}   = $response->{'1.3.6.1.4.1.9.9.96.1.1.1.1.12.' . $instance};
        $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.14.' . $instance, INTEGER, 6);
        return bless $cc, $class
    # Error
    } elsif ($response->{'1.3.6.1.4.1.9.9.96.1.1.1.1.10.' . $instance} == 4) {
        $response = $session->get_request('1.3.6.1.4.1.9.9.96.1.1.1.1.13.' . $instance);
        $session->set_request('1.3.6.1.4.1.9.9.96.1.1.1.1.14.' . $instance, INTEGER, 6);
        $LASTERROR = $err{$response->{'1.3.6.1.4.1.9.9.96.1.1.1.1.13.' . $instance}};
        return(undef)
    } else { 
        $LASTERROR = "Cannot determine success or failure";
        return(undef)
    }
}

sub config_copy_starttime {
    my $self = shift;
    return $self->{'_CONFIGCOPY_'}{'StartTime'}
}

sub config_copy_endtime {
    my $self = shift;
    return $self->{'_CONFIGCOPY_'}{'EndTime'}
}

sub cpu_info {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my ($type, $cpu5min);
    # IOS releases < 12.0(3)T
    if (($cpu5min = &_snmpgetnext($session,"1.3.6.1.4.1.9.2.1.58")) && (defined($cpu5min->[0]))) {
        $type = 1
    # 12.0(3)T < IOS releases < 12.2(3.5)
    } elsif (($cpu5min = &_snmpgetnext($session,"1.3.6.1.4.1.9.9.109.1.1.1.1.5")) && (defined($cpu5min->[0]))) {
        $type = 2
    # IOS releases > 12.2(3.5)
    } elsif (($cpu5min = &_snmpgetnext($session,"1.3.6.1.4.1.9.9.109.1.1.1.1.8")) && (defined($cpu5min->[0]))) {
        $type = 3
    } else {
        $LASTERROR = "Cannot determine CPU type";
        return(undef)
    }

    my %cpuType = (
        1 => 'IOS releases < 12.0(3)T',
        2 => '12.0(3)T < IOS releases < 12.2(3.5)',
        3 => 'IOS releases > 12.2(3.5)'
    );

    my @cpuName;
    # Get multiple CPU names
    if ($type > 1) {
        my $temp = &_snmpgetnext($session,"1.3.6.1.4.1.9.9.109.1.1.1.1.2");
        for (0..$#{$temp}) {
            if (defined(my $result = $session->get_request( -varbindlist => ['1.3.6.1.2.1.47.1.1.1.1.7.' . $temp->[$_]] ))) {
                $cpuName[$_] = $result->{'1.3.6.1.2.1.47.1.1.1.1.7.' . $temp->[$_]}
            } else {
                $LASTERROR = "Cannot get CPU name for type: $cpuType{$type}";
                return(undef)
            }
        }
    }

    my ($cpu5sec, $cpu1min);
    if ($type == 1) {
        $cpu5min = &_snmpgetnext($session,"1.3.6.1.4.1.9.2.1.58");
        $cpu5sec = &_snmpgetnext($session,"1.3.6.1.4.1.9.2.1.56");
        $cpu1min = &_snmpgetnext($session,"1.3.6.1.4.1.9.2.1.57")
    } elsif ($type == 2) {
        $cpu5min = &_snmpgetnext($session,"1.3.6.1.4.1.9.9.109.1.1.1.1.5");
        $cpu5sec = &_snmpgetnext($session,"1.3.6.1.4.1.9.9.109.1.1.1.1.3");
        $cpu1min = &_snmpgetnext($session,"1.3.6.1.4.1.9.9.109.1.1.1.1.4")
    } elsif ($type == 3) {
        $cpu5min = &_snmpgetnext($session,"1.3.6.1.4.1.9.9.109.1.1.1.1.8");
        $cpu5sec = &_snmpgetnext($session,"1.3.6.1.4.1.9.9.109.1.1.1.1.6");
        $cpu1min = &_snmpgetnext($session,"1.3.6.1.4.1.9.9.109.1.1.1.1.7")
    } else { } 

    my @CPUInfo;
    for my $cpu (0..$#{$cpu5min}) {
        my %CPUInfoHash;
        $CPUInfoHash{'Name'}   = $cpuName[$cpu];
        $CPUInfoHash{'5sec'}   = $cpu5sec->[$cpu];
        $CPUInfoHash{'1min'}   = $cpu1min->[$cpu];
        $CPUInfoHash{'5min'}   = $cpu5min->[$cpu];
        $CPUInfoHash{'_type_'} = $cpuType{$type};
        push @CPUInfo, \%CPUInfoHash
    }
    return \@CPUInfo
}

sub interface_getbyindex {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my $uIfx;
    my %args;
    if (@_ == 1) {
        ($uIfx) = @_;
        if ($uIfx !~ /^\d+$/) {
            $LASTERROR = "Not a valid ifIndex: $uIfx";
            return(undef)
        }
    } else {
        %args = @_;
        for (keys(%args)) {
            if ((/^-?interface$/i) || (/^-?index$/i)) {
                if ($args{$_} =~ /^\d+$/) {
                    $uIfx = $args{$_}
                } else {
                    $LASTERROR = "Not a valid ifIndex: $args{$_}";
                    return(undef)
                }
            }
        }
    }
    if (!defined($uIfx)) {
        $LASTERROR = "No ifIndex provided";
        return(undef)
    }
    my $rIf  = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.2');
    if (!defined($rIf)) {
        $LASTERROR = "Cannot get interface names from device";
        return(undef)
    }
    my $rIfx = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.1');

    for (0..$#{$rIfx}) {
        if ($rIfx->[$_] == $uIfx) {
            return $rIf->[$_]
        }
    }
    $LASTERROR = "Cannot find interface for ifIndex: $uIfx";
    return(undef)
}

sub interface_getbyname {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my %params = (
        'index' => 0
    );

    my %args;
    if (@_ == 1) {
        ($params{'uIf'}) = @_;
    } else {
        %args = @_;
        for (keys(%args)) {
            if (/^-?interface$/i) {
                $params{'uIf'} = $args{$_}
            } elsif (/^-?index$/i) {
                if ($args{$_} == 1) {
                    $params{'index'} = 1
                }
            }
        }
    }
    if (!exists($params{'uIf'})) {
        $LASTERROR = "No interface provided";
        return(undef)
    }

    my $rIf  = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.2');
    if (!defined($rIf)) {
        $LASTERROR = "Cannot get interface names from device";
        return(undef)
    }
    my $rIfx = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.1');

    # user Provided
    my @parts = split /([0-9])/, $params{'uIf'}, 2;
    my $uIfNamePart =  shift @parts;
    my $uIfNumPart  =  "@parts";
       $uIfNumPart  =~ s/\s+//;

    my @matches;
    my $idx;
    for (0..$#{$rIf}) {
        # Real Names
        @parts = split /([0-9])/, $rIf->[$_], 2;
        my $rIfNamePart =  shift @parts;
        my $rIfNumPart  =  "@parts";
           $rIfNumPart  =~ s/\s+//;
        if (($rIfNamePart =~ /^$uIfNamePart/i) && ($rIfNumPart eq $uIfNumPart)) {
            push @matches, $rIf->[$_];
            $idx = $rIfx->[$_]
        }
    }
    if (@matches == 1) {
        if ($params{'index'} == 0) {
            return "@matches"
        } else {
            return $idx
        }
    } elsif (@matches == 0) {
        $LASTERROR = "Cannot find interface: $params{'uIf'}";
        return(undef)
    } else {
        print "Interface $params{'uIf'} not specific - [@matches]";
        return(undef)
    }
}

sub interface_info {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my %params = (
        'ifs' => [-1]
    );

    my %args;
    if (@_ == 1) {
        ($params{'ifs'}) = @_;
        if (!defined($params{'ifs'} = _get_range($params{'ifs'}))) {
            return(undef)
        }
    } else {
        %args = @_;
        for (keys(%args)) {
            if (/^-?interface(?:s)?$/i) {
                if (!defined($params{'ifs'} = _get_range($args{$_}))) {
                    return(undef)
                }
            }
        }
    }

    my %IfInfo;
    for my $ifs (@{$params{'ifs'}}) {

        my $interface;
        if ($ifs == -1) {
            $interface = ''
        } else {
            $interface = '.' . $ifs
        }

        my %ret;
        for my $oid (1..$#IFKEYS) {
            $ret{$IFKEYS[$oid-1]} = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.' . $oid . $interface);
            if (!defined($ret{$IFKEYS[$oid-1]})) {
                $LASTERROR = "Cannot get interface info: 1.3.6.1.2.1.2.2.1.$oid$interface";
                return(undef)
            }
        }
        # Duplex is different OID
        $ret{$IFKEYS[9]} = &_snmpgetnext($session, '1.3.6.1.2.1.10.7.2.1.19' . $interface);

        my %UpDownStatus = (
            1 => 'UP',
            2 => 'DOWN',
            3 => 'TEST',
            4 => 'UNKNOWN',
            5 => 'DORMANT',
            6 => 'NOTPRESENT',
            7 => 'LOWLAYERDOWN'
        );
        my %DuplexType = (
            1 => 'UNKNOWN',
            2 => 'HALF',
            3 => 'FULL'
        );
        for my $idx (0..$#{$ret{$IFKEYS[0]}}) {
            my %IfInfoHash;
            $IfInfoHash{$IFKEYS[0]} = $ret{$IFKEYS[0]}->[$idx];
            $IfInfoHash{$IFKEYS[1]} = $ret{$IFKEYS[1]}->[$idx];
            $IfInfoHash{$IFKEYS[2]} = $ret{$IFKEYS[2]}->[$idx];
            $IfInfoHash{$IFKEYS[3]} = $ret{$IFKEYS[3]}->[$idx];
            $IfInfoHash{$IFKEYS[4]} = $ret{$IFKEYS[4]}->[$idx];
            $IfInfoHash{$IFKEYS[5]} = ($ret{$IFKEYS[5]}->[$idx] =~ /^\0/) ? unpack('H12', $ret{$IFKEYS[5]}->[$idx]) : (($ret{$IFKEYS[5]}->[$idx] =~ /^0x/) ? substr($ret{$IFKEYS[5]}->[$idx],2) : $ret{$IFKEYS[5]}->[$idx]);
            $IfInfoHash{$IFKEYS[6]} = exists($UpDownStatus{$ret{$IFKEYS[6]}->[$idx]}) ? $UpDownStatus{$ret{$IFKEYS[6]}->[$idx]} : $ret{$IFKEYS[6]}->[$idx];
            $IfInfoHash{$IFKEYS[7]} = exists($UpDownStatus{$ret{$IFKEYS[7]}->[$idx]}) ? $UpDownStatus{$ret{$IFKEYS[7]}->[$idx]} : $ret{$IFKEYS[7]}->[$idx];
            $IfInfoHash{$IFKEYS[8]} = $ret{$IFKEYS[8]}->[$idx];
            if (defined($ret{$IFKEYS[9]}->[$idx])) {
                $IfInfoHash{$IFKEYS[9]} = exists($DuplexType{$ret{$IFKEYS[9]}->[$idx]}) ? $DuplexType{$ret{$IFKEYS[9]}->[$idx]} : $ret{$IFKEYS[9]}->[$idx];
            } else {
                $IfInfoHash{$IFKEYS[9]} = '';
            }
            $IfInfo{$ret{$IFKEYS[0]}->[$idx]} = bless \%IfInfoHash
        }
    }
    return bless \%IfInfo, $class
}

sub interface_ip {
    my ($self, $arg) = @_;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    # IP Info
    my $IPIndex   = &_snmpgetnext($session, '1.3.6.1.2.1.4.20.1.2');
    if (!defined($IPIndex)) {
        $LASTERROR = "Cannot get interface IP info";
        return(undef)
    }
    my $IPAddress = &_snmpgetnext($session, '1.3.6.1.2.1.4.20.1.1');
    my $IPMask    = &_snmpgetnext($session, '1.3.6.1.2.1.4.20.1.3');

    my %mask = (
        "0.0.0.0"         => 0,  "128.0.0.0"       => 1,  "192.0.0.0"       => 2,
        "224.0.0.0"       => 3,  "240.0.0.0"       => 4,  "248.0.0.0"       => 5,
        "252.0.0.0"       => 6,  "254.0.0.0"       => 7,  "255.0.0.0"       => 8,
        "255.128.0.0"     => 9,  "255.192.0.0"     => 10, "255.224.0.0"     => 11,
        "255.240.0.0"     => 12, "255.248.0.0"     => 13, "255.252.0.0"     => 14,
        "255.254.0.0"     => 15, "255.255.0.0"     => 16, "255.255.128.0"   => 17,
        "255.255.192.0"   => 18, "255.255.224.0"   => 19, "255.255.240.0"   => 20,
        "255.255.248.0"   => 21, "255.255.252.0"   => 22, "255.255.254.0"   => 23,
        "255.255.255.0"   => 24, "255.255.255.128" => 25, "255.255.255.192" => 26,
        "255.255.255.224" => 27, "255.255.255.240" => 28, "255.255.255.248" => 29,
        "255.255.255.252" => 30, "255.255.255.254" => 31, "255.255.255.255" => 32
    );

    my %IPInfo;
    for (0..$#{$IPIndex}) {
        my %IPInfoHash;
        $IPInfoHash{$IPKEYS[0]} = $IPAddress->[$_];
        if (defined($arg) && ($arg >= 1)) {
            $IPInfoHash{$IPKEYS[1]} = $mask{$IPMask->[$_]}
        } else {
            $IPInfoHash{$IPKEYS[1]} = $IPMask->[$_]
        }
        push @{$IPInfo{$IPIndex->[$_]}}, \%IPInfoHash
    }
    return bless \%IPInfo, $class
}

sub interface_metrics {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my %params = (
        'ifs' => [-1],
    );
    for (@IFMETRICKEYS) {
        $params{$_} = 1
    }

    my %args;
    if (@_ == 1) {
        ($params{'ifs'}) = @_;
        if (!defined($params{'ifs'} = _get_range($params{'ifs'}))) {
            return(undef)
        }
    } else {
        %args = @_;
        for (keys(%args)) {
            if (/^-?interface(?:s)?$/i) {
                if (!defined($params{'ifs'} = _get_range($args{$_}))) {
                    return(undef)
                }
            } elsif (/^-?metric(?:s)?$/i) {
                for (@IFMETRICKEYS) {
                    $params{$_} = 0
                }
                if (ref($args{$_}) eq 'ARRAY') {
                    $params{'oids'} = '';
                    for my $mets (@{$args{$_}}) {
                        if (exists($params{ucfirst(lc($mets))})) {
                            $params{ucfirst(lc($mets))} = 1
                        } else {
                            $LASTERROR = "Invalid metric: $mets";
                            return(undef)
                        }
                    }
                } else {
                    $params{'oids'} = '';
                    if (exists($params{ucfirst(lc($args{$_}))})) {
                        $params{ucfirst(lc($args{$_}))} = 1
                    } else {
                        $LASTERROR = "Invalid metric: $args{$_}";
                        return(undef)
                    }
                }
            }
        }
    }

    my %IfMetric;
    for my $ifs (@{$params{'ifs'}}) {

        my $interface;
        if ($ifs == -1) {
            $interface = ''
        } else {
            $interface = '.' . $ifs
        }

        my %ret;
        $ret{'Index'} = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.1' . $interface);
        if (!defined($ret{'Index'})) {
            $LASTERROR = "Cannot get ifIndex: $interface";
            return(undef)
        }
        if ($params{'Multicasts'}) {
            $ret{'InMulticasts'}  = &_snmpgetnext($session, '1.3.6.1.2.1.31.1.1.1.2' . $interface);
            if (!defined($ret{'InMulticasts'})) {
                $LASTERROR = "Cannot get InMulticasts interface: $interface";
                return(undef)
            }
            $ret{'OutMulticasts'} = &_snmpgetnext($session, '1.3.6.1.2.1.31.1.1.1.4' . $interface);
            if (!defined($ret{'OutMulticasts'})) {
                $LASTERROR = "Cannot get OutMulticasts interface: $interface";
                return(undef)
            }
        }
        if ($params{'Broadcasts'}) {
            $ret{'InBroadcasts'}  = &_snmpgetnext($session, '1.3.6.1.2.1.31.1.1.1.3' . $interface);
            if (!defined($ret{'InBroadcasts'})) {
                $LASTERROR = "Cannot get InBroadcasts interface: $interface";
                return(undef)
            }
            $ret{'OutBroadcasts'} = &_snmpgetnext($session, '1.3.6.1.2.1.31.1.1.1.5' . $interface);
            if (!defined($ret{'OutBroadcasts'})) {
                $LASTERROR = "Cannot get OutBroadcasts interface: $interface";
                return(undef)
            }
        }
        if ($params{'Octets'}) {
            $ret{'InOctets'}  = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.10' . $interface);
            if (!defined($ret{'InOctets'})) {
                $LASTERROR = "Cannot get InOctets interface: $interface";
                return(undef)
            }
            $ret{'OutOctets'} = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.16' . $interface);
            if (!defined($ret{'OutOctets'})) {
                $LASTERROR = "Cannot get OutOctets interface: $interface";
                return(undef)
            }
        }
        if ($params{'Unicasts'}) {
            $ret{'InUnicasts'}  = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.11' . $interface);
            if (!defined($ret{'InUnicasts'})) {
                $LASTERROR = "Cannot get InUnicasts interface: $interface";
                return(undef)
            }
            $ret{'OutUnicasts'} = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.17' . $interface);
            if (!defined($ret{'OutUnicasts'})) {
                $LASTERROR = "Cannot get OutUnicasts interface: $interface";
                return(undef)
            }
        }
        if ($params{'Discards'}) {
            $ret{'InDiscards'}  = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.13' . $interface);
            if (!defined($ret{'InDiscards'})) {
                $LASTERROR = "Cannot get InDiscards interface: $interface";
                return(undef)
            }
            $ret{'OutDiscards'} = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.19' . $interface);
            if (!defined($ret{'OutDiscards'})) {
                $LASTERROR = "Cannot get OutDiscards interface: $interface";
                return(undef)
            }
        }
        if ($params{'Errors'}) {
            $ret{'InErrors'}  = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.14' . $interface);
            if (!defined($ret{'InErrors'})) {
                $LASTERROR = "Cannot get InErrors interface: $interface";
                return(undef)
            }
            $ret{'OutErrors'} = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.20' . $interface);
            if (!defined($ret{'OutErrors'})) {
                $LASTERROR = "Cannot get OutErrors interface: $interface";
                return(undef)
            }
        }
        if ($params{'Unknowns'}) {
            $ret{'InUnknowns'}  = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.15' . $interface);
            if (!defined($ret{'InUnknowns'})) {
                $LASTERROR = "Cannot get InUnknowns interface: $interface";
                return(undef)
            }
        }

        for my $idx (0..$#{$ret{'Index'}}) {
            my %IfMetricHash;
            $IfMetricHash{'InMulticasts'}  = $ret{'InMulticasts'}->[$idx];
            $IfMetricHash{'OutMulticasts'} = $ret{'OutMulticasts'}->[$idx];
            $IfMetricHash{'InBroadcasts'}  = $ret{'InBroadcasts'}->[$idx];
            $IfMetricHash{'OutBroadcasts'} = $ret{'OutBroadcasts'}->[$idx];
            $IfMetricHash{'InOctets'}      = $ret{'InOctets'}->[$idx];
            $IfMetricHash{'OutOctets'}     = $ret{'OutOctets'}->[$idx];
            $IfMetricHash{'InUnicasts'}    = $ret{'InUnicasts'}->[$idx];
            $IfMetricHash{'OutUnicasts'}   = $ret{'OutUnicasts'}->[$idx];
            $IfMetricHash{'InDiscards'}    = $ret{'InDiscards'}->[$idx];
            $IfMetricHash{'OutDiscards'}   = $ret{'OutDiscards'}->[$idx];
            $IfMetricHash{'InErrors'}      = $ret{'InErrors'}->[$idx];
            $IfMetricHash{'OutErrors'}     = $ret{'OutErrors'}->[$idx];
            $IfMetricHash{'InUnknowns'}    = $ret{'InUnknowns'}->[$idx];
            $IfMetric{$ret{'Index'}->[$idx]} = bless \%IfMetricHash
        }
    }
    return bless \%IfMetric, $class
}

sub interface_utilization {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my %params = (
        'polling' => 10
    );

    my %args;
    if (@_ != 1) {
        %args = @_;
        for (keys(%args)) {
            if ((/^-?polling$/i) || (/^-?interval$/i)) {
                if (($args{$_} =~ /^\d+$/) && ($args{$_} > 0)) {
                    $params{'polling'} = $args{$_}
                } else {
                    $LASTERROR = "Incorrect polling interval: $args{$_}";
                    return(undef)
                }
            } elsif (/^-?recursive$/i) {
                $params{'recur'} = $args{$_}
            }
        }
    }

    my $prev;
    if (exists($params{'recur'}) && (ref($params{'recur'}) eq __PACKAGE__)) {
        $prev = $params{'recur'}
    } else {
        if (!defined($prev = $self->interface_metrics(@_))) {
            $LASTERROR = "Cannot get initial utilization: " . $LASTERROR;
            return(undef)
        }
    }
    sleep $params{'polling'};
    my $curr;
    if (!defined($curr = $self->interface_metrics(@_))) {
        $LASTERROR = "Cannot get current utilization: " . $LASTERROR;
        return(undef)
    }

    my %IfUtil;
    for my $ifs (sort {$a <=> $b} (keys(%{$prev}))) {
        my %IfUtilHash;
        $IfUtilHash{'InMulticasts'}  = defined($curr->{$ifs}->{'InMulticasts'}) ? ($curr->{$ifs}->{'InMulticasts'} - $prev->{$ifs}->{'InMulticasts'}) / $params{'polling'} : undef;
        $IfUtilHash{'OutMulticasts'} = defined($curr->{$ifs}->{'OutMulticasts'}) ? ($curr->{$ifs}->{'OutMulticasts'} - $prev->{$ifs}->{'OutMulticasts'}) / $params{'polling'} : undef;
        $IfUtilHash{'InBroadcasts'}  = defined($curr->{$ifs}->{'InBroadcasts'}) ? ($curr->{$ifs}->{'InBroadcasts'} - $prev->{$ifs}->{'InBroadcasts'}) / $params{'polling'} : undef;
        $IfUtilHash{'OutBroadcasts'} = defined($curr->{$ifs}->{'OutBroadcasts'}) ? ($curr->{$ifs}->{'OutBroadcasts'} - $prev->{$ifs}->{'OutBroadcasts'}) / $params{'polling'} : undef;
        $IfUtilHash{'InOctets'}      = defined($curr->{$ifs}->{'InOctets'}) ? (($curr->{$ifs}->{'InOctets'} - $prev->{$ifs}->{'InOctets'}) * 8) / $params{'polling'} : undef;
        $IfUtilHash{'OutOctets'}     = defined($curr->{$ifs}->{'OutOctets'}) ? (($curr->{$ifs}->{'OutOctets'} - $prev->{$ifs}->{'OutOctets'}) * 8) / $params{'polling'} : undef;
        $IfUtilHash{'InUnicasts'}    = defined($curr->{$ifs}->{'InUnicasts'}) ? ($curr->{$ifs}->{'InUnicasts'} - $prev->{$ifs}->{'InUnicasts'}) / $params{'polling'} : undef;
        $IfUtilHash{'OutUnicasts'}   = defined($curr->{$ifs}->{'OutUnicasts'}) ? ($curr->{$ifs}->{'OutUnicasts'} - $prev->{$ifs}->{'OutUnicasts'}) / $params{'polling'} : undef;
        $IfUtilHash{'InDiscards'}    = defined($curr->{$ifs}->{'InDiscards'}) ? ($curr->{$ifs}->{'InDiscards'} - $prev->{$ifs}->{'InDiscards'}) / $params{'polling'} : undef;
        $IfUtilHash{'OutDiscards'}   = defined($curr->{$ifs}->{'OutDiscards'}) ? ($curr->{$ifs}->{'OutDiscards'} - $prev->{$ifs}->{'OutDiscards'}) / $params{'polling'} : undef;
        $IfUtilHash{'InErrors'}      = defined($curr->{$ifs}->{'InErrors'}) ? ($curr->{$ifs}->{'InErrors'} - $prev->{$ifs}->{'InErrors'}) / $params{'polling'} : undef;
        $IfUtilHash{'OutErrors'}     = defined($curr->{$ifs}->{'OutErrors'}) ? ($curr->{$ifs}->{'OutErrors'} - $prev->{$ifs}->{'OutErrors'}) / $params{'polling'} : undef;
        $IfUtilHash{'InUnknowns'}    = defined($curr->{$ifs}->{'InUnknowns'}) ? ($curr->{$ifs}->{'InUnknowns'} - $prev->{$ifs}->{'InUnknowns'}) / $params{'polling'} : undef;
        $IfUtil{$ifs} = bless \%IfUtilHash
    }
    $prev = bless \%IfUtil, $class;
    return wantarray ? ($prev, $curr) : $prev 
}

sub interface_updown {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my %op     = (
        'UP'   => 1,
        'DOWN' => 2
    );
    my %params = (
        'oper' => $op{'UP'}
    );

    my %args;
    my $oper = 'UP';
    if (@_ == 1) {
        ($params{'ifs'}) = @_;
        if (!defined($params{'ifs'} = _get_range($params{'ifs'}))) {
            return(undef)
        }
    } else {
        %args = @_;
        for (keys(%args)) {
            if (/^-?interface(?:s)?$/i) {
                if (!defined($params{'ifs'} = _get_range($args{$_}))) {
                    return(undef)
                }
            } elsif ((/^-?operation$/i) || (/^-?command$/i)) {
                if (exists($op{uc($args{$_})})) {
                    $params{'oper'} = $op{uc($args{$_})};
                    $oper = uc($args{$_})
                } else {
                    $LASTERROR = "Undefined operation";
                    return(undef)
                }
            }
        }
    }

    if (!defined($params{'ifs'})) {
        $params{'ifs'} = &_snmpgetnext($session, '1.3.6.1.2.1.2.2.1.1');
        if (!defined($params{'ifs'})) {
            $LASTERROR = "Cannot get interfaces to $oper";
            return(undef)
        }
    }

    my @intf;
    for (@{$params{'ifs'}}) {
        if (defined($session->set_request('1.3.6.1.2.1.2.2.1.7.' . $_, INTEGER, $params{'oper'}))) {
            push @intf, $_
        } else {
            $LASTERROR = "Failed to $oper interface $_";
            return(undef)
        }
    }
    return \@intf
}

sub line_clear {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my %params;
    my %args;
    if (@_ == 1) {
        ($params{'lines'}) = @_;
        if (!defined($params{'lines'} = _get_range($params{'lines'}))) {
            return(undef)
        }
    } else {
        %args = @_;
        for (keys(%args)) {
            if ((/^-?range$/i) || (/^-?line(?:s)?$/i)) {
                if (!defined($params{'lines'} = _get_range($args{$_}))) {
                    return(undef)
                }
            }
        }
    }

    if (!defined($params{'lines'})) {
        $params{'lines'} = &_snmpgetnext($session, '1.3.6.1.4.1.9.2.9.2.1.20');
        if (!defined($params{'lines'})) {
            $LASTERROR = "Cannot get lines to clear";
            return(undef)
        }
    }

    my @lines;
    for (@{$params{'lines'}}) {
        if (defined($session->set_request('1.3.6.1.4.1.9.2.9.10.0', INTEGER, $_))) {
            push @lines, $_
        } else {
            $LASTERROR = "Failed to clear line $_";
            return(undef)
        }
    }
    return \@lines
}

sub line_info {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my %ret;
    for my $oid (1..$#LINEKEYS + 1) {
        $ret{$LINEKEYS[$oid-1]} = &_snmpgetnext($session, '1.3.6.1.4.1.9.2.9.2.1.' . $oid);
        if (!defined($ret{$LINEKEYS[$oid-1]})) {
            $LASTERROR = "Cannot get line info: 1.3.6.1.4.1.9.2.9.2.1.$oid";
            return(undef)
        }
    }

    my %LineTypes = (
        2 => 'CON',
        3 => 'TRM',
        4 => 'LNP',
        5 => 'VTY',
        6 => 'AUX'
    );
    my %LineModem = (
        2 => 'none',
        3 => 'callin',
        4 => 'callout',
        5 => 'cts-reqd',
        6 => 'ri-is-cd',
        7 => 'inout'
    );
    my %LineFlow = (
        2 => 'none',
        3 => 'sw-in',
        4 => 'sw-out',
        5 => 'sw-both',
        6 => 'hw-in',
        7 => 'hw-out',
        8 => 'hw-both'
    );
    my %LineInfo;
    for my $lines (0..$#{$ret{$LINEKEYS[19]}}) {
        my %LineInfoHash;
        $LineInfoHash{$LINEKEYS[20]} = $ret{$LINEKEYS[20]}->[$lines];
        $LineInfoHash{$LINEKEYS[19]} = $ret{$LINEKEYS[19]}->[$lines];
        $LineInfoHash{$LINEKEYS[18]} = $ret{$LINEKEYS[18]}->[$lines];
        $LineInfoHash{$LINEKEYS[17]} = $ret{$LINEKEYS[17]}->[$lines];
        $LineInfoHash{$LINEKEYS[16]} = $ret{$LINEKEYS[16]}->[$lines];
        $LineInfoHash{$LINEKEYS[15]} = $ret{$LINEKEYS[15]}->[$lines];
        $LineInfoHash{$LINEKEYS[14]} = $ret{$LINEKEYS[14]}->[$lines];
        $LineInfoHash{$LINEKEYS[13]} = $ret{$LINEKEYS[13]}->[$lines];
        $LineInfoHash{$LINEKEYS[12]} = $ret{$LINEKEYS[12]}->[$lines];
        $LineInfoHash{$LINEKEYS[11]} = $ret{$LINEKEYS[11]}->[$lines];
        $LineInfoHash{$LINEKEYS[10]} = $ret{$LINEKEYS[10]}->[$lines];
        $LineInfoHash{$LINEKEYS[9]}  = $ret{$LINEKEYS[9]}->[$lines];
        $LineInfoHash{$LINEKEYS[8]}  = $ret{$LINEKEYS[8]}->[$lines];
        $LineInfoHash{$LINEKEYS[7]}  = $ret{$LINEKEYS[7]}->[$lines];
        $LineInfoHash{$LINEKEYS[6]}  = exists($LineModem{$ret{$LINEKEYS[6]}->[$lines]}) ? $LineModem{$ret{$LINEKEYS[6]}->[$lines]} : $ret{$LINEKEYS[6]}->[$lines];
        $LineInfoHash{$LINEKEYS[5]}  = exists($LineFlow{$ret{$LINEKEYS[5]}->[$lines]}) ? $LineFlow{$ret{$LINEKEYS[5]}->[$lines]} : $ret{$LINEKEYS[5]}->[$lines];
        $LineInfoHash{$LINEKEYS[4]}  = $ret{$LINEKEYS[4]}->[$lines];
        $LineInfoHash{$LINEKEYS[3]}  = $ret{$LINEKEYS[3]}->[$lines];
        $LineInfoHash{$LINEKEYS[2]}  = $ret{$LINEKEYS[2]}->[$lines];
        $LineInfoHash{$LINEKEYS[1]}  = exists($LineTypes{$ret{$LINEKEYS[1]}->[$lines]}) ? $LineTypes{$ret{$LINEKEYS[1]}->[$lines]} : $ret{$LINEKEYS[1]}->[$lines];
        $LineInfoHash{$LINEKEYS[0]}  = $ret{$LINEKEYS[0]}->[$lines];
        $LineInfo{$ret{$LINEKEYS[19]}->[$lines]} = bless \%LineInfoHash
    }
    return bless \%LineInfo, $class
}

sub line_sessions {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my %ret;
    for my $oid (1..$#SESSIONKEYS + 1) {
        $ret{$SESSIONKEYS[$oid-1]} = &_snmpgetnext($session, '1.3.6.1.4.1.9.2.9.3.1.' . $oid);
        if (!defined($ret{$SESSIONKEYS[$oid-1]})) {
            $LASTERROR = "Cannot get session info: 1.3.6.1.4.1.9.2.9.3.1.$oid";
            return(undef)
        }
    }

    my %SessionTypes = (
        1 => 'unknown',
        2 => 'PAD',
        3 => 'stream',
        4 => 'rlogin',
        5 => 'telnet',
        6 => 'TCP',
        7 => 'LAT',
        8 => 'MOP',
        9 => 'SLIP',
        10 => 'XRemote',
        11 => 'rshell'
    );
    my %SessionDir = (
        1 => 'unknown',
        2 => 'IN',
        3 => 'OUT'
    );
    my %SessionInfo;
    for my $sess (0..$#{$ret{$SESSIONKEYS[6]}}) {
        my %SessionInfoHash;
        $SessionInfoHash{$SESSIONKEYS[6]} = $ret{$SESSIONKEYS[6]}->[$sess];
        $SessionInfoHash{$SESSIONKEYS[5]} = $ret{$SESSIONKEYS[5]}->[$sess];
        $SessionInfoHash{$SESSIONKEYS[4]} = $ret{$SESSIONKEYS[4]}->[$sess];
        $SessionInfoHash{$SESSIONKEYS[3]} = $ret{$SESSIONKEYS[3]}->[$sess];
        $SessionInfoHash{$SESSIONKEYS[2]} = $ret{$SESSIONKEYS[2]}->[$sess];
        $SessionInfoHash{$SESSIONKEYS[1]} = exists($SessionDir{$ret{$SESSIONKEYS[1]}->[$sess]}) ? $SessionDir{$ret{$SESSIONKEYS[1]}->[$sess]} : $ret{$SESSIONKEYS[1]}->[$sess];
        $SessionInfoHash{$SESSIONKEYS[0]} = exists($SessionTypes{$ret{$SESSIONKEYS[0]}->[$sess]}) ? $SessionTypes{$ret{$SESSIONKEYS[0]}->[$sess]} : $ret{$SESSIONKEYS[0]}->[$sess];
        push @{$SessionInfo{$ret{$SESSIONKEYS[6]}->[$sess]}}, \%SessionInfoHash
    }
    return bless \%SessionInfo, $class
}

sub line_message {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my %params = (
        message => 'Test Message.',
        lines   => [-1]
    );

    my %args;
    if (@_ == 1) {
        ($params{'message'}) = @_
    } else {
        %args = @_;
        for (keys(%args)) {
            if (/^-?message$/i) {
                $params{'message'} = $args{$_}
            } elsif (/^-?line(?:s)?$/i) {
                if (!defined($params{'lines'} = _get_range($args{$_}))) {
                    return(undef)
                }
            }
        }
    }

    my $response;
    my @lines;
    for (@{$params{'lines'}}) {
          # Lines
        my $response = $session->set_request("1.3.6.1.4.1.9.2.9.4.0", INTEGER, $_);
          # Interval (reissue)
        $response = $session->set_request("1.3.6.1.4.1.9.2.9.5.0", INTEGER, 0);
          # Duration
        $response = $session->set_request("1.3.6.1.4.1.9.2.9.6.0", INTEGER, 0);
          # Text (256 chars)
        $response = $session->set_request("1.3.6.1.4.1.9.2.9.7.0", OCTET_STRING, $params{'message'});
          # Temp Banner (1=no 2=append)
        $response = $session->set_request("1.3.6.1.4.1.9.2.9.8.0", INTEGER, 1);
          # Send
        $response = $session->set_request("1.3.6.1.4.1.9.2.9.9.0", INTEGER, 1);
        if (defined($response)) {
            push @lines, $_
        } else {
            $LASTERROR = "Failed to send message to line $_";
            return(undef)
        }
    }
    # clear message
    $session->set_request("1.3.6.1.4.1.9.2.9.7.0", OCTET_STRING, "");
    if ($lines[0] == -1) { $lines[0] = "ALL" }
    return \@lines
}

sub line_numberof {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my $response;
    if (!defined($response = $session->get_request( -varbindlist => ['1.3.6.1.4.1.9.2.9.1.0'] ))) {
        $LASTERROR = "Cannot retrieve number of lines";
        return(undef)
    } else {
        return $response->{'1.3.6.1.4.1.9.2.9.1.0'}
    }
}

sub memory_info {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my $Name        = &_snmpgetnext($session, '1.3.6.1.4.1.9.9.48.1.1.1.2');
    if (!defined($Name)) {
        $LASTERROR = "Cannot get memory info";
        return(undef)
    }
    my $Alternate   = &_snmpgetnext($session, '1.3.6.1.4.1.9.9.48.1.1.1.3');
    my $Valid       = &_snmpgetnext($session, '1.3.6.1.4.1.9.9.48.1.1.1.4');
    my $Used        = &_snmpgetnext($session, '1.3.6.1.4.1.9.9.48.1.1.1.5');
    my $Free        = &_snmpgetnext($session, '1.3.6.1.4.1.9.9.48.1.1.1.6');
    my $LargestFree = &_snmpgetnext($session, '1.3.6.1.4.1.9.9.48.1.1.1.7');

    my @MemInfo;
    for my $mem (0..$#{$Name}) {
        my %MemInfoHash;
        $MemInfoHash{'Name'}        = $Name->[$mem];
        $MemInfoHash{'Alternate'}   = $Alternate->[$mem];
        $MemInfoHash{'Valid'}       = ($Valid->[$mem] == 1) ? 'TRUE' : 'FALSE';
        $MemInfoHash{'Used'}        = $Used->[$mem];
        $MemInfoHash{'Free'}        = $Free->[$mem];
        $MemInfoHash{'LargestFree'} = $LargestFree->[$mem];
        $MemInfoHash{'Total'}       = $Used->[$mem] + $Free->[$mem];
        push @MemInfo, \%MemInfoHash
    }
    return \@MemInfo
}

sub proxy_ping {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my $pp;
    foreach my $key (keys(%{$self})) {
        # everything but '_xxx_'
        $key =~ /^\_.+\_$/ and next;
        $pp->{$key} = $self->{$key}
    }

    my %params = (
        count => 1,
        host  => inet_ntoa((gethostbyname(hostname))[4]),
        size  => 64,
        wait  => 1
    );

    my %args;
    if (@_ == 1) {
        ($params{'host'}) = @_;
        if (defined(gethostbyname($params{'host'}))) {
            $params{'host'} = inet_ntoa((gethostbyname($params{'host'}))[4])
        } else {
            $LASTERROR = "Cannot resolve IP for $params{'host'}";
            return(undef)
        }
    } else {
        %args = @_;
        for (keys(%args)) {
            if ((/^-?host(?:name)?$/i) || (/^-?dest(?:ination)?$/i)) {
                $params{'host'} = $args{$_};
                if (defined(gethostbyname($params{'host'}))) {
                    $params{'host'} = inet_ntoa((gethostbyname($params{'host'}))[4])
                } else {
                    $LASTERROR = "Cannot resolve IP for $params{'host'}";
                    return(undef)
                }
            } elsif (/^-?size$/i) {
                if ($args{$_} =~ /^\d+$/) {
                    $params{'size'} = $args{$_}
                } else {
                    $LASTERROR = "Invalid size: $args{$_}";
                    return(undef)
                }
            } elsif (/^-?count$/i) {
                if ($args{$_} =~ /^\d+$/) {
                    $params{'count'} = $args{$_}
                } else {
                    $LASTERROR = "Invalid count: $args{$_}";
                    return(undef)
                }
            } elsif ((/^-?wait$/i) || (/^-?timeout$/i)) {
                if ($args{$_} =~ /^\d+$/) {
                    $params{'wait'} = $args{$_}
                } else {
                    $LASTERROR = "Invalid wait time: $args{$_}";
                    return(undef)
                }
            } elsif (/^-?vrf(?:name)?$/i) {
                $params{'vrf'} = $args{$_}
            }
        }
    }
    $pp->{_PROXYPING_}{'_params_'} = \%params;

    my $instance = int(rand(1024)+1024);
      # Prepare object by clearing row
    my $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.16.' . $instance, INTEGER, 6);
    if (!defined($response)) {
        $LASTERROR = "NOT SUPPORTED";
        return(undef)
    }

    # Convert destination to Hex equivalent
    my $dest;
    for (split(/\./, $params{'host'})) {
        $dest .= sprintf("%02x",$_)
    }

      # ciscoPingEntryStatus (5 = createAndWait, 6 = destroy)
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.16.' . $instance, INTEGER, 6);
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.16.' . $instance, INTEGER, 5);
      # ciscoPingEntryOwner (<anyname>)
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.15.' . $instance, OCTET_STRING, __PACKAGE__);
      # ciscoPingProtocol (1 = IP)
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.2.' . $instance, INTEGER, 1);
      # ciscoPingAddress (NOTE: hex string, not regular IP)
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.3.' . $instance, OCTET_STRING, pack('H*', $dest));
      # ciscoPingPacketTimeout (in ms)
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.6.' . $instance, INTEGER32, $params{'wait'}*100);
      # ciscoPingDelay (Set gaps (in ms) between successive pings)
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.7.' . $instance, INTEGER32, $params{'wait'}*100);
      # ciscoPingPacketCount
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.4.' . $instance, INTEGER, $params{'count'});
      # ciscoPingPacketSize (protocol dependent)
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.5.' . $instance, INTEGER, $params{'size'});

    if (exists($params{'vrf'})) {
          # ciscoPingVrfName (<name>)
        $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.17.' . $instance, OCTET_STRING, $params{'vrf'})
    }
      # Verify ping is ready (ciscoPingEntryStatus = 2)
    $response = $session->get_request('1.3.6.1.4.1.9.9.16.1.1.1.16.' . $instance);
    if (defined($response->{'1.3.6.1.4.1.9.9.16.1.1.1.16.' . $instance})) {
        if ($response->{'1.3.6.1.4.1.9.9.16.1.1.1.16.' . $instance} != 2) {
            $LASTERROR = "Ping not ready";
            return(undef)
        }
    } else {
        $LASTERROR = "NOT SUPPORTED (after setup)";
        return(undef)
    }

      # ciscoPingEntryStatus (1 = activate)
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.16.' . $instance, INTEGER, 1);

    # Wait sample interval
    sleep $params{'wait'};

      # Get results
    $response = $session->get_table('1.3.6.1.4.1.9.9.16.1.1.1');
    $pp->{'_PROXYPING_'}{'Sent'}     = $response->{'1.3.6.1.4.1.9.9.16.1.1.1.9.' . $instance}  || 0;
    $pp->{'_PROXYPING_'}{'Received'} = $response->{'1.3.6.1.4.1.9.9.16.1.1.1.10.' . $instance} || 0;
    $pp->{'_PROXYPING_'}{'Minimum'}  = $response->{'1.3.6.1.4.1.9.9.16.1.1.1.11.' . $instance} || 0;
    $pp->{'_PROXYPING_'}{'Average'}  = $response->{'1.3.6.1.4.1.9.9.16.1.1.1.12.' . $instance} || 0;
    $pp->{'_PROXYPING_'}{'Maximum'}  = $response->{'1.3.6.1.4.1.9.9.16.1.1.1.13.' . $instance} || 0;

      # destroy entry
    $response = $session->set_request('1.3.6.1.4.1.9.9.16.1.1.1.16.' . $instance, INTEGER, 6);
    return bless $pp, $class
}

sub proxy_ping_sent {
    my $self = shift;
    return $self->{'_PROXYPING_'}{'Sent'}
}

sub proxy_ping_received {
    my $self = shift;
    return $self->{'_PROXYPING_'}{'Received'}
}

sub proxy_ping_minimum {
    my $self = shift;
    return $self->{'_PROXYPING_'}{'Minimum'}
}

sub proxy_ping_average {
    my $self = shift;
    return $self->{'_PROXYPING_'}{'Average'}
}

sub proxy_ping_maximum {
    my $self = shift;
    return $self->{'_PROXYPING_'}{'Maximum'}
}

sub system_info {
    my $self  = shift;
    my $class = ref($self) || $self;

    my $session = $self->{'_SESSION_'};

    my $sysinfo;
    foreach my $key (keys(%{$self})) {
        # everything but '_xxx_'
        $key =~ /^\_.+\_$/ and next;
        $sysinfo->{$key} = $self->{$key}
    }

    my $response = &_snmpgetnext($session, '1.3.6.1.2.1.1');
    if (defined($response)) {

        if (defined($response->[0])) { $sysinfo->{'_SYSINFO_'}{'Description'} = $response->[0] }
        if (defined($response->[0])) { $sysinfo->{'_SYSINFO_'}{'ObjectID'}    = $response->[1] }
        if (defined($response->[0])) { $sysinfo->{'_SYSINFO_'}{'Uptime'}      = $response->[2] }
        if (defined($response->[0])) { $sysinfo->{'_SYSINFO_'}{'Contact'}     = $response->[3] }
        if (defined($response->[0])) { $sysinfo->{'_SYSINFO_'}{'Name'}        = $response->[4] }
        if (defined($response->[0])) { $sysinfo->{'_SYSINFO_'}{'Location'}    = $response->[5] }
        if (defined($response->[0])) { $sysinfo->{'_SYSINFO_'}{'Services'}    = $response->[6] }

        return bless $sysinfo, $class
    } else {
        $LASTERROR = "Cannot read system MIB";
        return(undef)
    }
}

sub system_info_description {
    my $self = shift;
    return $self->{'_SYSINFO_'}{'Description'}
}

sub system_info_objectID {
    my $self = shift;
    return $self->{'_SYSINFO_'}{'ObjectID'}
}

sub system_info_uptime {
    my $self = shift;
    return $self->{'_SYSINFO_'}{'Uptime'}
}

sub system_info_contact {
    my $self = shift;
    return $self->{'_SYSINFO_'}{'Contact'}
}

sub system_info_name {
    my $self = shift;
    return $self->{'_SYSINFO_'}{'Name'}
}

sub system_info_location {
    my $self = shift;
    return $self->{'_SYSINFO_'}{'Location'}
}

sub system_info_services {
    my ($self, $arg) = @_;

    if (defined($arg) && ($arg >= 1)) {
        return $self->{'_SYSINFO_'}{'Services'}
    } else {
        my %Services = (
            1  => 'Physical',
            2  => 'Datalink',
            4  => 'Network',
            8  => 'Transport',
            16 => 'Session',
            32 => 'Presentation',
            64 => 'Application'
        );
        my @Svcs;
        for (sort {$b <=> $a} (keys(%Services))) {
            push @Svcs, $Services{$_} if ($self->{'_SYSINFO_'}{'Services'} & int($_))
        }
        return \@Svcs
    }
}

sub system_info_osversion {
    my $self = shift;

    if ($self->{'_SYSINFO_'}{'Description'} =~ /Version ([^ ,\n\r]+)/) {
        return $1
    } else {
        return "Cannot determine OS Version"
    }
}

########################################################
# Subroutines
########################################################

sub password_decrypt {

    my $self = shift;
    my $class = ref($self) || $self;

    my $passwd;

    if ($self ne __PACKAGE__) {
        $passwd = $self
    } else {
        ($passwd) = @_
    }

    if (($passwd =~ /^[\da-f]+$/i) && (length($passwd) > 2)) {
        if (!(length($passwd) & 1)) {
            my $dec = "";
            my ($s, $e) = ($passwd =~ /^(..)(.+)/o);

            for (my $i = 0; $i < length($e); $i+=2) {
                # If we move past the end of the XOR key, reset
                if ($s > $#xlat) { $s = 0 }
                $dec .= sprintf "%c",hex(substr($e,$i,2))^$xlat[$s++]
            }
            return $dec
        }
    }
    $LASTERROR = "Invalid Password: $passwd";
    return(0)
}

sub password_encrypt {

    my $self = shift;
    my $class = ref($self) || $self;

    my ($cleartxt, $index);

    if ($self ne __PACKAGE__) {
        $cleartxt = $self;
        ($index) = @_
    } else {
        ($cleartxt, $index) = @_
    }

    my $start = 0;
    my $end = $#xlat;

    if (defined($index)) {
        if ($index =~ /^\d+$/) {
            if (($index < 0) || ($index > $#xlat)) {
                $LASTERROR = "Index out of range 0-$#xlat: $index";
                return(0)
            } else {
                $start = $index;
                $end   = $index
            } 
        } elsif ($index eq "") {
            # Do them all - currently set for that.
        } else {
            my $random = int(rand($#xlat + 1));
            $start = $random;
            $end   = $random
        }
    }

    my @passwds;
    for (my $j = $start; $j <= $end; $j++) {
        my $encrypt = sprintf "%02i", $j;
        my $s       = $j;

        for (my $i = 0; $i < length($cleartxt); $i++) {
            # If we move past the end of the XOR key, reset
            if ($s > $#xlat) { $s = 0 }
            $encrypt .= sprintf "%02X", ord(substr($cleartxt,$i,1))^$xlat[$s++]
        }
        push @passwds, $encrypt
    }
    return \@passwds
}

sub close {
    my $self = shift;
    $self->{_SESSION_}->close();
}

sub error {
    return($LASTERROR)
}

########################################################
# End Public Module
########################################################

########################################################
# Start Private subs
########################################################

sub _get_range {

    my ($opt) = @_;

    # If argument, it must be a number range in the form:
    #  1,9-11,7,3-5,15
    if ($opt !~ /^\d+([\,\-]\d+)*$/) {
        $LASTERROR = "Incorrect range format: $opt";
        return(undef)
    }

    my (@option, @temp, @ends);

    # Split the string at the commas first to get:  1 9-11 7 3-5 15
    @option = split(/,/, $opt);

    # Loop through remaining values for dashes which mean all numbers inclusive.
    # Thus, need to expand ranges and put values in array.
    for $opt (@option) {

        # If value has a dash '-', split and add 'missing' numbers.
        if ($opt =~ /-/) {

            # Ends are start and stop number of range.  For example, $opt = 9-11:
            # $ends[0] = 9
            # $ends[1] = 11
            @ends = split(/-/, $opt);

            for ($ends[0]..$ends[1]) {
                push @temp, $_
            }

        # No dash '-', move on
        } else {
            push @temp, $opt
        }
    }
    # return the sorted values of the temp array
    @temp = sort { $a <=> $b } (@temp);
    return \@temp
}

sub _snmpgetnext {

    my ($session, $oid) = @_;

    my (@oids, @vals);
    my $base = $oid;
    my $result = 0;

    while (defined($result = $session->get_next_request( -varbindlist => [$oid] ))) {
        my ($o, $v) = each(%{$result});
        if (oid_base_match($base, $o)) {
            push @vals, $v;
            push @oids, $o;
            $oid = $o
        } else {
            last
        }
    }
    if ((@oids == 0) && (@vals == 0)) {
        if (defined($result = $session->get_request($oid))) {
            push @vals, $result->{$oid};
            push @oids, $oid
        } else {
            return(undef)
        }
    }
    return (\@oids, \@vals)
}

########################################################
# End Private subs
########################################################

1;

__END__

########################################################
# Start POD
########################################################