Commit cfdf5c00 authored by Robert Ricci's avatar Robert Ricci

Add user-visible support for trunking.

Added support for Cisco switches to enable and disable trunking for a
port.

Added two new command-line options:
-N <port> <vlans>
	Enable trunking on a port, allowing the given list of VLANs to
	cross the trunk

-U <port>:
	Disable trunking for the port

This functionality is needed for Mike's firewall support.
parent 6b50669b
......@@ -43,6 +43,8 @@ sub usage {
Usage: $0 [-h] [-v] [-n] [-i device]
[-l] [-s] [-g]
[-m name [ports]]
[-T port name]
[-U port]
[-o name]
[-r pid eid]
[-t pid eid]
......@@ -83,6 +85,9 @@ Port Control:
-a <ports> Enable auto-negotiation of port speed/duplex
-p <10|100> <ports> Set speed of <ports> to 10 or 100 Mbps
-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
-U <port> Turn off trunking for the given <port>
More than one operation can be specified - However, beware that the order in
which operations will occur is undefined, and some combinations of operations
......@@ -96,7 +101,7 @@ END
my %opt = ();
Getopt::Long::Configure("no_ignore_case");
GetOptions(\%opt, 'a','c','d','e','g','h','i=s@','l','m=s@','M','n','o=s@',
'p=s','r','s','t','u=s','v','w','y=s','x=s','z=s');
'p=s','r','s','t','T=s','u=s','U','v','w','y=s','x=s','z=s');
# Unused: b,f,j,q
if ($opt{h}) {
......@@ -133,11 +138,27 @@ if ($opt{t} || $opt{r}) {
if (@ARGV) {
@optvlanids = @ARGV;
}
} elsif ($opt{d} || $opt{e} || $opt{a} || $opt{p} || $opt{u} || $opt{m}) {
} elsif ($opt{d} || $opt{e} || $opt{a} || $opt{p} || $opt{u} || $opt{m}
|| $opt{U}) {
#
# Options that take a list of ports
#
@ports = @ARGV;
} elsif ($opt{T}) {
#
# Options that take both a port and a list of VLANs - we require at least
# one VLAN to be given
#
if (!@ARGV) {
warn "ERROR: At least one VLAN required";
exit &usage;
}
@optvlanids = @ARGV;
#
# Set the @ports array so that we'll do proper permission checking on it
#
@ports = ($opt{T});
} else {
#
# Everything else
......@@ -164,6 +185,7 @@ if ($opt{g}) { push @commands, ["getstats"]; }
if ($opt{t}) { push @commands, ["tables"]; }
if ($opt{r}) { push @commands, ["reset"]; }
if ($opt{c}) { push @commands, ["recreate"]; }
if ($opt{U}) { push @commands, ["trunkdisable"]; }
#
# Commands that can appear once, and take an agurment
......@@ -171,6 +193,7 @@ if ($opt{c}) { push @commands, ["recreate"]; }
if ($opt{d}) { push @commands, ["portcontrol","disable"]; }
if ($opt{e}) { push @commands, ["portcontrol","enable"]; }
if ($opt{a}) { push @commands, ["portcontrol","auto"]; }
if ($opt{T}) { push @commands, ["trunkenable", $opt{T}]; }
#
# Commands that can occur more than once
......@@ -375,7 +398,7 @@ foreach my $command (@commands) {
@devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
last;
};
(/portcontrol/) && do {
(/portcontrol/ || /trunkdisable/) && do {
@devicenames = $opt{i}? @{$opt{i}} : getDeviceNames(@ports);
last;
};
......@@ -390,6 +413,11 @@ foreach my $command (@commands) {
@devicenames = getTestSwitches();
last;
};
(/trunkenable/) && do {
@devicenames = $opt{i}? @{$opt{i}} : getDeviceNames(@ports);
@vlans = @optvlanids;
last;
}
}
debug("Device names: " . join(",",@devicenames) . "\n");
......@@ -510,6 +538,15 @@ foreach my $command (@commands) {
$exitval += doRecreateVlans(\@stacks);
last;
};
/trunkenable/ && do {
my ($port) = @args;
$exitval += doTrunkEnable(\@stacks,$port,@vlans);
last;
}; # /trunkenable/ && do
/trunkdisable/ && do {
$exitval += doTrunkDisable(\@stacks,@ports);
last;
}; # /trunkenable/ && do
}
}
......@@ -1008,3 +1045,56 @@ sub doRecreateVlans($) {
return 1;
}
#
# Enable trunking on a port, and enable a set of VLANs on it
#
sub doTrunkEnable($$@) {
my $stacks = shift;
my $port = shift;
my @vlans = @_;
#
# Sanity checking
#
if (@$stacks != 1) {
die "Enabling trunk ports should only involve one stack\n" .
"Stacks are " . join(",",@$stacks) . "\n";
}
#
# Simple, just call the right function on the stack
#
my $stack = $$stacks[0];
print "Enabling trunking on $port ...\n";
return $stack->enableTrunking($port,@vlans);
}
#
# Disable trunking on a port
#
sub doTrunkDisable($$) {
my $stacks = shift;
my $port = shift;
#
# Sanity checking
#
if (@$stacks != 1) {
die "Disabling trunk ports should only involve one stack\n" .
"Stacks are " . join(",",@$stacks) . "\n";
}
#
# Simple, just call the right function on the stack
#
my $stack = $$stacks[0];
my $errors = 0;
foreach my $port (@ports) {
print "Disabling trunking on port $port ...\n";
if (!$stack->disableTrunking($port)) {
$errors++;
}
}
return $errors;
}
......@@ -1311,7 +1311,7 @@ sub setVlansOnTrunk($$$$) {
die "VLAN 1 passed to setVlanOnTrunk\n"
}
my $ifIndex = $self->{IFINDEX}{$modport};
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$modport);
#
# If this is part of an EtherChannel, we have to find the ifIndex for the
......@@ -1356,6 +1356,166 @@ sub setVlansOnTrunk($$$$) {
}
#
# Clear the list of allowed VLANs from a trunk
#
# usage: clearAllVlansOnTrunk(self, modport)
# modport: module.port of the trunk to operate on
# Returns 1 on success, 0 otherwise
#
sub clearAllVlansOnTrunk($$) {
my $self = shift;
my ($modport) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$modport);
#
# If this is part of an EtherChannel, we have to find the ifIndex for the
# channel.
# TODO: Perhaps this should be general - ie. $self{IFINDEX} should have
# the channel ifIndex the the port is in a channel. Not sure that
# this is _always_ beneficial, though
#
my $channel = snmpitGetFatal($self->{SESS},["pagpGroupIfIndex",$ifIndex]);
if (($channel =~ /^\d+$/) && ($channel != 0)) {
$ifIndex = $channel;
}
#
# Get the exisisting bitfield for allowed VLANs on the trunk
#
my $bitfield = snmpitGetFatal($self->{SESS},
["vlanTrunkPortVlansEnabled",$ifIndex]);
my $unpacked = unpack("B*",$bitfield);
# Put this into an array of 1s and 0s for easy manipulation
my @bits = split //,$unpacked;
# Clear the bit for every VLAN
foreach my $index (0 .. $#bits) {
$bits[$index] = 0;
}
# Pack it back up...
$unpacked = join('',@bits);
$bitfield = pack("B*",$unpacked);
# And save it back...
my $rv = $self->{SESS}->set(["vlanTrunkPortVlansEnabled",$ifIndex,$bitfield,
"OCTETSTR"]);
if ($rv) {
return 1;
} else {
return 0;
}
}
#
# Enable trunking on a port
#
# usage: enablePortTrunking(self, modport, nativevlan)
# 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) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$port);
#
# Clear out the list of allowed VLANs for this trunk port, so that when it
# comes up, there is not some race condition
#
my $rv = $self->clearAllVlansOnTrunk($port);
if (!$rv) {
warn "ERROR: Unable to clear VLANs on trunk\n";
return 0;
}
#
# Set the type of the trunk - we only do dot1q for now
#
my $trunkType = ["vlanTrunkPortEncapsulationType",$ifIndex,"dot1Q","INTEGER"];
$rv = snmpitSetWarn($self->{SESS},$trunkType);
if (!$rv) {
warn "ERROR: Unable to set encapsulation type\n";
return 0;
}
#
# Set the native VLAN for this trunk
#
my $nativeVlan = ["vlanTrunkPortNativeVlan",$ifIndex,$native_vlan,"INTEGER"];
$rv = snmpitSetWarn($self->{SESS},$nativeVlan);
if (!$rv) {
warn "ERROR: Unable to set native VLAN on trunk\n";
return 0;
}
#
# Finally, enable trunking!
#
my $trunkEnable = ["vlanTrunkPortDynamicState",$ifIndex,"on","INTEGER"];
$rv = snmpitSetWarn($self->{SESS},$trunkEnable);
if (!$rv) {
warn "ERROR: Unable to enable trunking\n";
return 0;
}
#
# Allow the native VLAN to cross the trunk
#
if (!$rv) {
warn "ERROR: Unable to enable native VLAN on trunk\n";
return 0;
}
return 1;
}
#
# Disable trunking on a port
#
# usage: disablePortTrunking(self, modport)
# modport: module.port of the trunk to operate on
# Returns 1 on success, 0 otherwise
#
sub disablePortTrunking($$) {
my $self = shift;
my ($port) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$port);
#
# Clear out the list of allowed VLANs for this trunk port
#
my $rv = $self->clearAllVlansOnTrunk($port);
if (!$rv) {
warn "ERROR: Unable to clear VLANs on trunk\n";
return 0;
}
#
# Disable trunking!
#
my $trunkDisable = ["vlanTrunkPortDynamicState",$ifIndex,"off","INTEGER"];
$rv = snmpitSetWarn($self->{SESS},$trunkDisable);
if (!$rv) {
warn "ERROR: Unable to enable trunking\n";
return 0;
}
return 1;
}
#
# Reads the IfIndex table from the switch, for SNMP functions that use
# IfIndex rather than the module.port style. Fills out the objects IFINDEX
......
......@@ -555,6 +555,111 @@ sub getStats($) {
return map $stats{$_}, sort {tbsort($a,$b)} keys %stats;
}
#
# Turns on trunking on a given port, allowing only the given VLANs on it
#
# usage: enableTrunking(self, port, vlan identifier list)
#
# returns: 1 on success
# returns: 0 on failure
#
sub enableTrunking($$@) {
my $self = shift;
my $port = shift;
my @vlan_ids = @_;
#
# On a Cisco, the first VLAN given becomes the native VLAN for the trunk
#
my $native_vlan_id = shift @vlan_ids;
if (!$native_vlan_id) {
print STDERR "ERROR: No native VLAN passed to enableTrunking()!\n";
return 0;
}
#
# Grab the VLAN number for the native VLAN
#
my $vlan_number = $self->{LEADER}->findVlan($native_vlan_id);
if (!$vlan_number) {
print STDERR "ERROR: Native VLAN $native_vlan_id does not exist!\n";
return 0;
}
#
# Split up the ports among the devices involved
#
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;
}
#
# Simply make the appropriate call on the device
#
print "Enable trunking: Port is $port, native VLAN is $native_vlan_id\n"
if ($self->{DEBUG});
my $rv = $device->enablePortTrunking($port, $vlan_number);
#
# If other VLANs were given, add them to the port too
#
if (@vlan_ids) {
my %vlan_numbers = $self->{LEADER}->findVlans(@vlan_ids);
my @vlan_numbers;
foreach my $vlan_id (@vlan_ids) {
#
# First, make sure that the VLAN really does exist
#
my $vlan_number = $vlan_numbers{$vlan_id};
if (!$vlan_number) {
warn "ERROR: VLAN $vlan_id not found on switch!";
next;
}
push @vlan_numbers, $vlan_number;
}
$device->setVlansOnTrunk($port,1,@vlan_numbers);
}
return $rv;
}
#
# Turns off trunking for a given port
#
# usage: disableTrunking(self, ports)
#
# returns: 1 on success
# returns: 0 on failure
#
sub disableTrunking($$) {
my $self = shift;
my $port = shift;
#
# Split up the ports among the devices involved
#
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;
}
#
# Simply make the appropriate call on the device
#
my $rv = $device->disablePortTrunking($port);
return $rv;
}
#
# Not a 'public' function - only needs to get called by other functions in
# this file, not external functions.
......
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