#! /usr/local/bin/perl # Poll concentrators for port usage data and keep track of which ports have # been idle. Detailed data is in $data, a summary report is in $report, and # the list of hosts to poll is in $config. # Revisions and modifications: # Ron Hood 13 Oct '93 First version # Ron Hood 16 Nov '93 Added total & active port counts # to summary, logfile # Ron Hood 25 Apr '94 Major rewrite: # - Pull data from SNM data.log # - Purge records after $goneDays # Ron Hood 9 May '94 Added idle time to data report # Ron Hood 26 Apr '95 Added Cabletron hubs & packIt sub # Ron Hood 22 Mar '96 Added Bay5xxx # Ron Hood 24 May '99 Rewrite to use SNMP queries rather # SNM data # Ron Hood 24 Jun '99 ID hubs via ObjectID instead of Descr # Ron Hood 16 Jul '99 Added Cat4003 # Ron Hood 10 Nov '99 Added Cat 2900 & BayStack 450 # Ron Hood 06 Jan '01 Added Cat 35xx # Ron Hood 01 Jun '01 Redirected stderr to /dev/null # skendric 19 Oct '01 Added 'sub skip', added Cat 3548 # skendric 31 Oct '01 Rewrote 'Notes' section # skendric 08 Nov '01 Added SWOG # Author: Ron Hood, rhood@fhcrc.org # # 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: # -Grab a copy of a hosts table # -Parse it for devices ending in the string "-esx" # -Assume that these are layer 1/2 packet forwarding devices # -Query each interface on the device for ifInOctets # -Store the result in an ASCII database # -Create a report showing which ports on which slots have not # seen a change in ifInOctets for 30 days # # Requirements: # -The target(s) must be pingable # -The OID for sysObjectID must be placed in the code (see the # %devices hash) # -Command-line snmpget and snmpwalk utilities # # Assumptions: # # Tested under: # -PERL v5.005_03 # -UCD-SNMP v4.2.3 # -Solaris 2.7 # # Instructions: # -Customize the script for your site: poke through the 'Header Stuff' # section and modify as appropriate # -Run manually and check for errors # -Once you've ironed out bugs at your site, run from cron every night # -Wait 30 days # -Look at the report file, validate # Begin script # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Header stuff # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= use Sys::Syslog; # Syslog library $syslog'host = "loghost"; openlog ("porter", '', 'local5'); #$debug = 1; # Where is everything? $datFile = "/home/netops/rpts/porter.data"; $repFile = "/home/netops/rpts/porter.rpt"; $logFile = "/home/netops/logs/porter.log"; # Popular defintiions for "$grabhosts" # include "/usr/bin/cat /etc/hosts" # and "/usr/bin/cat /home/me/mylist-of- # switches" $grabhosts = "/usr/bin/niscat hosts.org_dir"; $snmpget = "/usr/local/bin/snmpget -c"; $snmpwalk = "/usr/local/bin/snmpwalk -Os -c"; $ping = "/usr/sbin/ping"; $idleDays = 30; $goneDays = 5; @skip = qw/pit vax/; # I won't query devices with # these strings in their names $usage = "usage: p2\n"; chop ($date = `date +%m-%d-%y`) ; # Get the date & time chop ($time = `date +%H:%M`); $idleTime = time - $idleDays*24*60*60; $currTime = time - 24*60*60; @suffixes = ("-esx"); # Suffixes we care about @passwds = ("public", "not-secret"; # Passwords to try # Map sysObjectID to data collection subroutine %devices = ( 'enterprises.9.1.217', => 'mib2', # Cat 2900 'enterprises.9.1.248', => 'mib2', # Cat 3524 'enterprises.9.1.278', => 'mib2', # Cat 3548 'enterprises.9.1.287', => 'mib2', # Cat 3524-PWR 'enterprises.9.5.7', => 'cat_5x0x', # Cat 5000 'enterprises.9.5.17', => 'cat_5x0x', # Cat 5500 'enterprises.9.5.18', => 'mib2', # Cat 1900 'enterprises.9.5.20', => 'mib2', # Cat 2820 'enterprises.9.5.34', => 'cat_5x0x', # Cat 5505 'enterprises.9.5.40', => 'cat_5x0x', # Cat 4003 'enterprises.9.5.45', => 'cat_5x0x', # Cat 650x 'enterprises.9.5.46', => 'cat_5x0x', # Cat 4006 'enterprises.45.3.2.1' => 'syn_30x0', # Syn 3000 'enterprises.45.3.3.1' => 'syn_30x0', # Syn 3030 'enterprises.45.3.12.1' => 'syn_30x0', # Syn 281X 'enterprises.45.3.14.1' => 'syn_30x0', # Syn 281XSA 'enterprises.45.3.15.1' => 'bay_28k', # Bay 28115 'enterprises.45.3.22.1' => 'syn_5000', # BayStack 'enterprises.45.3.30.2' => 'mib2', # BayStack 350 'enterprises.45.3.35.1' => 'mib2', # BayStack 450 'enterprises.52.3.9.3.4.11' => 'ctron_sehi',# Cabletron SEHI 'enterprises.97.2.1' => 'mib2', # Tigerswitch 'enterprises.437.1.1.3.3.2' => 'mib2', # Cat 2100 ); # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Read in the data file # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= if (open (DATA, "$datFile")) { while () { # Read in each line next if (/^#/); # Skip comments # Read a line of data ($name, $slot, $port, $count, $cTime, $vTime) = split(' '); # Pack it into an associative array $ports {sprintf "%s %d %d", $name, $slot, $port} = "$name $slot $port $count $cTime $vTime"; } close (DATA); # Clean up } #============================================================= # Grab a copy of the hosts file #============================================================= foreach $host (split ('\n', `$grabhosts`)) { ($key, $alias, $ip, @comment) = split (' ', $host); next unless $key eq $alias; next unless ($key =~ /-/); # Skip hosts w/o a '-' push (@hosts, $key); } #============================================================= # Pick out the names of interest #============================================================= foreach $host (@hosts) { foreach $suffix (@suffixes) { next unless ($host =~ /$suffix$/); $host = skip ($host); if (defined $host) { push (@boxes, $host) } } } #============================================================= # Figure out the password and device type #============================================================= foreach $host (sort (@boxes)) { $hostType {$host} = ''; $hostPasswd {$host} = ''; $rtn = `$ping $host`; ($rtn =~ /is alive/) || (logIt ("No ICMP response from [$host]")) && next; foreach $passwd (@passwds) { $rtn = `$snmpget $passwd $host system.sysObjectID.0 2>/dev/null`; next unless ($rtn =~ /system.sysObjectID.0/); chomp ($rtn); $rtn =~ s/.*= OID: //s; $hostPasswd {$host} = $passwd; $hostType {$host} = $rtn; last; # Bail out of loop } foreach $sysObjectID (keys %devices) { ($hostType {$host} =~ /$sysObjectID/) && ($hostSub{$host} = $devices{$sysObjectID}); } # Couldn't find password if ($hostPasswd {$host} eq '') { logIt ("Couldn't determine SNMP password for [$host]"); } # Unknown system type if ($hostSub{$host} eq '') { logIt ("Unknown SNMP system -- [$host][$rtn]"); } } #============================================================= # Call the appropriate data-collection subroutine #============================================================= foreach $host (sort @boxes) { next unless $hostPasswd {$host}; next unless $hostSub {$host}; # There ought to be a cleaner way to do this -- maybe # pointers to subroutines or something -- but I can't # think of it right now. if ($hostSub{$host} eq "syn_30x0") { data_syn_30x0 ($host, $hostPasswd{$host}); } elsif ($hostSub{$host} eq "syn_5000") { data_syn_5000 ($host, $hostPasswd{$host}); } elsif ($hostSub{$host} eq "bay_28k") { data_bay_28k ($host, $hostPasswd{$host}); } elsif ($hostSub{$host} eq "mib2") { data_mib2 ($host, $hostPasswd{$host}); } elsif ($hostSub{$host} eq "cat_5x0x") { data_cat_5x0x ($host, $hostPasswd{$host}); } else { logIt ("Error: shouldn't have reached this line [$host][$hostSub{$host}]"); } } sub data_mib2 { my ($host, $passwd) = @_; my (@rtn, $val, $slot, $port, $count); my $oid = ".1.3.6.1.2.1.2.2.1.10"; @rtn = `$snmpwalk $passwd $host $oid 2>/dev/null`; foreach $val (@rtn) { ($port, $count) = ($val =~ /.(\d+)\s=.*(\d+)$/); $counts {sprintf "%s %d %d", $host, "1", $port} = $count; } } sub data_syn_30x0 { my ($host, $passwd) = @_; my (@rtn, $val, $slot, $port, $count); my $oid = ".1.3.6.1.4.1.45.1.3.2.3.1.1.7"; @rtn = `$snmpwalk $passwd $host $oid 2>/dev/null`; foreach $val (@rtn) { ($slot, $port, $count) = ($val =~ /(\d+)\.(\d+)\s=.*(\d+)$/); $counts {sprintf "%s %d %d", $host, $slot, $port} = $count; } } sub data_bay_28k { my ($host, $passwd) = @_; my (@rtn, $val, $slot, $port, $count); my $oid = ".1.3.6.1.4.1.45.1.7.6.2.1.1.8"; @rtn = `$snmpwalk $passwd $host $oid 2>/dev/null`; foreach $val (@rtn) { ($slot, $port, $count) = ($val =~ /(\d+)\.(\d+)\s=.*(\d+)$/); $counts {sprintf "%s %d %d", $host, $slot, $port} = $count; } } sub data_cat_5x0x { my ($host, $passwd) = @_; my (@rtn, $val, @slot, @port, $slot, $port, $count, $index); my $count_oid = ".1.3.6.1.2.1.2.2.1.10"; my $intfc_oid = ".1.3.6.1.4.1.9.5.1.4.1.1.11"; # Associate slot/port with index @rtn = `$snmpwalk $passwd $host $intfc_oid 2>/dev/null`; foreach $val (@rtn) { ($slot, $port, $index) = ($val =~ /(\d+)\.(\d+)\s=\s(\d+)/); $slot[$index] = $slot; $port[$index] = $port; } # Get the ifInOctet data @rtn = `$snmpwalk $passwd $host $count_oid 2>/dev/null`; foreach $val (@rtn) { ($index, $count) = ($val =~ /(\d+)\s=.*(\d+)$/); $counts {sprintf "%s %d %d", $host, $slot[$index], $port[$index]} = $count; } } sub data_syn_5000 { my ($host, $passwd) = @_; my (@rtn, $val, $slot, $port, $count); my $oid = ".1.3.6.1.4.1.45.1.6.6.2.1.1.2"; @rtn = `$snmpwalk $passwd $host $oid 2>/dev/null`; foreach $val (@rtn) { ($index, $count) = ($val =~ /(\d+)\s=.*(\d+)$/); # Skip all but 2xxxxx series next unless ($index =~ /2\d\d\d\d\d/); # Pick out the "slot" and port ($slot, $port) = ($index =~ /\d(\d\d)0(\d\d)/); $counts {sprintf "%s %d %d", $host, $slot, $port} = $count; } } # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Compare the current counts to those from the data file & update # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= foreach $triple (keys %counts) { # Get the current data $currCount = $counts {$triple}; # Get the datafile data ($host, $slot, $port, $dataCount, $dataCTime, $dataVTime) = split (' ', $ports {$triple}); if ($ports {$triple} eq '') { # New port $ports {$triple} = "$triple $currCount $currTime $currTime"; } elsif ($currCount == $dataCount) { # No change in byte count $ports {$triple} = "$triple $dataCount $dataCTime $currTime"; } else { # Recent activity $ports {$triple} = "$triple $currCount $currTime $currTime"; } } # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Put together a new data file # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= open (DATA, ">$datFile") || die "Can't open report file $datFile\n"; print DATA < (time - $goneDays*24*60*60)) && write; } close (DATA); # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Run through the data and see how many ports we have # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= foreach $key (keys %ports) { ($name, $slot, $port, $count, $cTime, $vTime) = split(' ', $ports{$key}); next unless ($vTime >= $currTime); ++$total_ports; next unless ($cTime > $idleTime); ++$active_ports; } # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Put together a summary report # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= open (REPORT, ">$repFile") || die "Can't open report file $repFile\n"; print REPORT <>$logFile")) { print (LOG "$date $time\t$active_ports\t$total_ports\n"); close (LOG); } # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Subroutines # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= sub bykeys { ($a_host, $a_slot, $a_port) = split (' ', $a); ($b_host, $b_slot, $b_port) = split (' ', $b); if (($a_host cmp $b_host) != 0) { $a_host cmp $b_host; } elsif (($a_slot <=> $b_slot) != 0) { $a_slot <=> $b_slot; } elsif (($a_port <=> $b_port) != 0) { $a_port <=> $b_port; } } # usage: &oldenough ($cTime, $vTime) sub oldenough { local ($a, $b) = @_; if ($b < $currTime) { 0; } # Stale data elsif ($a == 0) { 1; } # Never used elsif ($a > $idleTime) { 0; } # Not old enough else { 1; } # Old enough } sub packIt { local ($i, $iN, $iZ, @zero, @new, $string); # Convert interior numbers in a consecutive run to 0 for ($i = 0; $i < $#_; ++$i) { if (@_[$i-1] != @_[$i]-1) { @zero[$i] = @_[$i]; } elsif (@_[$i+1] != @_[$i]+1) { @zero[$i] = @_[$i]; } else { @zero[$i] = 0; } } @zero[0] = @_[0]; @zero[$#_] = @_[$#_]; # Eliminate consecutive 0s @new [0] = @zero [0]; for ($iZ = 1, $iN = 1; $iZ <= $#zero; ++$iZ, ++$iN) { if (@zero[$iZ] != 0) { @new[$iN] = @zero[$iZ]; } elsif (@zero[$iZ] != @new[$iN-1]) { @new[$iN] = @zero[$iZ]; } else { --$iN; } } # Convert single 0s to dashes $string = @new[0]; for ($i = 1; $i <= $#new; ++$i) { if ((@new[$i] != 0) && (@new[$i-1] != 0)) {$string .= ","; } if (@new[$i] == 0) { $string .= "-"; } else { $string .= "@new[$i]"; } } return $string; } sub logIt { my ($msg) = @_; if ($debug) { print "$msg\n"; } else { syslog ('notice', $msg); } } sub skip { my ($host) = @_; my ($pattern); foreach $pattern (@skip) { if ($host =~ /$pattern/) { undef $host; last; } } return $host; }