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

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) { ...@@ -45,7 +45,7 @@ if (!$COMMUNITY) {
###################################################################### ######################################################################
sub usage { sub usage {
print << "END"; print << "END";
Usage: $0 [-h] [-v] [-i device] Usage: $0 [-h] [-v] [-n] [-i device]
[-l] [-s] [-g] [-l] [-s] [-g]
[-m name [ports]] [-m name [ports]]
[-o name] [-o name]
...@@ -57,6 +57,7 @@ Usage: $0 [-h] [-v] [-i device] ...@@ -57,6 +57,7 @@ Usage: $0 [-h] [-v] [-i device]
General: General:
-h Display this help message -h Display this help message
-v Verbose mode -v Verbose mode
-n Test mode - don't actually make any changes
-i <device> Operate on <device>, overriding default device list. Can be -i <device> Operate on <device>, overriding default device list. Can be
given multiple times given multiple times
...@@ -66,6 +67,12 @@ VLAN Control: ...@@ -66,6 +67,12 @@ VLAN Control:
-l List all VLANs -l List all VLANs
-m <name> [ports] Create a new VLAN with name <name>, if it doesn't exist, -m <name> [ports] Create a new VLAN with name <name>, if it doesn't exist,
and put [ports] in it 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> -o <name> Delete the VLAN with name <name>
-c Delete ALL VLANs, and recreate from the database. ** USE -c Delete ALL VLANs, and recreate from the database. ** USE
WITH EXTREME CAUTION ** WITH EXTREME CAUTION **
...@@ -89,8 +96,9 @@ END ...@@ -89,8 +96,9 @@ END
my %opt = (); my %opt = ();
GetOptions(\%opt,'h','l','v','s','t','r','i=s@','m=s@','o=s@','p=s','u=s','d', GetOptions(\%opt, 'a','c','d','e','g','h','i=s@','l','m=s@','n','o=s@','p=s',
'e','a','g','c'); 'r','s','t','u=s','v','y=s','x=s','z=s');
# Unused: b,f,j,q,w
if ($opt{h}) { if ($opt{h}) {
exit &usage; exit &usage;
...@@ -208,6 +216,45 @@ if (!@commands) { ...@@ -208,6 +216,45 @@ if (!@commands) {
die "No operation given\n"; 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 # Step 3 - Set up the stack objects
...@@ -238,11 +285,37 @@ if ($pid && $eid) { ...@@ -238,11 +285,37 @@ if ($pid && $eid) {
# #
if (@ports) { 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)) { if (!TBNodeAccessCheck($UID,TB_NODEACCESS_MODIFYVLANS,@nodes)) {
die "You do not have permission to modify some or all of the nodes\n" . 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"; "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) { if ($TESTMODE) {
...@@ -256,7 +329,7 @@ if ($TESTMODE) { ...@@ -256,7 +329,7 @@ if ($TESTMODE) {
# #
snmpit_lib::init($debug); snmpit_lib::init($debug);
my $exitval; my $exitval = 0;
foreach my $command (@commands) { foreach my $command (@commands) {
# #
...@@ -333,14 +406,21 @@ foreach my $command (@commands) { ...@@ -333,14 +406,21 @@ foreach my $command (@commands) {
# #
my @stacks; my @stacks;
foreach my $stack_id (keys %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; my $stack;
debug("Stack $stack_id has type $stack_type\n"); debug("Stack $stack_id has type $stack_type\n");
SWITCH: for ($stack_type) { SWITCH: for ($stack_type) {
/cisco/ && do { /cisco/ && do {
require snmpit_cisco_stack; require snmpit_cisco_stack;
$stack = new snmpit_cisco_stack($stack_id,$debug,$COMMUNITY, $stack = new snmpit_cisco_stack($stack_id,$debug,$COMMUNITY,
@{$stacks{$stack_id}}); $supports_private, @{$stacks{$stack_id}});
last; last;
}; # /cisco/ }; # /cisco/
/intel/ && do { /intel/ && do {
...@@ -370,6 +450,10 @@ foreach my $command (@commands) { ...@@ -370,6 +450,10 @@ foreach my $command (@commands) {
# Finally, we just call the helper function for the operation that # Finally, we just call the helper function for the operation that
# is to be performed. # is to be performed.
###################################################################### ######################################################################
if ($opt{n}) {
print "Test mode, skipping operation\n";
next;
}
SWITCH: for ($operation) { SWITCH: for ($operation) {
/listvlans/ && do { /listvlans/ && do {
...@@ -623,7 +707,7 @@ sub doVlansFromTables($@) { ...@@ -623,7 +707,7 @@ sub doVlansFromTables($@) {
print " VLAN $vlan already exists\n"; print " VLAN $vlan already exists\n";
$errors += $stack->setPortVlan($vlan,@ports); $errors += $stack->setPortVlan($vlan,@ports);
} else { } else {
if (!$stack->createVlan($vlan,@ports)) { if (!$stack->createVlan($vlan,\@ports)) {
warn "ERROR: Failed to create VLAN with id $vlan\n"; 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 # Don't try to put ports in a VLAN if it couldn't be created
...@@ -719,7 +803,7 @@ sub doMakeVlan($$@) { ...@@ -719,7 +803,7 @@ sub doMakeVlan($$@) {
} }
} else { } else {
print "Creating VLAN $vlan_name ...\n"; 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 "VLAN creation ";
print $ok? "succeeded":"failed",".\n"; print $ok? "succeeded":"failed",".\n";
if (!$ok) { if (!$ok) {
...@@ -733,18 +817,24 @@ sub doMakeVlan($$@) { ...@@ -733,18 +817,24 @@ sub doMakeVlan($$@) {
# #
# Delete the given VLAN, if it exists # Delete the given VLAN, if it exists
# #
sub doDeleteVlan($$) { sub doDeleteVlan($@) {
my $stacks = shift; my $stacks = shift;
my $vlan_name = shift; my @vlan_names = @_;
my $errors = 0; my $errors = 0;
my $exists = 0; my %exists = ();
foreach my $stack (@$stacks) { foreach my $stack (@$stacks) {
if ($stack->vlanExists($vlan_name)) { my @existant_vlans;
$exists = 1; foreach my $vlan_name (@vlan_names) {
print "Deleting VLAN $vlan_name ...\n"; if ($stack->vlanExists($vlan_name)) {
my $ok = $stack->removeVlan($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 "VLAN deletion ";
print $ok? "succeeded":"failed",".\n"; print $ok? "succeeded":"failed",".\n";
if (!$ok) { if (!$ok) {
...@@ -753,9 +843,11 @@ sub doDeleteVlan($$) { ...@@ -753,9 +843,11 @@ sub doDeleteVlan($$) {
} }
} }
if (!$exists) { foreach my $vlan_name (@vlan_names) {
print "VLAN $vlan_name does not exist\n"; if (!$exists{$vlan_name}) {
$errors++; print "VLAN $vlan_name does not exist\n";
$errors++;
}
} }
return $errors; return $errors;
...@@ -837,9 +929,7 @@ sub doRecreateVlans($) { ...@@ -837,9 +929,7 @@ sub doRecreateVlans($) {
debug("Going to nuke " . join(',',@vlansToNuke) . "\n"); debug("Going to nuke " . join(',',@vlansToNuke) . "\n");
foreach my $vlan (@vlansToNuke) { doDeleteVlan($stacks,@vlansToNuke);
doDeleteVlan($stacks,$vlan);
}
# #
# Get a list of all experiments, so that we can re-create their VLANs # Get a list of all experiments, so that we can re-create their VLANs
......
...@@ -59,7 +59,7 @@ my $PORT_FORMAT_NODEPORT = 3; ...@@ -59,7 +59,7 @@ my $PORT_FORMAT_NODEPORT = 3;
# usage: new($classname,$devicename,$debuglevel) # usage: new($classname,$devicename,$debuglevel)
# returns a new object, blessed into the snmpit_cisco class. # 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) # The next two lines are some voodoo taken from perltoot(1)
my $proto = shift; my $proto = shift;
...@@ -69,6 +69,7 @@ sub new($$$$;$) { ...@@ -69,6 +69,7 @@ sub new($$$$;$) {
my $debugLevel = shift; my $debugLevel = shift;
my $switchtype = shift; my $switchtype = shift;
my $community = shift; my $community = shift;
my $supportsPrivate = shift;
# #
# Create the actual object # Create the actual object
...@@ -87,6 +88,7 @@ sub new($$$$;$) { ...@@ -87,6 +88,7 @@ sub new($$$$;$) {
$self->{BULK} = 1; $self->{BULK} = 1;
$self->{NAME} = $name; $self->{NAME} = $name;
$self->{COMMUNITY} = $community; $self->{COMMUNITY} = $community;
$self->{SUPPORTS_PRIVATE} = $supportsPrivate;
# Figure out some stuff about this switch # Figure out some stuff about this switch
$switchtype =~ /^(\w+)(-ios)?$/; $switchtype =~ /^(\w+)(-ios)?$/;
...@@ -112,7 +114,8 @@ sub new($$$$;$) { ...@@ -112,7 +114,8 @@ sub new($$$$;$) {
"$mibpath/SNMPv2-MIB.txt", "$mibpath/IANAifType-MIB.txt", "$mibpath/SNMPv2-MIB.txt", "$mibpath/IANAifType-MIB.txt",
"$mibpath/IF-MIB.txt", "$mibpath/RMON-MIB.txt", "$mibpath/IF-MIB.txt", "$mibpath/RMON-MIB.txt",
"$mibpath/CISCO-SMI.txt", "$mibpath/CISCO-TC.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") { if ($self->{OSTYPE} eq "CatOS") {
push @mibs, "$mibpath/CISCO-STACK-MIB.txt"; push @mibs, "$mibpath/CISCO-STACK-MIB.txt";
...@@ -245,6 +248,8 @@ sub convertPortFormat($$@) { ...@@ -245,6 +248,8 @@ sub convertPortFormat($$@) {
SWITCH: for ($sample) { SWITCH: for ($sample) {
(/^\d+$/) && do { $input = $PORT_FORMAT_IFINDEX; last; }; (/^\d+$/) && do { $input = $PORT_FORMAT_IFINDEX; last; };
(/^\d+\.\d+$/) && do { $input = $PORT_FORMAT_MODPORT; 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; $input = $PORT_FORMAT_NODEPORT; last;
} }
...@@ -470,25 +475,36 @@ sub findVlan($$;$) { ...@@ -470,25 +475,36 @@ sub findVlan($$;$) {
# Create a VLAN on this switch, with the given identifier (which comes from # Create a VLAN on this switch, with the given identifier (which comes from
# the database.) Picks its own switch-specific VLAN number to use. # 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 1 on success
# returns 0 on failure # 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 $self = shift;
my $vlan_id = 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 my $okay = 1;
# than using the names.
# TODO: Figure this out my $VlanType = 'vtpVlanEditType'; # vlan # is index
# my $VlanName = 'vtpVlanEditName'; # vlan # is index
my $VlanType = '.1.3.6.1.4.1.9.9.46.1.4.2.1.3.1'; # vlan # is index my $VlanSAID = 'vtpVlanEditDot10Said'; # vlan # is index
my $VlanName = '.1.3.6.1.4.1.9.9.46.1.4.2.1.4.1'; # vlan # is index my $VlanRowStatus = 'vtpVlanEditRowStatus'; # 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
# #
# We may have to do this multiple times - a few times, we've had the # We may have to do this multiple times - a few times, we've had the
...@@ -520,12 +536,12 @@ sub createVlan($$) { ...@@ -520,12 +536,12 @@ sub createVlan($$) {
# #
my $vlan_number = 2; # We need to start at 2 my $vlan_number = 2; # We need to start at 2
my $RetVal = snmpitGetFatal($self->{SESS}, my $RetVal = snmpitGetFatal($self->{SESS},
[$VlanRowStatus,$vlan_number]); [$VlanRowStatus,"1.$vlan_number"]);
$self->debug("Row $vlan_number got '$RetVal'\n",2); $self->debug("Row $vlan_number got '$RetVal'\n",2);
while (($RetVal ne 'NOSUCHINSTANCE') && ($vlan_number <= 1000)) { while (($RetVal ne 'NOSUCHINSTANCE') && ($vlan_number <= 1000)) {
$vlan_number += 1; $vlan_number += 1;
$RetVal = snmpitGetFatal($self->{SESS}, $RetVal = snmpitGetFatal($self->{SESS},
[$VlanRowStatus,$vlan_number]); [$VlanRowStatus,"1.$vlan_number"]);
$self->debug("Row $vlan_number got '$RetVal'\n",2); $self->debug("Row $vlan_number got '$RetVal'\n",2);
} }
if ($vlan_number > 1000) { if ($vlan_number > 1000) {
...@@ -550,11 +566,11 @@ sub createVlan($$) { ...@@ -550,11 +566,11 @@ sub createVlan($$) {
# Perform the actual creation. Yes, this next line MUST happen all in # Perform the actual creation. Yes, this next line MUST happen all in
# one set command.... # one set command....
# #
$RetVal = $self->{SESS}->set([[$VlanRowStatus,$vlan_number, $RetVal = $self->{SESS}->set([[$VlanRowStatus,"1.$vlan_number",
"createAndGo","INTEGER"], "createAndGo","INTEGER"],
[$VlanType,$vlan_number,"ethernet","INTEGER"], [$VlanType,"1.$vlan_number","ethernet","INTEGER"],
[$VlanName,$vlan_number,$vlan_id,"OCTETSTR"], [$VlanName,"1.$vlan_number",$vlan_id,"OCTETSTR"],
[$VlanSAID,$vlan_number,$SAID,"OCTETSTR"]]); [$VlanSAID,"1.$vlan_number",$SAID,"OCTETSTR"]]);
print "",($RetVal? "Succeeded":"Failed"), ".\n"; print "",($RetVal? "Succeeded":"Failed"), ".\n";
# #
...@@ -563,8 +579,50 @@ sub createVlan($$) { ...@@ -563,8 +579,50 @@ sub createVlan($$) {
if (!$RetVal) { if (!$RetVal) {
print STDERR "VLAN Create '$vlan_id' as VLAN $vlan_number " . print STDERR "VLAN Create '$vlan_id' as VLAN $vlan_number " .
"failed.\n"; "failed.\n";
$self->vlanUnlock();
next; next;
} else { } 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(); $RetVal = $self->vlanUnlock();
$self->debug("Got $RetVal from vlanUnlock\n"); $self->debug("Got $RetVal from vlanUnlock\n");
...@@ -578,7 +636,62 @@ sub createVlan($$) { ...@@ -578,7 +636,62 @@ sub createVlan($$) {
"get created - trying again\n"; "get created - trying again\n";
next; 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($$@) { ...@@ -600,13 +713,6 @@ sub setPortVlan($$@) {
my $vlan_id = shift; my $vlan_id = shift;
my @ports = @_; 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; my $errors = 0;
# #
...@@ -620,15 +726,41 @@ sub setPortVlan($$@) { ...@@ -620,15 +726,41 @@ sub setPortVlan($$@) {