Commit f66b9dbf authored by Robert Ricci's avatar Robert Ricci

A whole ball of snmpit improvements from Keith Sklower at Berkeley.

parent 10f1e248
......@@ -101,6 +101,8 @@ VLAN Control:
-N <name> Print out the VLAN number for the named VLAN
-c Delete ALL VLANs, and recreate from the database. ** USE
WITH EXTREME CAUTION **
-F Create all vlans in the given stack on the leader for
use in leader->{ALLVLANSONLEADER} (Internal use only)
Port Control:
-s List all ports, and show configuration information
......@@ -112,6 +114,7 @@ Port Control:
-u <half|full> <ports> Set duplex of <ports> to half or full
-T <port> <names> Enable trunking on the given <port>, and allow VLANs
with the given <names> across it
-E <port> <names> Like -T, but "Equal" mode; PVID is also tagged
-U <port> Turn off trunking for the given <port>
-b <ports> Print out port status for a set of ports
-B <statstring> Pass in a stat string from -b to restore status
......@@ -128,8 +131,8 @@ END
my %opt = ();
Getopt::Long::Configure("no_ignore_case");
GetOptions(\%opt, 'a','c','d','e','b','B=s@','g','h','i=s@','l','m=s@','M','n',
'N=s@','o=s@','p=s','q','r','s', 'S=s@', 't','T=s','u=s','U','v=s','w',
'y=s','x=s','z=s');
'N=s@','o=s@','p=s','q','r','s', 'S=s@','t','E=s','T=s','u=s','U','v=s','w',
'y=s','x=s','z=s','F');
# Unused: f,j
if ($opt{h}) {
......@@ -152,6 +155,7 @@ my $pid;
my $eid;
my @ports;
my @optvlanids = ();
my $equaltrunking = 0;
#
# Some operations have mandatory agruments - for others, make sure that
......@@ -176,12 +180,13 @@ if ($opt{t} || $opt{r}) {
# Options that take a list of ports
#
@ports = @ARGV;
} elsif ($opt{T}) {
} elsif ($opt{T} || $opt{E}) {
#
# Options that take both a port and a list of VLANs - we require at least
# one VLAN to be given
#
if (!@ARGV) {
if ($opt{E}) { $opt{T} = $opt{E}; $equaltrunking = 1;}
elsif (!@ARGV) {
tberror "At least one VLAN required";
exit &usage;
}
......@@ -219,9 +224,10 @@ if ($opt{r}) { push @commands, ["reset"]; }
if ($opt{c}) { push @commands, ["recreate"]; }
if ($opt{U}) { push @commands, ["trunkdisable"]; }
if ($opt{b}) { push @commands, ["portstatus"]; }
if ($opt{F}) { push @commands, ["synchleader"]; }
#
# Commands that can appear once, and take an agurment
# Commands that can appear once, and take an argument
#
if ($opt{d}) { push @commands, ["portcontrol","disable"]; }
if ($opt{e}) { push @commands, ["portcontrol","enable"]; }
......@@ -457,7 +463,7 @@ foreach my $command (@commands) {
my @devicenames;
my @vlans;
SWITCH: for ($operation) {
(/listvlans/ || /getstats/ || /make/ || /remove/ || /vlannumber/) && do {
(/listvlans/ || /getstats/ || /make/ || /remove/ || /vlannumber/ || /synchleader/) && do {
@devicenames = $supplied_switches?
@supplied_switches : getTestSwitches();
last;
......@@ -581,12 +587,6 @@ foreach my $command (@commands) {
@{$stacks{$stack_id}});
last;
};
/nortel/ && do {
require snmpit_nortel;
$stack = new snmpit_nortel($stack_id,$debug,
@{$stacks{$stack_id}});
last;
};
/generic/ && do {
require snmpit_stack;
$stack = new snmpit_stack($stack_id,$debug,
......@@ -648,6 +648,10 @@ foreach my $command (@commands) {
$exitval += doListVlans(\@stacks);
last;
}; # /listvlans/ && do
/synchleader/ && do {
$exitval += doSynchLeader(\@stacks);
last;
}; # /listvlans/ && do
/listports/ && do {
$exitval += doListPorts(\@stacks);
last;
......@@ -689,7 +693,7 @@ foreach my $command (@commands) {
last;
}; # /trunkenable/ && do
/trunkdisable/ && do {
$exitval += doTrunkDisable(\@stacks,@ports);
$exitval += doTrunkDisable(\@stacks,$ports[0]);
last;
}; # /trunkdisable/ && do
/portstatus/ && do {
......@@ -856,6 +860,35 @@ $vlan_id,$ddep, $pideid, $vname, $members
return 0;
}
#
# This routine does a number of illegal things. It is used
# to make sure the leader of a stack contains all vlans on all switches
# in the stack and is only useful for moving a stack back and forth
# between types "cisco" and "generic" (where $stack->{ALLVLANSONLEADER} must be
# set to 1.
#
sub doSynchLeader ($) {
my $stacks = shift;
my $errors = 0;
if (!TBAdmin()) {
die "Only admins are allowed to synchronize stacks\n";
}
foreach my $stack (@$stacks) {
my $leader = $stack->{LEADER};
my %vlans = $stack->findVlans();
while (my ($id,$num) = each %vlans) {
if (($num >= $leader->{MIN_VLAN}) &&
($num <= $leader->{MAX_VLAN}) &&
!($leader->vlanNumberExists($num))) {
$errors += $leader->createVlan($id, $num);
}
}
}
return $errors;
}
#
# Lists all ports on all stacks
#
......@@ -1110,6 +1143,8 @@ sub doVlansFromTables($@) {
my @vlans = @_;
my $errors = 0;
my $vlan_number;
my $oEopt = $equaltrunking;
if (@$stacks > 1) {
die "VLAN creation accross multiple stacks is not yet supported\n" .
......@@ -1124,6 +1159,13 @@ sub doVlansFromTables($@) {
return RemoteDoVlansFromTables(@vlans);
}
my @trunkedPorts = getExperimentTrunks($eid,$pid);
$equaltrunking = 1;
foreach my $port (@trunkedPorts) {
$errors += doTrunkEnable($stacks,$port);
}
$equaltrunking = $oEopt;
foreach my $vlan (@vlans) {
my @ports = getVlanPorts($vlan);
if ($stack->vlanExists($vlan)) {
......@@ -1131,13 +1173,14 @@ sub doVlansFromTables($@) {
if (!$quiet);
$errors += $stack->setPortVlan($vlan,@ports);
} else {
if (!$stack->createVlan($vlan,\@ports)) {
$vlan_number = $stack->createVlan($vlan,\@ports);
if (!$vlan_number) {
tberror "Failed to create VLAN with id $vlan";
#
# Don't try to put ports in a VLAN if it couldn't be created
#
$errors++;
}
} else { setVlanTag($vlan, $vlan_number); }
}
#
......@@ -1188,6 +1231,10 @@ sub doReset($@) {
}
my $errors = 0;
my @trunkedPorts = getExperimentTrunks($eid,$pid);
foreach my $port (@trunkedPorts) {
$errors += doTrunkDisable($stacks,$port);
}
#
# Just remove the VLAN from evey stack on which it exists. We keep a
# list and do them all at once for efficiency.
......@@ -1197,6 +1244,9 @@ sub doReset($@) {
if (!$stack->removeVlan(@existant_vlans)) {
$errors++;
}
foreach my $vlan (@existant_vlans) {
setVlanTag($vlan, 0);
}
}
return $errors;
}
......@@ -1453,7 +1503,7 @@ sub doTrunkEnable($$@) {
my $stack = $$stacks[0];
print "Enabling trunking on $port ...\n"
if (!$quiet);
return !($stack->enableTrunking($port,@vlans));
return !($stack->enableTrunking2($port,$equaltrunking,@vlans));
}
#
......@@ -1476,12 +1526,10 @@ sub doTrunkDisable($$) {
#
my $stack = $$stacks[0];
my $errors = 0;
foreach my $port (@ports) {
print "Disabling trunking on port $port ...\n"
if (!$quiet);
if (!$stack->disableTrunking($port)) {
$errors++;
}
}
return $errors;
}
......@@ -22,6 +22,7 @@ use English;
use SNMP;
use snmpit_lib;
use Socket;
use libtestbed;
#
# These are the commands that can be passed to the portControl function
......@@ -898,7 +899,8 @@ sub setPortVlan($$@) {
my $errors = 0;
if (!$self->vlanNumberExists($vlan_number)) {
print STDERR "ERROR: VLAN $vlan_number does not exist\n";
print STDERR "ERROR: VLAN $vlan_number does not exist on switch"
. $self->{NAME} . "\n";
return 1;
}
......@@ -935,33 +937,59 @@ sub setPortVlan($$@) {
$format = $PORT_FORMAT_IFINDEX;
}
#
# Convert ports from the format the were passed in to the correct format
#
my @portlist = $self->convertPortFormat($format,@ports);
#
# We'll keep track of which ports suceeded, so that we don't try to
# enable/disable, etc. ports that failed.
#
my @okports = ();
foreach my $port (@portlist) {
my ($index, $retval);
my %BumpedVlans = ();
foreach my $port (@ports) {
$self->debug("Putting port $port in VLAN $vlan_number\n");
#
# Check to see if it's a trunk ....
#
($index) = $self->convertPortFormat($PORT_FORMAT_IFINDEX, $port);
$retval = snmpitGetWarn($self->{SESS},
["vlanTrunkPortDynamicState",$index]);
if (!$retval) {
$errors++;
next;
}
if (!(($retval eq "on") || ($retval eq "onNoNegotiate"))) {
#
# Convert ports to the correct format
#
($index) = $self->convertPortFormat($format, $port);
#
# Make sure the port didn't get mangled in conversion
#
if (!defined $port) {
if (!defined $index) {
print STDERR "Port not found, skipping\n";
$errors++;
next;
}
$self->debug("Putting port $port in VLAN $vlan_number\n");
my $snmpvar = [$PortVlanMemb,$index,$vlan_number,'INTEGER'];
#
# Check to see if we are already in a VLAN
#
$retval = snmpitGet($self->{SESS},[$PortVlanMemb,$index]);
if (($retval ne "NOSUCHINSTANCE") &&
("$retval" ne "$vlan_number") && ("$retval" ne "1")) {
$BumpedVlans{$retval} = 1;
}
#
# Do the acutal SNMP command
#
my $snmpvar = [$PortVlanMemb,$port,$vlan_number,'INTEGER'];
my $retval = snmpitSetWarn($self->{SESS},$snmpvar);
$retval = snmpitSetWarn($self->{SESS},$snmpvar);
} else {
#
# We're here if it a trunk
#
$retval = $self->setVlansOnTrunk($port, 1, $vlan_number);
}
if (!$retval) {
$errors++;
next;
......@@ -988,6 +1016,15 @@ sub setPortVlan($$@) {
}
}
# When removing things from the control vlan for a firewall,
# need to tell stack to shake things up to flush FDB on neighboring
# switches.
#
my @bumpedlist = keys ( %BumpedVlans );
if (@bumpedlist) {
@{$self->{DISPLACED_VLANS}} = @bumpedlist;
}
return $errors;
}
......@@ -1257,6 +1294,38 @@ sub listVlans($) {
}
}
#
# Walk trunks for the VLAN members
#
($rows) = snmpitBulkwalkFatal($self->{SESS},["vlanTrunkPortDynamicStatus"]);
$self->debug("Trunk members walk returned " . scalar(@$rows) . " rows\n");
foreach my $rowref (@$rows) {
my ($name,$ifIndex,$status) = @$rowref;
$self->debug("Got $name $ifIndex $status\n",3);
if ($status ne "trunking") { next;}
my ($node) = $self->convertPortFormat($PORT_FORMAT_NODEPORT,$ifIndex);
if (!$node) {
my ($modport) = $self->convertPortFormat($PORT_FORMAT_MODPORT,$ifIndex);
$modport =~ s/\./\//;
$node = $self->{NAME} . ".$modport";
}
# Get the existing bitfield for allowed VLANs on this trunk
my $vlan_number = -1;
my $bitfield = snmpitGetFatal($self->{SESS},
["vlanTrunkPortVlansEnabled",$ifIndex]);
my $unpacked = unpack("B*",$bitfield);
# Put this into an array of 1s and 0s for easy manipulation
foreach my $bit (split //,$unpacked) {
$vlan_number++;
if ($bit == 0) { next; }
$self->debug("got vlan $vlan_number on trunk $node\n",3);
if ($Names{$vlan_number}) {
push @{$Members{$vlan_number}}, $node;
}
}
}
#
# Build a list from the name and membership lists
#
......@@ -1500,7 +1569,14 @@ sub setVlansOnTrunk($$$$) {
#
# Get the existing bitfield for allowed VLANs on the trunk
#
# If two snmpit process were manipulating trunks at the same
# time there is a potential race in which they would both
# retrieve the same bit vector, compute different results
# and then the trunk would not be correct, so we lock
# in the file system on boss, as that is likely to be faster
# than using the vlan lock buffer on the switch.
#
$self->lock();
my $bitfield = snmpitGetFatal($self->{SESS},
["vlanTrunkPortVlansEnabled",$ifIndex]);
my $unpacked = unpack("B*",$bitfield);
......@@ -1521,6 +1597,8 @@ sub setVlansOnTrunk($$$$) {
# And save it back...
my $rv = snmpitSetFatal($self->{SESS},
["vlanTrunkPortVlansEnabled",$ifIndex,$bitfield,"OCTETSTR"]);
$self->unlock();
if ($rv) {
return 1;
} else {
......@@ -1594,8 +1672,7 @@ sub clearAllVlansOnTrunk($$) {
# return value currently ignored.
sub resetVlanIfOnTrunk($$$) {
my $self = shift;
my ($modport, $value, $vlan_number) = @_;
my ($self, $modport, $vlan_number) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$modport);
......@@ -1619,6 +1696,7 @@ sub resetVlanIfOnTrunk($$$) {
#
# Get the exisisting bitfield for allowed VLANs on the trunk
#
$self->lock();
my $bitfield = snmpitGetFatal($self->{SESS},
["vlanTrunkPortVlansEnabled",$ifIndex]);
my $unpacked = unpack("B*",$bitfield);
......@@ -1663,20 +1741,21 @@ sub resetVlanIfOnTrunk($$$) {
# And save it back...
$rv = $self->{SESS}->set(["vlanTrunkPortVlansEnabled",$ifIndex,$bitfield,
"OCTETSTR"]);
$self->unlock();
return 0;
}
#
# Enable trunking on a port
#
# usage: enablePortTrunking(self, modport, nativevlan)
# usage: enablePortTrunking2(self, modport, nativevlan, equaltrunking)
# modport: module.port of the trunk to operate on
# nativevlan: VLAN number of the native VLAN for this trunk
# Returns 1 on success, 0 otherwise
#
sub enablePortTrunking($$$) {
my $self = shift;
my ($port,$native_vlan) = @_;
sub enablePortTrunking2($$$$) {
my ($self,$port,$native_vlan,$equaltrunking) = @_;
my $trunking_vlan = ($equaltrunking ? 1 : $native_vlan);
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$port);
......@@ -1703,7 +1782,7 @@ sub enablePortTrunking($$$) {
#
# Set the native VLAN for this trunk
#
my $nativeVlan = ["vlanTrunkPortNativeVlan",$ifIndex,$native_vlan,"INTEGER"];
my $nativeVlan = ["vlanTrunkPortNativeVlan",$ifIndex,$trunking_vlan,"INTEGER"];
$rv = snmpitSetWarn($self->{SESS},$nativeVlan);
if (!$rv) {
warn "ERROR: Unable to set native VLAN on trunk\n";
......@@ -1713,13 +1792,15 @@ sub enablePortTrunking($$$) {
#
# Finally, enable trunking!
#
my $trunkEnable = ["vlanTrunkPortDynamicState",$ifIndex,"on","INTEGER"];
my $trunkEnable = ["vlanTrunkPortDynamicState",$ifIndex,"onNoNegotiate","INTEGER"];
$rv = snmpitSetWarn($self->{SESS},$trunkEnable);
if (!$rv) {
warn "ERROR: Unable to enable trunking\n";
return 0;
}
if ($equaltrunking) { return 1; }
#
# Allow the native VLAN to cross the trunk
#
......@@ -1984,5 +2065,23 @@ sub debug($$;$) {
}
}
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;
}
# End with true
1;
......@@ -253,6 +253,8 @@ sub setPortVlan($$@) {
# Split up the ports among the devices involved
#
my %map = mapPortsToDevices(@ports);
my %trunks;
my @trunks;
if ($self->{PRUNE_VLANS}) {
#
......@@ -278,8 +280,8 @@ sub setPortVlan($$@) {
# Find out every switch which might have to transit this VLAN through
# its trunks
#
my %trunks = getTrunks();
my @trunks = getTrunksFromSwitches(\%trunks, keys %switches);
%trunks = getTrunks();
@trunks = getTrunksFromSwitches(\%trunks, keys %switches);
foreach my $trunk (@trunks) {
my ($src,$dst) = @$trunk;
$switches{$src} = $switches{$dst} = 1;
......@@ -327,6 +329,7 @@ sub setPortVlan($$@) {
}
}
my %BumpedVlans = ();
#
# Perform the operation on each switch
#
......@@ -343,10 +346,48 @@ sub setPortVlan($$@) {
# Simply make the appropriate call on the device
#
$errors += $device->setPortVlan($vlan_number,@{$map{$devicename}});
#
# When making firewalls, may have to flush FDB entries from trunks
#
if (defined($device->{DISPLACED_VLANS})) {
foreach my $vlan (@{$device->{DISPLACED_VLANS}}) {
$BumpedVlans{$vlan} = 1;
}
$device->{DISPLACED_VLANS} = undef;
}
}
if ($vlan_id ne 'default') {
$errors += (!$self->setVlanOnTrunks($vlan_number,1,@ports));
# if PRUNE were not in effect, then %trunks and @trunks
# aren't valid. If VTP weren't enforced however,
# merely calling setVlanOnTrunks() with just the specified
# ports would miss transit-only switches.
# maybe the phrase at the top should be if (PRUNE || !VTP ..)
# expecially if running VTP automatically creates the interswitch
# trunks.
# I'll let Rob R. decide what to do about that -- sklower.
$errors += (!$self->setVlanOnTrunks2($vlan_number,1,\%trunks,@trunks));
}
#
# When making firewalls, may have to flush FDB entries from trunks
#
foreach my $vlan (keys %BumpedVlans) {
foreach my $devicename ($self->switchesWithPortsInVlan($vlan)) {
my $dev = $self->{DEVICES}{$devicename};
foreach my $neighbor (keys %{$trunks{$devicename}}) {
my $trunkIndex = $dev->getChannelIfIndex(
@{$trunks{$devicename}{$neighbor}});
if (!defined($trunkIndex)) {
warn "unable to find channel information on $devicename ".
"for $devicename-$neighbor EtherChannel\n";
$errors += 1;
} else {
$dev->resetVlanIfOnTrunk($trunkIndex,$vlan);
}
}
}
}
return $errors;
......@@ -679,43 +720,55 @@ sub getStats($) {
#
# Turns on trunking on a given port, allowing only the given VLANs on it
#
# usage: enableTrunking(self, port, vlan identifier list)
# usage: enableTrunking2(self, port, equaltrunking, vlan identifier list)
#
# formerly was enableTrunking() without the predicate to decline to put
# the port in dual mode.
#
# returns: 1 on success
# returns: 0 on failure
#
sub enableTrunking($$@) {
sub enableTrunking2($$$@) {
my $self = shift;
my $port = shift;
my $equaltrunking = shift;
my @vlan_ids = @_;
#
# On a Cisco, the first VLAN given becomes the native VLAN for the trunk
# Split up the ports among the devices involved
#
my $native_vlan_id = shift @vlan_ids;
if (!$native_vlan_id) {
print STDERR "ERROR: No native VLAN passed to enableTrunking()!\n";
my %map = mapPortsToDevices($port);
my ($devicename) = keys %map;
my $device = $self->{DEVICES}{$devicename};
if (!defined($device)) {
warn "ERROR: Unable to find device entry for $devicename\n";
return 0;
}
#
# Grab the VLAN number for the native VLAN
# If !equaltrunking, the first VLAN given becomes the PVID for the trunk
#
my $vlan_number = $self->{LEADER}->findVlan($native_vlan_id);
if (!$vlan_number) {
print STDERR "ERROR: Native VLAN $native_vlan_id does not exist!\n";
my ($native_vlan_id, $vlan_number);
if ($equaltrunking) {
$native_vlan_id = "default";
$vlan_number = 1;
} else {
$native_vlan_id = shift @vlan_ids;
if (!$native_vlan_id) {
warn "ERROR: No VLAN passed to enableTrunking()!\n";
return 0;
}
#
# Split up the ports among the devices involved
# Grab the VLAN number for the native VLAN
#
my %map = mapPortsToDevices($port);
my ($devicename) = keys %map;
my $device = $self->{DEVICES}{$devicename};
if (!defined($device)) {
warn "ERROR: Unable to find device entry for $devicename\n";
return 0;
$vlan_number = $device->findVlan($native_vlan_id);
if (!$vlan_number) {
warn "Native VLAN $native_vlan_id was not on $devicename";
# This is painful
my $error = $self->setPortVlan($native_vlan_id,$port);
if ($error) {
warn ", and couldn't add it\n"; return 0;
} else { warn "\n"; }
}
}
#
......@@ -723,31 +776,28 @@ sub enableTrunking($$@) {