Commit 11e0ea5f authored by Robert Ricci's avatar Robert Ricci

Code for picking which VLANs are allowed to go across which trunks.

As much of it as possible switch-independant, and put in snmpit_lib.
Should work for arbitrarily complicated switch toplogies, as long as
there are not multiple trunks between two switches. (Multiple ports
combined into one trunk are fine, however.)

As a result of this, VLAN creations and deletions now need to operate
on all switches, not just on the ones that have ports in the VLAN.
This is because the traffic may have to traverse switches that have no
ports in the VLAN to reach other switches that do.

Not called yet. I've done simple testing, but need to do more, as this
could get us into major trouble if it has bugs.
parent f2e26210
......@@ -262,11 +262,11 @@ foreach my $command (@commands) {
my @devicenames;
my @vlans;
SWITCH: for ($operation) {
(/listvlans/ || /getstats/) && do {
(/listvlans/ || /getstats/ || /make/ || /remove/) && do {
@devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
last;
};
(/listports/ || /make/ || /remove/) && do {
(/listports/) && do {
@devicenames = $opt{i}? @{$opt{i}} :
(@ports? getDeviceNames(@ports) : getTestSwitches());
last;
......@@ -274,7 +274,7 @@ foreach my $command (@commands) {
(/tables/) && do {
@vlans = getExperimentVlans($pid,$eid);
@ports = getVlanPorts(@vlans);
@devicenames = $opt{i}? @{$opt{i}} : getDeviceNames(@ports);
@devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
last;
};
(/reset/) && do {
......
......@@ -923,6 +923,57 @@ sub getStats ($) {
}
#
# Enable, or disable, port on a trunk
#
# usage: removeVlan(self, vlan_number, modport, value)
# vlan_number: The cisco-native VLAN top operate on
# modport: module.port of the trunk to operate on
# value: 0 to disallow the VLAN on the trunk, 1 to allow it
# Returns 1 on success, 0 otherwise
#
sub setVlanOnTrunk($$$$) {
my $self = shift;
my ($vlan_number, $modport, $value) = @_;
#
# Some error checking
#
if (($value != 1) && ($value != 0)) {
die "Invalid value $value passed to setVlanOnTrunk\n";
}
if ($vlan_number == 1) {
die "VLAN 1 passed to setVlanOnTrunk\n"
}
my $ifIndex = $self->{IFINDEX}{$modport};
#
# Get the exisisting bitfield for allowed VLANs on the trunk
#
my $bitfield = $self->{SESS}->get(["vlanTrunkPortVlansEnabled",$ifIndex]);
my $unpacked = unpack("B*",$bitfield);
# Put this into an array of 1s and 0s for easy manipulation
my @bits = split //,$unpacked;
# Just set the bit of the one we want to change
$bits[$vlan_number] = $value;
# Pack it back up...
$unpacked = join('',@bits);
$bitfield = pack("B*",$unpacked);
# And save it back...
if (!$self->{SESS}->set(["vlanTrunkPortVlansEnabled",$ifIndex,$bitfield,
"OCTETSTR"])) {
return 1;
} else {
return 0;
}
}
#
# Reads the IfIndex table from the switch, for SNMP functions that use
# IfIndex rather than the module.port style. Fills out the objects IFINDEX
......@@ -942,20 +993,8 @@ sub readifIndex($) {
foreach my $rowref (@$rows) {
my ($name,$modport,$ifindex) = @$rowref;
my $node = portnum("$self->{NAME}:$modport");
if (defined $node) { # Make sure this is a port we care about
$self->debug("Got $name $modport $ifindex\n",3);
$self->debug("('$modport' == $node)\n",3);
$self->debug("($modport == $ifindex == $node)\n",2);
#
# IFINDEX is a two-way hash, so that we lookup based on either
# the ifindex or the modport representation
#
$self->{IFINDEX}{$modport} = $ifindex;
$self->{IFINDEX}{$ifindex} = $modport;
}
$self->{IFINDEX}{$modport} = $ifindex;
$self->{IFINDEX}{$ifindex} = $modport;
}
}
......
......@@ -397,5 +397,98 @@ sub getStats($) {
return map $stats{$_}, sort {tbsort($a,$b)} keys %stats;
}
#
# Not a 'public' function - only needs to get called by other functions in
# this file, not external functions.
#
# Enables or disables (depending on $value) a VLAN on all appropriate
# switches in a stack
#
# ONLY pass in @ports if you're SURE that they are the only ports in the
# VLAN - basically, only if you just created it. This is a shortcut, so
# that we don't have to ask all switches if they have any ports in the VLAN.
#
sub setVlanOnTrunks($$$;@) {
my $self = shift;
my $vlan_number = shift;
my $value = shift;
my @ports = @_;
#
# First, get a list of all trunks
#
my %trunks = getTrunks();
#
# Next, figure out which switches this VLAN exists on
#
my @switches;
if (@ports) {
#
# I'd rather not have to go out to the switches to ask which ones
# have ports in the VLAN. So, if they gave me ports, I'll just
# trust that those are the only ones in the VLAN
#
@switches = getDeviceNames(@ports);
} else {
#
# Since this may be a hand-created (not from the database) VLAN, the
# only way we can figure out which swtiches this VLAN spans is to
# ask them all.
#
foreach my $devicename (keys %{$self->{DEVICES}}) {
my $device = $self->{DEVICES}{$devicename};
foreach my $line ($device->listVlans()) {
my ($vlan_id, $vlan, $memberRef) = @$line;
if (($vlan == $vlan_number) && $memberRef && @$memberRef){
push @switches, $devicename;
}
}
}
}
#
# Next, get a list of the trunks that are used to move between these
# switches
#
my @trunks = getTrunksFromSwitches(\%trunks,@switches);
#
# Now, we go through the list of trunks that need to be modifed, and
# do it! We have to modify both ends of the trunk, or we'll end up wasting
# the trunk bandwidth.
#
my $errors = 0;
foreach my $trunk (@trunks) {
my ($src,$dst) = @$trunk;
if (!$self->{DEVICES}{$src}) {
warn "ERROR - Bad device $src found in setVlanOnTrunks!\n";
$errors++;
} else {
#
# On ciscos, we can use any port in the trunk, so we'll use the
# first
#
my $modport = $trunks{$src}{$dst}[0];
$errors += !($self->{DEVICES}{$src}->setVlanOnTrunk($vlan_number,
$modport,$value));
}
if (!$self->{DEVICES}{$dst}) {
warn "ERROR - Bad device $dst found in setVlanOnTrunks!\n";
$errors++;
} else {
#
# On ciscos, we can use any port in the trunk, so we'll use the
# first
#
my $modport = $trunks{$dst}{$src}[0];
$errors += !($self->{DEVICES}{$dst}->setVlanOnTrunk($vlan_number,
$modport,$value));
}
}
return $errors;
}
# End with true
1;
......@@ -10,7 +10,8 @@ use Exporter;
@EXPORT = qw( macport portnum Dev vlanmemb vlanid
getTestSwitches getVlanPorts getExperimentVlans getDeviceNames
getDeviceType getInterfaceSettings mapPortsToDevices
getSwitchStack getStackType tbsort );
getSwitchStack getStackType getTrunks getTrunksFromSwitches
tbsort );
use English;
use libdb;
......@@ -328,7 +329,9 @@ sub getStackType($) {
# Returns a structure representing all trunk links. It's a hash, keyed by
# switch, that contains hash references. Each of the second level hashes
# is keyed by destination, with the value being an array reference that
# contains the card.port pairs to which the trunk is conencted.
# contains the card.port pairs to which the trunk is conencted. For exammple,
# ('cisco1' => { 'cisco3' => ['1.1','1.2'] },
# 'cisco3' => { 'cisco1' => ['2.1','2.2'] } )
#
sub getTrunks() {
......@@ -353,6 +356,7 @@ sub getTrunks() {
# A reference to a hash, as returned by the getTrunks() function
# A reference to an array of unvisited switches: Use [keys %trunks]
# Two siwtch names, the source and the destination
#
sub getTrunkPath($$$$) {
my ($trunks, $unvisited, $src,$dst) = @_;
if ($src eq $dst) {
......@@ -409,6 +413,93 @@ sub getTrunkPath($$$$) {
}
}
#
# Returns a list of trunks, in the form [src, dest], from a path (as returned
# by getTrunkPath() ). For example, if the input is:
# (cisco1, cisco3, cisco4), the return value is:
# ([cisco1, cisco3], [cisco3, cisco4])
#
sub getTrunksFromPath(@) {
my @path = @_;
my @trunks = ();
my $lastswitch = "";
foreach my $switch (@path) {
if ($lastswitch) {
push @trunks, [$lastswitch, $switch];
}
$lastswitch = $switch;
}
return @trunks;
}
#
# Given a list of lists of trunks (returned by multiple getTrunksFromPath()
# calls), return a list of the unique trunks found in this list
#
sub getUniqueTrunks(@) {
my @trunkLists = @_;
my @unique = ();
foreach my $trunkref (@trunkLists) {
my @trunks = @$trunkref;
TRUNK: foreach my $trunk (@trunks) {
# Since source and destination are interchangable, we have to
# check both possible orderings
foreach my $unique (@unique) {
if ((($unique->[0] eq $trunk->[0]) &&
($unique->[1] eq $trunk->[1])) ||
(($unique->[0] eq $trunk->[1]) &&
($unique->[1] eq $trunk->[0]))) {
# Yep, it's already in the list - go to the next one
next TRUNK;
}
}
# Made it through, we must not have seen this one before
push @unique, $trunk;
}
}
return @unique;
}
#
# Given a trunk structure (as returned by getTrunks() ), and a list of switches,
# return a list of all trunks (in the [src, dest] form) that are needed to span
# all the switches (ie. which trunks the VLAN must be allowed on)
#
sub getTrunksFromSwitches($@) {
my $trunks = shift;
my @switches = @_;
#
# First, find the paths between each set of switches
#
my @paths = ();
foreach my $switch1 (@switches) {
foreach my $switch2 (@switches) {
push @paths, [ getTrunkPath($trunks, [ keys %$trunks ],
$switch1, $switch2) ];
}
}
#
# Now, make a list of all the the trunks used by these paths
#
my @trunkList = ();
foreach my $path (@paths) {
push @trunkList, [ getTrunksFromPath(@$path) ];
}
#
# Last, remove any duplicates from the list of trunks
#
my @trunks = getUniqueTrunks(@trunkList);
return @trunks;
}
#
# Used to sort a set of nodes in testbed order (ie. pc2 < pc10)
#
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment