#!/opt/vdops/bin/perl # This script reports the list of active IPSec VPN tunnels on the target device # V Who When What # --------------------------------------------------------------------------- # 1.4.0 skendric 2011-03-19 Display dead tunnels # 1.3.0 skendric 2011-02-21 Upgrade to Netops 1.4.0 # 1.2.0 skendric 2009-08-14 List both up and down tunnels # 1.1.2 skendric 2009-07-17 Update IP address <--> names hash # 1.1.1 skendric 2008-11-30 Add more IP address <--> name look-ups # 1.1.0 skendric 2008-05-11 Abandon cikeTunRemoteValue and employ # cikePeerLocal/RemoteAddr # 1.0.1 skendric 2008-01-17 More debug output # 1.0.0 skendric 2007-07-02 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: # -Accepts a command-line list of targets (if empty, employs a # hard-coded list) # -Queries the targets for IPSec tunnel related parameters # -Produces a report # # # 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 FHCRC::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 "show-tunnels" 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'; no autovivification qw(exists fetch); use Carp qw(carp cluck croak confess); use Data::Dumper; use English qw( -no_match_vars ); use Getopt::Std; use Net::IPv4Addr qw(ipv4_chkip); use Time::Duration; use FHCRC::Netops::HostTools 1.0.4; use FHCRC::Netops::NetopsTools 2.2.3; use FHCRC::Netops::NetopsData 1.4.0; use FHCRC::Netops::PingTools 1.1.7; use FHCRC::Netops::SNMPTools 1.5.3; use FHCRC::Netops::Utilities 1.4.4; # Declare global variables # All hashes keyed by target and consist of hash refs keyed by tunnel index my %active_tunnels; # Value of cikeGlobalActiveTunnels.0, keyed by # target my %cikeTunIndex; # Tunnel index (keyed by peer name) my %duration; # Length of time across which tunnel has been up my %encrypt_algorithm; # Encryption algorithm my %hash_algorithm; # Hash algorithm my %index_mapping; # Hash refs of hashes of cikeTunIndex keyed # by cipSecTunIkeTunnelIndex (keyed by target) my %lifetime; # Tunnel lifetime my $local_peer; # IP address of our end of the tunnels my %peer_iid_of; # Hash of references to hashes of iids (keyed by # peer), keyed by target my %peer; # IP address of peer my %status; # Up or down my %total_tunnels; # Value of cikeGlobalInitTunnels.0, keyed by # target my %traffic; # Number of bytes which have traversed the tunnel my %tunnel_name; # Hard-coded names of tunnels, indexed by # IP address of tunnel terminator # Define global variables $program_name = 'show-tunnels'; $usage = 'Usage: show-tunnels -s {yes|no} [-d {integer}] [-r] [-a | -e {expr} | -f {filename} | target1 target2 target3 ...]'; $version = '1.4.0'; # VPN details $local_peer = '10.114.1.10'; # Grab arguments getopts('ad:e:f:rs:', \%option); @target = @ARGV; # Domain-specific stuff $option{s} = 'yes' unless defined $option{s}; %tunnel_name = ( '68.91.214.6' => 'charms', '215.35.45.4' => 'dejarnette', '258.41.35.235' => 'gems', '60.248.33.35' => 'icad', '128.83.181.205' => 'impac', '128.258.225.235' => 'onbase', '145.142.233.205' => 'pinnacle', '128.158.127.65' => 'pacs', '51.114.138.115' => 'pyxis', ); # 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 do_the_work(); # Do the work info_after(); # Insert dashes for dead tunnels print_report(); # Print report } ##### End Main Program ############################################### ######################################################################## # Do the work: acquire image name ######################################################################## sub do_the_work { # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Acquiring tunnel information...'); # Loop through targets TARGET: for my $target (@target) { my (%arg, $val, $vb); say "Processing $target" if $debug; # Acquire cikeGlobalInitTunnels.0 %arg = ( host => $target, oid => 'cikeGlobalInitTunnels.0', translate => 0); $val = snmpGet(\%arg); $val //= 0; say " $target has initiated $val tunnels" if $debug; $total_tunnels{$target} = $val; # Skip uninteresting targets next TARGET unless $total_tunnels{$target} > 0; # Acquire cikeGlobalActiveTunnels.0 say 'Getting cikeGlobalActiveTunnels.0' if $debug > 3; $val = snmpGet( {host => $target, oid => 'cikeGlobalActiveTunnels.0'} ); $val //= 0; say " $target is hosting $val active tunnels" if $debug; $active_tunnels{$target} = $val; # Acquire tunnel peers $vb = snmpWalk( {host => $target, oid => 'cikeTunRemoteValue'} ); for my $varbind (@$vb) { my ($iid, $ip, $peer); $ip = scrub_ip($varbind->{val}); $iid = $varbind->{iid}; $peer = $tunnel_name{$ip} if defined $tunnel_name{$ip}; $peer{$target}->{$iid} = $peer; $cikeTunIndex{$target}->{$peer} = $iid; } if ($debug > 1) { for my $iid (keys %{$peer{$target}}) { say " $target talks to $peer{$target}->{$iid}"; } } # # Acquire tunnel local peers # say 'Walking cikePeerLocalAddr' if $debug > 3; # $vb = snmpWalk( {host=>$target, oid=>' cikePeerLocalAdd', translate=>1} ); # for my $varbind (@$vb) { # my ($iid, $peer); # $peer = scrub_ip($varbind->{val}); # unless ($peer eq $local_peer) { # ($iid) = ($varbind->{iid} =~ /\.(\d+)\Z/); # $peer = $tunnel_name{$peer} if defined $tunnel_name{$peer}; # $peer{$target}->{$iid} = $peer; # $duration{$target}->{$iid} += 0; # $traffic{$target}->{$iid} += 0; # $peer_iid_of{$target}->{$peer} = $iid; # } # } # if ($debug > 3) { # for my $iid (keys %{$peer{$target}}) { # say " $target talks to $peer{$target}->{$iid}"; # } # } # # Acquire tunnel remote peers # $vb = snmpWalk( {host=>$target, oid=>'cikePeerRemoteAddr', translate=>1} ); # for my $varbind (@$vb) { # my ($iid, $peer); # $peer = scrub_ip($varbind->{val}); # unless ($peer eq $local_peer) { # ($iid) = ($varbind->{iid} =~ /\.(\d+)\Z/); # $peer = $tunnel_name{$peer} if defined $tunnel_name{$peer}; # $peer{$target}->{$iid} = $peer; # $duration{$target}->{$iid} += 0; # $traffic{$target}->{$iid} += 0; # $peer_iid_of{$target}->{$peer} = $iid; # } # } # if ($debug > 3) { # for my $iid (keys %{$peer{$target}}) { # say " $target talks to $peer{$target}->{$iid}"; # } # } # # # Look up cipSecTunIkeTunnelIndex # $vb = snmpWalk( {host => $target, oid => 'cipSecTunIkeTunnelIndex'} ); # for my $varbind (@$vb) { # my ($cipSecTunIkeTunnelIndex, $cikeTunIndex); # $cipSecTunIkeTunnelIndex = $varbind->{iid}; # $cikeTunIndex = $varbind->{val}; # #($cikeTunIndex) = ($varbind->{iid}) =~ /\.(\d+)\.\d+\Z/; # $index_mapping{$target}->{$cipSecTunIkeTunnelIndex} = $cikeTunIndex; # } # if ($debug > 1) { # say 'Index mapping'; # for my $iid (sort keys %{$index_mapping{$target}}) { # say "$iid => ", $index_mapping{$target}->{$iid}; # } # } # Acquire tunnel time $vb = snmpWalk( {host => $target, oid => 'cikeTunActiveTime'} ); for my $varbind (@$vb) { my ($iid, $seconds); $seconds = int $varbind->{val} / 100; $iid = $varbind->{iid}; $duration{$target}->{$iid} = $seconds; } # Acquire encryption algorithm $vb = snmpWalk( {host => $target, oid => 'cikeTunEncryptAlgo'} ); for my $varbind (@$vb) { my ($iid, $alg); $alg = $varbind->{val}; $iid = $varbind->{iid}; $encrypt_algorithm{$target}->{$iid} = $alg; } # Acquire hash algorithm $vb = snmpWalk( {host => $target, oid => 'cikeTunHashAlgo'} ); for my $varbind (@$vb) { my ($iid, $hash); $hash = $varbind->{val}; $iid = $varbind->{iid}; $hash_algorithm{$target}->{$iid} = $hash; } # Acquire tunnel lifetime $vb = snmpWalk({host => $target, oid => 'cikeTunLifeTime', translate => 0}); for my $varbind (@$vb) { my ($iid, $lifetime); $lifetime = $varbind->{val}; $iid = $varbind->{iid}; $lifetime{$target}->{$iid} = $lifetime; } # Acquire tunnel status $vb = snmpWalk({host => $target, oid => 'cikeTunStatus'} ); for my $varbind (@$vb) { my ($iid, $status); $status = $varbind->{val}; $iid = $varbind->{iid}; $status{$target}->{$iid} = $status; } # # Acquire tunnel InOctets # $vb = snmpWalk( {host => $target, oid => 'cipSecTunHcInOctets', translate => 0} ); # for my $varbind (@$vb) { # my ($iid, $inoctets, $cikeTunIndex); # $inoctets = $varbind->{val}; # $iid = $varbind->{iid}; # $cikeTunIndex = $index_mapping{$target}->{$iid}; # $traffic{$target}->{$cikeTunIndex} = $inoctets; # } # # # Acquire tunnel OutOctets # $vb = snmpWalk( {host => $target, oid => 'cipSecTunHcInDecompOctets', translte => 0} ); # for my $varbind (@$vb) { # my ($iid, $outoctets, $cikeTunIndex); # $outoctets = $varbind->{val}; # $iid = $varbind->{iid}; # $cikeTunIndex = $index_mapping{$target}->{$iid}; # $traffic{$target}->{$cikeTunIndex} += $outoctets; # } # # # Prettify traffic # for my $iid (keys %{$traffic{$target}}) { # my $octets = $traffic{$target}->{$iid}; # given ($octets) { # when ($_ < 1000) { # $traffic{$target}->{$iid} = $octets . ' B'; # } # when ($_ < 1000000) { # $traffic{$target}->{$iid} = int($octets / 1000) . ' KB'; # } # when ($_ < 1000000000) { # $traffic{$target}->{$iid} = int($octets / 1000000) . ' MB'; # } # when ($_ < 1000000000000) { # $traffic{$target}->{$iid} = int($octets / 1000000000) . ' GB'; # } # when ($_ < 1000000000000000) { # $traffic{$target}->{$iid} = int($octets / 1000000000000) . ' TB'; # } # default { # $traffic{$target}->{$iid} = 'big'; # } # } # } # Entertain operator print $BANG if $mode eq 'interactive'; } # Make things look pretty say "\n" if $mode eq 'interactive'; # Debug info if ($debug > 2) { say 'Dumping %peer'; for my $target (sort keys %peer) { say $target; for my $iid (sort keys %{$peer{$target}}) { say " Iid: $iid => ", $peer{$target}->{$iid}; } } say(''); say 'Dumping %cikeTunIndex'; for my $target (sort keys %peer) { say $target; for my $peer (sort keys %{$cikeTunIndex{$target}}) { say " Peer: $peer => ", $cikeTunIndex{$target}->{$peer}; } } say(''); say 'Dumping %index_mapping'; for my $target (sort keys %peer) { say $target; for my $iid (sort keys %{$index_mapping{$target}}) { say " Iid: $iid => ", $index_mapping{$target}->{$iid}; } } say(''); say 'Dumping %duration'; for my $target (sort keys %peer) { say $target; for my $iid (sort keys %{$duration{$target}}) { say " Duration: $iid => ", $duration{$target}->{$iid}; } } say(''); say 'Dumping %traffic'; for my $target (sort keys %peer) { say $target; for my $iid (sort keys %{$traffic{$target}}) { say " Traffic: $iid => ", $traffic{$target}->{$iid}; } } } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Insert dashes for missing tunnels ######################################################################## sub info_after { my $iid = 1; my @names; # Debug trace trace_location('begin') if $debug; # Insert dashes marking dead tunnels @names = sort values %tunnel_name; TARGET: for my $target (@target) { say "Considering $target" if $debug > 1; next TARGET unless (defined $active_tunnels{$target} and $active_tunnels{$target} > 0 ); NAME: for my $name (@names) { say " Is $name live?" if $debug > 2; next NAME if exists $cikeTunIndex{$target}->{$name}; say 'No, it is not' if $debug > 2; $cikeTunIndex{$target}->{$name} = $iid; $duration{$target}->{$iid} = $DASH; $encrypt_algorithm{$target}->{$iid} = $DASH; $hash_algorithm{$target}->{$iid} = $DASH; $lifetime{$target}->{$iid} = $DASH; $status{$target}->{$iid} = $DASH; $iid++; } } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Tell the operator what I discovered ######################################################################## sub print_report { my $handle; my $total = @target; my $now = get_now(); # 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; } # Direct output to screen or to file if ($mode eq 'interactive') { $handle = *STDOUT; } else { open $handle, '>', $report_file or die "Cannot open $report_file: $!\n"; } if ($mode eq 'report') { print {$handle} <{$peer}; $duration = $duration{$target}->{$iid}; $encrypt = $encrypt_algorithm{$target}->{$iid}; $hash = $hash_algorithm{$target}->{$iid}; $lifetime = $lifetime{$target}->{$iid}; $status = $status{$target}->{$iid}; if ($first == 1) { printf {$handle} "%-16s %4s %4s %8s %6s %8s\n", $peer, $encrypt, $hash, $lifetime, $status, $duration; $first = 0; } else { printf {$handle} "%11s %-16s %4s %4s %8s %6s %8s\n", $SPACE, $peer, $encrypt, $hash, $lifetime, $status, $duration; } } } unless ($handle =~ /STDOUT/) { close $handle or warn "Cannot close $report_file: $!\n"; } # Make things look pretty log_it("Ending $PROGRAM_NAME"); print_it("\n\nEnding $PROGRAM_NAME"); # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Output help ######################################################################## sub HELP_MESSAGE { print <