Commit 2d7b6b82 authored by Robert Ricci's avatar Robert Ricci

New features:

    Accept [switch.]<module>/<port> format for ports, so that we can
	deal with ports not in the database (mostly for my own
	debugging sanity.)
    A -n option that prevents assign from changing hardware settings
    	(though, unlike TESTMODE, does read some information from
	the switches)
    Private VLAN support, through the -x,-y, and -z switches. There
	are only 5 letters of the alphabet left, so I've given up on
	memnonic switches.
    Worked a bit on making VLAN deletion more efficient, but with
    	little sucess

Private VLANs work like so:
Make a primary private VLAN with:
snmpit -m myvlan-primary -y primary
Attach a community VLAN to it like so:
snmpit -m myvlan-community -y community -x myvlan-primary -z cisco2.1/15
Put some ports into the community VLAN:
snmpit -m myvlan-community pc1:0 pc2:0
parent f96728ab
......@@ -45,7 +45,7 @@ if (!$COMMUNITY) {
######################################################################
sub usage {
print << "END";
Usage: $0 [-h] [-v] [-i device]
Usage: $0 [-h] [-v] [-n] [-i device]
[-l] [-s] [-g]
[-m name [ports]]
[-o name]
......@@ -57,6 +57,7 @@ Usage: $0 [-h] [-v] [-i device]
General:
-h Display this help message
-v Verbose mode
-n Test mode - don't actually make any changes
-i <device> Operate on <device>, overriding default device list. Can be
given multiple times
......@@ -66,6 +67,12 @@ VLAN Control:
-l List all VLANs
-m <name> [ports] Create a new VLAN with name <name>, if it doesn't exist,
and put [ports] in it
-y <type> When used with -m, the new VLAN becomes a private VLAN
of type <type>
-x <primary> When used with -y, assocates the new private VLAN with
the primary VLAN named <primary>
-z <port> Used with -y and -x, to specify which port is to be used
with the private VLAN
-o <name> Delete the VLAN with name <name>
-c Delete ALL VLANs, and recreate from the database. ** USE
WITH EXTREME CAUTION **
......@@ -89,8 +96,9 @@ END
my %opt = ();
GetOptions(\%opt,'h','l','v','s','t','r','i=s@','m=s@','o=s@','p=s','u=s','d',
'e','a','g','c');
GetOptions(\%opt, 'a','c','d','e','g','h','i=s@','l','m=s@','n','o=s@','p=s',
'r','s','t','u=s','v','y=s','x=s','z=s');
# Unused: b,f,j,q,w
if ($opt{h}) {
exit &usage;
......@@ -208,6 +216,45 @@ if (!@commands) {
die "No operation given\n";
}
#
# Options that affect other commands
#
#
# Arguments for making private VLANs
#
# Build up a list of extra arguments to be passed to createVlan()
my @pvlanArgs = ();
if ($opt{y}) {
#
# Make sure the private VLAN type they gave is valid, and make sure they
# gave the other required arugments for certain types
#
if ($opt{y} ne "primary" && $opt{y} ne "isolated" &&
$opt{y} ne "community") {
die "Unknown private VLAN type $opt{y}\n";
}
@pvlanArgs = $opt{y};
if ($opt{y} ne "primary") {
if (!$opt{x} || !$opt{z}) {
warn "**** -x and -z must be given when -y is $opt{y}!\n";
exit &usage;
}
#
# Fix up ports given in the module/port format, like we do below for
# ports from @ARGV
#
if ($opt{z} =~ /^\d+\/\d+?$/) {
if ($opt{i} && @{$opt{i}} == 1) {
$opt{z} = $opt{i}->[0] . "." . $opt{z};
} else {
die "The module/port format is only legal if exactly one -i " .
"argument has been given\n";
}
}
push @pvlanArgs,$opt{x},$opt{z};
}
}
######################################################################
# Step 3 - Set up the stack objects
......@@ -238,11 +285,37 @@ if ($pid && $eid) {
#
if (@ports) {
my @nodes = map /^([^:]+)/, @ports;
#
# Allow ports to be given in one of two forms: node:port, or switch.port.
# Only admins can do the latter, of course...
#
my (@nodes, @switchports);
foreach my $port (@ports) {
if ($port =~ /^([^:]+):\d+$/) {
push @nodes, $1;
} elsif ($port =~ /^([^.]+)\.\d+(\/\d+)?$/) {
push @switchports, $port;
} elsif ($port =~ /^\d+\/\d+?$/) {
if ($opt{i} && @{$opt{i}} == 1) {
$port = $opt{i}->[0] . "." . $port;
push @switchports, $port;
} else {
die "The module/port format is only legal if exactly one -i " .
"argument has been given\n";
}
} else {
die "Bad format for port $port\n"
}
}
if (!TBNodeAccessCheck($UID,TB_NODEACCESS_MODIFYVLANS,@nodes)) {
die "You do not have permission to modify some or all of the nodes\n" .
"that will be affected by the operation you requested\n";
}
if (@switchports && !TBAdmin()) {
die "Only admins are allowed to modify switch ports directly\n";
}
}
if ($TESTMODE) {
......@@ -256,7 +329,7 @@ if ($TESTMODE) {
#
snmpit_lib::init($debug);
my $exitval;
my $exitval = 0;
foreach my $command (@commands) {
#
......@@ -333,14 +406,21 @@ foreach my $command (@commands) {
#
my @stacks;
foreach my $stack_id (keys %stacks) {
my $stack_type = getStackType($stack_id);
my ($stack_type, $supports_private) = getStackType($stack_id);
#
# Safety check - make sure the stack supports private VLANs if -y was
# given
#
if ($opt{y} && !$supports_private) {
die "Switch stack $stack_id does not support private VLANs\n";
}
my $stack;
debug("Stack $stack_id has type $stack_type\n");
SWITCH: for ($stack_type) {
/cisco/ && do {
require snmpit_cisco_stack;
$stack = new snmpit_cisco_stack($stack_id,$debug,$COMMUNITY,
@{$stacks{$stack_id}});
$supports_private, @{$stacks{$stack_id}});
last;
}; # /cisco/
/intel/ && do {
......@@ -370,6 +450,10 @@ foreach my $command (@commands) {
# Finally, we just call the helper function for the operation that
# is to be performed.
######################################################################
if ($opt{n}) {
print "Test mode, skipping operation\n";
next;
}
SWITCH: for ($operation) {
/listvlans/ && do {
......@@ -623,7 +707,7 @@ sub doVlansFromTables($@) {
print " VLAN $vlan already exists\n";
$errors += $stack->setPortVlan($vlan,@ports);
} else {
if (!$stack->createVlan($vlan,@ports)) {
if (!$stack->createVlan($vlan,\@ports)) {
warn "ERROR: Failed to create VLAN with id $vlan\n";
#
# Don't try to put ports in a VLAN if it couldn't be created
......@@ -719,7 +803,7 @@ sub doMakeVlan($$@) {
}
} else {
print "Creating VLAN $vlan_name ...\n";
my $ok = $stack->createVlan($vlan_name,@ports);
my $ok = $stack->createVlan($vlan_name,\@ports,@pvlanArgs);
print "VLAN creation ";
print $ok? "succeeded":"failed",".\n";
if (!$ok) {
......@@ -733,18 +817,24 @@ sub doMakeVlan($$@) {
#
# Delete the given VLAN, if it exists
#
sub doDeleteVlan($$) {
sub doDeleteVlan($@) {
my $stacks = shift;
my $vlan_name = shift;
my @vlan_names = @_;
my $errors = 0;
my $exists = 0;
my %exists = ();
foreach my $stack (@$stacks) {
if ($stack->vlanExists($vlan_name)) {
$exists = 1;
print "Deleting VLAN $vlan_name ...\n";
my $ok = $stack->removeVlan($vlan_name);
my @existant_vlans;
foreach my $vlan_name (@vlan_names) {
if ($stack->vlanExists($vlan_name)) {
$exists{$vlan_name} = 1;
push @existant_vlans, $vlan_name;
}
}
if (@existant_vlans) {
print "Deleting VLAN(s) " . join(",",@existant_vlans) . " ...\n";
my $ok = $stack->removeVlan(@existant_vlans);
print "VLAN deletion ";
print $ok? "succeeded":"failed",".\n";
if (!$ok) {
......@@ -753,9 +843,11 @@ sub doDeleteVlan($$) {
}
}
if (!$exists) {
print "VLAN $vlan_name does not exist\n";
$errors++;
foreach my $vlan_name (@vlan_names) {
if (!$exists{$vlan_name}) {
print "VLAN $vlan_name does not exist\n";
$errors++;
}
}
return $errors;
......@@ -837,9 +929,7 @@ sub doRecreateVlans($) {
debug("Going to nuke " . join(',',@vlansToNuke) . "\n");
foreach my $vlan (@vlansToNuke) {
doDeleteVlan($stacks,$vlan);
}
doDeleteVlan($stacks,@vlansToNuke);
#
# Get a list of all experiments, so that we can re-create their VLANs
......
......@@ -59,7 +59,7 @@ my $PORT_FORMAT_NODEPORT = 3;
# usage: new($classname,$devicename,$debuglevel)
# returns a new object, blessed into the snmpit_cisco class.
#
sub new($$$$;$) {
sub new($$$$$$) {
# The next two lines are some voodoo taken from perltoot(1)
my $proto = shift;
......@@ -69,6 +69,7 @@ sub new($$$$;$) {
my $debugLevel = shift;
my $switchtype = shift;
my $community = shift;
my $supportsPrivate = shift;
#
# Create the actual object
......@@ -87,6 +88,7 @@ sub new($$$$;$) {
$self->{BULK} = 1;
$self->{NAME} = $name;
$self->{COMMUNITY} = $community;
$self->{SUPPORTS_PRIVATE} = $supportsPrivate;
# Figure out some stuff about this switch
$switchtype =~ /^(\w+)(-ios)?$/;
......@@ -112,7 +114,8 @@ sub new($$$$;$) {
"$mibpath/SNMPv2-MIB.txt", "$mibpath/IANAifType-MIB.txt",
"$mibpath/IF-MIB.txt", "$mibpath/RMON-MIB.txt",
"$mibpath/CISCO-SMI.txt", "$mibpath/CISCO-TC.txt",
"$mibpath/CISCO-VTP-MIB.txt", "$mibpath/CISCO-PAGP-MIB.txt");
"$mibpath/CISCO-VTP-MIB.txt", "$mibpath/CISCO-PAGP-MIB.txt",
"$mibpath/CISCO-PRIVATE-VLAN-MIB.txt");
if ($self->{OSTYPE} eq "CatOS") {
push @mibs, "$mibpath/CISCO-STACK-MIB.txt";
......@@ -245,6 +248,8 @@ sub convertPortFormat($$@) {
SWITCH: for ($sample) {
(/^\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; };
$input = $PORT_FORMAT_NODEPORT; last;
}
......@@ -470,25 +475,36 @@ sub findVlan($$;$) {
# Create a VLAN on this switch, with the given identifier (which comes from
# the database.) Picks its own switch-specific VLAN number to use.
#
# usage: createVlan($self, $vlan_id)
# usage: createVlan($self, $vlan_id [,$private_type [,$private_primary,
# $private_port]])
# returns 1 on success
# returns 0 on failure
# if $private_type is given, creates a private VLAN - if private_type
# is 'community' or 'isolated', then the assocated primary VLAN and
# promiscous port must also be given
#
sub createVlan($$) {
sub createVlan($$;$$$) {
my $self = shift;
my $vlan_id = shift;
my $okay = 0;
my ($private_type,$private_primary,$private_port);
if (@_) {
$private_type = shift;
if ($private_type ne "primary") {
$private_primary = shift;
$private_port = shift;
}
} else {
$private_type = "normal";
}
#
# NOTE: I'm not sure why I have to give these as numeric OIDs rather
# than using the names.
# TODO: Figure this out
#
my $VlanType = '.1.3.6.1.4.1.9.9.46.1.4.2.1.3.1'; # vlan # is index
my $VlanName = '.1.3.6.1.4.1.9.9.46.1.4.2.1.4.1'; # vlan # is index
my $VlanSAID = '.1.3.6.1.4.1.9.9.46.1.4.2.1.6.1'; # vlan # is index
my $VlanRowStatus = '.1.3.6.1.4.1.9.9.46.1.4.2.1.11.1'; # vlan # is index
my $okay = 1;
my $VlanType = 'vtpVlanEditType'; # vlan # is index
my $VlanName = 'vtpVlanEditName'; # vlan # is index
my $VlanSAID = 'vtpVlanEditDot10Said'; # vlan # is index
my $VlanRowStatus = 'vtpVlanEditRowStatus'; # vlan # is index
#
# We may have to do this multiple times - a few times, we've had the
......@@ -520,12 +536,12 @@ sub createVlan($$) {
#
my $vlan_number = 2; # We need to start at 2
my $RetVal = snmpitGetFatal($self->{SESS},
[$VlanRowStatus,$vlan_number]);
[$VlanRowStatus,"1.$vlan_number"]);
$self->debug("Row $vlan_number got '$RetVal'\n",2);
while (($RetVal ne 'NOSUCHINSTANCE') && ($vlan_number <= 1000)) {
$vlan_number += 1;
$RetVal = snmpitGetFatal($self->{SESS},
[$VlanRowStatus,$vlan_number]);
[$VlanRowStatus,"1.$vlan_number"]);
$self->debug("Row $vlan_number got '$RetVal'\n",2);
}
if ($vlan_number > 1000) {
......@@ -550,11 +566,11 @@ sub createVlan($$) {
# Perform the actual creation. Yes, this next line MUST happen all in
# one set command....
#
$RetVal = $self->{SESS}->set([[$VlanRowStatus,$vlan_number,
$RetVal = $self->{SESS}->set([[$VlanRowStatus,"1.$vlan_number",
"createAndGo","INTEGER"],
[$VlanType,$vlan_number,"ethernet","INTEGER"],
[$VlanName,$vlan_number,$vlan_id,"OCTETSTR"],
[$VlanSAID,$vlan_number,$SAID,"OCTETSTR"]]);
[$VlanType,"1.$vlan_number","ethernet","INTEGER"],
[$VlanName,"1.$vlan_number",$vlan_id,"OCTETSTR"],
[$VlanSAID,"1.$vlan_number",$SAID,"OCTETSTR"]]);
print "",($RetVal? "Succeeded":"Failed"), ".\n";
#
......@@ -563,8 +579,50 @@ sub createVlan($$) {
if (!$RetVal) {
print STDERR "VLAN Create '$vlan_id' as VLAN $vlan_number " .
"failed.\n";
$self->vlanUnlock();
next;
} else {
#
# Handle private VLANs - Part I: Stuff that has to be done while we
# have the edit buffer locked
#
if ($self->{SUPPORTS_PRIVATE} && $private_type ne "normal") {
#
# First, set the private VLAN type
#
my $PVlanType = "cpvlanVlanEditPrivateVlanType";
print " Setting private VLAN type to $private_type ... ";
$RetVal = $self->{SESS}->set([$PVlanType,"1.$vlan_number",$private_type,
'INTEGER']);
print "",($RetVal? "Succeeded":"Failed"), ".\n";
if (!$RetVal) {
$okay = 0;
}
if ($okay) {
#
# Now, if this isn't a primary VLAN, associate it with its
# primary VLAN
#
if ($private_type ne "primary") {
my $PVlanAssoc = "cpvlanVlanEditAssocPrimaryVlan";
my $primary_number = $self->findVlan($private_primary);
if (!$primary_number) {
print " **** Error - Primary VLAN " .
"$private_primary could not be found\n";
$okay = 0;
} else {
print " Associating with $private_primary (#$primary_number) ... ";
$RetVal = $self->{SESS}->set([[$PVlanAssoc,"1.$vlan_number",
$primary_number,"INTEGER"]]);
print "", ($RetVal? "Succeeded":"Failed"), ".\n";
if (!$RetVal) {
$okay = 0;
}
}
}
}
}
$RetVal = $self->vlanUnlock();
$self->debug("Got $RetVal from vlanUnlock\n");
......@@ -578,7 +636,62 @@ sub createVlan($$) {
"get created - trying again\n";
next;
}
return 1;
if ($self->{SUPPORTS_PRIVATE} && $private_type ne "normal" &&
$private_type ne "primary") {
#
# Handle private VLANs - Part II: Set up the promiscuous port -
# this has to be done after we release the edit buffer
#
my $SecondaryPort = 'cpvlanPromPortSecondaryRemap';
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,
$private_port);
if (!$ifIndex) {
print STDERR " **** ERROR - unable to find promiscous " .
"port $private_port!\n";
$okay = 0;
}
if ($okay) {
print " Setting promiscuous port to $private_port ... ";
#
# Get the existing bitfield used to maintain the mapping
# for the port
#
my $bitfield = $self->{SESS}->get([$SecondaryPort,$ifIndex]);
my $unpacked = unpack("B*",$bitfield);
#
# Put this into an array of 1s and 0s for easy manipulation
# We have to pad this out to 128 bits, because it's given
# back as the empty string if no bits are set yet.
#
my @bits = split //,$unpacked;
foreach my $bit (0 .. 127) {
if (!defined $bits[$bit]) {
$bits[$bit] = 0;
}
}
$bits[$vlan_number] = 1;
# Pack it back up...
$unpacked = join('',@bits);
$bitfield = pack("B*",$unpacked);
# And save it back...
$RetVal = $self->{SESS}->set([$SecondaryPort,$ifIndex,$bitfield,
"OCTETSTR"]);
print "", ($RetVal? "Succeeded":"Failed"), ".\n";
}
}
return $okay;
}
}
......@@ -600,13 +713,6 @@ sub setPortVlan($$@) {
my $vlan_id = shift;
my @ports = @_;
my $PortVlanMemb;
if ($self->{OSTYPE} eq "CatOS") {
$PortVlanMemb = "vlanPortVlan"; #index is ifIndex
} elsif ($self->{OSTYPE} eq "IOS") {
$PortVlanMemb = "vmVlan"; #index is ifIndex
}
my $errors = 0;
#
......@@ -620,15 +726,41 @@ sub setPortVlan($$@) {
$self->debug("Found VLAN with ID $vlan_id: $vlan_number\n");
#
# Convert ports from the format the were passed in to the correct format
# If this switch supports private VLANs, check to see if the VLAN we're
# putting it into is a secondary private VLAN
#
my $privateVlan = 0;
if ($self->{SUPPORTS_PRIVATE}) {
$self->debug("Checking to see if vlan is private ... ");
my $PrivateType = "cpvlanVlanPrivateVlanType";
my $type = snmpitGetFatal($self->{SESS},[$PrivateType,"1.$vlan_number"]);
$self->debug("type is $type ... ");
if ($type eq "community" || $type eq "isolated") {
$self->debug("It is\n");
$privateVlan = 1;
} else {
$self->debug("It isn't\n");
}
}
my $PortVlanMemb;
my $format;
if ($self->{OSTYPE} eq "CatOS") {
$format = $PORT_FORMAT_MODPORT;
if (!$privateVlan) {
$PortVlanMemb = "vlanPortVlan"; #index is ifIndex
$format = $PORT_FORMAT_MODPORT;
} else {
$PortVlanMemb = "cpvlanPrivatePortSecondaryVlan";
$format = $PORT_FORMAT_IFINDEX;
}
} elsif ($self->{OSTYPE} eq "IOS") {
$PortVlanMemb = "vmVlan"; #index is ifIndex
$format = $PORT_FORMAT_IFINDEX;
}
#
# Convert ports from the format the were passed in to the correct format
#
my @portlist = $self->convertPortFormat($format,@ports);
#
......@@ -778,10 +910,10 @@ sub removeVlan($@) {
#
# Perform the actual removal
#
my $VlanRowStatus = '.1.3.6.1.4.1.9.9.46.1.4.2.1.11.1'; # vlan is index
my $VlanRowStatus = 'vtpVlanEditRowStatus'; # vlan is index
print " Removing VLAN #$vlan_number ... ";
my $RetVal = $self->{SESS}->set([$VlanRowStatus,$vlan_number,
my $RetVal = $self->{SESS}->set([$VlanRowStatus,"1.$vlan_number",
"destroy","INTEGER"]);
if ($RetVal) {
print "Succeeded.\n";
......@@ -815,15 +947,9 @@ sub UpdateField($$$@) {
my $Status = 0;
my $err = 0;
foreach my $port (@ports) {
my $trans = $self->{IFINDEX}{$port};
if (defined $trans) {
if (defined (portnum("$self->{NAME}:$trans"))) {
$trans = "$trans,".portnum("$self->{NAME}:$trans");
} else {
$trans = "$trans,".portnum("$self->{NAME}:$port");
}
} else {
$trans = "???";
my ($trans) = convertPortFormat($PORT_FORMAT_NODEPORT,$port);
if (!defined $trans) {
$trans = ""; # Guard against some uninitialized value warnings
}
$self->debug("Checking port $port ($trans) for $val...");
$Status = snmpitGetFatal($self->{SESS},[$OID,$port]);
......@@ -911,7 +1037,8 @@ sub listVlans($) {
$self->debug("Got $name $modport $vlan_number\n",3);
my ($node) = $self->convertPortFormat($PORT_FORMAT_NODEPORT,$modport);
if (!$node) {
$node = "port$modport";
$modport =~ s/\./\//;
$node = $self->{NAME} . ".$modport";
}
push @{$Members{$vlan_number}}, $node;
}
......
......@@ -44,6 +44,7 @@ sub new($$$#@) {
my $stack_id = shift;
my $debuglevel = shift;
my $community = shift;
my $supports_private = shift;
my @devicenames = @_;
#
......@@ -95,7 +96,7 @@ sub new($$$#@) {
(/cisco6509/ || /cisco4006/) && do {
use snmpit_cisco;
$device = new snmpit_cisco($devicename,$self->{DEBUG},$type,
$community);
$community,$supports_private);
if (!$device) {
die "Failed to create a device object for $devicename\n";
} else {
......@@ -253,15 +254,16 @@ sub setPortVlan($$@) {
# returns: 1 on success
# returns: 0 on failure
#
sub createVlan($$;@) {
sub createVlan($$$;$$$) {
my $self = shift;
my $vlan_id = shift;
my @ports = @_;
my @ports = @{shift()};
my @otherargs = @_;
#
# We just need to create the VLAN on the stack leader
#
my $okay = $self->{LEADER}->createVlan($vlan_id);
my $okay = $self->{LEADER}->createVlan($vlan_id,@otherargs);
#
# We need to add the ports to VLANs at the stack level, since they are
......
......@@ -206,25 +206,35 @@ sub getExperimentPorts ($$) {
sub getDeviceNames(@) {
my @ports = @_;
my %devices = ();
print "getDeviceNames: Passed in " . join(",",@ports) . "\n" if $debug;
foreach my $port (@ports) {
$port =~ /^(.+):(.+)$/;
my ($node,$card) = ($1,$2);
if (!defined($node) || !defined($card)) { # Oops, $card can be 0
die "Bad port given: $port\n";
}
my $result = DBQueryFatal("SELECT node_id2 FROM wires " .
"WHERE node_id1='$node' AND card1=$card");
if (!$result->num_rows()) {
warn "No database entry found for port $port - Skipping\n";
#
# Accept either node:port or switch.port
#
my $device;
if ($port =~ /^([^:]+):(.+)$/) {
my ($node,$card) = ($1,$2);
if (!defined($node) || !defined($card)) { # Oops, $card can be 0
die "Bad port given: $port\n";
}
my $result = DBQueryFatal("SELECT node_id2 FROM wires " .
"WHERE node_id1='$node' AND card1=$card");
if (!$result->num_rows()) {
warn "No database entry found for port $port - Skipping\n";
next;
}
# This is a loop, on the off chance chance that a single port on a
# node can be connected to multiple ports on the switch.
while (my @row = $result->fetchrow()) {
$device = $row[0];
}
} elsif ($port =~ /^([^.]+)\.\d+(\/\d+)?$/) {
$device = $1;
} else {
warn "Invalid format for port $port - Skipping\n";
next;