#!/usr/bin/perl -wT # # mcastsum - summarize router multicast state using Net::SNMP # 2005-02-20, dwcarder: misc hacks to prettify output # 2005-10-18-a,jtk: limited public release # 2005-10-18-b,jtk: added msdpEnabled check # 2005-10-20-a,jtk: comments/doc # # This script simply pulls some simple state information out of a # multicast enabled router and summarizes the results. It was written # quickly and not without a lot of sanity checks though it should not # cause any harm other than a few failed SNMP queries if it doesn't # work for you. This script was mainly written as a quick exmaple # in hopes that it might be useful as a quick view into multicast # router resource conditions or to encourage a better tool or set of # tools to do similar things. # # You'll need Net::SNMP. Run as follows: # # ./mcastsum -r -c # # TODO: more summary info # better error/sanity checking # more descriptive output use strict; $|=1; use Getopt::Long; use Net::SNMP qw(oid_lex_sort); ## for troubleshooting, e.g. print Dumper($var) #use Data::Dumper; ## # global vars my $options = 0; # GetOptions return code my $community = ""; # SNMP community string my $router = ""; # router hostname or IP address my $result; # generic SNMP status handler my $interface; # interface id place holder my @if_indexes = (); # interface id list my %mcast = (); # multicast state hash # oid variables my $sysName = ".1.3.6.1.2.1.1.5.0"; my $ifIndex = ".1.3.6.1.2.1.2.2.1.1"; my $ifName = ".1.3.6.1.2.1.31.1.1.1.1"; my $igmpInterfaceJoins = ".1.3.6.1.3.59.1.1.1.1.11"; my $igmpInterfaceLeaves = ".1.3.6.1.3.59.1.1.1.1.12"; my $igmpInterfaceGroups = ".1.3.6.1.3.59.1.1.1.1.13"; my $ipMRouteInIfIndex = ".1.3.6.1.3.60.1.1.2.1.5"; my $ipMRouteInterfaceInMcastOctets = ".1.3.6.1.3.60.1.1.4.1.5"; my $ipMRouteInterfaceOutMcastOctets = ".1.3.6.1.3.60.1.1.4.1.6"; my $msdpNumSACacheEntries = ".1.3.6.1.3.92.1.1.3.0"; my $msdpEnabled = ".1.3.6.1.3.92.1.1.1.0"; $options = GetOptions( "c=s" => \$community, "r=s" => \$router ); if(!$community || !$router) { usage(); } # check for community and router # setup SNMP session # my ($session, $error) = Net::SNMP->session( -hostname=>$router, -community=>$community); if(!defined($session)) { dienice(__LINE__, "SNMP session creation error: " . $error); } # sysName $result = $session->get_request($sysName); if(defined($result)) { print "# sysName: ", $result->{$sysName}, "\n"; } else { print "# sysName: [ unknown ]\n"; } # ifIndex print "# Getting router interface list...\n"; $result = $session->get_table(-baseoid => $ifIndex); # TODO: what is a better way to check for error? if(!defined($result)) { dienice(__LINE__, "SNMP get error: " . $error); } # build ifIndex array foreach my $oid (oid_lex_sort(keys(%{$result}))) { push(@if_indexes,$result->{$oid}); } # Here is the hash hashes structure we're building: # # %mcast = { # ifIndex => { # 'ifName' => name, # 'igmpInterfaceJoins' => joins, # 'igmpInterfaceLeaves' => leaves, # 'igmpInterfaceGroups => groups, # 'ipMRouteInterfaceInMcastOctets => octets, # 'ipMRouteInterfaceOutMcastOctets => octets # } # ... # } # # TODO # get ifName print "# Getting interface names...\n"; foreach $interface (@if_indexes) { $result = $session->get_request("$ifName.$interface"); # TODO: do an error check $mcast{$interface}{'ifName'} = $result->{"$ifName.$interface"}; } # note: PIM uses a null interface, manually add it to %mcast $mcast{'0'}{'ifName'} = "null"; $mcast{'0'}{'igmpInterfaceJoins'} = -1; $mcast{'0'}{'igmpInterfaceLeaves'} = -1; $mcast{'0'}{'igmpInterfaceGroups'} = -1; $mcast{'0'}{'ipMRouteInterfaceInMcastOctets'} = -1; $mcast{'0'}{'ipMRouteInterfaceOutMcastOctets'} = -1; # get igmpInterfaceJoins print "# Getting interface IGMP joins...\n"; foreach $interface (@if_indexes) { $result = $session->get_request("$igmpInterfaceJoins.$interface"); # note, some are likely to come back as undef if(!defined($result)) { # undefined interfaces receive value = -1 $mcast{$interface}{'igmpInterfaceJoins'} = -1; } else { # TODO: do an error check $mcast{$interface}{'igmpInterfaceJoins'} = $result->{"$igmpInterfaceJoins.$interface"}; } } # get igmpInterfaceGroups print "# Getting interface IGMP group membership...\n"; foreach $interface (@if_indexes) { $result = $session->get_request("$igmpInterfaceGroups.$interface"); # note, some are likely to come back as undef if(!defined($result)) { # undefined interfaces receive value = -1 $mcast{$interface}{'igmpInterfaceGroups'} = -1; } else { # TODO: do an error check $mcast{$interface}{'igmpInterfaceGroups'} = $result->{"$igmpInterfaceGroups.$interface"}; } } # get igmpInterfaceLeaves print "# Getting interface IGMP leaves...\n"; foreach $interface (@if_indexes) { $result = $session->get_request("$igmpInterfaceLeaves.$interface"); # note, some are likely to come back as undef if(!defined($result)) { # undefined interfaces receive value = -1 $mcast{$interface}{'igmpInterfaceLeaves'} = -1; } else { # TODO: do an error check $mcast{$interface}{'igmpInterfaceLeaves'} = $result->{"$igmpInterfaceLeaves.$interface"}; } } # get ipMRouteInIfIndex print "# Getting mroute list...\n"; $result = $session->get_table(-baseoid => $ipMRouteInIfIndex); # TODO: what is a better way to check for error? if(!defined($result)) { dienice(__LINE__, "SNMP get error: " . $error); } # calculate interface ipMRouteInIfIndex totals foreach my $oid (oid_lex_sort(keys(%{$result}))) { $mcast{$result->{$oid}}{'ipMRouteInIfIndex'}++; } # get ipMRouteInterfaceInMcastOctets print "# Getting ipMRouteInterfaceInMcastOctets...\n"; foreach $interface (@if_indexes) { $result = $session->get_request("$ipMRouteInterfaceInMcastOctets.$interface"); # note, some are likely to come back as undef if(!defined($result)) { # undefined interfaces receive value = -1 $mcast{$interface}{'ipMRouteInterfaceInMcastOctets'} = -1; } else { # TODO: do an error check $mcast{$interface}{'ipMRouteInterfaceInMcastOctets'} = $result->{"$ipMRouteInterfaceInMcastOctets.$interface"}; } } # get ipMRouteInterfaceOutMcastOctets print "# Getting ipMRouteInterfaceOutMcastOctets...\n"; foreach $interface (@if_indexes) { $result = $session->get_request("$ipMRouteInterfaceOutMcastOctets.$interface"); # note, some are likely to come back as undef if(!defined($result)) { # undefined interfaces receive value = -1 $mcast{$interface}{'ipMRouteInterfaceOutMcastOctets'} = -1; } else { # TODO: do an error check $mcast{$interface}{'ipMRouteInterfaceOutMcastOctets'} = $result->{"$ipMRouteInterfaceOutMcastOctets.$interface"}; } } print " Int joins leaves groups routes inoctets outoctets\n"; foreach my $key (sort {numonly($mcast{$a}{'ifName'}) <=> numonly($mcast{$b}{'ifName'})} keys %mcast) { my $joins = 0; my $leaves = 0; my $groups = 0; my $routes = 0; my $inoctets = 0; my $outoctets = 0; if($mcast{$key}{'igmpInterfaceJoins'} > 0) { $joins = $mcast{$key}{'igmpInterfaceJoins'}; } if($mcast{$key}{'igmpInterfaceLeaves'} > 0) { $leaves = $mcast{$key}{'igmpInterfaceLeaves'}; } if($mcast{$key}{'igmpInterfaceGroups'} > 0) { $groups = $mcast{$key}{'igmpInterfaceGroups'}; } if(defined($mcast{$key}{'ipMRouteInIfIndex'})) { $routes = $mcast{$key}{'ipMRouteInIfIndex'}; } if($mcast{$key}{'ipMRouteInterfaceInMcastOctets'} > 0) { $inoctets = $mcast{$key}{'ipMRouteInterfaceInMcastOctets'}; } if($mcast{$key}{'ipMRouteInterfaceOutMcastOctets'} > 0) { $outoctets = $mcast{$key}{'ipMRouteInterfaceOutMcastOctets'}; } if ($joins||$leaves||$groups||$routes||$inoctets||$outoctets) { printf("%7s %8d %8d %8d %8d %12u %12u \n", $mcast{$key}{'ifName'},$joins,$leaves,$groups,$routes,$inoctets,$outoctets); } } # get msdpNumSACAcheEntries $result = $session->get_request($msdpEnabled); # TODO: do an error check if($result->{"$msdpEnabled"} == 1) { print "# Getting MSDP SA cache...\n"; $result = $session->get_request($msdpNumSACacheEntries); # TODO: do an error check print " MSDP SA count: ", $result->{"$msdpNumSACacheEntries"}, "\n"; } else { print "# MSDP disabled, skipping...\n"; } # TODO: get most popular groups across interfaces # TODO: get most popular IGMP/PIM/MSDP group # TODO: get most popular PIM/MSDP source # TODO: calculate SA diff between MSDP routers - maybe different script $result=$session->close; if($result) { dienice(__LINE__, "SNMP session close error: " . $error); } ### subroutines # incorrect command line parameters sub usage { print STDERR "Usage: $0 -c community_string -r router\n"; exit(1); } ## dienice() - something bad happened, exit gracefully ## sub dienice { #TODO: validate passed vars my $linenum = shift; my $message = shift; print STDERR "WARNING(", $linenum, "): ", $message, "\n"; print STDERR "Operation aborted.\n"; exit 1; } sub numonly { my $val = shift; $val =~ m/(\d+)/; my $ret = $1; if (!defined($ret)) { $ret = '0'; } return ($ret); }