######################################################################### # package SwitchTools.pm # This Perl module contains routines for querying switches # V Who When What # --------------------------------------------------------------------------- # 1.1.1 skendric 2008-07-02 flag_uplink can handle ifDescr too # 1.1.0 skendric 2008-06-30 Add dot1d routines # 1.0.0 skendric 2008-06-16 First Version # # # # Authors: Stuart Kendrick # # Source: http://www.skendric.com/device/soma # # This software is available under the GNU GENERAL PUBLIC LICENSE, see # http://www.fsf.org/licenses/gpl.html # package FHCRC::VDOPS::SwitchTools; #### Load modules #### use strict; use warnings; use Carp qw(carp cluck croak confess); use Data::Dumper; use Exporter; use List::MoreUtils qw(all any notall none uniq); use Net::IPAddress qw(validaddr); use Perl6::Say; use Switch; use lib '/home/soma/lib'; use FHCRC::VDOPS::SomaData; use FHCRC::VDOPS::SNMPTools; use FHCRC::VDOPS::Utilities; #### Set-up export stuff #### our @ISA = qw(Exporter); our @EXPORT = qw( describe_switch flag_uplink extract_dot1d_base_port_if_index extract_dot1d_tp_fdb_address extract_dot1d_tp_fdb_port extract_if_name extract_if_name_by_vlan extract_vtp_vlan_state ); # Declare package local variables ##### Only subroutines below here #### ######################################################################## # Given a switch, return a reference to an anonymous list of modules & # ports contained within that switch ######################################################################## sub describe_switch { my %arg; my $esx = shift; # IP address of switch my @ports; # List of 'module/port' my $val; # Reference to an array of Varbinds # Debug trace trace_location('begin') if $debug > 2; # Sanity check confess 'No parameters!' unless defined $esx; # Debug info say "Describing $nodename{$esx}" if $debug > 2; # Grab data structure %arg = ( host => $esx, oid => $ifName_oid ); $val = snmpWalk(\%arg); print_it("Walk of ifName on $esx is empty") unless defined $val; # Walk through the data structure VARBIND: for my $varbind (@$val) { my ($ifName, $port); # Pull out ifName $ifName = $varbind->{val}; # Skip uplinks next VARBIND if flag_uplink($esx, $ifName); # ifNames come in varying formats. Here are the three formats # for which I'm looking. Notice that I ignore Stack currently, # grabbing just slot and port: 2/19, Fa2/18, Gi1/0/22 ($port) = ($ifName =~ /(\d{1,2}\/\d{1,2})$/); next VARBIND unless defined $port; say " Found port $port" if $debug == 7; # Save results push @ports, $port; } # Debug trace trace_location('end') if $debug > 2; return [ @ports ]; } ####################################################################### # Given a switch and ifName or ifDescr, return '1' if it smells like # an uplink port, return '0' otherwise ######################################################################## sub flag_uplink { my $dots; # Number of dots in $esx (should be 3) my $esx = shift; # Switch IP address my $flag; # '1' if it smells like an uplink port, # '0' otherwise my $name = shift; # ifName/ifDescr to analyze my $sys; # Shortcut to %sysObjectID # Debug trace trace_location('begin') if $debug == 10; # Sanity check confess 'Not enough parameters' unless (defined $esx and defined $name); $esx = get_ipaddr($esx) unless validaddr($esx); confess 'Something is wrong with first argument' unless defined $esx; # Define variables $flag = 0; $sys = $sysObjectID{$esx}; # We use Te ports (TenGigabitEthernet) only as uplinks switch ($name) { case /Te/ { $flag = 1 } case /FEC/ { $flag = 1 } case /GEC/ { $flag = 1 } case /Po/ { $flag = 1 } } # Based on sysObjectID, make an educated guess as to whether or not # we use this as an uplink port switch ($sys) { case /cat4948|catalyst4948/ { # Ports Gi1/45-48 on Cat 4948s my @if = qw{Gi1/45 Gi1/46 Gi1/47 Gi1/48 GigabitEthernet1/45 GigabitEthernet1/46 GigabitEthernet1/47 GigabitEthernet1/48}; $flag = 1 if any { $_ eq $name } @if; } case /cat45/ { # Ports Gi1/1-6 on Cat 4500s $flag = 1 if $name =~ /Gi1|GigabitEthernet1/; } case /wsc4003/ { # Ports 2/1-2 on Cat 4003 running CatOS $flag = 1 if ($name eq '2/1' or $name eq '2/2'); } case /wsc4/ { # Ports 1/1-2 on Cat 4K running CatOS $flag = 1 if ($name eq '1/1' or $name eq '1/2'); } case /cat35/ { # Ports Gi1/-2 on Cat 35xx my @if = qw{Gi0/1 Gi0/2 GigabitEthernet0/1 GigabitEthernet0/2}; $flag = 1 if any { $_ eq $name } @if; } case /catalyst37/ { # Ports Gi1/0/25-28 on Cat 37xx my @if = qw{Gi1/0/25 Gi1/0/26 Gi1/0/27 Gi1/0/28 GigabitEthernet1/0/25 GigabitEthernet1/0/26 GigabitEthernet1/0/27 GigabitEthernet1/0/28}; $flag = 1 if any { $_ eq $name } @if; } } # Debug info say " $esx{$esx}: $name is an uplink" if ($flag and $debug == 8); # Debug trace trace_location('end') if $debug == 10; return $flag; } ####################################################################### # Given a switch and a reference to a list of VLANs, grab the # dot1dBasePortIfIndex table for each VLAN it contains, and return a # reference to a hash containing ifIndex numbers keyed by bridge port # numbers. ######################################################################## sub extract_dot1d_base_port_if_index { my $port; # iid in ifIndex (bridge port number) my $csIndex; # 'community string index' my %dot1dBasePortIfIndex; # Hash of references to dot1dBasePortIfIndex # hashes, keyed by VLAN my $esx = shift; # Switch my $ifIndex; # val in dot1dBasePortIfIndex my %ifIndex; # Hash of ifIndex numbers keyed by # $port my $vlans = shift; # Reference to the list of VLANs living # within $esx my $val; # Reference to an array of Varbinds my $vlan; # Current VLAN we are examining # Debug trace trace_location('begin') if $debug > 2; # Sanity check confess 'Not enough parameters' unless (defined $esx and defined $vlans); confess 'Wrong type' unless ref $vlans eq 'ARRAY'; $esx = get_ipaddr($esx) unless validaddr($esx); confess 'Something is wrong with first argument' unless defined $esx; # Walk vlans for my $vlan (@$vlans) { my %arg; my $read = $snmp_read{$esx} . '@' . $vlan; # Walk dot1dBasePortIfIndex say 'Walking dot1dBasePortIfIndex' if $debug > 3; %arg = ( host => $esx, oid => $dot1dBasePortIfIndex_oid, read => $read ); $val = snmpWalk(\%arg); print_it("Walk of dot1dBasePortIfIndex on $esx is empty") unless defined $val; # Walk through the data structure undef %dot1dBasePortIfIndex; for my $varbind (@$val) { # Decompose port and ifIndex $port = $varbind->{iid}; $ifIndex = $varbind->{val}; # Build data structure $dot1dBasePortIfIndex{$port} = $ifIndex; # Debug info if ($debug == 4) { say "$esx{$esx} / $vlan sees ifIndex $ifIndex at bridgePort $port"; } } # Store a reference to an anonymous copy of %dot1dBasePortIfIndex $ifIndex{$vlan} = { %dot1dBasePortIfIndex }; } # Debug trace trace_location('end') if $debug > 2; return \%ifIndex; } ####################################################################### # Given a switch and a reference to a list of VLANs, grab the # dot1dTpFdbAddress table for each VLAN it contains, and return a # reference to a hash of references to hashes containing MAC addresses # keyed by 'bigIndex'. ######################################################################## sub extract_dot1d_tp_fdb_address { my $bigIndex; # iid in dot1dTpFdbPort and dot1dTpFdbAddress my $csIndex; # 'community string index' my %dot1dTpFdbAddress; # Hash of MAC addresses keyed by bigIndex my $esx = shift; # Switch my $mac; # val in dot1dTpFdbAddress my %macs; # Hash of references to dot1dTpFdbAddress # hashes, keyed by VLAN my $val; # Reference to an array of Varbinds my $vlan; # Current VLAN we are examining my @vlans; # List of VLANs in this switch my $vlans = shift; # Reference to the list of VLANs living # within $esx # Debug trace trace_location('begin') if $debug > 2; # Sanity check confess 'Must provide a switch' unless defined $esx; confess 'Must provide a list of VLANs' unless defined $vlans; confess 'VLAN list must be an array ref' unless ref $vlans eq 'ARRAY'; $esx = get_ipaddr($esx) unless validaddr($esx); confess 'Something is wrong with first argument' unless defined $esx; # Walk through vlans for my $vlan (@{$vlans}) { my %arg; my $read = $snmp_read{$esx} . '@' . $vlan; # Walk dot1dTpFdbAddress say 'Walking dot1dTpFdbAddress' if $debug > 3; %arg = ( host => $esx, oid => $dot1dTpFdbAddress_oid, read => $read, translate => 1 ); $val = snmpWalk(\%arg); print_it("Walk of dot1dTpFdbAddress on $esx is empty") unless defined $val; # Walk through the data structure undef %dot1dTpFdbAddress; VARBIND: for my $varbind (@$val) { # I call this 'bigIndex' $bigIndex = $varbind->{iid}; say " Processing bigIndex $bigIndex" if $debug == 8; # Pull out MAC address $mac = $varbind->{val}; $mac = normalize_mac($mac); next VARBIND unless defined $mac; # Build data structure $dot1dTpFdbAddress{$bigIndex} = $mac; # Debug info say " $esx{$esx} / vlan $vlan sees MAC $mac at bigIndex $bigIndex" if ($debug == 4 or $debug == 8); } # Store a reference to an anonymous copy of %dot1dTpFdbAddress $macs{$vlan} = { %dot1dTpFdbAddress }; } # Debug trace trace_location('end') if $debug > 2; return \%macs; } ####################################################################### # Given a switch and a reference to a list of VLANs, grab the # dot1dTpFdbPort table for each VLAN it contains, and return a reference # to a hash containing bridge port numbers. ######################################################################## sub extract_dot1d_tp_fdb_port { my $bigIndex; # iid in dot1dTpFdbPort and dot1dTpFdbAddress my $csIndex; # 'community string index' my $esx = shift; # Switch my $port; # val in dot1dTpFdbPort my %ports; # Hash of references to dot1dTpFdbPort # hashes, keyed by VLAN my %dot1dTpFdbPort; # Hash of bridge port numbers keyed by # bigIndex my $val; # Reference to an array of Varbinds my $vlan; # Current VLAN we are examining my $vlans = shift; # Reference to the list of VLANs living # within $esx # Debug trace trace_location('begin') if $debug > 2; # Sanity check confess 'Not enough parameters' unless defined $esx and defined $vlans; confess 'Wrong type' unless ref $vlans eq 'ARRAY'; $esx = get_ipaddr($esx) unless validaddr($esx); confess 'Something is wrong with first argument' unless defined $esx; # Walk vlans for my $vlan (@{$vlans}) { my %arg; my $read = $snmp_read{$esx} . '@' . $vlan; # Walk dot1dTpFdbPort say 'Walking dot1dTpFdbPort' if $debug > 3; %arg = ( host => $esx, oid => $dot1dTpFdbPort_oid, read => $read ); $val = snmpWalk(\%arg); print_it("Unable to walk dot1dTpFdbPort on $esx") unless defined $val; # Walk through the data structure undef %dot1dTpFdbPort; for my $varbind (@$val) { # I call this 'bigIndex' $bigIndex = $varbind->{iid}; # Pull out bridge port number $port = $varbind->{val}; # Save data $dot1dTpFdbPort{$bigIndex} = $port; # Debug info if ($debug == 4) { say "$esx{$esx} / $vlan sees bridgePort $port at bigIndex $bigIndex"; } } # Store a reference to an anonymous copy of %dot1dTpFdbPort $ports{$vlan} = { %dot1dTpFdbPort }; } # Debug trace trace_location('end') if $debug > 2; return \%ports; } ####################################################################### # Given a switch, grab the ifAdminStatus table and return a reference # to a hash containing ifAdminStatus values keyed by ifIndex numbers ######################################################################## sub extract_if_admin_status { my %arg; my $esx = shift; # Switch my %ifAdminStatus; # Hash of ifAdminStatus, keyed by ifIndex my $val; # Reference to an array of Varbinds # Debug trace trace_location('begin') if $debug > 3; # Sanity check confess 'No parameters' unless defined $esx; $esx = get_ipaddr($esx) unless validaddr($esx); confess 'Something is wrong with first argument' unless defined $esx; # Walk ifAdminStatus say 'Walking ifAdminStatus' if $debug > 3; %arg = ( host => $esx, oid => $ifAdminStatus_oid ); $val= snmpWalk(\%arg); print_it("Walk of ifAdminStatus on $esx is empty") unless defined $val; # Walk through the data structure for my $varbind (@$val) { my ($ifIndex, $ifAdminStatus); # Pull out ifIndex $ifIndex = $varbind->{iid}; # Pull out ifAdminStatus $ifAdminStatus = $varbind->{val}; switch($ifAdminStatus) { case 1 { $ifAdminStatus = 'up' } case 2 { $ifAdminStatus = 'down' } case 3 { $ifAdminStatus = 'testing' } else { $ifAdminStatus = $QUERY } } # Save data $ifAdminStatus{$ifIndex} = $ifAdminStatus; # Debug info if ($debug == 4) { say "$esx{$esx} sees ifAdminStatus $ifAdminStatus at ifIndex $ifIndex"; } } # Debug trace trace_location('end') if $debug > 3; return \%ifAdminStatus; } ####################################################################### # Given a switch, grab the ifName table and return a reference to a hash # containing ifName strings keyed by ifIndex numbers. ######################################################################## sub extract_if_name { my %arg; my $esx = shift; # Switch my %ifName; # Hash of ifNames, keyed by ifIndex my $val; # Reference to an array of Varbinds # Debug trace trace_location('begin') if $debug > 3; # Sanity check confess 'No parameters' unless defined $esx; $esx = get_ipaddr($esx) unless validaddr($esx); confess 'Something is wrong with first argument' unless defined $esx; # Walk ifName say 'Walking ifName' if $debug > 3; %arg = ( host => $esx, oid => $ifName_oid ); $val = snmpWalk(\%arg); print_it("Walk of ifName on $esx is empty") unless defined $val; # Walk through the data structure for my $varbind (@$val) { my ($ifIndex, $ifName); # Pull out ifIndex $ifIndex = $varbind->{iid}; # Pull out ifName $ifName = $varbind->{val}; # Save data $ifName{$ifIndex} = $ifName; # Debug info if ($debug == 4) { say "$esx{$esx} sees ifName $ifName at ifIndex $ifIndex"; } } # Debug trace trace_location('end') if $debug > 3; return \%ifName; } ####################################################################### # Given a switch and a reference to a list of VLANs, grab the ifName # table for each VLAN it contains, and return a reference to a hash # containing ifName strings keyed by ifIndex numbers. ######################################################################## sub extract_if_name_by_vlan { my $esx = shift; # Switch my %ifName; # Hash of ifNames, keyed by ifIndex my %portName; # Hash of references to ifName # hashes, keyed by VLAN my $val; # Reference to an array of Varbinds my $vlan; # Current VLAN we are examining my $vlans = shift; # Reference to the list of VLANs living # within $esx # Debug trace trace_location('begin') if $debug > 3; # Sanity check confess 'Not enough parameters' unless defined $esx and defined $vlans; confess 'Wrong type' unless ref $vlans eq 'ARRAY'; $esx = get_ipaddr($esx) unless validaddr($esx); confess 'Something is wrong with first argument' unless defined $esx; # Walk VLANs for my $vlan (@$vlans) { my %arg; my $read = $snmp_read{$esx} . '@' . $vlan; # Walk ifName say 'Walking ifName' if $debug > 3; %arg = ( host => $esx, oid => $ifName_oid, read => $read ); $val = snmpWalk(\%arg); print_it("Walk of ifName on $esx is empty") unless defined $val; # Walk through the data structure undef %ifName; for my $varbind (@$val) { my ($ifIndex, $ifName); # Pull out ifIndex $ifIndex = $varbind->{iid}; # Pull out ifName $ifName = $varbind->{val}; # Save data $ifName{$ifIndex} = $ifName; # Debug info if ($debug == 4) { say "$esx{$esx} / $vlan sees ifName $ifName at ifIndex $ifIndex"; } } # Store a reference to an anonymous copy of %dot1dBasePortIfIndex $portName{$vlan} = { %ifName }; } # Debug trace trace_location('end') if $debug > 3; return \%portName; } ######################################################################## # Given a switch, grab its vtpVlanState table, process it to extract # active VLANs, and return a reference to the resulting list. ######################################################################## sub extract_vtp_vlan_state { my %arg; my $esx = shift; # Switch my $status; # operational, suspended, mtuTooBigForDevice, # or mtuTooBigForTrunk my $val; # Reference to an array containing an # array of Varbinds my $vlan; # Current VLAN my @vlans; # VLANs found in this esx # Debug trace trace_location('begin') if $debug > 2; # Sanity check confess 'No parameters' unless defined $esx; $esx = get_ipaddr($esx) unless validaddr($esx); confess 'Something is wrong with switch name' unless defined $esx; # Grab the VLAN data structure say 'Walking vtpVlanState' if $debug > 3; %arg = ( host => $esx, oid => $vtpVlanState_oid ); $val = snmpWalk(\%arg); print_it("Walk of vtpVlanState on $esx is empty") unless defined $val; # Walk through the data structure VLAN: for my $varbind (@$val) { # Pull out VLAN number $vlan = $varbind->{iid}; ($vlan) = ($vlan) =~ /1\.(\d+)/; next VLAN if $vlan == 1; next VLAN if $vlan =~ /^1002/; next VLAN if $vlan =~ /^1003/; next VLAN if $vlan =~ /^1004/; next VLAN if $vlan =~ /^1005/; # Pull out status $status = $varbind->{val}; next VLAN unless $status == 1; # operational # Keep track of what we find push @vlans, $vlan; } # Debug info say "$esx{$esx} contains vlans: @vlans" if $debug > 2; # Debug trace trace_location('end') if $debug > 2; return \@vlans; } 1;