From cfdf5c00efcb3d4989e7e62c93186531b95d58ed Mon Sep 17 00:00:00 2001 From: Robert Ricci <ricci@cs.utah.edu> Date: Wed, 1 Dec 2004 22:13:07 +0000 Subject: [PATCH] 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. --- tbsetup/snmpit.in | 96 +++++++++++++++++++- tbsetup/snmpit_cisco.pm | 162 +++++++++++++++++++++++++++++++++- tbsetup/snmpit_cisco_stack.pm | 105 ++++++++++++++++++++++ 3 files changed, 359 insertions(+), 4 deletions(-) diff --git a/tbsetup/snmpit.in b/tbsetup/snmpit.in index a61aa397e0..72d6e660a8 100755 --- a/tbsetup/snmpit.in +++ b/tbsetup/snmpit.in @@ -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; +} diff --git a/tbsetup/snmpit_cisco.pm b/tbsetup/snmpit_cisco.pm index 33c13cef79..7721ccebe5 100644 --- a/tbsetup/snmpit_cisco.pm +++ b/tbsetup/snmpit_cisco.pm @@ -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 diff --git a/tbsetup/snmpit_cisco_stack.pm b/tbsetup/snmpit_cisco_stack.pm index 48d050a621..9a7afaea32 100644 --- a/tbsetup/snmpit_cisco_stack.pm +++ b/tbsetup/snmpit_cisco_stack.pm @@ -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. -- GitLab