#!/usr/bin/perl -w
#
# {{{EMULAB-LGPL
#
# This file is part of the Emulab network testbed software.
#
# This file 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 file 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 file. If not, see .
#
# }}}
#
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
# Copyright (c) 2004-2014 Regents, University of California.
# All rights reserved.
#
#
# snmpit module for HP procurve and flexfabric (comware) switches.
# The Comware module (snmpit_h3c) inherits from the base (snmpit_hp) class.
#
# XXX: krw: Rename base HP module here so that we don't actually use
# this for the procurve models until I have more time to test.
package snmpit_hpupd;
use strict;
$| = 1; # Turn off line buffering on output
use English;
use SNMP;
use Carp qw(cluck);
use lib '/usr/testbed/lib';
use snmpit_lib;
use libtestbed;
use Lan;
use Port;
#
# These are the commands that can be passed to the portControl function
# below
#
my %cmdOIDs =
(
"enable" => ["ifAdminStatus","up"],
"disable"=> ["ifAdminStatus","down"],
"1000mbit"=> ["hpSwitchPortFastEtherMode","auto-1000Mbits"],
"100mbit"=> ["hpSwitchPortFastEtherMode","auto-100Mbits"],
"10mbit" => ["hpSwitchPortFastEtherMode","auto-10Mbits"],
"auto" => ["hpSwitchPortFastEtherMode","auto-neg"],
"full" => ["hpSwitchPortFastEtherMode","full"],
"half" => ["hpSwitchPortFastEtherMode","half"],
);
#
# some long OIDs that get used frequently.
#
my $normOID = "dot1qVlanStaticUntaggedPorts";
my $forbidOID = "dot1qVlanForbiddenEgressPorts";
my $egressOID = "dot1qVlanStaticEgressPorts";
my $aftOID = "dot1qPortAcceptableFrameTypes";
my $createOID = "dot1qVlanStaticRowStatus";
#
# Enterprise OID for toggling jumbo frame support on a vlan
#
my $jumboOID = '1.3.6.1.4.1.11.2.14.11.5.1.12.1.8.1.1.1';
#
# Openflow OIDs, only number format now.
#
#my $ofOID = 'iso.org.dod.internet.private.enterprises.11.2.14.11.5.1.7.1.35';
my $ofOID = '1.3.6.1.4.1.11.2.14.11.5.1.7.1.35';
my $ofEnableOID = $ofOID.'.1.1.2';
my $ofControllerOID = $ofOID.'.1.1.3';
my $ofListenerOID = $ofOID.'.1.1.4';
my $ofFailModeOID = $ofOID.'.1.1.11';
my $ofSupportOID = $ofOID.'.2.1.0';
# This string is enough now, but the Openflow OID may change in future.
# The maintainers should keep in mind of this ID.
my $ofListenerVarNameMarker = '35.1.1.4';
#
# Ports can be passed around in three formats:
# ifindex: positive integer corresponding to the interface index (eg. 42)
# modport: dotted module.port format, following the physical reality of
# Cisco switches (eg. 5.42)
# nodeport: node:port pair, referring to the node that the switch port is
# connected to (eg. "pc42:1")
#
# See the function convertPortFormat below for conversions between these
# formats
#
my $PORT_FORMAT_IFINDEX = 1;
my $PORT_FORMAT_MODPORT = 2;
my $PORT_FORMAT_NODEPORT = 3; # XXX - not used anymore here.
my $PORT_FORMAT_PORT = 4;
#
# Creates a new object.
#
# usage: new($classname,$devicename,$debuglevel,$community)
# returns a new object, blessed into the snmpit_intel class.
#
sub new($$$;$) {
# The next two lines are some voodoo taken from perltoot(1)
my $proto = shift;
my $class = ref($proto) || $proto;
my $name = shift;
my $debugLevel = shift;
my $community = shift;
#
# Create the actual object
#
my $self = {};
#
# Set the defaults for this object
#
if (defined($debugLevel)) {
$self->{DEBUG} = $debugLevel;
} else {
$self->{DEBUG} = 0;
}
$self->{BLOCK} = 1;
$self->{CONFIRM} = 1;
$self->{NAME} = $name;
#
# Get config options from the database
#
my $options = getDeviceOptions($self->{NAME});
if (!$options) {
warn "ERROR: Getting switch options for $self->{NAME}\n";
return undef;
}
$self->{MIN_VLAN} = $options->{'min_vlan'};
$self->{MAX_VLAN} = $options->{'max_vlan'};
if ($community) { # Allow this to over-ride the default
$self->{COMMUNITY} = $community;
} else {
$self->{COMMUNITY} = $options->{'snmp_community'};
}
# Use jumbo frames?
if (exists($options->{'use_jumbo'})
&& $options->{'use_jumbo'} == 1) {
$self->{DOJUMBO} = 1;
} else {
$self->{DOJUMBO} = 0;
}
#
# set up hashes for internal use
#
$self->{IFINDEX} = {};
$self->{TRUNKINDEX} = {};
$self->{TRUNKS} = {};
$self->{cmdOIDs} = \%cmdOIDs;
# other global variables
$self->{DOALLPORTS} = 0;
$self->{DOALLPORTS} = 1;
$self->{SKIPIGMP} = 1;
if ($self->{DEBUG}) {
print "snmpit_hp initializing $self->{NAME}, " .
"debug level $self->{DEBUG}\n" ;
}
#
# Set up SNMP module variables, and connect to the device
#
$SNMP::debugging = ($self->{DEBUG} - 2) if $self->{DEBUG} > 2;
my $mibpath = '/usr/local/share/snmp/mibs';
&SNMP::addMibDirs($mibpath);
&SNMP::addMibFiles("$mibpath/SNMPv2-SMI.txt", "$mibpath/SNMPv2-TC.txt",
"$mibpath/SNMPv2-MIB.txt", "$mibpath/IANAifType-MIB.txt",
"$mibpath/IF-MIB.txt", "$mibpath/BRIDGE-MIB.txt",
"$mibpath/IEEE8023-LAG-MIB.txt",
"$mibpath/HP-ICF-OID.txt", "$mibpath/HH3C-OID-MIB.txt");
$SNMP::save_descriptions = 1; # must be set prior to mib initialization
SNMP::initMib(); # parses default list of Mib modules
$SNMP::use_enums = 1; # use enum values instead of only ints
warn ("Opening SNMP session to $self->{NAME}...") if ($self->{DEBUG});
$self->{SESS} = new SNMP::Session(DestHost => $self->{NAME},Version => "2c",
Timeout => 3000000, Retries=> 9, Community => $self->{COMMUNITY});
if (!$self->{SESS}) {
#
# Bomb out if the session could not be established
#
warn "WARNING: Unable to connect via SNMP to $self->{NAME}\n";
return undef;
}
#
# The bless needs to occur before readifIndex(), since it's a class
# method
#
#
# Sometimes the SNMP session gets created when there is no connectivity
# to the device so let's try something simple
#
my $test_case = snmpitGet($self->{SESS}, ["sysObjectID", 0], 1);
if (!defined($test_case)) {
warn "WARNING: Unable to retrieve via SNMP from $self->{NAME}\n";
return undef;
}
$self->{HPTYPE} = SNMP::translateObj($test_case);
$class = $self->{H3C} = 'snmpit_h3c'
if ($test_case =~ '^.1.3.6.1.4.1.25506');
bless($self,$class);
$self->readifIndex();
return $self;
}
# Attempt to repeat an action until it succeeds
sub hammer($$$;$) {
my ($self, $closure, $id, $retries) = @_;
if (!defined($retries)) { $retries = 12; }
for my $i (1 .. $retries) {
my $result = $closure->();
if (defined($result) || ($retries == 1)) { return $result; }
warn $id . " ... will try again\n";
sleep 1;
}
warn $id . " .. giving up\n";
return undef;
}
# shorthand
sub printSnmpErr($$) {
my ($sess, $id) = @_;
if ($sess->{ErrorNum}) {
print "$id had error number " . $sess->{ErrorNum} .
" and had error string " . $sess->{ErrorStr} . "\n";
}
}
sub get1($$$) {
my ($self, $obj, $instance) = @_;
my $id = $self->{NAME} . "::get1($obj.$instance)";
my $closure = sub () {
my $RetVal = snmpitGet($self->{SESS}, [$obj, $instance], 1);
if (!defined($RetVal)) { sleep 4;}
return $RetVal;
};
my $RetVal = $self->hammer($closure, $id, 40);
printSnmpErr($self->{SESS}, $id)
if (!defined($RetVal));
return $RetVal;
}
sub set($$;$$) {
my ($self, $varbind, $id, $retries) = @_;
if (!defined($id)) { $id = $self->{NAME} . ":set "; }
if (!defined($retries)) { $retries = 2; }
my $sess = $self->{SESS};
my $closure = sub () {
my $RetVal = $sess->set($varbind);
my $status = $RetVal;
if (!defined($RetVal)) {
$status = "(undefined)";
printSnmpErr($sess, $id);
}
return $RetVal;
};
my $RetVal = $self->hammer($closure, $id, $retries);
return $RetVal;
}
sub mirvPortSet($) {
my ($bitfield) = @_;
$bitfield = substr($bitfield,0,127);
my $unpacked = unpack("B*",$bitfield);
return [split //, $unpacked];
}
sub testPortSet($$) {
my ($bitfield, $index) = @_;
return @{mirvPortSet($bitfield)}[$index];
}
#
# opPortSet($op, $bitfield, @indices)
# set or clear bits in a port set, $op > 0 means set, otherwise clear
#
sub opPortSet($$@) {
my ($op, $bitfield, @indices) = @_;
my @bits = mirvPortSet($bitfield);
foreach my $index (@indices) { $bits[$index] = $op > 0 ? 1 : 0; }
return pack("B*", join('',@bits));
}
# Translate a list of ifIndexes to a PortSet
sub listToPortSet($@)
{
my $self = shift;
my @ports = @_;
my ($max, $portbitstring, $portSet, $port);
my @portbits;
$self->debug("listToPortsSet: input @ports\n",2);
if (scalar @ports) {
@ports = sort { $b <=> $a} @ports;
$max = ($ports[0] | 7);
} else { $max = 7 ; }
$port = 0;
while ($port <= $max) { $portbits[$port] = 48 ; $port++; }
while (scalar @ports) { $port = pop @ports; $portbits[$port - 1] = 49; }
$self->debug("portbits after insertions: @portbits\n",2);
$portbitstring = pack "C*", @portbits;
$self->debug("listToPortSet output string $portbitstring \n");
$portSet = pack "B*", $portbitstring ;
return $portSet;
}
# Translate a PortSet to a list of ifIndexes
sub bitSetToList($)
{
my ($arrayref) = @_;
my @ports = ();
my $max = scalar (@$arrayref);
for (my $port = 0; $port < $max; $port++)
{ if (@$arrayref[$port]) { push @ports, (1 + $port); } }
return @ports;
}
sub portSetToList($$) {
my ($self, $portset) = @_;
return bitSetToList(mirvPortSet($portset));
}
sub getOidToMappedList($$$) {
my ($self, $oid, $idx) = @_;
if (my $bits = $self->get1($oid,$idx)) {
return $self->portSetToList($bits);
} else { return (); }
}
sub trunkedPorts($@) {
my ($self, @ports) = @_;
my ($trunks, $dualPorts, $modes, $pids, $j, @nports) = ({}, {});
@$dualPorts{$self->portSetToList($self->get1($forbidOID,1))} = undef;
if (@ports) {
$modes = [ map { [ $aftOID, $_ ] } @ports ];
$j = $self->{SESS}->get($modes);
} else {
($modes) = $self->{SESS}->bulkwalk(0,32, [$aftOID]);
}
foreach my $aref (@$modes) {
my ($name, $ifIndex, $val) = @$aref;
if ($val eq 'admitOnlyVlanTagged') {
$$trunks{$ifIndex} = 1;
} elsif (exists($$dualPorts{$ifIndex})) {
$$trunks{$ifIndex} = $self->get1('dot1qPvid', $ifIndex);
}
}
return ($trunks);
}
#
# Set a variable associated with a port. The commands to execute are given
# in the cmdOIs hash above
#
# usage: portControl($self, $command, @ports)
# returns 0 on success.
# returns number of failed ports on failure.
# returns -1 if the operation is unsupported
#
sub portControl ($$@) {
my $self = shift;
my $cmd = shift;
my @ports = @_;
my $cmdOIDs = $self->{cmdOIDs};
$self->debug("portControl: $cmd -> (@ports)\n");
#
# Find the command in the %cmdOIDs hash (defined at the top of this file).
# Some commands involve multiple SNMP commands, so we need to make sure
# we get all of them
#
if (defined $$cmdOIDs{$cmd}) {
my @oid = @{$$cmdOIDs{$cmd}};
my $errors = 0;
while (@oid) {
my $myoid = shift @oid;
my $myval = shift @oid;
$errors += $self->UpdateField($myoid,$myval,@ports);
}
return $errors;
} else {
#
# Command not supported
#
$self->debug("Unsupported port control command '$cmd' ignored.\n");
return 0;
}
}
#
# Get a vlan's ifindex given it's tag
#
sub getVlanIfindexFromTag($$) {
my ($self, $tag) = @_;
my $id = $self->{NAME} . "::getVlanIfindexFromTag";
my ($rows) = snmpitBulkwalkFatal($self->{SESS}, ["ifDescr"]);
if (!@$rows) {
warn "$id: ERROR: No interface description rows returned ".
"while attempting to search for vlan ifindex!\n";
return undef;
}
foreach my $rowref (@$rows) {
my ($name,$ifindex,$descr) = @$rowref;
next unless $descr =~ /VLAN(\d+)/i;
if ($tag == $1) {
return $ifindex;
}
}
warn "$id: no ifindex found for vlan with tag: $tag\n";
return undef;
}
#
# Set jumbo frames on a vlan
#
sub setVlanJumbo($$) {
my ($self, $tag) = @_;
my $id = $self->{NAME} . "::setVlanJumbo";
my $vifindex = $self->getVlanIfindexFromTag($tag);
goto bad if !defined($vifindex);
$self->debug("id: Enabling jumbo frames on vlan $tag (ifindex: $vifindex)\n");
my $res = $self->set([$jumboOID,$vifindex,1,"INTEGER"], $id);
goto bad if !defined($res);
return 0;
bad:
warn "$id: Could not enable jumbo frames for vlan with tag: $tag\n";
return 1;
}
#
# HP's refuse to create vlans with display names that can
# be interpreted as vlan numbers
#
sub convertVlanName($) {
my $id = shift;
my $new;
if ( $id =~ /^_(\d+)$/) {
$new = $1;
return ((($new > 0) && ($new < 4095)) ? $new : $id);
}
if ( $id =~ /^(\d+)$/) {
$new = $1;
return ((($new > 0) && ($new < 4095)) ? "_$new" : $id);
}
return $id;
}
#
# Try to pull a VLAN number out of a long OID string
#
sub parseVlanNumberFromOID($) {
my ($oid) = @_;
# OID must be a dotted string
my (@elts) = split /\./, $oid;
if (scalar(@elts) < 2) {
return undef;
}
# Second-to-last element must be the right text string or numeric ID
if ($elts[$#elts-1] eq "dot1qVlanStaticName" || $elts[$#elts-1] eq "1") {
# Last element must be numeric
if ($elts[$#elts] =~ /\d+/) {
return $elts[$#elts];
} else {
return undef;
}
} else {
return undef;
}
}
sub checkLACP($$) {
my ($self, $port) = @_;
if (my $j = $self->{TRUNKINDEX}{$port})
{ $port = $j + $self->{TRUNKOFFSET}; }
return $port;
}
#
# Convert a set of ports to an alternate format. The input format is detected
# automatically. See the declarations of the constants at the top of this
# file for a description of the different port formats.
#
# usage: convertPortFormat($self, $output format, @ports)
# returns a list of ports in the specified output format
# returns undef if the output format is unknown
#
# TODO: Add debugging output, better comments, more sanity checking
#
sub convertPortFormat($$@) {
my $self = shift;
my $output = shift;
my @ports = @_;
my @results = ();
#
# Avoid warnings by exiting if no ports given
#
if (!@ports) {
return ();
}
#
# We determine the type by sampling the first port given
#
my $sample = $ports[0];
if (!defined($sample)) {
warn "convertPortFormat: Given a bad list of ports\n";
return undef;
}
my $input;
SWITCH: for ($sample) {
(Port->isPort($sample)) && do { $input = $PORT_FORMAT_PORT; last; };
(/^\d+$/) && do { $input = $PORT_FORMAT_IFINDEX; last; };
(/^\d+\.\d+$/) && do { $input = $PORT_FORMAT_MODPORT; last; };
(/^$self->{NAME}\.\d+\/\d+$/) && do { $input = $PORT_FORMAT_MODPORT;
@ports = map {/^$self->{NAME}\.(\d+)\/(\d+)$/; "$1.$2";} @ports; last; };
warn "convertPortFormat: Unknown input port format: $sample\n";
return ();
}
#
# It's possible the ports are already in the right format
#
if ($input == $output) {
if ($input == $PORT_FORMAT_IFINDEX) {
@results = map $self->checkLACP($_), @ports;
goto done;
}
$self->debug("Not converting, input format = output format\n",3);
return @ports;
}
my $name = $self->{NAME};
if ($input == $PORT_FORMAT_IFINDEX) {
my $ifxModport = sub ($) {
my ($port, $modport) = ($_, $self->{IFINDEX}{$_});
print "$name: no modport for ifindex $port\n" unless ($modport);
return $modport ? $modport : "1.$port";
};
my @modports = map $ifxModport->($_), @ports;
if ($output == $PORT_FORMAT_MODPORT) {
$self->debug("Converting ifindex to modport\n",3);
@results = @modports;
goto done;
} elsif ($output == $PORT_FORMAT_PORT) {
$self->debug("Converting ifindex to Port object\n",3);
@results = map Port->LookupByStringForced("$name:$_"), @modports;
goto done;
}
} elsif ($input == $PORT_FORMAT_MODPORT) {
if ($output == $PORT_FORMAT_IFINDEX) {
$self->debug("Converting modport to ifindex\n",3);
@results = map $self->{IFINDEX}{$_}, @ports;
goto done;
} elsif ($output == $PORT_FORMAT_PORT) {
$self->debug("Converting ifindex to Port object\n",3);
@results = map Port->LookupByStringForced("$name:$_"), @ports;
goto done;
}
} elsif ($input == $PORT_FORMAT_PORT) {
if ($output == $PORT_FORMAT_IFINDEX) {
$self->debug("Converting Port object to ifindex\n",3);
@results = map $self->{IFINDEX}{(split /:/,
($_->node_id() eq $self->{NAME})?
$_->toTripleString():
$_->getOtherEndTripleString()
)[1]}, @ports;
goto done;
} elsif ($output == $PORT_FORMAT_MODPORT) {
$self->debug("Converting Port object to modport\n",3);
@results = map { (split /:/,
($_->node_id() eq $self->{NAME})?
$_->toTripleString():
$_->getOtherEndTripleString()
)[1]} @ports;
goto done;
}
}
#
# Some combination we don't know how to handle
#
warn "convertPortFormat: Bad input/output combination ($input/$output)\n";
return ();
done:
#
# The control flow change was to facilitate trying to debug an
# odd problem in Utah on the procuve-geni switches.
#
if (@results) {
foreach my $port (@results) {
my $inport = shift(@ports);
if (!defined($port)) {
print STDERR "convertPortFormat: Bad conversion for $inport ".
"($input/$output)\n";
return ();
}
#print STDERR "convertPortFormat: $inport/$port\n";
}
}
return @results;
}
#
# Check to see if the given 802.1Q VLAN tag exists on the switch
#
# usage: vlanNumberExists($self, $vlan_number)
# returns 1 if the VLAN exists, 0 otherwise
#
sub vlanNumberExists($$) {
my ($self, $vlan_number) = @_;
my $rv = $self->get1("dot1qVlanStaticRowStatus", $vlan_number);
if (!defined($rv) || !$rv || $rv ne "active") {
return 0;
}
return 1;
}
#
# Given VLAN indentifiers from the database, finds the 802.1Q VLAN
# number for them. If not VLAN id is given, returns mappings for the entire
# switch.
#
# usage: findVlans($self, @vlan_ids)
# returns a hash mapping VLAN ids to 802.1Q VLAN numbers
# any VLANs not found have NULL VLAN numbers
#
sub findVlans($@) {
my $self = shift;
my @vlan_ids = @_;
my %mapping = ();
my $id = $self->{NAME} . "::findVlans";
my ($count, $name, $vlan_number, $vlan_name) = (scalar(@vlan_ids));
$self->debug("$id\n");
if ($count > 0) { @mapping{@vlan_ids} = undef; }
#
# Find all VLAN names. Do one to get the first field...
#
my ($rows) = $self->{SESS}->bulkwalk(0,32, ["dot1qVlanStaticName"]);
foreach my $rowref (@$rows) {
($name,$vlan_number,$vlan_name) = @$rowref;
$self->debug("$id: Got $name $vlan_number $vlan_name\n",2);
# Hack to get around some strange behavior
if ((!defined($vlan_number) || $vlan_number eq "") &&
defined(parseVlanNumberFromOID($name))) {
$vlan_number = parseVlanNumberFromOID($name);
$self->debug("Changed vlan_number to $vlan_number\n",3);
}
$vlan_name = convertVlanName($vlan_name);
#
# We only want the names - we ignore everything else
#
if (!@vlan_ids || exists $mapping{$vlan_name}) {
$self->debug("$id: $vlan_name=>$vlan_number\n",2);
$mapping{$vlan_name} = $vlan_number;
}
}
return %mapping;
}
#
# Given a VLAN identifier from the database, find the 802.1Q VLAN
# number that is assigned to that VLAN. Retries several times (to account
# for propagation delays) unless the $no_retry option is given.
#
# usage: findVlan($self, $vlan_id,$no_retry)
# returns the VLAN number for the given vlan_id if it exists
# returns undef if the VLAN id is not found
#
sub findVlan($$;$) {
my $self = shift;
my $vlan_id = shift;
my $no_retry = shift;
my $id = $self->{NAME} . ":findVlan";
$self->debug("$id ( $vlan_id )\n",2);
my $max_tries = $no_retry ? 1 : 5;
#
# We try this a few times, with 5 second sleeps, since it can take
# a while for VLAN information to propagate
#
my $closure = sub () {
my %mapping = $self->findVlans($vlan_id);
my $vlan_number = $mapping{$vlan_id};
if (defined($vlan_number)) { return $vlan_number; }
sleep 4;
return undef;
};
return $self->hammer($closure,$id,$max_tries);
}
#
# Create a VLAN on this switch, with the given identifier (which comes from
# the database) and given 802.1Q tag number.
#
# usage: createVlan($self, $vlan_id, $vlan_number)
# returns the new VLAN number on success
# returns 0 on failure
#
sub createVlan($$$) {
my $self = shift;
my $vlan_id = shift;
my $vlan_number = shift;
my $id = $self->{NAME} . ":createVlan";
if (!defined($vlan_number)) {
warn "$id called without supplying vlan_number";
return 0;
}
my $check_number = $self->findVlan($vlan_id,1);
if (defined($check_number)) {
if ($check_number != $vlan_number) {
warn "$id: recreating vlan id $vlan_id which has ".
"existing vlan number $check_number with the new number ".
"$vlan_number\n";
return 0;
}
}
my $vlan ="$vlan_number";
my $hpvlan_id = convertVlanName("$vlan_id");
my $VlanName = 'dot1qVlanStaticName'; # vlan # is index
$self->debug("createVlan: name $vlan_id number $vlan_number \n");
#
# Perform the actual creation. Yes, this next line MUST happen all in
# one set command....
#
my $closure = sub () {
my $RetVal = $self->set
([[$VlanName,$vlan,$hpvlan_id,"OCTETSTR"],
[$createOID,$vlan, "createAndGo","INTEGER"]], "$id: creation");
if (defined($RetVal)) { return $RetVal; }
#
# Sometimes we loose responses, or on the second time around
# it might refuse to create a vlan that's already there, so wait
# a bit to see if it exists (also so as to not get too agressive
# with the switch which caused nortels to crash with IGMP stuff)
#
sleep (2);
$RetVal = $self->get1($createOID, $vlan);
if (defined($RetVal) && ($RetVal ne "active")) { return undef ;}
return $RetVal;
};
my $RetVal = $self->hammer($closure, "$id: creation");
if (!defined($RetVal)) { return 0; }
print " Creating VLAN $vlan_id as VLAN #$vlan_number on " .
"$self->{NAME} ...\n";
#
# Check that it happened.
#
$RetVal = $self->get1($VlanName, $vlan);
if (!defined($RetVal) || ("$RetVal" ne $hpvlan_id)) {
warn "$id: created vlan $vlan_id with name $RetVal" .
"instead of $hpvlan_id\n";
}
#
# Enable jumbo frames, if switch option is set.
#
if ($self->{DOJUMBO}) {
if ($self->setVlanJumbo($vlan_number) != 0) {
warn "$id: enable jumbo failed for vlan $vlan_id ...\n";
}
}
if ($self->{SKIPIGMP}) { return $vlan_number ; }
my $IgmpEnable = 'hpSwitchIgmpState';
$RetVal = $self->get1($IgmpEnable, $vlan);
$closure = sub () {
my $check = $self->set([[$IgmpEnable,$vlan,"enable","INTEGER"]]);
if (!defined($check)) { sleep (5);}
return $check;
};
$RetVal = $self->hammer($closure, "$id: setting snooping", 3);
if (!defined($RetVal)) { return 0; }
$closure = sub () {
my $check = $self->get1($IgmpEnable, $vlan);
if (!defined($check) || ($check ne "enable"))
{ sleep (4); return undef ;}
return $check;
};
$RetVal = $self->hammer($closure, "$id: checking snooping");
if (!defined($RetVal)) { return 0; }
return $vlan_number;
}
#
# gets the forbidden, untagged, and egress lists for a vlan
# sends back as a 3 element array of lists. (Thats the order
# the packet traces for the HP had them in).
#
sub getVlanLists($$) {
my ($self, $vlan) = @_;
my $ret = [0, 0, 0];
@$ret[0] = $self->{H3C} ? [0] : mirvPortSet($self->get1($forbidOID, $vlan));
@$ret[1] = mirvPortSet($self->get1($normOID, $vlan));
@$ret[2] = mirvPortSet($self->get1($egressOID, $vlan));
return $ret;
}
#
# sets the forbidden, untagged, and egress lists for a vlan
# sends back as a 3 element array of lists.
# (Thats the order we saw in a tcpdump of vlan creation.)
# (or in some cases 6 elements for 2 vlans).
#
sub setVlanLists($@) {
my ($self, @args) = @_;
my $oids = [$forbidOID, $normOID, $egressOID];
my $j = 0; my $todo = [ 0 ];
while (@args) {
my $vlan = shift @args;
my $arrayref = shift @args;
foreach my $i (0, 1, 2) {
next if (($i != 2) && $self->{H3C});
@$todo[$j++] = [ @$oids[$i], $vlan,
pack("B*",join('', @{@$arrayref[$i]})), "OCTETSTR"];
}
}
$j = $self->set($todo);
if (!defined($j)) { cluck "vlists failed\n";}
return $j;
}
#
# Some design comments about HP switch conventions:
# This code was written after having observed packet traces while
# running the HP management tool, which appeared to remove a port
# from a vlan merely by getting and setting the 3 OID in getVlanLists
# or transferred it between vlans by setting a pair of the 3 OIDS.
# It seems that a port may be an untagged member of at most one vlan
# but may be a tagged member of several others, i.e. the packets
# ared rx'd from the port or tx'd to the port with vlan tags added.
# If a port appears only in the $egressOIDs and never in a $normOID
# (so that it is like a normal interswitch single wire trunk), the
# The $aftOID = admitOnlyVlanTagged, without our having to set it.
# The OID describing the PVID of a port seem to correspond to
# the untagged membership of the port in some vlan, and it does
# not seem necessary to set this OID either.
# In this case, the port has $aftOID (dot1QPortAceptableFrameType) = admitAll.
# If we want the port to be like dual mode port on a foundry or cisco, we can
# merely add the port to the egress OID of any additonal vlan.
# However, if a switch belongs to a unique vlan, and is an untagged member,
# there would be no way to determine if the intent was to be a dual mode port
# or for the port have the semantics of most other switches, i.e. adding it
# to one vlan takes it out of the first.
# Since this is the common case, we record the dual mode intent
# by marking the port as being forbidden to join vlan 1.
#
# Put the given ports in the given VLAN. The VLAN is given as an 802.1Q
# tag number.
#
# usage: setPortVlan($self, $vlan_number, @ports)
# returns 0 on sucess.
# returns the number of failed ports on failure.
#
sub setPortVlan($$@) {
my $self = shift;
my $vlan_number = shift;
my @ports = @_;
my $id = $self->{NAME} . "::setPortVlan($vlan_number)";
$self->debug($id);
my %vlansToPorts; # i.e. bumpedVlansToListOfPorts
my @newTaggedPorts = ();
my ($errors, $portIndex, $pvid, $rv, @protoTrunks) = (0);
#
# Run the port list through portmap to find the ports on the switch that
# we are concerned with
#
return 0 unless(@ports);
my @portlist = $self->convertPortFormat($PORT_FORMAT_IFINDEX, @ports);
return -1
if (!@portlist);
$self->debug("ports: " . join(",",@ports) . "\n");
$self->debug("as ifIndexes: " . join(",",@portlist) . "\n");
#
# Need to determine status and remove ports from default_vlan
# before adding to other. This is a read_modify_write so lock out
# other instances of snmpit_hp.
my $defaultInfo = $self->getVlanLists(1);
foreach $portIndex (@portlist) {
# check for three easy cases
# a. known dual port.
# b. the ports is not allocated
# c. the port is a trunk
# case a: This is a dual port, so it doesn't have to leave its PVID.
if (@{@$defaultInfo[0]}[$portIndex - 1]) {
push @newTaggedPorts, $portIndex;
next;
}
# case b: Unallocated untrunked port.
if (@{@$defaultInfo[1]}[$portIndex - 1]) {
$pvid = 1;
} else {
my $tagOnly = $self->get1($aftOID,$portIndex);
if ($tagOnly eq "admitOnlyVlanTagged") {
# case c: Trunk Port
if (@{@$defaultInfo[2]}[$portIndex - 1]) {
# Total grot - happens when port was made an equal trunk
# having no vlans, so is on vlan 1, and might be disabled.
# It might also be the case that this version of the driver
# is being run for the first time on a switch where
# we were previously sloppier about membership in vlan 1.
push @protoTrunks, $portIndex;
}
push @newTaggedPorts, $portIndex;
next;
} else {
# case d: untrunked port leaving another vlan.
# a little more work - get its PVID and assume that
# is the vlan which it is leaviing. and toss it
# into the bumpedVlan hash.
$pvid = $self->get1("dot1qPvid", $portIndex);
}
}
push @{$vlansToPorts{$pvid}}, $portIndex # autovivifies if null
if (defined($pvid) && ($pvid != $vlan_number));
}
@{$self->{DISPLACED_VLANS}} = grep {$_ != 1;} keys %vlansToPorts;
$self->lock();
my $newInfo = $self->getVlanLists($vlan_number);
foreach my $vlan (keys %vlansToPorts) {
my $oldInfo = $self->getVlanLists($vlan) ;
foreach $portIndex (@{$vlansToPorts{$vlan}}) {
@{@$oldInfo[1]}[$portIndex-1] = 0;
@{@$newInfo[1]}[$portIndex-1] = 1;
@{@$oldInfo[2]}[$portIndex-1] = 0;
@{@$newInfo[2]}[$portIndex-1] = 1;
}
$self->setVlanLists($vlan, $oldInfo, $vlan_number, $newInfo);
}
# Now add tagged ports separately, just to be safe.
if (@newTaggedPorts) {
foreach $portIndex (@newTaggedPorts)
{ @{@$newInfo[2]}[$portIndex-1] = 1; }
$self->setVlanLists($vlan_number, $newInfo);
}
$self->unlock();
# We need to make sure the ports get enabled, and protoTrunks cleaned up.
if (@protoTrunks) {
$rv = $self->delPortVlan(1, @protoTrunks) +
$self->portControl("enable", @protoTrunks);
$errors += $rv;
warn "$id: $rv failures with protoTrunks\n"
if ($rv);
}
my $onoroff = ($vlan_number ne "1") ? "enable" : "disable";
$self->debug("$id; will $onoroff" . join(',',@ports) . "...\n");
if ( $rv = $self->portControl($onoroff, @ports) ) {
warn "$id: Port enable had $rv failures.\n";
$errors += $rv;
}
return $errors;
}
sub not_in($$@) {
my $self = shift;
my $value = shift;
my @list = @_;
return 0 == scalar(grep {$_ == $value;} @list);
}
#
# Remove the given ports from the given VLAN. The VLAN is given as an 802.1Q
# tag number.
#
# usage: delPortVlan($self, $vlan_number, @ports)
# returns 0 on sucess.
# returns the number of failed ports on failure.
#
sub delPortVlan($$@) {
my $self = shift;
my $vlan_number = shift;
my @ports = @_;
return $self->updateOneVlan(0,0,0,$vlan_number,@ports);
}
sub updateOneVlan($$$$$@)
{
my ($self,$forbid,$untag,$mem,$vlan_number,@ports) = @_;
$self->debug($self->{NAME} . "::updateOneVlan($untag,$mem,$vlan_number) ");
#
# Run the port list through portmap to find the ports on the switch that
# we are concerned with
#
$self->lock();
my @portlist = $self->convertPortFormat($PORT_FORMAT_IFINDEX, @ports);
return -1
if (!@portlist);
$self->debug("ports: " . join(",",@ports) . "\n");
$self->debug("as ifIndexes: " . join(",",@portlist) . "\n");
my $vlist = $self->getVlanLists($vlan_number);
foreach my $port (@portlist) {
next if ($self->{ifx2didx} && !($port = $self->{ifx2d1dx}));
@{@$vlist[0]}[$port - 1] = $forbid if ($vlan_number eq "1");
@{@$vlist[1]}[$port - 1] = $untag;
@{@$vlist[2]}[$port - 1] = $mem;
}
my $result = $self->setVlanLists($vlan_number, $vlist);
if (!defined($result)) {
print STDERR $self->{NAME} .
": updateOneVlan($forbid,$untag,$mem,$vlan_number) failed for: ";
foreach my $port (@ports) {
my $ifindex = shift(@portlist);
print STDERR "$port:$ifindex ";
}
print STDERR "\n";
}
$self->unlock();
return defined($result) ? 0 : 1;
}
#
# Disables all ports in the given VLANS. Each VLAN is given as a VLAN
# 802.1Q tag value.
#
# usage: removePortsFromVlan(self,@vlan)
# returns 0 on sucess.
# returns the number of failed ports on failure.
#
sub removePortsFromVlan($@) {
my $self = shift;
my @vlan_numbers = @_;
my $errors = 0;
my $id = $self->{NAME} . "::removePortsFromVlan";
foreach my $vlan_number (@vlan_numbers) {
my @ports = $self->portSetToList($self->get1($egressOID, $vlan_number));
if (@ports) {
$errors += $self->removeSomePortsFromVlan($vlan_number, @ports);
}
}
return $errors;
}
#
# Removes and disables some ports in a given VLAN.
# The VLAN is given as a VLAN 802.1Q tag value.
#
# usage: removeSomePortsFromVlan(self,vlan,@ports)
# returns 0 on sucess.
# returns the number of failed ports on failure.
#
sub removeSomePortsFromVlan($$@) {
my ($self, $vlan_number, @ports) = @_;
my ($errors, $changes, $id, %porthash, $tagOnly, $pvid) =
(0, 0, $self->{NAME} . "::removeSomePortsFromVlan");
# Callers should know better.
return 0
if (!@ports);
@ports = $self->convertPortFormat($PORT_FORMAT_IFINDEX,@ports);
return -1
if (! @ports);
@porthash{@ports} = @ports;
my $dualPorts = mirvPortSet($self->get1($forbidOID, 1)); # array
foreach my $portIndex (@ports) {
if (@$dualPorts[$portIndex-1]) {
# We make any dual ports whose PVID is this vlan into equaltrunks.
# A dualtrunked port whose primary vlan is not this vlan
# can be deleted by the code dealing with normal ports
$pvid = $self->get1("dot1qPvid", $portIndex);
next if ("$pvid" ne $vlan_number);
$self->updateOneVlan(0,0,1,$vlan_number,$portIndex); #make tagged
$self->updateOneVlan(0,0,0,1,$portIndex); # clear dual marker.
# fall through to next case
}
$tagOnly = $self->get1($aftOID,$portIndex);
if ($tagOnly eq "admitOnlyVlanTagged") {
if ($self->delPortVlan($vlan_number, $portIndex)) {
# assume it failed because it belonged to no other vlans
# so add it to vlan 1 and try again.
$self->updateOneVlan(0,0,1,1,$portIndex);
if ($self->delPortVlan($vlan_number, $portIndex)) {
warn "$id: failed to remove ifIndex $portIndex\n";
}
}
delete $porthash{$portIndex};
}
}
# Now, remove the remaining ports from the vlan.
$self->lock();
my $defaultLists = $self->getVlanLists(1);
my $vLists = $self->getVlanLists($vlan_number);
my @portlist = bitSetToList(@$vLists[2]);
$self->debug("$id $vlan_number: @portlist\n",2);
foreach my $portIndex (@portlist) {
next unless exists($porthash{$portIndex});
if (@{@$vLists[1]}[$portIndex - 1]) {
# otherwise, port is tagged.
@{@$defaultLists[1]}[$portIndex - 1] = 1;
@{@$defaultLists[2]}[$portIndex - 1] = 1;
$self->debug("disabling port $portIndex "
. "from vlan $vlan_number \n" );
$self->set(["ifAdminStatus",$portIndex,"down","INTEGER"],$id);
}
@{@$vLists[1]}[$portIndex - 1] = 0;
@{@$vLists[2]}[$portIndex - 1] = 0;
$changes++;
}
$errors += $self->setVlanLists($vlan_number, $vLists, 1, $defaultLists)
if ($changes > 0);
$self->unlock();
return $errors;
}
#
# Remove the given VLANs from this switch. Removes all ports from the VLAN,
# The VLAN is given as a VLAN identifier from the database.
#
# usage: removeVlan(self,int vlan)
# returns 1 on success
# returns 0 on failure
#
#
sub removeVlan($@) {
my $self = shift;
my @vlan_numbers = @_;
my $errors = 0;
my $name = $self->{NAME};
$self->removePortsFromVlan(@vlan_numbers);
foreach my $vlan_number (@vlan_numbers) {
#
# Perform the actual removal
#
print " Removing VLAN # $vlan_number ... ";
my $RetVal = $self->set([[$createOID,$vlan_number,"destroy","INTEGER"]]);
if ($RetVal) {
print "Removed VLAN $vlan_number on switch $name.\n";
} else {
print STDERR "Removing VLAN $vlan_number failed on switch $name.\n";
$errors++;
}
# The next call would buy time to let switch consolidate itself
# Nortels have bizarre failures when quickly creating and destroying
# vlans with IGMP snooping enabled.
# $self->{SESS}->bulkwalk(0,32,[$createOID]);
}
return ($errors == 0) ? 1 : 0;
}
#
# XXX: Major cleanup
#
sub UpdateField($$$@) {
my ($self, $OID, $val, @ports)= @_;
my $id = $self->{NAME} . "::UpdateField OID $OID value $val";
$self->debug("$id: ports @ports\n");
my $result = 0;
my $oidval = $val;
my ($Status, $portname, $row);
foreach $portname (@ports) {
my ($row) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$portname);
next
if (!defined($row));
$self->debug("checking row $row for $val ...\n");
$Status = $self->get1($OID,$row);
if (!defined($Status)) {
print STDERR "id: Port $portname No answer from device\n";
next;
}
$self->debug("Port $portname, row $row was $Status\n");
if ($OID eq "hpSwitchPortFastEtherMode") {
#
# Procurves use the same mib variable to set both
# speed and duplex concurrently; only certain
# combinations are permitted. (We won't support
# auto-10-100MBits. And at least the 5400 series
# doesn't seem to support full-duplex-1000Mbits.)
#
my @state = split "-", $Status;
if (($val eq "half") || ($val eq "full")) {
if ($state[0] eq "auto") {
if (($state[1] eq "neg") || ($state[1] eq "10")) {
# can't autospeed with specific duplex.
$oidval = ($val eq "half") ?
"half-duplex-100Mbits" : "full-duplex-100Mbits";
} elsif ($state[1] eq "1000Mbits") {
$oidval = $Status;
} else {
$oidval = $val . "-duplex-" . $state[1] ;
}
} else {
$oidval = $val . "-duplex-" . $state[2] ;
}
} else {
if (($val eq "auto-neg") || ($val eq "auto-1000Mbits") ||
($state[1] ne "duplex")) {
$oidval = $val;
} else {
my @valarr = split "-", $val;
$oidval = $state[0] . "-duplex-" . "$valarr[1]";
}
}
}
if ($Status ne $oidval) {
$self->debug("Setting $portname (r $row) to $oidval...");
$Status = $self->set([[$OID,$row,$oidval,"INTEGER"]]);
$result = (defined($Status)) ? 0 : -1;
$self->debug($result ? "failed.\n" : "succeeded.\n");
}
}
return $result;
}
#
# Determine if a VLAN has any members
# (Used by stack->switchesWithPortsInVlan())
#
sub vlanHasPorts($$) {
my ($self, $vlan_number) = @_;
my $portset = $self->get1($egressOID,$vlan_number);
if (defined($portset)) {
my @ports = $self->portSetToList($portset);
if (@ports) { return 1; }
}
return 0;
}
#
# List all VLANs on the device
#
# usage: listVlans($self)
# see snmpit_cisco_stack.pm for a description of return value format
#
sub listVlans($) {
my $self = shift;
my (%Names, %Numbers, %Members, %Normports);
my ($vlan_name, $oid, $vlan_number, $value, $rowref);
my ($modport, $node, $ifIndex, @portlist, @memberlist);
$self->debug($self->{NAME} . "::listVlans()\n",1);
my $maxport = $self->{MAXPORT};
#
# Walk the tree to find the VLAN names
#
my ($rows) = $self->{SESS}->bulkwalk(0,32,["dot1qVlanStaticName"]);
foreach $rowref (@$rows) {
($oid, $vlan_number, $vlan_name) = @$rowref;
$self->debug("Got $oid $vlan_number $vlan_name\n",3);
# Hack to get around some strange behavior
if ((!defined($vlan_number) || $vlan_number eq "") &&
defined(parseVlanNumberFromOID($oid))) {
$vlan_number = parseVlanNumberFromOID($oid);
$self->debug("Changed vlan_number to $vlan_number\n",3);
}
next if ("$vlan_number" eq "1");
$vlan_name = convertVlanName($vlan_name);
if (!$Names{$vlan_number}) {
$Names{$vlan_number} = $vlan_name;
@{$Members{$vlan_number}} = ();
}
}
#
# Walk the tree for the VLAN members
#
($rows) = $self->{SESS}->bulkwalk(0,32,["dot1qVlanStaticEgressPorts"]);
foreach $rowref (@$rows) {
($oid,$vlan_number,$value) = @$rowref;
next if ("$vlan_number" eq "1");
@portlist = $self->portSetToList($value);
if ($self->{d1dx2ifx}) {
@portlist = map { $self->{d1dx2ifx}{$_}} @portlist ;
}
$self->debug("Got $oid $vlan_number @portlist\n",3);
foreach $ifIndex (@portlist) {
($node) = $self->convertPortFormat($PORT_FORMAT_PORT,$ifIndex);
if (!$node) {
($modport) = $self->convertPortFormat
($PORT_FORMAT_MODPORT,$ifIndex);
$node = Port->LookupByStringForced($self->{NAME} . ":$modport");
}
# Let's be clear about what kind of connection this is and
# get the right port object. If there is an endpoint here,
# get that. If this is a trunk, then the member we want to
# put in the list is the "local" side (this switch's port).
my $mbrport;
if ($node->is_trunk_port()) {
$mbrport = $node;
} else {
# getOtherEndPort() will return the object upon which the
# method is invoked if it fails to lookup the other side.
$mbrport = $node->getOtherEndPort();
}
push @{$Members{$vlan_number}}, $mbrport;
$self->debug("$devicename:$vlan_number $node:$mbrport\n", 3);
if (!$Names{$vlan_number}) {
$self->debug("listVlans: WARNING: port $node in non-existant " .
"VLAN $vlan_number\n", 1);
}
}
}
#
# Build a list from the name and membership lists
#
my @list = ();
foreach my $vlan_id (sort keys %Names) {
push @list, [$Names{$vlan_id},$vlan_id,$Members{$vlan_id}];
}
#$self->debug($self->{NAME} .":". join("\n",(map {join ",", @$_} @list))."\n");
return @list;
}
#
# List all ports on the device
#
# usage: listPorts($self)
# see snmpit_cisco_stack.pm for a description of return value format
#
sub listPorts($) {
my $self = shift;
my %Able = ();
my %Link = ();
my %auto = ();
my %speed = ();
my %duplex = ();
my $ifTable = ["ifAdminStatus",0];
#
# Get the ifAdminStatus (enabled/disabled) and ifOperStatus
# (up/down)
#
my ($varname, $modport, $ifIndex, $portIndex, $status, $portname);
$self->{SESS}->getnext($ifTable);
do {
($varname,$ifIndex,$status) = @{$ifTable};
$self->debug("$varname $ifIndex $status\n");
if (($ifIndex <= $self->{MAXPORT}) && ($varname =~ /AdminStatus/)) {
$Able{$ifIndex} = ($status =~/up/ ? "yes" : "no");
}
$self->{SESS}->getnext($ifTable);
} while ( $varname =~ /^ifAdminStatus$/) ;
#
# Get the port configuration, including speed, duplex, and whether or not
# it is autoconfiguring
#
foreach $ifIndex (keys %Able) {
if ($status = $self->{SESS}->get(["ifOperStatus",$ifIndex])) {
$Link{$ifIndex} = $status;
}
# HP combines speed and duplex and it has to be teased apart lexically.
if ($status = $self->get1("hpSwitchPortFastEtherMode",$ifIndex)) {
my @parse = split("-duplex-", $status);
if (2 == scalar(@parse)) {
$duplex{$ifIndex} = $parse[0];
$speed{$ifIndex} = $parse[1];
} else {
@parse = split("auto-",$status);
$duplex{$ifIndex} = "auto";
$speed{$ifIndex} = $parse[1];
if ($speed{$ifIndex} eq "neg") {
$speed{$ifIndex} = "auto";
}
}
}
};
#
# Put all of the data gathered in the loop into a list suitable for
# returning
#
my @rv = ();
foreach my $id ( keys %Able ) {
$modport = $self->{IFINDEX}{$id};
$portname = $self->{NAME} . ":$modport";
my $port = Port->LookupByTriple($portname);
if (defined($port)) {
$port = $port->getOtherEndPort();
}
#
# Skip ports that don't seem to have anything interesting attached
#
if (!$port && $self->{DOALLPORTS}) {
$modport =~ s/\./\//;
$port = Port->LookupByStringForced($self->{NAME} . ":$modport");
}
if (!$port) {
$self->debug("$id ($modport) not connected, skipping\n");
next;
}
if (! defined ($speed{$id}) ) { $speed{$id} = " "; }
if (! defined ($duplex{$id}) ) { $duplex{$id} = " "; }
push @rv, [$port,$Able{$id},$Link{$id},$speed{$id},$duplex{$id}];
}
return @rv;
}
#
# Get statistics for ports on the switch
#
# usage: getStats($self)
# see snmpit_cisco_stack.pm for a description of return value format
#
#
sub getStats() {
my $self = shift;
#
# Walk the tree for the VLAN members
#
my $vars = new SNMP::VarList(['ifInOctets'],['ifInUcastPkts'],
['ifInNUcastPkts'],['ifInDiscards'],
['ifInErrors'],['ifInUnknownProtos'],
['ifOutOctets'],['ifOutUcastPkts'],
['ifOutNUcastPkts'],['ifOutDiscards'],
['ifOutErrors'],['ifOutQLen']);
my @stats = $self->{SESS}->bulkwalk(0,32,$vars);
#
# We need to flip the two-dimentional array we got from bulkwalk on
# its side, and convert ifindexes into node:port
#
my $i = 0;
my %stats;
foreach my $array (@stats) {
while (@$array) {
my ($name,$ifindex,$value) = @{shift @$array};
# See comments in walkTable above.
if (! defined $self->{IFINDEX}{$ifindex}) { next; }
my $po = convertPortFromString("$self->{NAME}:".$self->{IFINDEX}{$ifindex});
if (! defined $po) { next; } # Skip if we don't know about it
my $port = $po->getOtherEndPort()->toTripleString();
${$stats{$port}}[$i] = $value;
}
$i++;
}
return map [convertPortFromString($_),@{$stats{$_}}], sort {tbsort($a,$b)} keys %stats;
}
#
# Used to flush FDB entries easily
#
# usage: resetVlanIfOnTrunk(self, modport, vlan)
#
sub resetVlanIfOnTrunk($$$) {
my ($self, $modport, $vlan) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$modport);
return -1
if (!$ifIndex);
$self->debug($self->{NAME} . "::resetVlanIfOnTrunk m $modport "
. "vlan $vlan ifIndex $ifIndex\n",1);
if ($self->{d1dx2ifx}) { $ifIndex = $self->{d1x2ifx}{$ifIndex}; }
my $vlan_ports = $self->get1($egressOID, $vlan);
if (testPortSet($vlan_ports, $ifIndex - 1)) {
$self->setVlansOnTrunk($modport,0,$vlan);
$self->setVlansOnTrunk($modport,1,$vlan);
}
return 0;
}
#
# Get the ifindex for an EtherChannel (trunk given as a list of ports)
#
# usage: getChannelIfIndex(self, ports)
# Returns: undef if more than one port is given, and no channel is found
# an ifindex if a channel is found and/or only one port is given
#
# N.B. by Sklower - cisco's use this to put vlans on multiwire trunks;
# it gets called from _stack.pm
#
# HP's also require a different ifindex for putting a vlan on a multiwire
# trunk from the individual ifindex from any constituent port.
#
# although Rob Ricci's vision is that this would only get called when putting
# vlans on multi-wire interswitch trunks and the check would happen in
# _stack, it is 1.) possible to use snmpit -i Switch / to do
# maintenance functions of vlans and so you should check for each port
# any way, and 2.) the check is cheap and can be done in convertPortFormat.
#
sub getChannelIfIndex($@) {
my $self = shift;
my @ports = @_;
my @ifIndexes = $self->convertPortFormat($PORT_FORMAT_IFINDEX,@ports);
my $ifindex = undef;
return undef
if (! @ifIndexes);
#
# Try to get a channel number for each one of the ports in turn - we'll
# take the first one we get
#
foreach my $port (@ifIndexes) {
if ($port) { $ifindex = $port; last; }
}
return $ifindex;
}
#
# Enable, or disable, port on a trunk
#
# usage: setVlansOnTrunk(self, modport, value, vlan_numbers)
# modport: module.port of the trunk to operate on
# value: 0 to disallow the VLAN on the trunk, 1 to allow it
# vlan_numbers: An array of 802.1Q VLAN numbers to operate on
# Returns 1 on success, 0 otherwise
#
sub setVlansOnTrunk($$$$) {
my ($self, $modport, $value, @vlan_numbers) = @_;
my $errors = 0;
my $id = $self->{NAME} . "::setVlansOnTrunk";
#
# Some error checking
#
if (($value != 1) && ($value != 0)) {
warn "$id: Invalid value $value.\n";
return 0;
}
if (grep(/^1$/,@vlan_numbers)) {
warn "$id: will not set port $modport on VLAN 1.\n";
return 0;
}
$self->debug("$id: m $modport v $value nums @vlan_numbers\n");
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX, $modport);
return undef
if (! $ifIndex);
#
# Make sure ostensible trunk is either trunk or dual mode
#
my $trunks = $self->trunkedPorts($ifIndex);
if (!$$trunks{$ifIndex}) {
warn "$id: port $modport is not trunked.\n";
return 0;
}
foreach my $vlan_number (@vlan_numbers) {
if ($self->updateOneVlan(0, 0, $value, $vlan_number, $modport)) {
$errors++;
warn "$id:couldn't " . (($value == 1) ? "add" : "remove") .
" port $modport on vlan $vlan_number\n" ;
}
}
return !$errors;
}
#
# Enable trunking on a port
#
# usage: enablePortTrunking2(self, modport, nativevlan, equaltrunking[, drop])
# modport: module.port of the trunk to operate on
# nativevlan: VLAN number of the native VLAN for this trunk
# equaltrunk: don't do dual mode; tag PVID also.
# exclude: need to choose something other than this, which actually
# is the current PVID for this port.
# Returns 1 on success, 0 otherwise
#
sub enablePortTrunking2($$$$) {
my ($self,$port,$native_vlan,$equaltrunking) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$port);
return 0
if (!$ifIndex);
my $id = $self->{NAME} .
"::enablePortTrunking($port,$native_vlan,$equaltrunking)";
my ($defLists, $rv);
if ((!$equaltrunking) && (!defined($native_vlan) || ($native_vlan <= 1))) {
warn "$id: inappropriate or missing PVID for trunk\n";
return 0;
}
#
# Deal with already trunked ports here, so as to not disrupt traffic
#
my $tagOnly = $self->get1($aftOID,$ifIndex);
my $dualPorts = mirvPortSet($self->get1($forbidOID, 1)); # array
my $pvid = $self->get1("dot1qPvid", $ifIndex);
if ($tagOnly eq "admitOnlyVlanTagged") {
if ($equaltrunking) {
# We've been called redundantly, so just add the vlan
return 1 if ($native_vlan eq "1");
# the following enables empty trunks and drops vlan 1 from them;
return ! $self->setPortVlan($native_vlan, $port);
} else {
# update to dual mode
return (!$self->updateOneVlan(0, 1, 1, $native_vlan, $port)) # untag
&& (!$self->updateOneVlan(1, 0, 0, 1, $port)); # mark
}
} elsif (@$dualPorts[$ifIndex-1]) {
if ($equaltrunking) {
$rv = $self->updateOneVlan(0, 0, 0, 1, $port) || # clear marker
$self->updateOneVlan(0, 0, 1, $pvid, $port) || # make untagged
(($native_vlan ne "$pvid") &&
$self->updateOneVlan(0, 0, 1, $native_vlan, $port));
return !$rv;
}
return 1 if ($native_vlan eq "$pvid"); # port already member
$self->updateOneVlan(0, 0, 1, $pvid, $port); # make pvid tagged
$self->updateOneVlan(0, 1, 1, $native_vlan, $port); # make untagged
} elsif ($native_vlan ne "$pvid") {
warn "$id: Unable to add Trunk to native VLAN\n"
if $self->setPortVlan($native_vlan, $port);
}
#
# Set port type apropriately.
#
if ($equaltrunking) {
$rv = $self->updateOneVlan(0, 0, 1, $native_vlan, $port); # untag
} else {
$rv = $self->updateOneVlan(1, 0, 0, 1, $port); # mark dual
}
return !$rv;
}
#
# Internal helper for trunking
#
sub otherTrunkedVlans($$$)
{
my ($self, $portIndex, $pvid) = @_;
my @others;
#
# we have to walk all of the blasted vlans to find out
# which ones have this port as a member.
#
my ($rows) = $self->{SESS}->bulkwalk(0,32, [$egressOID]);
foreach my $rowref (@$rows) {
my ($ignored_name, $vnum, $portset) = @$rowref;
push @others, $vnum
if (testPortSet($portset, $portIndex - 1) && ($vnum ne "$pvid"));
}
return @others;
}
#
# Disable trunking on a port
#
# usage: disablePortTrunking(self, modport)
# Returns 1 on success, 0 otherwise
#
sub disablePortTrunking($$) {
my ($self, $port) = @_;
my $id = $self->{NAME} . "::disablePortTrunking($port)";
my ($portIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$port);
return 0
if (!$portIndex);
my $native_vlan = $self->get1("dot1qPvid",$portIndex) || 1;
my @remvlans = $self->otherTrunkedVlans($portIndex, $native_vlan);
if ($native_vlan eq "1") {
$self->updateOneVlan(0, 1, 1, 1, $port); # make untagged
warn "$id: Unable to disable former equaltrunk\n"
if ($self->portControl("disable",$port));
} else {
$self->updateOneVlan(0, 0, 0, 1, $port); # clear marker
$self->updateOneVlan(0, 1, 1, $native_vlan, $port); # make untagged;
}
foreach my $vlan_number (@remvlans) {
warn "$id: Unable to remove VLAN $vlan_number\n"
if $self->delPortVlan($vlan_number, $portIndex);
}
return 1;
}
my %blade_sizes = (
hpSwitchJ8697A => 24, # hp5406zl
hpSwitchJ8698A => 24, # hp5412zl
hpSwitchJ8770A => 24, # hp4204
hpSwitchJ8773A => 24 # hp4208
);
sub calcModPort($$) {
my ($self, $ifindex, $j, $port, $mod) = @_;
my $bladesize = $blade_sizes{$self->{HPTYPE}};
if (defined($bladesize)) {
$j = $ifindex - 1;
$port = 1 + ($j % $bladesize);
$mod = 1 + int ($j / $bladesize);
} else
{ $mod = 1; $port = $ifindex; }
return "$mod.$port";
}
#
# Reads the IfIndex table from the switch, for SNMP functions that use
# IfIndex rather than the module.port style. Fills out the objects IFINDEX
# members,
#
# usage: readifIndex(self)
# returns nothing but sets the instance variable IFINDEX.
#
# TODO: XXXXXXXXXXXXXXXXXXXXXXX - the 288 is a crock;
# for some reason doing an swalk of ifType returns 161 instead of
# "ieee8023adLag"; we should walk ifType look for the least ifIndex of
# that type and walk hpSwitchPortTrunkType looking for the least lacpTrk
# to figure out the offset.
sub readifIndex($) {
my $self = shift;
my ($maxport, $maxtrunk, $name, $ifindex, $iidoid, $port, $mod, $j) = (0,0);
$self->debug($self->{NAME} . "::readifIndex:\n", 2);
my ($rows) = snmpitBulkwalkFatal($self->{SESS}, ["hpSwitchPortTrunkGroup"]);
my $t_off = $self->{TRUNKOFFSET} = 288;
foreach my $rowref (@$rows) {
($name,$ifindex,$iidoid) = @$rowref;
$self->debug("got $name, $ifindex, iidoid $iidoid\n", 2);
$self->{TRUNKINDEX}{$ifindex} = $iidoid;
if ($iidoid) { push @{$self->{TRUNKS}{$iidoid}}, $ifindex; }
if ($ifindex > $maxport) { $maxport = $ifindex;}
if ($iidoid > $maxtrunk) { $maxtrunk = $iidoid;}
}
while (($ifindex, $iidoid) = each %{$self->{TRUNKINDEX}}) {
my $modport = $self->calcModPort($ifindex);
my $portindex = $iidoid ? ($t_off + $iidoid) : $ifindex ;
$self->{IFINDEX}{$modport} = $portindex;
$self->{IFINDEX}{$ifindex} = $modport;
$self->debug("$ifindex, $modport\n", 2);
}
foreach $j (keys %{$self->{TRUNKS}}) {
$ifindex = $j + $t_off;
if (my $lref = $self->{TRUNKS}{$j}) {
$port = $self->{IFINDEX}{@$lref[0]}; #actually modport
} else { $port = "1." . $ifindex; } # the else should never happen
$self->{IFINDEX}{$ifindex} = $port;
$self->{IFINDEX}{$port} = $ifindex;
$self->{TRUNKINDEX}{$ifindex} = 0; # simplifies convertPortIndex
$self->debug("$ifindex, $modport\n", 2);
}
$self->{MAXPORT} = $maxport;
$self->{MAXTRUNK} = $maxtrunk;
}
#
# Read a set of values for all given ports.
#
# usage: getFields(self,ports,oids)
# ports: Reference to a list of ports, in any allowable port format
# oids: A list of OIDs to reteive values for
#
# On sucess, returns a two-dimensional list indexed by port,oid
#
sub getFields($$$) {
my $self = shift;
my ($ports,$oids) = @_;
my @ifindicies = $self->convertPortFormat($PORT_FORMAT_IFINDEX,@$ports);
return ()
if (! @ifindicies);
my @oids = @$oids;
#
# Put together an SNMP::VarList for all the values we want to get
#
my @vars = ();
foreach my $ifindex (@ifindicies) {
foreach my $oid (@oids) {
push @vars, ["$oid","$ifindex"];
}
}
#
# If we try to ask for too many things at once, we get back really bogus
# errors. So, we limit ourselves to an arbitrary number that, by
# experimentation, works.
#
my $maxvars = 16;
my @results = ();
while (@vars) {
my $varList = new SNMP::VarList(splice(@vars,0,$maxvars));
my $rv = $self->{SESS}->get($varList);
push @results, @$varList;
}
#
# Build up the two-dimensional list for returning
#
my @return = ();
foreach my $i (0 .. $#ifindicies) {
foreach my $j (0 .. $#oids) {
my $val = shift @results;
$return[$i][$j] = $$val[2];
}
}
return @return;
}
#
# Prints out a debugging message, but only if debugging is on. If a level is
# given, the debuglevel must be >= that level for the message to print. If
# the level is omitted, 1 is assumed
#
# Usage: debug($self, $message, $level)
#
sub debug($$;$) {
my $self = shift;
my $string = shift;
my $debuglevel = shift;
if (!(defined $debuglevel)) {
$debuglevel = 1;
}
if ($self->{DEBUG} >= $debuglevel) {
print STDERR $string;
}
}
my $lock_held = 0;
sub lock($) {
my $self = shift;
my $token = "snmpit_" . $self->{NAME};
if ($lock_held == 0) {
my $old_umask = umask(0);
die if (TBScriptLock($token,0,1800) != TBSCRIPTLOCK_OKAY());
umask($old_umask);
}
$lock_held = 1;
}
sub unlock($) {
if ($lock_held == 1) { TBScriptUnlock();}
$lock_held = 0;
}
#
# Enable Openflow
#
sub enableOpenflow($$) {
my $self = shift;
my $vlan = shift;
my $RetVal;
$RetVal = $self->set([$ofEnableOID, $vlan, 1, "INTEGER"]);
if (!defined($RetVal)) {
warn "ERROR: Unable to enable Openflow on VLAN $vlan\n";
return 0;
}
return 1;
}
#
# Disable Openflow
#
sub disableOpenflow($$) {
my $self = shift;
my $vlan = shift;
my $RetVal;
$RetVal = $self->set([$ofEnableOID, $vlan, 2, "INTEGER"]);
if (!defined($RetVal)) {
warn "ERROR: Unable to disable Openflow on VLAN $vlan\n";
return 0;
}
return 1;
}
#
# Set controller
#
sub setOpenflowController($$$$) {
my $self = shift;
my $vlan = shift;
my $controller = shift;
my $option = shift;
my $RetVal;
$RetVal = $self->set([$ofControllerOID, $vlan, $controller, "OCTETSTR"]);
if (!defined($RetVal)) {
warn "ERROR: Unable to set controller on VLAN $vlan\n";
return 0;
}
if (defined($option) && $option eq "fail-secure") {
$RetVal = $self->set([$ofFailModeOID, $vlan, 1, "INTEGER"]);
if (!defined($RetVal)) {
warn "ERROR: Unable to set controller option $option ".
"on VLAN $vlan\n";
return 0;
}
}
return 1;
}
#
# Set listener
#
sub setOpenflowListener($$$) {
my $self = shift;
my $vlan = shift;
my $listener = shift;
my $RetVal;
$RetVal = $self->set([$ofListenerOID, $vlan, $listener, "OCTETSTR"]);
if (!defined($RetVal)) {
warn "ERROR: Unable to set listener on VLAN $vlan\n";
return 0;
}
return 1;
}
#
# Get used listener ports
#
sub getUsedOpenflowListenerPorts($) {
my $self = shift;
my %ports = ();
my $listener = [$ofListenerOID,0];
#
# Get all listeners and gather their ports
#
my ($varname, $vlan, $connstr);
$self->{SESS}->getnext($listener);
do {
($varname, $vlan, $connstr) = @{$listener};
$self->debug("listener: $varname $vlan $connstr \n");
if ($varname =~ /$ofListenerVarNameMarker/) {
my ($proto, $port) = split(":", $connstr);
if (defined($port)){
$ports{$port} = 1;
}
#
# the SNMP session with MIB gives varname with strings not numbers, but
# the string names can't be used to get the next entry in table! So we
# have to use the numbered OID. To get the next entry, we must
# append the current instance ID, which is the last section of the dotted
# varname, to the numbered OID.
#
my $lastdot = rindex($varname, '.');
$listener->[0] = $ofListenerOID.".".substr($varname, $lastdot+1);
$self->{SESS}->getnext($listener);
}
} while ($varname =~ /$ofListenerVarNameMarker/);
return %ports;
}
#
# Check if Openflow is supported on this switch
#
sub isOpenflowSupported($) {
my $self = shift;
my $ret;
$ret = $self->get1($ofSupportOID, 0);
if (defined($ret) && $ret ne 'NOSUCHOBJECT') {
return 1;
} else {
return 0;
}
}
package snmpit_h3c;
use strict;
use English;
use snmpit_lib;
# use libtestbed;
# use SNMP;
use vars qw(@ISA);
@ISA = 'snmpit_hpupd';
# Some differences between h3c switches earlier HP switches:
# The literature claims that it supports the IETF P and Q bridge mibs
# with the exception of the $forbidOID.
# However, the $aftOID is useless in determining if a port is trunked;
# we have to use a private mib variable, hh3cifVLANType.
# The interpretation of the
# dot1qVlanStatic{Untagged,Egress}Ports are different in that
# for the older HP's the index of the bit is taken to be an ifIndex
# whereas on the h3c's it interpreted as an index in the dot13d agreegate
# index space which appears to have different numbering.
# putting a port into vlan requires only setting the appropriate bit
# in just the dot1qVlanStaticEgressPorts variable, instead of the 6 instances
# required as an atomic action for the older HP's.
my $typeOID = 'hh3cifVLANType';
# some h3c specific helper functions
sub getOidToMappedList($$$) {
my ($self, $oid, $idx) = @_;
my $bits = substr($self->get1($oid,$idx), 0, $self->{USELIM});
return map { $self->{d1dx2ifx}{$_} } $self->portSetToList($bits);
}
sub mapListToOid($@) {
my ($self, @l) = @_;
return $self->listToPortSet(map { $self->{ifx2d1dx}{$_} } @l );
}
sub getVlanMembers($$) {
my ($self, $vlan) = @_;
my @allmems = $self->getOidToMappedList($egressOID,$vlan);
my @normmems = $self->getOidToMappedList($normOID,$vlan);
my $mems = { map { ($_, 'tagged') } @allmems } ;
@$mems{@normmems} = ('untagged') x @normmems ;
return $mems;
}
sub setVlanMembers($$$) {
my ($self, $vlan, $mems, $rv) = @_;
my $id = $self->{NAME} . '::setvLanMembers';
$rv = $self->set([$egressOID,$vlan,$self->mapListToOid(keys %$mems)], $id);
return (!defined($rv));
}
sub leBitsToList($$) {
my ($self,$bits) = @_;
my ($lim, $i, @result) = ((8 * length($bits)), -1);
while (++$i < $lim) { if (vec($bits,$i++,1)) { push @result, $i; } }
return @result;
}
sub removeSomeVlanMembers($$$@) {
my ($self, $vlan, $mems, @ports) = @_;
my @disable = grep {$$mems{$_} && ($$mems{$_} eq 'untagged')} @ports;
my $duals = $self->trunkedPorts(@disable);
@disable = grep {!$$duals{$_}} @disable;
delete @$mems{@ports};
return $self->setVlanMembers($vlan, $mems)
+ (@disable ? $self->portControl("disable", @disable): 0);
}
#
# published interfaces description in snmpit_hp package
#
sub removeSomePortsFromVlan($$@) {
my ($self, $vlan, @ports) = @_;
@ports = $self->convertPortFormat($PORT_FORMAT_IFINDEX,@ports);
$self->lock();
my $mems = $self->getVlanMembers($vlan);
my $errors = $self->removeSomeVlanMembers($vlan, $mems, @ports);
$self->unlock();
return $errors;
}
sub removePortsFromVlan($@) {
my ($self, @vlans) = @_;
my $errors = 0;
$self->lock();
foreach my $vlan (@vlans) {
my $mems = $self->getVlanMembers($vlan);
$errors += $self->removeSomeVlanMembers($vlan, $mems, keys %$mems);
}
$self->unlock();
return $errors;
}
sub enablePortTrunking2($$$$) {
my ($self,$port,$native_vlan,$equaltrunking) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$port);
my $id = $self->{NAME} .
"::enablePortTrunking($port,$native_vlan,$equaltrunking)";
my $errors = 0;
$self->lock();
my ($trunks, $oldpid) = ($self->trunkedPorts($ifIndex));
if (!($oldpid = $$trunks{$ifIndex})) {
$errors = $self->set([$typeOID, $ifIndex, 'vLANTrunk']);
$errors += $self->removeSomePortsFromVlan(1,$ifIndex);
} else {
$errors = $self->portControl("enable", $ifIndex);
}
$native_vlan = 1 if ($equaltrunking);
$errors += $self->setPortVlan($native_vlan, $ifIndex)
if ($native_vlan != 1);
$errors += !defined($self->set(['dot1qPvid', $ifIndex, $native_vlan]))
if ((defined($oldpid)&&($oldpid!=$native_vlan)) || ($native_vlan!=1));
$self->unlock();
return $errors == 0;
}
sub disablePortTrunking($$) {
my ($self, $port) = @_;
my $id = $self->{NAME} . "::disablePortTrunking($port)";
my ($portIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$port);
my $trunks = $self->trunkedPorts($portIndex);
my $native_vlan = $$trunks{$portIndex};;
return 0 unless ($native_vlan);
my @remvlans = $self->otherTrunkedVlans($portIndex, 0);
foreach my $vlan_number (@remvlans) {
warn "$id: Unable to remove VLAN $vlan_number\n"
if $self->removeSomePortsFromVlan($vlan_number, $portIndex);
}
my $errors = $self->set([$typeOID, $portIndex, 'access'])
+$self->setPortVlan($native_vlan, $portIndex);
return $errors == 0;
}
sub setPortVlan($$@) {
my ($self, $vlan_number, @ports) = @_;
my ($portIndex, $pvid, $rv, @protoTrunks, @newTaggedPorts);
my %vlansToPorts; # i.e. bumpedVlansToListOfPorts
my $id = $self->{NAME} . "::setPortVlan($vlan_number)";
my @portlist = $self->convertPortFormat($PORT_FORMAT_IFINDEX, @ports);
return 0 unless(@ports);
$self->debug($id);
$self->debug("ports: " . join(",",@ports) . "\n");
$self->debug("as ifIndexes: " . join(",",@portlist) . "\n");
my $oldTrunks = $self->trunkedPorts(@portlist);
foreach $portIndex (@portlist) {
if (exists($$oldTrunks{$portIndex})) {
push @newTaggedPorts, $portIndex;
next;
}
$pvid = $self->get1("dot1qPvid", $portIndex);
push @{$vlansToPorts{$pvid}}, $portIndex
if (defined($pvid) && ($pvid != $vlan_number));
}
@{$self->{DISPLACED_VLANS}} = grep {$_ != 1;} keys %vlansToPorts;
$self->lock();
my $newInfo = $self->getVlanMembers($vlan_number);
foreach my $vlan (keys %vlansToPorts)
{ map { $$newInfo{$_} = 'untagged'} (@{$vlansToPorts{$vlan}}); }
# Now add tagged ports separately, just to be safe.
@$newInfo{@newTaggedPorts} = ('tagged') x @newTaggedPorts
if (@newTaggedPorts);
my $errors = $self->setVlanMembers($vlan_number,$newInfo);
$self->unlock();
my $onoroff = ($vlan_number ne "1") ? "enable" : "disable";
$self->debug("$id; will $onoroff" . join(',',@ports) . "...\n");
if ( $rv = $self->portControl($onoroff, @ports) ) {
warn "$id: Port enable had $rv failures.\n";
$errors += $rv;
}
return $errors;
}
# h3c variants of snmpit_hp helpers;
my %h3c_cmdOIDs =
(
"enable" => ["ifAdminStatus","up"],
"disable"=> ["ifAdminStatus","down"],
"10000mbit"=> ["hh3cifEthernetSpeed","S10000M"],
"1000mbit"=> ["hh3cifEthernetSpeed","S10000M"],
"100mbit"=> ["hh3cifEthernetSpeed","S100M"],
"10mbit" => ["hh3cifEthernetSpeed","S10M"],
"auto" => ["hh3cifEthernetDuplex","auto"],
"full" => ["hh3cifEthernetDuplex","full"],
"half" => ["hh3cifEthernetDuplex","half"],
);
sub readifIndex($) {
my $self = shift;
my ($t_off, $maxport, $name, $ifindex, $iidoid, $port, $mod) = (0,0);
$self->debug($self->{NAME} . "::readifIndex:\n", 2);
my ($rows) = snmpitBulkwalkFatal($self->{SESS}, ["ifType"]);
foreach my $rowref (@$rows) {
($name,$ifindex,$iidoid) = @$rowref;
$self->debug("got $name, $ifindex, iidoid $iidoid\n", 2);
$maxport = $ifindex if ($ifindex > $maxport);
my $modport = $self->calcModPort($ifindex);
my $portindex = $ifindex ;
$self->{IFINDEX}{$modport} = $portindex;
$self->{IFINDEX}{$ifindex} = $modport;
}
($rows) = snmpitBulkwalk($self->{SESS}, ["dot3adAggPortListTable"]);
map {if (($t_off == 0) || ($t_off > @$_[1])) {$t_off = @$_[1];};} @$rows;
if ($t_off > 0) { $t_off--; }
foreach my $rowref (@$rows) {
($name,$ifindex,$iidoid) = @$rowref;
my @mems = $self->portSetToList($iidoid);
next unless(@mems);
$self->{TRUNKS}{$ifindex - $t_off} = [ @mems];
map {$self->{TRUNKINDEX}{$_} = $ifindex - $t_off; } @mems;
my $modport = $self->{IFINDEX}{$mems[0]};
foreach $port (@mems, $ifindex) {
$name = $self->{IFINDEX}{$port};
$self->{IFINDEX}{$port} = $modport;
$self->{IFINDEX}{$name} = $ifindex;
}
}
($rows) = snmpitBulkwalk($self->{SESS}, ["dot1dBasePortIfIndex"]);
$self->{d1dx2ifx} = { map { (@$_[1], @$_[2])} @$rows };
$self->{ifx2d1dx} = { map { (@$_[2], @$_[1])} @$rows };
if ($maxport > 0) { $maxport = (($maxport - 1) | 31) + 1 };
$self->{MAXPORT} = $maxport;
$self->{USELIM} = $maxport / 8;
$self->{TRUNKOFFSET} = $t_off;
$self->{cmdOIDs} = \%h3c_cmdOIDs;
}
sub trunkedPorts($@) {
my ($self, @ports) = @_;
my ($trunks, $modes, $pids, $j) = ({});
if (@ports) {
$modes = [ map { [ $typeOID, $_ ] } @ports ];
$j = $self->{SESS}->get($modes);
} else {
($modes) = $self->{SESS}->bulkwalk(0,32, [$typeOID]);
}
@ports = map { @$_[1] } grep { @$_[2] ne 'access' } @$modes;
if (@ports) {
$pids = [ map { [ 'dot1qPvid', $_ ] } @ports ];
$j = $self->{SESS}->get($pids);
map { $$trunks{@$_[1]} = @$_[2]} @$pids;
}
return ($trunks);
}
sub otherTrunkedVlans($$$)
{
my ($self, $portIndex, $pvid) = @_;
my $highbits = $self->get1('hh3cifVLANTrunkAllowListHigh',$portIndex);
my $lowbits = $self->get1('hh3cifVLANTrunkAllowListLow',$portIndex);
my @others = ($self->leBitsToList($lowbits),
map {$_ + 2048} $self->leBitsToList($highbits));
return (grep { $_ ne "$pvid" } @others);
}
# End with true
1;