#!/usr/bin/perl -w # -*- perl -*- ############################################################################### # # genCatConfig - Generate Cricket config tree for a Cisco Catalyst switch. # # Copyright (C) 2000 Mike Fisher and Tech Data Corporation # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # Created: 02/16/00 - Mike Fisher (no spam!) # # Mods: See CHANGES.genCatConfig # ############################################################################### BEGIN { $gInstallRoot = (($0 =~ m:^(.*/):)[0] || "./") . ".."; } use lib "$gInstallRoot/lib"; use strict; no strict 'refs'; use snmpUtils; use Common::Log; my $VERSION = 'genCatConfig 1.4.0'; ############################################################################### sub usage { #' print STDERR < - Set SNMP read community. (default: public) --community -C - Same as -c, but include community in the config also. --addcommunity -e - Include EtherChannel aggregate interfaces. --etherchannels -h - Print this message. -help -l - Force all subdirectory names to lowercase. --lowercase -n - Don't show VLAN or ISL trunk status. --novlan -p - Collect data for the listed ports only. --ports -s [%2chmt] - Collect only the specified switch stats. -switchstats [%2chmt] -t - Include data from module temperature sensors. --temperature (experimental) -v - Verbose mode. Print SNMP collection status as we go. --verbose -V - Same as -v plus a hardware list when done. --moreverbose --version - Print the script version and exit. -2 - Use SNMP version 2c and retrieve high capacity --snmpv2c counters where possible. Portlist is a comma separated list of module/port combinations or the word "none". Eg. "4/6,5/*,6/9-12" specifies port 6 on module 4, all ports on module 5, and ports 9 through 12 on module 6. "none" specifies that no ports on the switch be monitored. The switch stats for the -s flag are: % - Total switch traffic (default) 2 - Layer 2 switching engine stats (default) c - CPU utilization (default) h - hardware status (default) m - memory utilization (default) EOD #' exit 1; } ############################################################################### ### MIB OID definitions. ############################################################################### my %OID = ( # from mib-2.system. 'sysName' => '1.3.6.1.2.1.1.5.0', 'sysLocation' => '1.3.6.1.2.1.1.6.0', # from mib-2.interfaces.ifTable.ifEntry 'ifDescr' => '1.3.6.1.2.1.2.2.1.2', 'ifAdminStatus' => '1.3.6.1.2.1.2.2.1.7', # from mib-2.ifMIB.ifMIBObjects.ifXTable.ifXEntry 'ifName' => '1.3.6.1.2.1.31.1.1.1.1', 'ifHCInOctets' => '1.3.6.1.2.1.31.1.1.1.6', 'ifConnector' => '1.3.6.1.2.1.31.1.1.1.17', # from mib-2.entityMIB.entityMIBObjects.entityPhysical. # entPhysicalTable.entPhysicalEntry 'entPhysicalDescr' => '1.3.6.1.2.1.47.1.1.1.1.2', 'entPhysicalContainedIn' => '1.3.6.1.2.1.47.1.1.1.1.4', 'entPhysicalName' => '1.3.6.1.2.1.47.1.1.1.1.7', # from cisco.workgroup.ciscoStackMIB.systemGrp 'sysTraffic' => '1.3.6.1.4.1.9.5.1.1.8.0', 'sysBootedImage' => '1.3.6.1.4.1.9.5.1.1.38.0', # from cisco.workgroup.ciscoStackMIB.chassisGrp 'chassisNumSlots' => '1.3.6.1.4.1.9.5.1.2.14.0', 'chassisModel' => '1.3.6.1.4.1.9.5.1.2.16.0', # from cisco.workgroup.ciscoStackMIB.moduleGrp.moduleTable 'moduleIndex' => '1.3.6.1.4.1.9.5.1.3.1.1.1', 'moduleType' => '1.3.6.1.4.1.9.5.1.3.1.1.2', 'moduleStatus' => '1.3.6.1.4.1.9.5.1.3.1.1.10', 'moduleName' => '1.3.6.1.4.1.9.5.1.3.1.1.13', 'moduleModel' => '1.3.6.1.4.1.9.5.1.3.1.1.17', 'moduleStandbyStatus' => '1.3.6.1.4.1.9.5.1.3.1.1.21', 'moduleSlotNum' => '1.3.6.1.4.1.9.5.1.3.1.1.25', # from cisco.workgroup.ciscoStackMIB.portGrp.portTable 'portModIndex' => '1.3.6.1.4.1.9.5.1.4.1.1.1', 'portIndex' => '1.3.6.1.4.1.9.5.1.4.1.1.2', 'portCrossIndex' => '1.3.6.1.4.1.9.5.1.4.1.1.3', 'portName' => '1.3.6.1.4.1.9.5.1.4.1.1.4', 'portType' => '1.3.6.1.4.1.9.5.1.4.1.1.5', 'portIfIndex' => '1.3.6.1.4.1.9.5.1.4.1.1.11', # from cisco.workgroup.ciscoStackMIB.vlanGrp.vlanPortTable 'vlanPortVlan' => '1.3.6.1.4.1.9.5.1.9.3.1.3', 'vlanPortIslVlansAllowed' => '1.3.6.1.4.1.9.5.1.9.3.1.5', 'vlanPortIslOperStatus' => '1.3.6.1.4.1.9.5.1.9.3.1.8', # from cisco.ciscoMgmt.ciscoSwitchEngineMIB. # cseMIBObjects.cseL2Objects.cseL2StatsTable 'cseL2ForwardedTotalPkts' => '1.3.6.1.4.1.9.9.97.1.1.1.1.3', 'cseL2NewAddressLearns' => '1.3.6.1.4.1.9.9.97.1.1.1.1.4', 'cseL2AddrLearnFailures' => '1.3.6.1.4.1.9.9.97.1.1.1.1.5', 'cseL2DstAddrLookupMisses' => '1.3.6.1.4.1.9.9.97.1.1.1.1.6', # from cisco.ciscoMgmt.entitySensorMIB.entitySensorMIBObjects. # entSensorValues.entSensorValueTable 'entSensorType' => '1.3.6.1.4.1.9.9.91.1.1.1.1.1', 'entSensorValue' => '1.3.6.1.4.1.9.9.91.1.1.1.1.4', 'entSensorStatus' => '1.3.6.1.4.1.9.9.91.1.1.1.1.5', ); ############################################################################### my($script) = $0 =~ /\/([^\/]+)$/; $script = "genCatConfig" if (!defined $script); my $savedargs = join(" ", @ARGV); my $community = "public"; my $verbose = 0; my $listall = 0; my $lowercase = 0; my %collectable = (); my $collectall = 1; my $addcomm = 0; my $etherchannels = 0; my $show_vlans = 1; my $show_traffic = 1; my $show_cpu = 1; my $show_mem = 1; my $show_layer2 = 1; my $show_hwstats = 1; my $show_temps = 0; my $usev2c = 0; while (@ARGV && $ARGV[0] =~ /^-/) { my $arg = shift; if ($arg eq '-a' || $arg eq '--allports') { $listall = 1; } elsif ($arg eq '-c' || $arg eq '--community') { $community = shift; } elsif ($arg eq '-C' || $arg eq '--addcommunity') { $community = shift; $addcomm = 1; } elsif ($arg eq '-e' || $arg eq '--etherchannels') { $etherchannels = 1; } elsif ($arg eq '-h' || $arg eq '--help') { usage(); } elsif ($arg eq '-l' || $arg eq '--lowercase') { $lowercase = 1; } elsif ($arg eq '-n' || $arg eq '--novlan') { $show_vlans = 0; } elsif ($arg eq '-p' || $arg eq '--ports') { %collectable = portlist2array(shift); $collectall = 0; } elsif ($arg eq '-s' || $arg eq '--switchstats') { my $flags = shift; $show_traffic = ($flags =~ /%/); $show_cpu = ($flags =~ /c/); $show_mem = ($flags =~ /m/); $show_layer2 = ($flags =~ /[2]/); $show_hwstats = ($flags =~ /h/); } elsif ($arg eq '-t' || $arg eq '--temperature') { $show_temps = 1; } elsif ($arg eq '-v' || $arg eq '--verbose') { $verbose = 1; $| = 1; } elsif ($arg eq '-V' || $arg eq '--moreverbose') { $verbose = 2; $| = 1; } elsif ($arg eq '--version') { print "$VERSION\n"; exit; } elsif ($arg eq '-2' || $arg eq '--snmpv2c') { $usev2c = 1; } else { print STDERR "Error: Unknown flag: $arg\n"; exit 1; } } usage() if (@ARGV != 1); if ($collectall) { $listall = 1; $collectable{'dummy'} = 1; } my $switch = $ARGV[0]; my $snmp = "$community\@$switch"; $snmp .= ':::::2c' if ($usev2c); ### Get all the data we need first... my($swmodel) = snmpUtils::get($snmp, $OID{'chassisModel'}); my($chassisNumSlots) = snmpUtils::get($snmp, $OID{'chassisNumSlots'}); if (!defined $swmodel) { print STDERR "Error: Switch $switch failed to respond!\n"; exit; } my($swname) = snmpUtils::get($snmp, $OID{'sysName'}); my($swloc) = snmpUtils::get($snmp, $OID{'sysLocation'}); my($swimg) = snmpUtils::get($snmp, $OID{'sysBootedImage'}); ($swimg) = $swimg =~ /[\.\_](\d-\d-\d)[\.\-]/; my(@data); ### Get MIB2 interface description and admin status. my(%ifdescr, %ifname); if (%collectable) { %ifdescr = gettable('ifDescr'); %ifname = gettable('ifName'); # %ifadminstatus = gettable('ifAdminStatus'); } ### Get port info. my(%portifindex, %portname, %porttype); if (%collectable) { %portifindex = reverse gettable('portIfIndex'); %portname = gettable('portName'); %porttype = gettable('portType'); } ### Get port VLAN assignment info. my(%portvlan, %islvlans, %islstatus); if (%collectable && $show_vlans) { %portvlan = gettable('vlanPortVlan'); %islvlans = gettable('vlanPortIslVlansAllowed'); %islstatus = gettable('vlanPortIslOperStatus'); } ### Get module info. my %moduleindex = gettable('moduleIndex'); my %moduletype = gettable('moduleType'); my %modulestatus = gettable('moduleStatus'); my %modulename = gettable('moduleName'); my %modulemodel = gettable('moduleModel'); my %moduleslotnum = reverse gettable('moduleSlotNum'); my %moduleStandbyStatus = gettable('moduleStandbyStatus'); ### Get MIB2 entity data. my %entPhysicalDescr = gettable('entPhysicalDescr'); my %entityInSlot = reverse gettable('entPhysicalContainedIn'); ### Get entity sensor data, if requested and supported. my(%entSensorType, %entSensorValue, %entSensorStatus); if ($show_temps) { %entSensorType = gettable('entSensorType'); if (%entSensorType) { %entSensorValue = gettable('entSensorValue'); %entSensorStatus = gettable('entSensorStatus'); } else { $show_temps = 0; } } ### Walk ifHCInOctets if we're going to use SNMP version 2c. my %ifHCInOctets; if ($usev2c) { %ifHCInOctets = gettable('ifHCInOctets'); } ### Try and get layer 2 switch engine stats. my %l2stats; if ($show_layer2) { %l2stats = gettable('cseL2ForwardedTotalPkts'); } ### If no module-to-slot map came back from the switch, it's probably ### running 4.x or earlier code. Make a best guest at slot assignments ### based in the module index. if (!%moduleslotnum) { print STDERR "WARNING: No module-to-slot map returned. (probably due to ", "old switch code)\n Guessing at slot numbers...\n"; %moduleslotnum = %moduleindex; } ### If no layer two stats were returned (probably a C4000), don't add them ### to the config. if (!%l2stats) { print STDERR "WARNING: This switch doesn't return layer 2 switch engine ", "stats.\n Skipping...\n"; $show_layer2 = 0; } ### Check to see if this switch is capable of returning EtherChannel stats. if ($etherchannels && !$usev2c && ( $swimg =~ /^5-4-[123]/ || $swimg =~ /^5-5-[1]/)) { print STDERR "WARNING: EtherChannel stats are broken in Catalyst OS ", "5.4.[123], 5.5.1,\n and possibly others when using ", "SNMP version 1. ", "Skipping...\n"; $etherchannels = 0; } ### Now build the config tree... my($m,$s,$p,$i,$n,$x,$file); ### Generate the switch level configs... print "\n$switch - $swmodel - $swloc\n\n" if ($verbose > 1); my $switchdir = subdir($switch); ### ...Switch engine targets. openfile($file = "$switchdir/stats"); writetarget($file, "--default--", 'switch' => $switch, 'directory-desc' => "$swmodel - $swloc", 'long-desc' => "%short-desc%", ($addcomm) ? ('snmp-community' => $community) : (), ($usev2c) ? ('target-type' => 'switch-port-hc', 'snmp-version' => '2c', ) : (), ); writetarget($file, "cpu", 'target-type' => 'switch-cpu', 'display-name' => 'CPU Utilization', 'inst' => 'map(cpu-stats)', 'short-desc' => "$swmodel - $swloc", 'order' => 90, ) if ($show_cpu); writetarget($file, "memory", 'target-type' => 'switch-mem', 'display-name' => 'Memory Utilization', 'inst' => 'map(mem-stats)', 'short-desc' => "$swmodel - $swloc", 'order' => 80, ) if ($show_mem); writetarget($file, "traffic", 'target-type' => 'switch-traffic', 'display-name' => 'Switch Traffic', 'short-desc' => "$swmodel - $swloc", 'inst' => 0, 'order' => 70, ) if ($show_traffic); writetarget($file, "status", 'target-type' => 'switch-hwstatus', 'display-name' => 'Hardware Status', 'short-desc' => "$swmodel - $swloc", 'long-desc' => "Values less than zero indicate a missing ". "component.
Values greater than zero ". "indicate alarms.", 'inst' => 0, 'order' => 65, ) if ($show_hwstats); writetarget($file, "layer2", 'target-type' => 'switch-layer2', 'display-name' => 'Layer 2 Switching Engine', 'short-desc' => "$swmodel - $swloc", 'inst' => (keys %l2stats)[0], 'order' => 60, ) if ($show_layer2); close($file); ### Generate the module level configs... my($sdesc, $ldesc, $vsdesc, $vldesc, $dir); foreach $s (keys %moduleslotnum) { # Physical slot # in chassis. my $e = $entityInSlot{$s+1}; # Entity index for this slot. my $m = $moduleslotnum{$s}; # Module index for this slot. ### Skip this module if the status is not "ok". if (!defined $modulestatus{$m} || $modulestatus{$m} != 2) { printf(" slot %2d: module status = %d (not ok) - skipping\n\n", $s, $modulestatus{$m}) if ($verbose > 1); next; } ### Build descriptions out of the pieces we have... $sdesc = ($modulename{$m}) ? "$modulename{$m} - " : ""; if (defined $e && defined $entPhysicalDescr{$e}) { $sdesc .= $entPhysicalDescr{$e}; } else { $sdesc .= $modulemodel{$m}; } printf(" slot %2d: %s\n\n", $s, $sdesc) if ($verbose > 1); $dir = subdir("$switchdir/Slot_$s"); ### Write the Defaults file. writedefaults($dir, 'directory-desc' => $sdesc, 'module-number' => $s, ); ### Generate the port level configs... openfile($file = "$dir/ports"); writetarget($file, "--default--", 'directory-desc' => "" ); foreach $i (keys %portifindex) { next if ($portifindex{$i} !~ /^$s\./); # Skip if not on this module. $p = $portifindex{$i}; # This is "module.port". ($n) = $p =~ /\.(\d+)/; # Get the port number on the module. $x = 1000 - $n; # Arbitrary index to sort on. # Increase this if a card has > 1000 ports. $collectable{"$s/$n"} = 1 if ($collectall); next if (!$listall && !$collectable{"$s/$n"}); ### Build the short and long descriptions. $sdesc = $ldesc = $vsdesc = $vldesc = ''; if (defined $islstatus{$p} && $islstatus{$p} == 1) { $vsdesc = '(ISL trunk)'; if (defined $islvlans{$p}) { $vldesc = 'ISL trunk (VLANS ' . demask($islvlans{$p}). ')
'; } else { $vldesc = 'ISL trunk
'; } } elsif (defined $portvlan{$p}) { $vsdesc = "(VLAN $portvlan{$p})"; $vldesc = "VLAN $portvlan{$p}
"; } $sdesc .= $portname{$p} if (defined $portname{$p}); $sdesc .= ($sdesc && $vsdesc) ? ' ' : ''; $sdesc .= $vsdesc if ($vsdesc); $sdesc .= ($sdesc && defined $ifdescr{$i}) ? ' - ' : ''; $sdesc .= $ifdescr{$i} if (defined $ifdescr{$i}); $ldesc .= "$portname{$p}
" if (defined $portname{$p}); $ldesc .= $vldesc if ($vldesc); $ldesc .= $ifdescr{$i} if (defined $ifdescr{$i}); $collectable{"$s/$n"} = 0 unless (defined $collectable{"$s/$n"} && $porttype{$p} != 31); my $collected = ($collectable{"$s/$n"}) ? '* ' : ''; my @snmpver = ($usev2c && !defined $ifHCInOctets{$i}) ? ('target-type' => 'switch-port', 'snmp-version' => '1', ) : (); printf("%2s port %2d: %s\n", $collected, $n, $sdesc) if ($verbose > 1); writetarget($file, "Port_$n", 'port-number' => $n, 'display-name' => "Port $s/$n", 'short-desc' => $sdesc, 'long-desc' => $ldesc, 'order' => $x, 'collect' => $collectable{"$s/$n"}, @snmpver, ); } print "\n" if ($verbose > 1); close($file); } ### Deal with ether channels. if ($etherchannels) { $dir = subdir("$switchdir/EtherChannels"); openfile($file = "$dir/channels"); writetarget($file, "--default--", 'directory-desc' => "EtherChannel Aggregate Interfaces", 'inst' => "map(ifName)", 'short-desc' => "", ); foreach $i (keys %ifname) { if ($ifname{$i} =~ /^[FG]EC/) { my $target = $ifname{$i}; $target =~ s/[\/\s:]/_/g; $target =~ s/[,]/-/g; my @snmpver = ($usev2c && !defined $ifHCInOctets{$i}) ? ('target-type' => 'switch-port', 'snmp-version' => '1', ) : (); writetarget($file, $target, 'interface-name' => $ifname{$i}, 'display-name' => $ifname{$i}, @snmpver, ); } } close($file); } ### Create temperature config. if ($show_temps) { $dir = subdir("$switch/Temperatures"); openfile($file = "$dir/sensors"); writetarget($file, "--default--", 'directory-desc' => "Temperature Sensors", 'short-desc' => "", 'inst' => 0, ); foreach $s (1..$chassisNumSlots) { my $e = $entityInSlot{$s+1}; # Entity index for this slot. my $m = $moduleslotnum{$s}; # module index for this slot. next if (!defined $e); # No module present. $sdesc = ($modulename{$m}) ? "$modulename{$m} - " : ""; if (defined $entPhysicalDescr{$e}) { $sdesc .= $entPhysicalDescr{$e}; } else { $sdesc .= $modulemodel{$m}; } my $stype = 0; my $ttype; foreach $i (grep(($_ > $e && $_ < ($e + 1000)), keys %entSensorType)) { if ($entSensorType{$i} == 8 && $entSensorStatus{$i} == 1) { $stype |= 2 ** ($i - $e); } } if ($stype == 30) { $ttype = 'line-card-temp'; } elsif ($stype == 414) { $ttype = 'sup-temp'; } else { print(STDERR "WARNING: Unknown temp sensor configuration for ", "module $s (entity $e) - Skipping...\n"); next; } writetarget($file, "module_$s", 'inst1' => $e+1, 'inst2' => $e+2, 'inst3' => $e+3, 'inst4' => $e+4, 'inst7' => $e+7, 'inst8' => $e+8, 'display-name' => "Module $s", 'short-desc' => $sdesc, 'target-type' => $ttype, ); } close($file); } exit; ############################################################################### # sub subdir { my($dir) = @_; $dir = lc($dir) if ($lowercase); if (! -d $dir) { mkdir($dir, 0755) || die "Can't mkdir $dir"; } return($dir); } ############################################################################### # # Open the named file and write a header comment. sub openfile { my($file) = @_; open($file, ">$file") || die "Can't open $file"; print($file "# Generated by $VERSION\n", "# Date: ", scalar(localtime(time)), "\n", "# Args: $savedargs\n\n"); } ############################################################################### sub writepair { my($file, $name, $value) = @_; my $quote = ''; my $tabs = int(((24 - length($name))/8) + 0.5); if (defined($value)) { # quote empty (or white-space only) lines, or lines which # will have embedded spaces. $quote = '"' if ($value eq '' || $value =~ /\s/); print($file " $name", "\t"x$tabs, "= $quote$value$quote\n"); } } ############################################################################### sub writetarget { my($file, $name, %value) = @_; my $key; print $file "target $name\n"; foreach $key (sort keys %value) { writepair($file, $key, $value{$key}); } print $file "\n"; } ############################################################################### sub writedefaults { my($path, %value) = @_; my $file = "$path/Defaults"; openfile($file); writetarget($file, "--default--", %value); close($file); # open(DEFAULTS, ">$path/Defaults") || die "Can't open $path/Defaults"; # print DEFAULTS "# Generated by $VERSION\n\n"; # writetarget('DEFAULTS', "--default--", %value); # close(DEFAULTS); } ############################################################################### sub gettable { my($name) = @_; if (!defined $OID{$name}) { print STDERR "ERROR: $name is undefined"; return undef; } print "Walking $name... " if ($verbose); my @data = snmpUtils::walk($snmp,$OID{$name}); my @tmp = map({split(/:/,$_,2)} @data); print scalar(@tmp)/2,"\n" if ($verbose); return @tmp; } ############################################################################### sub portlist2array { my($plist) = @_; my($chunk, $port, %plist); if ($plist eq "none") { return %plist; } foreach $chunk (split(/,/, $plist)) { my($module,$ports) = split(/\//,$chunk,2); if ($module !~ /^\d+$/) { print STDERR "Warning - Invalid port specification: $chunk\n"; next; } if ($ports eq "*") { foreach $port (1..48) { $plist{"$module/$port"} = 1; } } elsif ($ports =~ /^(\d+)-(\d+)$/) { foreach $port ($1..$2) { $plist{"$module/$port"} = 1; } } elsif ($ports =~ /^\d+$/) { $plist{"$module/$ports"} = 1; } else { print STDERR "Warning - Invalid port specification: $chunk\n"; } } return %plist; } ############################################################################### sub demask { my($mask) = @_; my $str = ''; my $first = -2; my $last = -2; foreach my $x (0..1023) { if (vec($mask, $x, 1)) { if ($first < 0) { $first = $last = $x; } elsif ($x == $last + 1) { $last++; } } else { next if ($first < 0); if ($last == $first) { $str .= ",$last"; } elsif ($last == $first+1) { $str .= ",$first,$last"; } else { $str .= ",$first-$last"; } $first = $last = -2; } } $str =~ s/^,//; return $str; } ###############################################################################