#!/opt/vdops/bin/perl # This script counts the number of active MAC addresses in switches and logs # the result # V Who When What # --------------------------------------------------------------------------- # 1.1.0 skendric 2011-02-21 Upgrade to Netops 1.4.0 # 1.0.0 skendric 2010-05-10 First Version # # Author: Stuart Kendrick, sbk {put at sign here} skendric {put dot here} com # # Source: http://www.skendric.com/device # # This software is available under the GNU GENERAL PUBLIC LICENSE, see # http://www.fsf.org/licenses/gpl.html # # # This script takes the following approach: # -Parses the hosts table for a list of targets (or accepts a command- # line list) # -Uses one of several methods to extract the per VLAN MAC address count # -Logs what it finds to syslog and to a text log file # # # Requirements: # -The following MIB modules stashed in /opt/vdops/share/snmp/mibs, # or wherever it is that you store MIB modules: # CISCO-PRODUCTS-MIB.my # # -PERL modules: the WI::Netops collection # # # Assumptions: # # # Tested on: # -perl-5.12.2 # -net-snmp-5.6 # # # Instructions: # -Customize the script for your site: find the 'user-configurable # variables' section and modify as appropriate # -Type "count-cam-table" to see the command-line options # -Try it out # # # # Caveats: # # # Known Bugs: # # # To do: # -Add support for SNMPv3 # # Begin script # Load modules use strict; use warnings; use feature 'say'; use feature 'switch'; use Carp qw(carp cluck croak confess); use Data::Dumper; use English qw( -no_match_vars ); use Getopt::Std; use WI::Netops::HostTools 1.0.4; use WI::Netops::NetopsTools 2.2.3; use WI::Netops::NetopsData 1.4.0; use WI::Netops::PingTools 1.1.7; use WI::Netops::SNMPTools 1.5.3; use WI::Netops::Utilities 1.4.4; # Declare global variables my $log_file; # Where to log total MAC counts my %mac; # Hash (keyed by target) of hash refs (keyed by # vlan) of array refs, listing the MAC addresss # (for that VLAN in that device) my %mac_count; # Hash (keyed by target) of scalars, counting the # sum of MAC addresses in that device my $total_macs; # Sum of MAC addresses across all devices my %vlan; # Hash of array refs, keyed by target, listing # the active VLANs in that device # Define global variables $program_name = 'count-cam-table'; $usage = 'Usage: count-cam-table -s {yes|no} [-d {integer}] [-r] [-a | -e {expr} | -f {filename} | target1 target2 target3 ...]'; $version = '1.1.0'; # Report stuff $log_file = '/home/netops/logs/count-cam-table.log'; # Grab arguments getopts('ad:e:f:rs:', \%option); @target = @ARGV; # Set mode if ($option{r}) { $mode = 'report' } elsif (-t STDIN) { $mode = 'interactive' } else { $mode = 'batch' } ### Begin Main Program ############################################### { check_args(); # Check arguments read_config(); # Read Netops config file compile_mibs(); # Compile MIB files build_target(); # Populate @target target_check(); # Look for errors in @target basic_info(); # Gather information info_before(); # Acquire VLANs do_the_work(); # Do the work write_log(); # Record what we found } ##### End Main Program ############################################### ######################################################################## # Gather information ######################################################################## sub info_before { my @remove; my @vlans; # Debug trace trace_location('begin') if $debug; # Notify operator say 'Acquiring VLANs...' if $mode eq 'interactive'; # Loop through targets TARGET: for my $target (@target) { my ($vb, @vlans); # Debug info say "Processing $target" if $debug; # Extract VLANs from vtpVlanState $vb = snmpWalk( {host => $target, oid => 'vtpVlanState'} ); if (@$vb > 0) { VLAN: for my $varbind (@$vb) { my $vlan; $vlan = $varbind->{iid}; ($vlan) = ($vlan) =~ /1\.(\d+)/; # Skip built-in VLANs 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, ignore non-functional VLANs my $status = $varbind->{val}; next VLAN unless $status eq 'operational'; # Keep track of what we find push @vlans, $vlan; } } # End 'Extract VLANs from vtpVlanState' # Otherwise, extract VLANs from ifDescr else { $vb = snmpWalk( {host => $target, oid => $ifDescr_oid} ); if (@$vb == 0) { print_it("\n$target not responding to walk on ifDescr, skipping"); push @remove, $target; next TARGET; } # Walk varbind, looking for VLANs IFDESCR: for my $varbind (@$vb) { my ($if_descr, $vlan); $if_descr = $varbind->{val}; ($vlan) = ($if_descr =~ /Vlan(\d+)/); next IFDESCR unless defined $vlan; next IFDESCR if $vlan == 1; push @vlans, $vlan; } } # End 'Extract VLANs from ifDescr # Error check unless (@vlans > 0) { print_it("\n$target does not contain VLANs, skipping"); push @remove, $target; next TARGET; } # Save the result @vlans = sort @vlans; $vlan{$target} = \@vlans; say " Contains VLANs @vlans" if $debug > 1; # Entertain operator print $BANG if $mode eq 'interactive'; } # End 'Loop through targets' # Make things pretty say "\n" if $mode eq 'interactive'; # Remove entries which failed checks prune_basic(@remove); # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Do the work: acquire image name ######################################################################## sub do_the_work { my @remove; # Debug trace trace_location('begin') if $debug; # Notify operator say 'Acquiring MAC forwarding tables...' if $mode eq 'interactive'; # Loop through targets TARGET: for my $target (@target) { say "Processing $target" if $debug; # Walk through VLANs for my $vlan (@{$vlan{$target}}) { my (@macs, $read, $vb); say " Handling VLAN $vlan" if $debug > 1; $read = $snmp_read{$target} . '@' . $vlan; $vb = snmpWalk( {host=>$target, read=>$read, oid=>'dot1dTpFdbAddress'} ); # Walk through this VLANs MAC forwarding table MAC: for my $varbind (@$vb) { my $mac = $varbind->{val}; $mac = normalize_mac($mac); next MAC unless defined $mac; push @macs, $mac; say " $mac" if $debug == 8; } # Save the result $mac_count{$target} += @macs; $mac{$target}->{$vlan} = \@macs; $total_macs += @macs; # Log per VLAN MAC address count my $sum = @macs; log_it("Vlan $vlan on $target contains $sum MAC addresses"); } # Log per device MAC address count log_it("$target contains $mac_count{$target} MAC addresses"); # Entertain operator print $BANG if $mode eq 'interactive'; } # Make things look pretty say "\n" if $mode eq 'interactive'; # Remove entries which failed checks prune_basic(@remove); # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Tell the operator what I discovered ######################################################################## sub write_log { my $handle; my $total = @target; my $now = get_date(); # Debug trace trace_location('begin') if $debug; # If we are running in test mode, skip this routine unless ($dome) { print_it("Running in test mode, cannot print a meaningful report\n"); return 1; } # Notify operator say 'Writing log...' if $mode eq 'interactive'; # Write data if (open my $log, '>>', $log_file) { print {$log} "$now\t$total_macs\n"; close $log or warn "Cannot close $log_file: $!"; } else { warn "Cannot open $log_file: $!"; } # Make things look pretty print_it("\n\nEnding $PROGRAM_NAME"); # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Output help ######################################################################## sub HELP_MESSAGE { print <