#!/usr/bin/perl -w use strict; use Net::Ping; use threads; ########################################################################################################################## # # PURPOSE: This script tries to determine if the cables are plugged into the correct ports in the EX2200 switch in a store # # LOGIC: 1) get store number from command line argument # 2) lookup store IP address using store number # 3) Ping devices in store to populate ARP table in SRX # 4) Collect ARP table from SRX and ethernet-switching table from EX2200 with an expect script # 5) Parse the outputs and merge based on MAC address. Ignore all devices in VLAN.16 as they are wireless devices # 6) Special handling for WLA plugged into port 47. This is OK only if there is not another WLA in port 46 # 7) Create report or pass info to next program # # USER INPUT: The store number for the store to be tested # # HARDCODED INPUT: $storeFile - this is a file created from allstores.xlsx that maps store number to IP address and the # other data in allstores. this script only needs the store IP address # $portFile - this file contains a mapping between the last octet of the IP address of a store device, # the port number that it should be plugged into, and the description of the device. # $user - userid to access SRX and EX2200 # $pwd - password to access SRX and EX2200 # # USAGE: perl chackwiring.pl # # RETURN CODES: 0 - success # 101 - store IP address not in $storeFile # 102 - cannot reach store # 103 - could not open file created by expect script # 104 - could not open $portFile # 105 - could not open $storeFile # 106 - could not clear the old data in $filename prior to running expect script # The following return codes are bit flags and can be combined by adding: # 1 - could not connect to SRX # 2 - could not connect to EX2200 # 4 - did not parse any show arp records # 8 - did not parse any show ethernet-switching table records # ########################################################################################################################## # ============================ # user changeable variables # ============================ my $user = "root"; my $pwd = "Pa55word"; my $portFile = "porttable"; # table with port to IP association my $storeFile = "store_data_2.txt"; # data about stores extracted from allstores.xlsx my $debugLevel = 10; my $icmpTimeout = 1; # timeout for ping, default of 1 is probably ok. my @pingList = (); # ============================ # hashes used for data storage # ============================ my %cable_hash = (); my %port_hash = (); my %device_info_hash = (); my %store_info_hash = (); my %device_ip_hash = (); my %port_to_mac_hash = (); # ========================================= # debug info to calculate run time # ========================================= if ($debugLevel) { my $t1 = getTimestamp(); print "starting time is $t1\n"; } # ========================================= # get requested store from command line # ========================================= my $storeNum = 0; if ( $#ARGV > -1) { $storeNum = $ARGV[0]; print "asking for store $storeNum\n" if ($debugLevel > 5 ); } # ========================================= # get store info so we can get store's IP address # ========================================= getStoreInfo(); unless (defined ( $store_info_hash{$storeNum}{ip})) { print "store $storeNum not found\n"; exit 101; } # ========================================= # variables filled in based on store number # ========================================= my $storeIp = $store_info_hash{$storeNum}{ip}; print "StoreIP is $storeIp\n" if ($debugLevel > 0 ); my $base_ip = $store_info_hash{$storeNum}{ip}; my $srx_ip = $base_ip . ".193"; my $ex_ip = $base_ip . ".2"; my $filename = "report_" . $storeNum . ".txt"; my $fatalError = 0; # ========================================= # count number of reachable devices in store # ========================================= my $upCount = 0; # ========================================= # ping store devices to populate ARP table # need to ping any address that might be # assigned to a device plugged into the EX # ========================================= addRangeToPingList( 1, 15); # ISP, safes, RILO ,ATG, ATMs addRangeToPingList( 20, 24); # POS addRangeToPingList( 30, 34); # POS pinpad addRangeToPingList( 43, 44); # scanner and GOT docking stations addRangeToPingList( 50, 51); # printer addRangeToPingList( 60, 68 ); # HVAC, training, DVR addRangeToPingList( 154, 158 ); # WLA $upCount = pingArrayThreaded ($base_ip); # ========================================= # debug info to calculate run time # ========================================= if ($debugLevel) { my $t2 = getTimestamp(); print "ping complete at time is $t2\n"; } # ========================================= # if we can't reach anything, store is down # ========================================= unless ($upCount ) { print "cannot reach store\n"; exit 102; } # ========================================= # clear the data file for the expect script output # ========================================= open( OUTFILE, ">", $filename ) or do { $fatalError = 106; print "FATAL_ERROR: could not clear $filename to avoid stale data\n"; exit $fatalError; }; print OUTFILE "Cleared to avoid stale data\nIf this message is here after running the script, the expect script did not run\n"; close OUTFILE; # ========================================= # debug info to calculate run time # ========================================= if ($debugLevel) { my $t4 = getTimestamp(); print "calling expect script at time is $t4\n"; } # ========================================= # get show arp and show ethernet-switching table # ========================================= my $datestamp = getTimestamp(); my $expectCommand = "/home/jstank01/test/showarp.exp " . $srx_ip . " " . $ex_ip . " " . $user . " " . $pwd . " " . $filename; system ( $expectCommand ); # ========================================= # debug info to calculate run time # ========================================= if ($debugLevel) { my $t5 = getTimestamp(); print "completed expect script at time is $t5\n"; } # ========================================= # get association between port number and # IP address # ========================================= getPortData(); # ========================================= # parse show arp and show ethernet-switching table # ========================================= parseData(); # ========================================= # print the report # ========================================= printData($storeNum, $datestamp ); # ========================================= # debug info to calculate run time # ========================================= if ($debugLevel) { my $t3 = getTimestamp(); print "script complete at time is $t3\n"; } # normal termination exit 0; ##################################################################### # This subroutine pings a single IP address # it is executed in a thread ##################################################################### sub threadedPing { my ($ipaddr) = @_; my $p=Net::Ping->new("icmp", $icmpTimeout ); unless($p->ping($ipaddr)){ return 0; } else { return 1} } ##################################################################### # This subroutine parses the data file created by # the expect script. The file contains the output # of "show arp" from the SRE and the output of # "show ethernet-switching table" from the EX # Once it determines which device is in each port, # it adds the expected port for the device to the # record. Additionally, it handles the special case # of 2 WLAs in the store. ##################################################################### sub parseData { open( INFILE, $filename ) or do { $fatalError = 103; print "FATAL_ERROR: could not open $filename (expect output) for reading\n"; exit $fatalError; }; my $ap_in_46 = 0; my $arpRecordCount = 0; my $switchRecordCount = 0; while ( ) { chomp; my $line = $_; # match ARP entry from SRX if ($line =~ /([0-9A-Fa-f\:]+)[ \t]+([0-9]+\.[0-9]+\.[0-9]+\.([0-9]+))[ \t]+([^ \t]+)[ \t]+(vlan\.([0-9]+))/) { my $mac = $1; my $ip = $2; my $lastOctet = $3; my $vlan = $5; my $vlanNum = $6; # ignore vlan 16 which is wireless devices if (16 != $vlanNum) { $arpRecordCount++; $cable_hash{$mac}{ip} = $ip; $cable_hash{$mac}{vlan} = $vlan; $cable_hash{$mac}{last_octet} = $lastOctet; if (exists ($port_hash{$lastOctet}{port})) { $cable_hash{$mac}{correct_port} = $port_hash{$lastOctet}{port}; $cable_hash{$mac}{description} = $port_hash{$lastOctet}{description}; } else { # octet not found in port table, put in 999 because unknown $cable_hash{$mac}{correct_port} = 999; } } # endif (16 != $vlanNum) } # endif match ARP entry # match ethernet switching table entry if ($line =~ /([^ ]+)[ \t]+([0-9A-Fa-f\:]+)[ \t]+([^ \t]+)[ \t]+[0-9]+[ \t]+(ge-0\/0\/([0-9]+))/) { $switchRecordCount ++; my $mac = $2; my $fullPort = $4; my $shortPort = $5; $cable_hash{$mac}{full_port} = $fullPort; $cable_hash{$mac}{short_port} = $shortPort; } # endif ethernet-switching table entry if ($line =~ /EXPECT_ERROR.*SRX/) { $fatalError = 1; print "FATAL ERROR: Could not connect to SRX\n"; } if ($line =~ /EXPECT_ERROR.*EX/) { $fatalError += 2; print "FATAL ERROR: Could not connect to EX2200\n"; } } # end while ( ) close INFILE; # ============================================ # create hash which associates port numbers to # mac addresses. We only care about ports that # have an IP address. Also check if there is about # valid WLA plugged into port 46. This is used # to see if it is ok to have a WLA in port 47 # ============================================ foreach my $mac (keys %cable_hash) { if ((defined $cable_hash{$mac}{ip}) && (defined $cable_hash{$mac}{full_port}) ){ $port_to_mac_hash{$cable_hash{$mac}{short_port}} = $mac; if ((46 == $cable_hash{$mac}{short_port}) && (46 == $cable_hash{$mac}{correct_port})) { $ap_in_46 = 1; } } } # ============================================ # handle stores that have 2 WLAs. Update correct # port for port 47 if there is a valid WLA in # port 46 # ============================================ if ($ap_in_46 && ( defined $port_to_mac_hash{47}) ){ if (46 == $cable_hash{$port_to_mac_hash{47}}{correct_port}) { $cable_hash{$port_to_mac_hash{47}}{correct_port} =47; } } # ============================================ # make sure we have ARP info and ethernet-switching # table info # # ============================================ unless ($arpRecordCount) { print "FATAL ERROR: Did not get ARP records from SRX\n"; $fatalError += 4; } unless ($switchRecordCount) { print "FATAL ERROR: Did not get ethernet-switching table records from EX2200\n"; $fatalError += 8; } exit $fatalError if ($fatalError); } # end sub parseData ##################################################################### # This is a dummy routine to print the results # of looking a the cabling in the store. This # should be replaced with a subroutine that puts # the data where it can be sent to the end user # MAC and IP address not printed unless debugging is on ##################################################################### sub printData { my ($storeNum, $datestamp ) = @_; print "Cabling report for store $storeNum generated at $datestamp\n\n"; foreach my $key (sort { $a <=> $b } keys %port_to_mac_hash) { my $mac = $port_to_mac_hash{$key}; if ($debugLevel >10 ) { print $mac; print "\t"; print $cable_hash{$mac}{ip}; print "\t"; print $cable_hash{$mac}{last_octet}; print "\t"; } printf '%-18s' , $cable_hash{$mac}{description}; print "\t"; print $cable_hash{$mac}{full_port}; print "\t"; if ($debugLevel >10 ) { print $cable_hash{$mac}{short_port}; print "\t"; print $cable_hash{$mac}{correct_port}; print "\t"; } if ( $cable_hash{$mac}{correct_port} != $cable_hash{$mac}{short_port}) { print "Move cable in port " . $cable_hash{$mac}{short_port} . " to port " . $cable_hash{$mac}{correct_port}; } else { print "OK"; } print "\n"; } } # end sub printData ##################################################################### # This subroutine reads the file that has the # expected last octet of the IP address that belongs # in each port. ##################################################################### sub getPortData { open( INFILE, $portFile ) or do { $fatalError = 104; print "FATAL_ERROR: could not open $filename (port to address associations) for reading\n"; exit $fatalError; }; while ( ) { chomp; my $line = $_; if ($line =~ /([0-9]+)[ \t]+([0-9]+)[\t]+(.*)/) { my $octet = $1; my $port = $2; my $description = $3; $port_hash{$octet}{port} = $port; $port_hash{$octet}{description} = $description; }elsif ($line =~ /([0-9]+)[ \t]+([0-9]+)/) { my $octet = $1; my $port = $2; $port_hash{$octet}{port} = $port; $port_hash{$octet}{description} = ""; } } close INFILE; } # end sub getPortData #################################################################### # # read the store info file # This file contains the info from allstores.xlsx in a # script friendly format # ##################################################################### sub getStoreInfo { my $COL_hostname = 0; my $COL_ip = 8; my $COL_t1_addr = 15; my $COL_t1_peer_addr = 14; #my $COL_avn_nbr = my $COL_local_as = 16; my $COL_st0_unit0_addr = 12; my $COL_st0_unit0_peer = 11; my $COL_st0_unit1_addr = 10; my $COL_st0_unit1_peer = 9; my $COL_state = 3; my $COL_city =2; # ===================================================== # device variables for all stores # ===================================================== open( STOREFILE, $storeFile ) or do { $fatalError = 105; print "FATAL_ERROR: could not open $storeFile (store information) for reading\n"; exit $fatalError; }; # ===================================================== # go through all devices and build hash based on ip # # ===================================================== while () { chomp; my $line = $_; if ($line =~ //){ my @input_tags = split("\t", $line); for my $i (0 .. $#input_tags) { if ($input_tags[$i] =~ // ) { $COL_hostname = $i; } if ($input_tags[$i] =~ // ) { $COL_ip = $i; } if ($input_tags[$i] =~ // ) { $COL_t1_addr = $i; } if ($input_tags[$i] =~ // ) { $COL_t1_peer_addr = $i; } if ($input_tags[$i] =~ // ) { $COL_local_as = $i; } if ($input_tags[$i] =~ // ) { $COL_st0_unit0_addr = $i; } if ($input_tags[$i] =~ // ) { $COL_st0_unit0_peer = $i; } if ($input_tags[$i] =~ // ) { $COL_st0_unit1_addr = $i; } if ($input_tags[$i] =~ // ) { $COL_st0_unit1_peer = $i; } if ($input_tags[$i] =~ // ) { $COL_state = $i; } if ($input_tags[$i] =~ // ) { $COL_city = $i; } # if ($input_tags[$i] =~ // ) { $COL_city = $i; # $COL_state = -1; } } next; } if ($line =~ /hostname[ \t]+ip/) {next;} # ===================================================== # read variables for a store # ===================================================== my @input_vars = split("\t", $line); my $store_number = $input_vars[$COL_hostname]; my $ip = $input_vars[$COL_ip]; if ($ip =~ /([0-9]*\.[0-9]*\.[0-9]*)/) { $ip = $1; } my $t1_addr = $input_vars[$COL_t1_addr]; my $t1_peer_addr = $input_vars[$COL_t1_peer_addr]; my $avn_nbr = '13979'; my $local_as = $input_vars[$COL_local_as]; #$input_vars[16]; my $st0_unit0_addr = $input_vars[$COL_st0_unit0_addr]; my $st0_unit0_peer = $input_vars[$COL_st0_unit0_peer]; my $st0_unit1_addr = $input_vars[$COL_st0_unit1_addr]; my $st0_unit1_peer = $input_vars[$COL_st0_unit1_peer]; my $state = $input_vars[$COL_state]; my $city = $input_vars[$COL_city]; my $bb_static_ip = '1.1.1.2/30'; #$input_vars[2]; my $bb_static_nh = '1.1.1.1'; #$input_vars[2]; # print "adding store <$store_number> ip <$ip>\n"; $device_ip_hash{$ip} = $store_number; $store_info_hash{$store_number}{ip} =$ip; $store_info_hash{$store_number}{state} =$state; } } # end getStoreInfo #################################################################### # # This subroutine creates a timestamp # # ##################################################################### sub getTimestamp { my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time); if ( $year > 99 ) { $year = $year + 1900; } $mon = $mon + 1; $mon = sprintf("%02d", $mon); $sec = sprintf("%02d", $sec); $min = sprintf("%02d", $min); $hour = sprintf("%02d", $hour); $mday = sprintf("%02d", $mday); $year = sprintf("%02d", $year); $wday = sprintf("%02d", $wday); $yday = sprintf("%02d", $yday); $isdst = sprintf("%02d", $isdst); my $datestamp = $mon . '-' . $mday . '-' .$year . ' ' . $hour . ':' . $min . ':' . $sec; return($datestamp); } # end sub getTimestamp #################################################################### # # This subroutine adds a range of octets to the list of devices to # be pinged. This list only contains the 4th octet of the addess # The first 3 octets are specified in $base_ip ##################################################################### sub addRangeToPingList { my ( $start , $end) = @_; my $octet = $start; while ($octet <= $end) { push (@pingList, $octet); $octet++; } } # end sub addRangeToPingList ##################################################################### # This subroutine pings a list of IP addresses where # the first 3 octets are specified in $base_ip and the # 4th octet of each device to be pinged is in the array called pingList ##################################################################### sub pingArrayThreaded { my ($base_ip) = @_; my $upCount = 0; my $octet; foreach my $octet (@pingList) { my $ipaddr = $base_ip . '.' . $octet; my $thr = threads->new(\&threadedPing , $ipaddr); $octet++; } # end while my @running = threads->list(threads::running); while ($#running > 0) { print "running " . $#running . " threads\n" if ($debugLevel >5); my @joinable = threads->list(threads::joinable); foreach my $joinableThr (@joinable) { $upCount += $joinableThr->join(); } sleep(1); @running = threads->list(threads::running); } my @joinable = threads->list(threads::joinable); foreach my $joinableThr (@joinable) { $upCount += $joinableThr->join(); } return ($upCount); } # end sub pingRange