#!/opt/vdops/bin/perl # This script automates the process of applying an IOS interface config snippet # across most interfaces on a device # V Who When What # --------------------------------------------------------------------------- # 1.6.0 skendric 2011-02-21 Upgrade to Netops 1.4.0 # 1.5.1 skendric 2010-06-25 Allow $error{$target} to be undef # 1.5.0 skendric 2010-02-03 Upgrade to perl 5.10.1 # 1.4.0 skendric 2009-09-11 More options over which interfaces to include # 1.3.2 skendric 2009-05-21 Support SNMP.pm # 1.3.1 skendric 2009-03-11 Prettify operator output # 1.3.0 skendric 2009-03-10 Allow operator to skip various types of ports # 1.2.0 skendric 2009-03-09 Refactor to gather port info in dedicated # functions # 1.1.1 skendric 2009-03-08 Refine how we search for uplink ports # 1.1.0 skendric 2009-03-01 Add support for access and aux VLAN changes # 1.0.2 skendric 2009-02-27 More checks on config snippet integrity # 1.0.1 skendric 2009-02-16 Support new find_named_ports output # 1.0.0 skendric 2009-02-16 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: # -Examines an operator-provided config snippet, performing (minimal) # validation # -Examines the operator-provided device, identifying its physical # interfaces # -Creates a file which applies the config snippet to all Ethernet # interfaces, except for uplink ports, dedicated management ports, # administratively disabled ports, and, optionally, trunked and/or # named ports # -Uploads the file to the specified device # -If the operator specifies multiple devices, then the script repeats # the process for each device # # # Requirements: # -The target(s) must be pingable # -The script must have file system access to the tftp directory # -PERL modules: the WI::Netops collection # -Hardware must be Catalyst 37xx, 45xx, 49xx, 65xx # # # 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 # -Create the config file which will be uploaded # -Run this script # # # 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 File::Temp; use Getopt::Std; use List::MoreUtils qw( any ); use Perl6::Slurp; use Regexp::Common; use WI::Netops::CiscoTools 1.4.3; use WI::Netops::HostTools 1.0.4; use WI::Netops::IFTools 1.3.1; use WI::Netops::NetopsData 1.4.0; use WI::Netops::NetopsTools 2.2.3; use WI::Netops::SNMPTools 1.5.3; use WI::Netops::Utilities 1.4.4; # Declare global variables my @always_skip_these_if; # List of interfaces which we will always skip my %cdp_device; # Hash of hash refs, tracking the contents of # cdpCacheDeviceId for each target's interfaces my %cdp_interface_enable; # Hash of hash refs, tracking the value of # cdpInterfaceEnable for each target's interfaces my $config_snippet; # Name of file containing the statements to be # applied to each interface my @config_lines; # $config_snippet slurped into an array my $config_text; # $config_snippet slurped into a scalar my $create_configs_only; # Boolean specifying whether the operator wants us # to just produce the proposed config files (1) or # to steam ahead full-bore (0) my %if_admin_status; # Hash of hash refs, tracking ifAdminStatus for each # target's interfaces my %if_alias; # Hash of hash refs, tracking ifAlias for each # target's interfaces my %if_descr; # Hash of hash refs, tracking ifDescr for each # target's interfaces my %port_base_vlan; # Hash of hash refs, tracking base VLAN membership # for each target's interfaces my %port_voice_vlan; # Hash of hash refs, tracking voice VLAN membership # for each target's interfaces my $skip_cdp_disabled_ports; # Boolean for specifying whether the operator # wants us to skip ports on which CDP has been # disabled (and which, therefore, may be leading # another network device, a fact to which we are # now blind) my $skip_disabled_ports; # Boolean for specifying whether the operator wants # us to skip administratively disabled (ifAdminStatus) # ports my $skip_named_ports; # Boolean for specifying whether the operator wants # us to skip named (ifAlias) ports my $skip_serial_ports; # Boolean for specifying whether the operator wants # us to skip serial ports my $skip_trunked_ports; # Boolean for specifying whether the operator wants # us to skip trunked ports my $skip_uplink_ports; # Boolean for specifying whether the operator wants # us to skip uplink ports. (We define uplink ports # as being a port connecting to a device whose CDP- # reported name matches one of the strings in # @uplink_strings); my $skip_vlan_ports; # Boolean for specifying whether the operator wants # us to skip Vlan interfaces my @supported_hardware; # List of box types which we support my @uplink_strings; # If we see one of these strings in CDP info, then # we consider the port to be an uplink and exclude it # from modification my %uplinks; # Hash of array refs, listing ifDescr which we # skipped because we considered them uplinks my $vlan_aware; # Boolean indicating whether or not we will mess # with VLAN membership of interfaces. The operator # sets this by including the string 'xxxxx' in the # config snippet as a wild card for the access VLAN # number for this interface and 'yyyyy' for the # auxiliary VLAN number my %vlan_trunk_port_dynamic_state; # Hash of hashes, identifying the value # of vlanTrunkPortDynamicState my %vlan_trunk_port_dynamic_status; # Hash of hashes, identifying the value # of vlanTrunkPortDynamicStatus my %vlan_trunk_port_encapsulation_oper_type; # Hash of hashes, identifying the # value of vlanTrunkPortEncapsulationOperType my %vlan_trunk_port_encapsulation_type; # Hash of hashes, identifying the value # of vlanTrunkPortEncapsulationType my $wait; # Seconds to wait for device to complete upload # Define global variables $program_name = 'mod-interface'; $usage = 'Usage: mod-interface -s {yes|no} -c {filename} [-j {yes|no}] [-k {yes|no}] [-l {yes|no}] [-m {yes|no}] [-p {yes|no}] [-t {yes|no}] [-u {yes|no}] [-v {yes|no}] [-d {integer}] [-a | -e {expr} | -f {filename} | target1 target2 target3 ...]'; $version = '1.6.0'; # TFTP Stuff $tftp_server = $EMPTY_STR; $tftp_server = get_my_ipaddr() if $tftp_server eq $EMPTY_STR; $tftp_server_name = get_nodename($tftp_server); # We always skip these interfaces @always_skip_these_if = qw/Loopback0 Null0 Vlan1/; # Supported hardware @supported_hardware = qw/cat356 cat37 cat45 cat49 cat65/; # Strings which identify uplink ports (in CDP info) @uplink_strings = qw/-ap -esx -rtr -vpn/; # The number of seconds I'm willing to wait for file upload to complete $wait = 360; # Default modes $create_configs_only = 1; $skip_cdp_disabled_ports = 1; $skip_disabled_ports = 1; $skip_named_ports = 1; $skip_serial_ports = 1; $skip_trunked_ports = 1; $skip_uplink_ports = 1; $skip_vlan_ports = 1; # Grab arguments getopts('ad:c:e:f:j:l:m:p:s:t:u:v:w:', \%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 check_local_args(); # Check args specific to this script 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 sanity_check(); # Check for major errors info_before(); # Gather more information print_before(); # Tell operator what I will do do_the_work(); # Do it print_after(); # Tell the operator what I did } ##### End Main Program ################################################# ######################################################################## # Check arguments specific to this script ######################################################################## sub check_local_args { # Debug trace trace_location('begin') if $debug; # Check arguments unless (defined $option{c}) { say '-c {filename} must specify a config snippet'; die "$usage\n"; } $config_snippet = $option{c}; # Check skip down ports mode if (defined $option{j}) { given ($option{j}) { when ('yes') { $skip_disabled_ports = 1 } when ('no') { $skip_disabled_ports = 0 } default { say "The -j option must be either 'yes' or 'no'"; die "$usage\n"; } } } # Check skip named ports mode if (defined $option{m}) { given ($option{m}) { when ('yes') { $skip_named_ports = 1 } when ('no') { $skip_named_ports = 0 } default { say "The -m option must be either 'yes' or 'no'"; die "$usage\n"; } } } # Check skip disabled CDP ports mode if (defined $option{p}) { given ($option{p}) { when ('yes') { $skip_cdp_disabled_ports = 1 } when ('no') { $skip_cdp_disabled_ports = 0 } default { say "The -p option must be either 'yes' or 'no'"; die "$usage\n"; } } } # Check skip trunked ports mode if (defined $option{t}) { given ($option{t}) { when ('yes') { $skip_trunked_ports = 1 } when ('no') { $skip_trunked_ports = 0 } default { say "The -t option must be either 'yes' or 'no'"; die "$usage\n"; } } } # Check skip uplink ports mode if (defined $option{u}) { given ($option{u}) { when ('yes') { $skip_uplink_ports = 1 } when ('no') { $skip_uplink_ports = 0 } default { say "The -u option must be either 'yes' or 'no'"; die "$usage\n"; } } } # Check skip VLAN interfaces mode if (defined $option{v}) { given ($option{v}) { when ('yes') { $skip_vlan_ports = 1 } when ('no') { $skip_vlan_ports = 0 } default { say "The -v option must be either 'yes' or 'no'"; die "$usage\n"; } } } # Check create configs only if (defined $option{w}) { given ($option{w}) { when ('yes') { $create_configs_only = 1 } when ('no') { $create_configs_only = 0 } default { say "The -w option must be either 'yes' or 'no'"; die "$usage\n"; } } } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Do the work: build config file, upload it ######################################################################## sub do_the_work { # Debug trace trace_location('begin') if $debug; # Log the config file log_file_contents("$tftp_dir/$config_snippet"); # Loop through the list of targets TARGET: for my $target (@target) { my ($if_admin_status_ref, $if_descr_ref, $port_base_vlan_ref, $port_voice_vlan_ref, $temp_file, $tftp_file, @uplinks); # Announce start say '--------------------------------------------------------' if $mode eq 'interactive'; print_it("Beginning to process $target"); # Create temporary file to hold contents of upload snippet $temp_file = File::Temp::tempnam($tftp_dir, "$target-$program_name-"); die "$temp_file exists, and I wanted to use that name\n" if -e $temp_file; die "Cannot create $temp_file\n" unless touch_file($temp_file, 0644); # Replace file system path with tftp path ($tftp_file) = ($temp_file) =~ /$tftp_path\/(.*?)$/; $tftp_file = $tftp_path . $SLASH . $tftp_file; # Extract target-specific material from global hashes $if_descr_ref = $if_descr{$target}; $if_admin_status_ref = $if_admin_status{$target}; # Open temp file for writing open my $handle, '>', $temp_file or die "Cannot open $temp_file: $!"; # Walk ifDescr, finding access-layer interfaces PORT: for my $if_index (sort {$a <=> $b} keys %{$if_descr{$target}}) { my ($if_d, $if_descr, $iid); $if_descr = $if_descr{$target}->{$if_index}; # Debug info say "Examining $if_descr at ifIndex $if_index" if $debug == 8; # Skip this interface if ifDescr is empty if (not defined $if_descr or $if_descr eq $EMPTY_STR) { my $if_name = get_if_name($target, $if_index); say " skipping $if_name / $if_index because ifDescr is undefined" if $mode eq 'interactive'; log_it("For $target, skipping $if_name / $if_index because ifDescr is undefined"); next PORT; } # Create an abbreviated form of ifDescr, for use when talking to the # operator $if_d = shrink_if_descr($if_descr); # Skip Loopback interfaces if ($if_descr =~ /Loopback/) { say " skipping $if_descr because it is a Loopback interface" if ($mode eq 'interactive' and $debug); log_it("For $target, skipping $if_descr because it is a Loopback interface"); next PORT; } # Skip Null interfaces if ($if_descr =~ /Null/) { say " skipping $if_descr because it is a Null interface" if ($mode eq 'interactive' and $debug); log_it("For $target, skipping $if_descr because it is a Null interface"); next PORT; } # Skip Unrouted interfaces if ($if_descr =~ /unrouted/) { say " skipping $if_descr because it is a Unrouted interface" if ($mode eq 'interactive' and $debug); log_it("For $target, skipping $if_descr because it is a Unrouted interface"); next PORT; } # Skip dedicated management interfaces (Catalyst 4948s) if ($if_descr eq 'FastEthernet1') { say " skipping $if_descr because it is a dedicated mgmt interface" if ($mode eq 'interactive' and $debug); log_it("For $target, skipping $if_descr because it is a dedicated mgmt interface"); next PORT; } # Skip the built-in Vlan interfaces if (any { $if_descr eq $_ } @always_skip_these_if) { say " skipping $if_descr because it belongs to \@always_skip_these_if" if ($mode eq 'interactive' and $debug); log_it("For $target, skipping $if_descr because it belongs to \@always_skip_these_if"); next PORT; } # Skip VLAN interfaces if the operator wants us to do this if ($if_descr =~ /Vlan/ and $skip_vlan_ports) { say " skipping $if_descr because it is a Vlan port" if ($mode eq 'interactive' and $debug); log_it("For $target, skipping $if_descr because it is a Vlan port"); next PORT; } # Skip Serial ports if ($if_descr =~ /Serial/ and $skip_serial_ports) { say " skipping $if_descr because it is Serial" if ($mode eq 'interactive' and $debug); log_it("For $target, skipping $if_descr because it is Serial"); next PORT; } # ifDescr should look like this, where the stuff in squirrelly parens is # optional. If it doesn't, then bail # Fast|Gigabit|TenGigabit|EthernetA/B{/C}|Vlanx{xyz} ($iid) = ($if_descr) =~ /[Ethernet|Serial|Vlani](.*)$/; unless (defined $iid and $iid ne $EMPTY_STR) { say " skipping $if_descr because I do not recognize its format" if $mode eq 'interactive'; log_it("For $target, skipping $if_descr because I do not recognize its format"); next PORT; } # If the operator wants us to skip 'uplink' ports, check CDP info if ($skip_uplink_ports) { # If the port has a CDP neighbor if (defined $cdp_device{$target}->{$if_index}) { my $device = $cdp_device{$target}->{$if_index}; # If the CDP neighbor's name contains one of the uplink strings if (any {$device =~ /$_/} @uplink_strings) { say " skipping $if_d because it is connected to $device" if $mode eq 'interactive'; log_it("For $target, skipping $if_descr because it connected to $device"); push @uplinks, $if_descr; next PORT; # Skip port } # End 'If the CDP neighbor's name contains...' } # End 'If the port has a CDP neighbor' } # End 'If the operator wants us to skip' # If the operator wants us messing only with ports running CDP, check # for CDP enabled if ($skip_cdp_disabled_ports) { unless ($cdp_interface_enable{$target}->{$if_index} eq 'true') { say " $if_descr is not running CDP, skipping"; log_it("$if_descr is not running CDP, skipping"); next PORT; } } # If the operator doesn't want us messing with administratively disabled # ports, then skip them if ($skip_disabled_ports) { # Skip unless the port is adminstratively 'up' given ($if_admin_status{$target}->{$if_index}) { when ('up') { # The port is administratively up; this is normal; continue } when ('down') { say " skipping $if_d because it is administratively down" if $mode eq 'interactive'; log_it("For $target, skipping $if_d because it is administratively down"); next PORT; } when ('testing') { say " skipping $if_d because it is 'testing'" if $mode eq 'interactive'; log_it("For $target, skipping $if_d because it is 'testing'"); next PORT; } default { say " skipping $if_d because I cannot determine ifAdminStatus" if $mode eq 'interactive'; log_it("For $target, skipping $if_d because I cannot determine ifAdminStatus"); next PORT; } } # End given } # End 'If the operator doesn't want us messing with administratively' # If the operator doesn't want us messing with named ports, skip them if ($skip_named_ports) { my $if_alias = $if_alias{$target}->{$if_index}; if (defined $if_alias and $if_alias ne $EMPTY_STR) { say " skipping $if_d because it is named '$if_alias'" if $mode eq 'interactive'; log_it("For $target, skipping $if_d because it is named '$if_alias'"); next PORT; } } # End 'If the operator doesn't want us messing with named ports ...' # If the operator doesn't want us messing with trunked ports, skip them if ($skip_trunked_ports) { my ($etype_ref, $tstate_ref, $tstatus_ref, $eoper_ref); # Extract target-specific material from global hashes $etype_ref = $vlan_trunk_port_encapsulation_type{$target}; $tstate_ref = $vlan_trunk_port_dynamic_state{$target}; $tstatus_ref = $vlan_trunk_port_dynamic_status{$target}; $eoper_ref = $vlan_trunk_port_encapsulation_oper_type{$target}; # Skip unless set to negotiate my $etype = $etype_ref->{$if_index}; unless ($etype eq 'negotiate') { say " skipping $if_d because trunk encap set to $etype"; log_it("For $target, skipping $if_d because trunk encap set to $etype"); next PORT; } # Skip if dynamic state is 'trunking' my $state = $tstate_ref->{$if_index}; if ($state eq 'trunking') { say " skipping $if_d because trunk dynamic state set to $state"; log_it("For $target, skipping $if_d because trunk dynamic state set to $state"); next PORT; } if # Skip if dynamic status is 'trunking' my $status = $tstatus_ref->{$if_index}; if ($status eq 'trunking') { say " skipping $if_d because trunk dynamic status set to $status"; log_it("For $target, skipping $if_d because trunk dynamic status set to $status"); next PORT; } # Skip unless 'negotiating' or 'notApplicable' my $eoper = $eoper_ref->{$if_index}; unless ($eoper eq 'negotiating' or $eoper eq 'notApplicable') { say " skipping $if_d because trunk encap set to $eoper"; log_it("For $target, skipping $if_d because trunk encap set to $eoper"); next PORT; } } # End 'If the operator doesn't want us messing with trunked ports...' # OK, we believe this is an access-layer port feeding an end-station # which is administratively 'up' and otherwise trouble-free if ($debug > 3) { my ($base, $voice); $base = $port_base_vlan{$target}->{$if_index}; $voice = $port_voice_vlan{$target}->{$if_index}; if (defined $base and defined $voice) { say " belongs to base vlan $base and voice vlan $voice"; } elsif (defined $base) { say " belongs to base vlan $base"; } elsif (defined $voice) { say " belongs to voice vlan $voice"; } else { say " $if_descr / $if_index"; } } # Add 'interface {Fast|Gigabit|TenGigabit}EthernetA/B{/C}' to the # snippet we plan to upload print {$handle} "interface $if_descr\n"; say ' adding to upload file' if $debug == 8; # Walk through the config snippet LINE: for my $config_line (@config_lines) { my $line = $config_line; # Replace wildcard strings if this is a VLAN-aware upload if ($vlan_aware) { # Skip unless we can identify base VLAN membership unless (defined $port_base_vlan{$target}->{$if_index}) { say " vmVlan not defined for $if_descr"; next PORT; } # Skip unless we can identify voice VLAN membership unless (defined $port_voice_vlan{$target}->{$if_index}) { say " vmVoiceVlanID not defined for $if_descr"; next PORT; } # Replace wildcards if ($line =~ /vlan xxxxx/) { $line =~ s/xxxxx/$port_base_vlan{$target}->{$if_index}/; } elsif ($line =~ /vlan yyyyy/) { next LINE if $port_voice_vlan{$target}->{$if_index} == 4096; $line =~ s/yyyyy/$port_voice_vlan{$target}->{$if_index}/; } } # End 'Replace wildcard strings if this is a VLAN-aware upload # Write this line print {$handle} " $line"; } # Separate interface stanzas with a bang print {$handle} "$BANG\n"; } # End 'Walk ifDescr, finding access-layer interfaces' # Finish the snippet print {$handle} "end\n"; # IOS config snippets must end with 'end' close $handle or warn "Cannot close $temp_file: $!"; # Upload config file, unless we are running in trial mode unless ($create_configs_only) { my $result = upload_config($target, $tftp_file); $error{$target} = 'Failed to upload config file' unless $result; } # Clean-up if ($debug) { say "Leaving $temp_file behind; you delete it"; } else { given ($create_configs_only) { when (0) {unlink $temp_file or warn "Cannot delete $temp_file: $!" } when (1) { say "Trial mode: see $temp_file" } } } # Save list of uplink ports $uplinks{$target} = \@uplinks; # Announce completion print_it("Done processing $target"); # Make things look pretty say "--------------------------------------------------------\n\n" if $mode eq 'interactive'; # Pause briefly sleep $short; } # End 'Loop through the list of targets' # Debug trace trace_location('end') if $debug; # If in trial mode, quit if ($create_configs_only) { say 'Running in trial mode, quitting'; exit(1); } return 1; } ######################################################################## # Gather vlan membership from CISCO-VLAN-MEMBERSHIP-MIB ######################################################################## sub gather_vlan_membership { # Debug trace trace_location('begin') if $debug; # Loop through targets, gathering VLAN information for my $target (@target) { my (%pbv, %pvv); # Debug info say 'Processing $target' if $debug > 2; # Acquire base VLAN membership say 'Walking vmVlan' if $debug > 3; my $vm_ref = snmpWalk({host => $target, oid => '.1.3.6.1.4.1.9.9.68.1.2.2.1.2'}); for my $varbind (@$vm_ref) { my $vlan = $varbind->{val}; my $iid = $varbind->{iid}; $pbv{$iid} = $vlan; say " ifIndex $iid belongs to access vlan $vlan" if $debug == 8; } # Acquire voice VLAN membership say 'Walking vmVoiceVlanId' if $debug > 3; my $vv_ref = snmpWalk({host=> $target, oid => '.1.3.6.1.4.1.9.9.68.1.5.1.1.1'}); for my $varbind (@$vv_ref) { my $vlan = $varbind->{val}; my $iid = $varbind->{iid}; $pvv{$iid} = $vlan; say " ifIndex $iid belongs to voice vlan $vlan" if $debug == 8; } # Save the results $port_base_vlan{$target} = \%pbv; $port_voice_vlan{$target} = \%pvv; } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Gather more information ######################################################################## sub info_before { # Debug trace trace_location('begin') if $debug; # Notify operator say 'Gathering more information...' if $mode eq 'interactive'; # Loop through targets, characterizing interfaces for my $target (@target) { # Grab cdpCacheDeviceId my ($cdp_ref, %cdp); $cdp_ref = snmpWalk( {host => $target, oid => 'cdpCacheDeviceId'} ); for my $varbind (@$cdp_ref) { my ($cdp_id, $if_index); $cdp_id = $varbind->{val}; $if_index = $varbind->{iid}; # Doing this means that I capture only one CDP neighbor per interface. # This will probably bite me at some point, but at the moment I'm feeling # lazy ($if_index) = ($if_index) =~ /^(\d+)\./; $cdp{$if_index} = $cdp_id; } $cdp_device{$target} = \%cdp; # Grab ifAlias my ($il_ref, %il); say 'Walking ifAlias' if $debug > 3; $il_ref = snmpWalk( {host => $target, oid => 'IF-MIB::ifAlias'} ); for my $varbind (@$il_ref) { my ($if_alias, $if_index); $if_alias = $varbind->{val}; $if_index = $varbind->{iid}; ($if_alias = $if_alias) =~ s/"//g; $il{$if_index} = $if_alias; } $if_alias{$target} = \%il; # Grab ifAdminStatus my ($ia_ref, %ia); $ia_ref = snmpWalk( {host => $target, oid => 'ifAdminStatus'} ); for my $varbind (@$ia_ref) { my $status = $varbind->{val}; my $if_index = $varbind->{iid}; $ia{$if_index} = $status; } $if_admin_status{$target} = \%ia; # Grab VLAN information if this is a VLAN-aware config snippet gather_vlan_membership() if $vlan_aware; # If the operator wants us to skip trunked ports, gather trunking info # from CISCO-VTP-MIB if ($skip_trunked_ports) { # Grab vlanTrunkPortEncapsulationType my ($et_ref, %et); say 'Walking vlanTrunkPortEncapsulationType' if $debug > 3; $et_ref = snmpWalk({host=>$target, oid => '.1.3.6.1.4.1.9.9.46.1.6.1.1.3'}); for my $varbind (@$et_ref) { my $etype = $varbind->{val}; my $if_index = $varbind->{iid}; $et{$if_index} = $etype; } $vlan_trunk_port_encapsulation_type{$target} = \%et; # Grab vlanTrunkPortDynamicState my ($tstate_ref, %tstate); say 'Walking vlanTrunkPortDynamicState' if $debug > 3; $tstate_ref = snmpWalk({host=>$target, oid => '.1.3.6.1.4.1.9.9.46.1.6.1.1.13'}); for my $varbind (@$tstate_ref) { my $tstatetate = $varbind->{val}; my $if_index = $varbind->{iid}; $tstate{$if_index} = $tstatetate; } $vlan_trunk_port_dynamic_state{$target} = \%tstate; # Grab vlanTrunkPortDynamicStatus my ($tstatus_ref, %tstatus); say 'Walking vlanTrunkPortDynamicStatus' if $debug > 3; $tstatus_ref = snmpWalk({host=>$target, oid => '.1.3.6.1.4.1.9.9.46.1.6.1.1.14'}); for my $varbind (@$tstatus_ref) { my $tstatus = $varbind->{val}; my $if_index = $varbind->{iid}; $tstate{$if_index} = $tstatus; } $vlan_trunk_port_dynamic_status{$target} = \%tstate; # Grab vlanTrunkPortEncapsulationOperType my ($eoper_ref, %eoper); say 'Walking vlanTrunkPortEncapsulationOperType' if $debug > 3; $eoper_ref = snmpWalk({host=>$target, oid => '.1.3.6.1.4.1.9.9.46.1.6.1.1.16'}); for my $varbind (@$eoper_ref) { my $ttype = $varbind->{val}; my $if_index = $varbind->{iid}; $eoper{$if_index} = $ttype; } $vlan_trunk_port_encapsulation_oper_type{$target} = \%eoper; } # End 'If the operator wants us to skip trunked ports... # Entertain the operator print $BANG if $mode eq 'interactive'; } # End 'Loop through targets' # Make things look pretty say "\n" if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Tell operator what I did ######################################################################## sub print_after { # Debug trace trace_location('begin') if $debug; # If running from cron, don't print report return 1 if $mode eq 'batch'; # Print report if ($dome) { say "\n# Here is what I did\n"; } else { say "\n# Here is what I would have done, had you been serious"; } print < 0) { print "\n\n\n\n"; print < 10; @config_lines = slurp "$tftp_dir/$config_snippet"; # Load file into an array @lines = slurp "$tftp_dir/$config_snippet"; # Check for VLAN messing by looking for the string 'xxxxx' and/or 'yyyyy' # ini the config snippet $vlan_aware = 1 if any { $_ =~ /vlan xxxxx/i } @lines; $vlan_aware = 1 if any { $_ =~ /vlan yyyyy/i } @lines; say 'This config snippet is VLAN-aware' if ($debug and $vlan_aware); # Check for trailing 'end' or 'exit' statements $last = pop @lines; given ($last) { when (/end/) { die "Fatal: remove trailing 'end' statement from $config_snippet\n"; } when (/exit/) { die "Fatal: remove trailing 'exit' statement from $config_snippet\n"; } } # Walk through targets TARGET: for my $target (@target) { # Skip unless we support this flavor of hardware unless (any { $box{$target} =~ /$_/ } @supported_hardware) { say " $target is a $box{$target}, which I do not support" if ($mode eq 'interactive' and $debug); log_it("$target is a $box{$target}, which I do not support"); push @remove, $target; next TARGET; } # Verify that CDP is running globally my ($cdp_global_run); $cdp_global_run = snmpGet( {host => $target, oid => 'cdpGlobalRun.0'} ); unless (defined $cdp_global_run and $cdp_global_run eq 'true') { say " $target is not running CDP, skipping" if ($mode eq 'interactive' and $debug); log_it("$target is not running CDP, skipping"); push @remove, $target; next TARGET; } # Grab ifDescr my ($id_ref, %id); $id_ref = snmpWalk( {host => $target, oid => 'IF-MIB::ifDescr'} ); for my $varbind (@$id_ref) { my ($if_descr, $if_index); $if_descr = $varbind->{val}; $if_index = $varbind->{iid}; ($if_descr = $if_descr) =~ s/"//g; $id{$if_index} = $if_descr; } $if_descr{$target} = \%id; # Grab cdpInterfaceEnable my ($cdp_ref, %cdp); $cdp_ref = snmpWalk( {host => $target, oid => 'cdpInterfaceEnable'} ); for my $varbind (@$cdp_ref) { my $status = $varbind->{val}; my $if_index = $varbind->{iid}; $cdp{$if_index} = $status; } $cdp_interface_enable{$target} = \%cdp; # Entertain the operator print $BANG if $mode eq 'interactive'; } # Remove entries which failed checks prune_basic(@remove); # Make things look pretty say "\n" if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Output help ######################################################################## sub HELP_MESSAGE { print <