#!/usr/bin/perl # # # Written 2/11/05 by Brian Tuchten # # This script is provided with no warrenty of any kind. # Use at your own risk. As with any script, there are usually bugs # which may cause unforseen network system problems. # # IMPORTANT NOTE: This script is dependent on a lot of files, and # also the assumption that pinging any remote gateway, either over ethernet # or ppp will yield a valid result. If remote gateways will not respond to pings # then the link (either ethernet or ppp) will be bouncing up and down. # # # Another Note: This script will remove default routes on startup if needed, # and add/remove default routes while running. If you are accessing the unit # this script is running on from a remote subnet, you might loose your current # connection when starting this script up. # # This script doesn't use any Perl libraries as it was written # to run on a Linux embedded unit. # # This script should be run as the root user. # # This scripts main purpose is to allow IP fallback routing # between 2 ethernet/ppp connections. # # Ethernet IP information is read from the appropriate # /etc/sysconfig/network-scripts/ifcfg-[$PRIMARY_INT | $SECONDARY_INT] # file. # # # FAILOVER between 2 Ethernet interfaces (on same subnet) : # # If this script is to be used as a failover mode, where # if the PRIMARY ethernet is failing, and the script switches to the # SECONDARY, which is also on the same subnet, then only # 1 (one) interface will be operational at a time. So if ETH0 # fails, then it will be brought down, and ETH1 will be brought # ip. This script does NOT use the network scripts located in # /etc/sysconfig/network-scripts/ with ifup/ifdown, but uses "ifconfig" #to reconfigure interfaces up and down, and add/remove default routes. # # # # FALLBACK from one Ethernet to another Ethernet (diff subnet): # # If this script is to be used as a fallback between 2 ethernet # interfaces which are NOT on the same subnet, then both ethernet # interfaces will be up at the same time. The script will only # add/remove the default route pointing to the active interface. # # # # FALLBACK from one Ethernet connection to a PPP connection: # # If this script is to be used as fallback from ehternet to PPP dial, # then if the ethernet gateway becomes unavailable, the default route # for the primary ethernet device will be removed, and a command to # dial the ppp backup link will be initiated. If the ppp link goes down # it will be redialed. Once the primary ethernet is back up, the ppp session # will be terminated, and the default route for the primary ethernet will # be restored. # # # # FALLBACK from one PPP connection to another PPP connection: # # If this script is to be used as fallback from one PPP connection to # another PPP connection, you should have both PPP dialers working first. # The script is meant to use commands like "/bin/pppd call someisp" where # someisp is the file /etc/ppp/peers/someisp and also has a chat file associated with it. # The script should be aloowed to control both connections, i.e. neither one # should be up at boot time. The PRIMARY PPP will be brought up from the command # listed in the config file for PRIMARY_COMMAND= . # The script will attempt to: # # start the proccess from PRIMARY_COMMAND= # check for the process pid # get the ppp interface from the matching pid in /var/run/pppN.pid # get the IP and P-T-P IP from the pppN interface # attempt to ping the remote P-T-P IP # # # The script should never exit, but "sleep forever" incase of a problem. # This is incase the script is started by init::respawn # # # Currently this script will only work with Static IP addresses on the ethernet # interfaces. # No DHCP or PPPoE. # # Report any bugs to brian jbmelectronics.com # # Last Updated 4/8/05 # #open (STDERR,'>&STDOUT'); $| = 1; my $version = "1.04"; #strip off the /'s and get just the program name. my $prog_name = &RealName; #paths to programs we need to function: $ping_cmd = &get_command("ping",1); $route_cmd = &get_command("route",1); $ifconfig_cmd = &get_command("ifconfig",1); $grep_cmd = &get_command("grep",1); $ps_cmd = &get_command("ps",1); $ps_cmd_option = "auxw"; #do not change this $pidof_cmd = &get_command("pidof",1); $killall_cmd = &get_command("killall",1); $kill_cmd = &get_command("kill",1); $ipcalc_cmd = &get_command("ipcalc",1); #for resetting a JBM Embedded Wavecom modem $reset_wavecom_cmd = &get_command("ResetWaveCom",0); #use logger for writing to the syslog $logger_cmd = &get_command("logger",0); if ( -e "$logger_cmd" ) { $use_logger = 1; } #get the path for snmptrap, and set up some flags $has_snmptrap = 0; $use_snmp = 0; $snmptrap_cmd = &get_command("snmptrap",0); if ( -e "$snmptrap_cmd" ) { $has_snmptrap = 1; } #check for another running proccess/write pid &write_pid(); #our config file $config_file = "/etc/ipfallback.conf"; #setup some system commands to call when we need them $route_add = "$route_cmd add"; $route_del = "$route_cmd del"; #the DIR where to write temp files $temp_dir = "/tmp"; #where we read the ethernet IP information from $ethernet_config_file = "/etc/sysconfig/network-scripts/ifcfg-"; #extra config stuff $use_reset_wavecom = 0; $USE_IPSEC = ""; $IPSEC_DOWN_COMMAND = "ipsec setup stop"; $IPSEC_UP_COMMAND = "ipsec setup start"; #print a startup message &syslog("v$version started."); # #Read the config file variables in # if ( &check_file("$config_file") ) { %config_hash = &read_config("$config_file"); ($PRIMARY_TYPE = $config_hash{"PRIMARY_TYPE"}) =~ s/\s//g; ($PRIMARY_INT = $config_hash{"PRIMARY_INT"}) =~ s/\s//g; ($PRIMARY_IP_GATEWAY = $config_hash{"PRIMARY_IP_GATEWAY"}) =~ s/\s//g; $PRIMARY_COMMAND = $config_hash{"PRIMARY_COMMAND"}; ($SECONDARY_TYPE = $config_hash{"SECONDARY_TYPE"}) =~ s/\s//g; ($SECONDARY_INT = $config_hash{"SECONDARY_INT"}) =~ s/\s//g; ($SECONDARY_IP_GATEWAY = $config_hash{"SECONDARY_IP_GATEWAY"}) =~ s/\s//g; $SECONDARY_COMMAND = $config_hash{"SECONDARY_COMMAND"}; $CHECK_INTERVAL = $config_hash{"CHECK_INTERVAL"}; $USE_RESET_JBM_WAVECOM = $config_hash{"USE_RESET_JBM_WAVECOM"}; ($PACKET_LOSS_LIMIT = $config_hash{"PACKET_LOSS_LIMIT"}) =~ s/[^0-9](3)//g; ($MAX_PPP_TRY = $config_hash{"MAX_PPP_TRY"}) =~ s/[^0-9]//g; ($DEBUG_LEVEL = $config_hash{"DEBUG_LEVEL"}) =~ s/[^1-3]//g; #for snmp stuff if ( $has_snmptrap ) { ($SNMP_HOST = $config_hash{"SNMP_HOST"}) =~ s/\s//g; ($SNMP_COMMUNITY = $config_hash{"SNMP_COMMUNITY"}) =~ s/\s//g; } } else { &syslog("** Cannot read config file $config_file"); &loop_forever(); } #some sanity checking if ( int($DEBUG_LEVEL) ) { $debug_level = int($DEBUG_LEVEL); } else { $debug_level = 0; } #this is the limit to say an interface is unusable/down. Since 10 pings are sent, #it should be multiples of 10, up to 100 $PACKET_LOSS_LIMIT =~ s/[^0-9]//g; if ( int($PACKET_LOSS_LIMIT) < 10 ) { &syslog("PACKET_LOSS_LIMIT < 10%, setting it to 10%."); $PACKET_LOSS_LIMIT = "10"; } elsif ( int($PACKET_LOSS_LIMIT) > 100 ) { &syslog("PACKET_LOSS_LIMIT > 100%, setting it to 100%."); $PACKET_LOSS_LIMIT = "100"; } else { &syslog("PACKET_LOSS_LIMIT threshold set to $PACKET_LOSS_LIMIT\%."); } #defaults for some vars $both_ethernet = 0; $both_ppp = 0; $same_subnet = 0; $primary_up = 0; $primary_is_ethernet = 0; $primary_is_ppp = 0; $secondary_up = 0; $secondary_is_ethernet = 0; $secondary_is_ppp = 0; $secondary_is_ethernet = 0; if ( $PRIMARY_TYPE =~ /^ethernet$/i ) { #read in the IP information from "$ethernet_config_file$PRIMARY_INT" if ( -e "$ethernet_config_file$PRIMARY_INT" ) { &syslog("Reading file $ethernet_config_file$PRIMARY_INT"); %primary_hash = &read_config("$ethernet_config_file$PRIMARY_INT"); $PRIMARY_IP = $primary_hash{"IPADDR"}; $PRIMARY_IP_MASK = $primary_hash{"NETMASK"}; $PRIMARY_IP_NETWORK = $primary_hash{"NETWORK"}; $PRIMARY_IP_BROADCAST = $primary_hash{"BROADCAST"}; if ($debug_level) { &syslog("PRIMARY_IP = $PRIMARY_IP"); &syslog("PRIMARY_IP_MASK = $PRIMARY_IP_MASK"); &syslog("PRIMARY_IP_NETWORK = $PRIMARY_IP_NETWORK"); &syslog("PRIMARY_IP_BROADCAST = $PRIMARY_IP_BROADCAST"); } } else { &syslog("PRIMARY Ethernet config file $ethernet_config_file$PRIMARY_INT does not exist."); &loop_forever(); } # #sanity checking time. # #check the IP, mask, network, broadcast for IPV4 formatting if ( ! &check_ipaddress($PRIMARY_IP) ) { &syslog("** Config file error : PRIMARY_IP ($PRIMARY_IP) is invalid."); &loop_forever(); } if ( ! &check_ipaddress($PRIMARY_IP_MASK) ) { &syslog("** Config file error : PRIMARY_IP_MASK ($PRIMARY_IP_MASK) is invalid."); &loop_forever(); } if ( ! &check_ipaddress($PRIMARY_IP_NETWORK) ) { &syslog("** Config file error : PRIMARY_IP_NETWORK ($PRIMARY_IP_NETWORK) is invalid."); &loop_forever(); } if ( ! &check_ipaddress($PRIMARY_IP_BROADCAST) ) { &syslog("** Config file error : PRIMARY_IP_BROADCAST ($PRIMARY_IP_BROADCAST) is invalid."); &loop_forever(); } #check the primary gateway for proper IPV4 formatting if ( ! &check_ipaddress($PRIMARY_IP_GATEWAY) ) { &syslog("** Config file error : PRIMARY_IP_GATEWAY ($PRIMARY_IP_GATEWAY) is invalid."); &loop_forever(); } #check that the gateway is in the block for $PRIMARY_INT. If it isn't then we will get an error when adding #the default route for this interface if ( ! &validate_gateway_in_subnet($PRIMARY_IP_GATEWAY,$PRIMARY_IP_MASK,$PRIMARY_IP_NETWORK,$PRIMARY_IP_BROADCAST)) { &syslog("** Config file error : PRIMARY_IP_GATEWAY ($PRIMARY_IP_GATEWAY) is not in the netmask block for $PRIMARY_INT."); &loop_forever(); } #check to see if the PRIMARY_GATEWAY is the same as the PRIMARY_IP if ( $PRIMARY_IP eq $PRIMARY_IP_GATEWAY ) { &syslog("** Config file error : PRIMARY_IP ($PRIMARY_IP) on interface $PRIMARY_INT is the same as $PRIMARY_IP_GATEWAY"); &loop_forever(); } #PRIMARY commands for up/down interfaces and routes $primary_default_route = "default gw $PRIMARY_IP_GATEWAY dev $PRIMARY_INT"; $primary_interface_up = "$ifconfig_cmd $PRIMARY_INT $PRIMARY_IP netmask $PRIMARY_IP_MASK broadcast $PRIMARY_IP_BROADCAST up"; $primary_interface_down = "$ifconfig_cmd $PRIMARY_INT down"; #check the IP format for the entries in the config #PRIMARY is always ethernet, so IP info needs to be defined #the next group of checks are for the PRIMARY interface if ( $PRIMARY_INT !~ /^eth\d+$/ ) { &syslog("** Config file error : PRIMARY_INT Ethernet ($PRIMARY_INT) is invalid."); &loop_forever(); } #check that this primary is already up, ortherwise we do nothing. $output = `$ifconfig_cmd | $grep_cmd \"^$PRIMARY_INT\"`; chomp($output); if ( $output =~ /^\s*$/ ) { &syslog("Ethernet error: PRIMARY_INT $PRIMARY_INT should be active."); &syslog("Attempting to bring up interface PRIMARY_INT $PRIMARY_INT."); system("$primary_interface_up 2> /dev/null"); #check that this secondary is now up, ortherwise we do nothing. $output = `$ifconfig_cmd | $grep_cmd \"$PRIMARY_INT\"`; chomp($output); if ( $output =~ /^\s*$/ ) { &syslog("Primary Ethernet $PRIMARY_INT did not come up. Check its config before running this script."); &loop_forever(); } } #mark the primary interface ethernet $primary_is_ethernet = 1; #check for $PRIMARY_INT has the only default route before proceeding $temp = `$route_cmd -n 2> /dev/null`; @route_table = split/\n/, $temp; $def_routes = 0; $pri_route = 0; my @DEL_ROUTE_LIST = (); foreach(@route_table) { chomp; #turn multiple spaces into just 1 for syslog display $_ =~ s/\s+/ /g; #print "route table: $_\n" if ($debug_level); #default routes should have this format: #field 0 1 2 3 4 5 6 7 #0.0.0.0 10.100.0.1 0.0.0.0 UG 0 0 0 eth1 if ( $_ =~ /^0\.0\.0\.0.*$/ ) { &syslog("Default route found: \"$_\""); $def_routes++; my @tmp = split/\s+/, $_; if ( $tmp[1] !~ m/^$PRIMARY_IP_GATEWAY$/ ) { push(@DEL_ROUTE_LIST ,"$route_cmd del default gw $tmp[1] dev $tmp[7]"); } if ( $_ =~ m/^.*$PRIMARY_INT/i ) { &syslog("PRIMARY Default route found: \"$_\""); $pri_route++; } else { } } } if ( $def_routes > 1 ) { &syslog("Routing problem, multiple default routes found. Only the PRIMARY should have a default route."); #&loop_forever(); } elsif ( !$pri_route ) { &syslog("Routing problem, PRIMARY interface $PRIMARY_INT should have the only default route."); #&syslog("Adding default route for PRIMARY interface $PRIMARY_INT"); #&syslog("** running command: $route_add $primary_default_route") if ($debug_level); #system("$route_add $primary_default_route 2> /dev/null"); #&loop_forever(); } if ( $pri_route > 1 ) { &syslog("Routing problem, PRIMARY interface $PRIMARY_INT has multiple default routes."); #&loop_forever(); } if ( @DEL_ROUTE_LIST ) { &syslog("Fixing routing problems: Removing all default routes."); foreach(@DEL_ROUTE_LIST) { chomp; system("$_ 2> /dev/null"); &syslog("** running command: $_"); } &syslog("Fixing routing problems: Adding default route for $PRIMARY_INT to $PRIMARY_IP_GATEWAY."); &syslog("running command: $route_add $primary_default_route"); system("$route_add $primary_default_route 2> /dev/null"); } } elsif ( $PRIMARY_TYPE =~ /^ppp$/i ) { if ( $PRIMARY_COMMAND =~ /^\s*$/ ) { &syslog("** Config file error : PRIMARY_TYPE is ppp but PRIMARY_COMMAND is empty."); &loop_forever(); } &syslog("PRIMARY_TYPE is set to ppp."); &syslog("PRIMARY_COMMAND to bring up ppp will be \"$PRIMARY_COMMAND\""); #mark the primary interface as ppp $primary_is_ppp = 1; } else { &syslog("** Config file error : PRIMARY_TYPE can only be \"ethernet\" or \"ppp\". Sleeping forever."); &loop_forever(); } # #the next group of checks are for the SECONDARY interface # if ( $SECONDARY_TYPE =~ /^ethernet$/i) { $secondary_is_ethernet = 1; &syslog("SECONDARY_TYPE set to ethernet."); if ( $primary_is_ethernet ) { $both_ethernet = 1; } if ( $SECONDARY_INT !~ /^eth\d+$/ ) { &syslog("** Config file error : SECONDARY_INT Ethernet ($SECONDARY_INT) is invalid."); &loop_forever(); } if ( $SECONDARY_INT eq $PRIMARY_INT ) { &syslog("** Config file error : PRIMARY_INT and SECONDARY_INT cannot be the same."); &loop_forever(); } #read in the IP information from "$ethernet_config_file$SECONDARY_INT" if ( -e "$ethernet_config_file$SECONDARY_INT" ) { &syslog("Reading file $ethernet_config_file$SECONDARY_INT") if ( $debug_level ); %secondary_hash = &read_config("$ethernet_config_file$SECONDARY_INT"); $SECONDARY_IP = $secondary_hash{"IPADDR"}; $SECONDARY_IP_MASK = $secondary_hash{"NETMASK"}; $SECONDARY_IP_NETWORK = $secondary_hash{"NETWORK"}; $SECONDARY_IP_BROADCAST = $secondary_hash{"BROADCAST"}; if ($debug_level) { &syslog("SECONDARY_IP = $SECONDARY_IP"); &syslog("SECONDARY_IP_MASK = $SECONDARY_IP_MASK"); &syslog("SECONDARY_IP_NETWORK = $SECONDARY_IP_NETWORK"); &syslog("SECONDARY_IP_BROADCAST = $SECONDARY_IP_BROADCAST"); } } else { &syslog("SECONDARY Ethernet config file $ethernet_config_file$SECONDARY_INT does not exist."); &loop_forever(); } #check the IP, mask, network, broadcast for IPV4 formatting if ( ! &check_ipaddress($SECONDARY_IP) ) { &syslog("** Config file error : SECONDARY_IP ($SECONDARY_IP) is invalid."); &loop_forever(); } if ( ! &check_ipaddress($SECONDARY_IP_MASK) ) { &syslog("** Config file error : SECONDARY_IP_MASK ($SECONDARY_IP_MASK) is invalid."); &loop_forever(); } if ( ! &check_ipaddress($SECONDARY_IP_NETWORK) ) { &syslog("** Config file error : SECONDARY_IP_NETWORK ($SECONDARY_IP_NETWORK) is invalid."); &loop_forever(); } if ( ! &check_ipaddress($SECONDARY_IP_BROADCAST) ) { &syslog("** Config file error : SECONDARY_IP_BROADCAST ($SECONDARY_IP_BROADCAST) is invalid."); &loop_forever(); } #check the primary gateway for proper IPV4 formatting if ( ! &check_ipaddress($SECONDARY_IP_GATEWAY) ) { &syslog("** Config file error : SECONDARY_IP_GATEWAY ($SECONDARY_IP_GATEWAY) is invalid."); &loop_forever(); } #check that the gateway is in the block for $PRIMARY_INT. If it isn't then we will get an error when adding #the default route for this interface if ( ! &validate_gateway_in_subnet($SECONDARY_IP_GATEWAY,$SECONDARY_IP_MASK,$SECONDARY_IP_NETWORK,$SECONDARY_IP_BROADCAST)) { &syslog("** Config file error : SECONDARY_IP_GATEWAY ($SECONDARY_IP_GATEWAY) is not in the netmask block for $SECONDARY_INT."); &loop_forever(); } #check to see if the SECONDARY_IP_GATEWAY is the same as the SECONDARY_IP if ( $SECONDARY_IP eq $SECONDARY_IP_GATEWAY ) { &syslog("** Config file error : SECONDARY_IP ($SECONDARY_IP) on interface $SECONDARY_INT is the same as $SECONDARY_IP_GATEWAY"); &loop_forever(); } #check to see if the SECONDARY interface is up already $output = `$ifconfig_cmd | $grep_cmd \"$SECONDARY_INT\" 2>&1`; chomp($output); if ( $output !~ /^\s*$/ ) { $secondary_up = 1; } #if both interfaces are on the same subnet, then only one can #be active at a time. if ( ($PRIMARY_IP_NETWORK eq $SECONDARY_IP_NETWORK) && ($PRIMARY_IP_BROADCAST eq $SECONDARY_IP_BROADCAST) ) { $same_subnet = 1; &syslog("Both interfaces $PRIMARY_INT and $SECONDARY_INT appear to be on the same subnet."); if ( 1 == $secondary_up ) { &syslog("Ethernet error: PRIMARY and SECONDARY ETH interfaces are both active and on the same subnet."); &syslog("Taking down SECONDARY interface $SECONDARY_INT."); system("$ifconfig_cmd $SECONDARY_INT down"); $secondary_up = 0; } } } #if the backup (SECONDARY) is ppp, then we need a system command to dial the PPP connection elsif ( $SECONDARY_TYPE =~ /^ppp$/i ) { $secondary_is_ppp = 1; &syslog("SECONDARY_TYPE is set to ppp."); if ( $SECONDARY_COMMAND =~ /^\s*$/ ) { &syslog("** Config file error : SECONDARY_TYPE is ppp but SECONDARY_COMMAND is empty."); &loop_forever(); } } else { &syslog("** Config file error : SECONDARY_TYPE can only be \"ethernet\" or \"ppp\", sleeping forever."); &loop_forever(); } #SECONDARY commands for up/down interfaces and routes $secondary_interface_up = "$ifconfig_cmd $SECONDARY_INT $SECONDARY_IP netmask $SECONDARY_IP_MASK broadcast $SECONDARY_IP_BROADCAST up"; $secondary_interface_down = "$ifconfig_cmd $SECONDARY_INT down"; $secondary_default_route = "default gw $SECONDARY_IP_GATEWAY dev $SECONDARY_INT"; if ( $secondary_is_ethernet && !$secondary_up && !$same_subnet ) { &syslog("Ethernet error: SECONDARY_INT $SECONDARY_INT should be active."); &syslog("Attempting to bring up interface SECONDARY_INT $SECONDARY_INT."); system("$secondary_interface_up 2> /dev/null"); #check that this secondary is now up, ortherwise we do nothing. $output = `$ifconfig_cmd | $grep_cmd \"$SECONDARY_INT\"`; chomp($output); if ( $output =~ /^\s*$/ ) { &syslog("Secondary Ethernet $SECONDARY_INT did not come up. Check its config before running this script."); &loop_forever(); } } #kill our pppd command if it was running when we started if ( $secondary_is_ppp ) { my $pid = &get_pid($SECONDARY_COMMAND); if ($pid) { &syslog("SECONDARY_COMMAND \"$SECONDARY_COMMAND\" is running with PID $pid; killing it."); system("$kill_cmd -9 $pid 2>&1 > /dev/null") } } #kill our pppd command if it was running when we started if ( $primary_is_ppp ) { my $pid = &get_pid($PRIMARY_COMMAND); if ($pid) { &syslog("PRIMARY_COMMAND \"$PRIMARY_COMMAND\" is running with PID $pid; killing it."); system("$kill_cmd -9 $pid 2>&1 > /dev/null") } } if ( $primary_is_ppp && $secondary_is_ppp ) { $both_ppp = 1; } if ( $primary_is_ppp || $secondary_is_ppp ) { if ( int($MAX_PPP_TRY) > 5 ) { $MAX_PPP_TRY = 5; &syslog("MAX_PPP_TRY > 5, setting it to 5"); } if ( int($MAX_PPP_TRY) < 1 ) { $MAX_PPP_TRY = 1; &syslog("MAX_PPP_TRY < 1, setting it to 1"); } } #this is the number of seconds to check between pings $CHECK_INTERVAL =~ s/[^0-9]//g; if ( $both_ppp ) { if ( int($CHECK_INTERVAL) < 30 ) { &syslog("CHECK_INTERVAL < 30, setting it to 30 seconds since both interfaces to watch are PPP."); $CHECK_INTERVAL = 30; } } else { if ( int($CHECK_INTERVAL) < 10 ) { &syslog("CHECK_INTERVAL < 10, setting it to 10 seconds."); $CHECK_INTERVAL = 10; } elsif ( int($CHECK_INTERVAL) > 600 ) { &syslog("CHECK_INTERVAL > 300, setting it to 300 seconds."); $CHECK_INTERVAL = 300; } else { &syslog("CHECK_INTERVAL set to $CHECK_INTERVAL seconds."); } } #for resetting JBM Embedded Wavecom modems #if the JBM driver is loaded, and the cellmodem is found if ( -e "/proc/feature_codes/ttyS1_cellmodem" ) { if ( $USE_RESET_JBM_WAVECOM =~ /yes/i ) { if ( -e "$reset_wavecom_cmd" ) { &syslog("Config option USE_RESET_JBM_WAVECOM=yes , using program $reset_wavecom_cmd for resetting"); $use_reset_wavecom = 1; } else { &syslog("Warning: Config option USE_RESET_JBM_WAVECOM=yes, but cannot find program ResetWaveCom for resetting"); } } } #If the PRIMARY is PPP, if there is a default route then PPP will not overwrite it #even if it is set in the PPP config file, so remove any default routes if ( $primary_is_ppp ) { #check for $PRIMARY_INT has the only default route before proceeding $temp = `$route_cmd -n 2> /dev/null`; @route_table = split/\n/, $temp; $def_routes = 0; $pri_route = 0; my @DEL_ROUTE_LIST = (); foreach(@route_table) { chomp; #turn multiple spaces into just 1 for syslog display $_ =~ s/\s+/ /g; #print "route table: $_\n" if ($debug_level); #default routes should have this format: #field 0 1 2 3 4 5 6 7 #0.0.0.0 10.100.0.1 0.0.0.0 UG 0 0 0 eth1 if ( $_ =~ /^0\.0\.0\.0.*$/ ) { &syslog("Default route found: \"$_\""); $def_routes++; my @tmp = split/\s+/, $_; push(@DEL_ROUTE_LIST ,"$route_cmd del default gw $tmp[1] dev $tmp[7]"); } } if ( @DEL_ROUTE_LIST ) { &syslog("PRIMARY_TYPE is set to \"ppp\" and pppd will not overwrite default routes"); &syslog("Fixing routing problems: Removing all default routes."); foreach(@DEL_ROUTE_LIST) { chomp; system("$_ 2> /dev/null"); &syslog("** running command: $_"); } } } #setup some SNMP stuff #if we have the snmptrap program, then set this up if configured $use_snmptrap = 0; if ( $has_snmptrap && $primary_is_ethernet ) { &syslog("Program snmptrap is available"); if ( ! &check_ipaddress($SNMP_HOST) ) { &syslog("Config error : SNMP_HOST IP ($SNMP_HOST) is not valid"); } else { if ( $SNMP_COMMUNITY =~ /^\s*$/ ) { &syslog("SNMP_COMMUNITY not defined, setting community to \"public\""); $SNMP_COMMUNITY = "public"; } else { &syslog("SNMP_COMMUNITY set to \"$SNMP_COMMUNITY\""); } $snmptrap_down = "$snmptrap_cmd -v 1 -c $SNMP_COMMUNITY $SNMP_HOST 1 $PRIMARY_IP 2 0 '' 1 s"; $snmptrap_up = "$snmptrap_cmd -v 1 -c $SNMP_COMMUNITY $SNMP_HOST 1 $PRIMARY_IP 3 0 '' 1 s"; #"some message" $use_snmptrap = 1; } } else { &syslog("snmptrap is disabled"); } ############################################################## #The main loops to start checking for network problems #there are several of them depending on how we are configured ############################################################## $first_run = 1; $secondary_up = 0; $primary_up = 1; #assume primary is up, if its ppp, it will come up #if both ethernets are on the same subnet #then we check the current interface which is active. #IF that interface's gateway stops responding, we use #"ifconfig" to bring up the SECONDARY, and then bring down #the PRIMARY, and alter the routing table #This method uses a flip-flop since only one interface can be #active at a time, we can only switch from one to another when the #currently active interface fails if ( $both_ethernet && $same_subnet ) { while (1) { #don't sleep the first time through if ( !$first_run ) { &syslog("Sleeping for $CHECK_INTERVAL seconds.") if ($debug_level); &long_sleep($CHECK_INTERVAL); } $first_run = 0; if ( $primary_up ) { if ( ! &CheckPing($PRIMARY_INT, $PRIMARY_IP_GATEWAY) ) { &syslog("PRIMARY_INT $PRIMARY_INT is down, or has packet loss."); &syslog("Taking down interface $PRIMARY_INT, Bringing up interface $SECONDARY_INT"); system("$primary_interface_down"); system("$secondary_interface_up"); system("$route_add $secondary_default_route"); &secondary_up; &send_snmptrap_down("$PRIMARY_INT down, fallback to $SECONDARY_INT ($SECONDARY_IP)"); } } else # ( $secondary_up ) { if ( ! &CheckPing($SECONDARY_INT, $SECONDARY_IP_GATEWAY) ) { &syslog("SECONDARY_INT $SECONDARY_INT is down, or has packet loss."); &syslog("Taking down interface $SECONDARY_INT, Bringing up interface $PRIMARY_INT"); system("$secondary_interface_down"); system("$primary_interface_up"); system("$route_add $primary_default_route"); &primary_up; &send_snmptrap_up("$SECONDARY_INT down, bringing $PRIMARY_INT back up"); } } } #end while loop } #end if ( $same_subnet ) #both interfaces are not on the same subnet, so they should both always be UP, but #we add/remove default routes accordingly as needed if ( $both_ethernet && !$same_subnet ) { while (1) { #don't sleep the first time through if ( !$first_run ) { &syslog("Sleeping for $CHECK_INTERVAL seconds.") if ($debug_level); &long_sleep($CHECK_INTERVAL); } $first_run = 0; #force both interfaces to be up to make sure someone didn't take them down system("$primary_interface_up"); system("$secondary_interface_up"); if ( $primary_up ) { #last check, the PRIMARY was UP. Check to see if it is still all good. if ( ! &CheckPing($PRIMARY_INT, $PRIMARY_IP_GATEWAY) ) { &syslog("PRIMARY_INT $PRIMARY_INT is down, or has packet loss."); &remove_default_routes(); &syslog("Adding default route ($SECONDARY_IP_GATEWAY) for interface $SECONDARY_INT."); system("$route_add $secondary_default_route"); &secondary_up; &send_snmptrap_down("$PRIMARY_INT down, fallback to $SECONDARY_INT ($SECONDARY_IP)"); } } else # ( $secondary_up ) { #last check, the SECONDARY was UP. Check to see if it is still all good. #if not, then switch back to the PRIMARY if ( ! &CheckPing($SECONDARY_INT, $SECONDARY_IP_GATEWAY) ) { &syslog("SECONDARY_INT $SECONDARY_INT is down, or has packet loss."); &remove_default_routes(); &syslog("Adding default route ($PRIMARY_IP_GATEWAY) for interface $PRIMARY_INT."); system("$route_add $primary_default_route"); &primary_up; &send_snmptrap_down("$SECONDARY_INT down, bringing back up $PRIMARY_INT"); } #The SECONDARY is still up, but lets check to see if the PRIMARY is back up. elsif ( &CheckPing($PRIMARY_INT, $PRIMARY_IP_GATEWAY) ) { &syslog("PRIMARY_INT $PRIMARY_INT is back above threshold limits."); &remove_default_routes(); &syslog("Adding default route ($PRIMARY_IP_GATEWAY) for interface $PRIMARY_INT."); system("$route_add $primary_default_route"); &primary_up; &send_snmptrap_up("$PRIMARY_INT back up"); } } } # end while loop } #end if ( $both_ethernet && !$same_subnet ) #the primary is ethernet and the secondary is ppp #both interfaces are not ethernet, the secondary is a backup dialer using ppp #we have the PRIMARY IP information already, but the SECONDARY IP info we will need #to set from the PPP interface on every cycle, so we know what IP to ping #The PRIMARY should always be up, but we remove the default route to allow PPP to dial and #become the default route. We should also check to see if our PPP process is still running #by checking PID we get back when PPPD is forced to run in the background, or the process list. if ( $primary_is_ethernet && $secondary_is_ppp ) { while (1) { #don't sleep the first time through if ( !$first_run ) { &syslog("Sleeping for $CHECK_INTERVAL seconds.") if ($debug_level); &long_sleep($CHECK_INTERVAL); } $first_run = 0; #force both interfaces to be up to make sure someone didn't take them down #or maybe something wierd happened. anyways, we are paranoid since we run unattended. #system("$primary_interface_up"); #system("$secondary_interface_up") if ( $primary_up ) { if ( ! &CheckPing($PRIMARY_INT, $PRIMARY_IP_GATEWAY) ) { &remove_default_routes(); if ( &bring_up_secondary ) { &secondary_up; &send_snmptrap_down("$PRIMARY_INT down, fallback to $SECONDARY_INT ($SECONDARY_IP)"); } next; } } else # ( $secondary_up ) { #make sure the SECONDARY is still up $secondary_command_pid = &get_pid($SECONDARY_COMMAND); #look for the name of the interface $SECONDARY_INT = &get_ppp_int_from_pid($secondary_command_pid); if ( ! $secondary_command_pid ) { if ( &bring_up_secondary ) { &secondary_up; } next; } else { &syslog("Secondary interface $SECONDARY_INT still running with PID $secondary_command_pid."); #check if our PRIMARY is back up if ( &CheckPing($PRIMARY_INT, $PRIMARY_IP_GATEWAY) ) { &syslog("Primary interface $PRIMARY_INT back up."); &syslog("Adding Primary interface $PRIMARY_INT default route."); system("$route_add $primary_default_route"); $secondary_command_pid = &get_pid($SECONDARY_COMMAND); if ( $secondary_command_pid ) { &syslog("Killing SECONDARY $SECONDARY_INT proccess PID $secondary_command_pid"); system("$kill_cmd $secondary_command_pid 2>&1 > /dev/null"); } &primary_up; &send_snmptrap_up("$PRIMARY_INT back up"); next; } } #get the PPP interface IP and P-T-P IP address ($SECONDARY_IP, $SECONDARY_IP_GATEWAY) = &get_ppp_ips($SECONDARY_INT); #if we have valid IPs from our PPP interface, then check that it is still #getting traffic through. if ( &check_ipaddress($SECONDARY_IP) && &check_ipaddress($SECONDARY_IP_GATEWAY) ) { #hmmm, our PPP interface is not getting pings....we should restart it if ( ! &CheckPing($SECONDARY_INT, $SECONDARY_IP_GATEWAY) ) { #since PPP is still up, but not getting pings through, #kill it, and then try to bring it up again. $pid = &get_pid($SECONDARY_COMMAND); if ( $pid ) { system("$kill_cmd -9 $pid 2>&1 > /dev/null") } #try to bring it up again if ( &bring_up_secondary ) { &secondary_up; next; } } } else { #we didn't get valid IPs from the PPP interface, It may have gone down, so we should #try to bring up the PPP connection again. if ( &bring_up_secondary ) { &secondary_up; next; } } } #end if secondary is flagged as being up } #end while loop } #end if ( $primary_is_ethernet && $secondary_is_ppp ) #This gets called when both connections are going to be PPP #when we startup, we are told that the primary ppp connection is up, even though it shouldn't be #so we should fail to find it at first, but then bring it up. if ( $both_ppp ) { my $primary_try = 0; my $secondary_try = 0; my $last_primary_pid = 0; my $last_primary_int = ""; my $last_secondary_pid = 0; my $last_secondary_int = ""; while (1) { #don't sleep the first time through if ( !$first_run ) { &syslog("Sleeping for $CHECK_INTERVAL seconds.") if ($debug_level); &long_sleep($CHECK_INTERVAL); } $first_run = 0; if ( $primary_up ) { #check the ppp process is still running $primary_command_pid = &get_pid($PRIMARY_COMMAND); if ( $primary_command_pid ) { &syslog("PRIMARY_COMMAND \"$PRIMARY_COMMAND\" running, got PID $primary_command_pid."); #get the ppp interface from the pid get_ppp_int_from_pid() $PRIMARY_INT = &get_ppp_int_from_pid($primary_command_pid); #we didn't get a ppp interface from the pid file /var/run/pppN.pid if ( $PRIMARY_INT !~ /ppp\d+/i ) { &syslog("Cannot find a ppp interface matching PID $primary_command_pid yet"); #increment the counter, and we check to see if ppp is up on the next cycle $primary_try++; if ( $primary_try >= $MAX_PPP_TRY ) { &syslog("Max attempts ($primary_try) reached for PRIMARY_COMMAND \"$PRIMARY_COMMAND\""); #if we were told this is a JBM Embedded Wavecom wireless modem #then reset it in case it might be not responding to AT commands if ( $use_reset_wavecom ) { &syslog("Running system command \"$reset_wavecom_cmd\""); system("$reset_wavecom_cmd"); #no reason to sleep here #sleep(5); } #max attempts reached, kill this process, fallback to the secondary &syslog("Killing PRIMARY_COMMAND PID $primary_command_pid"); system("$kill_cmd -9 $primary_command_pid 2>&1 > /dev/null"); sleep(1); #start the primary ppp process &syslog("Attempting to run SECONDARY_COMMAND \"$SECONDARY_COMMAND\""); $secondary_command_pid = &run_command($SECONDARY_COMMAND); if ( $secondary_command_pid ) { &syslog("SECONDARY_COMMAND \"$SECONDARY_COMMAND\" running with PID $secondary_command_pid"); } #increment the primary try counter $secondary_try = 0; #sleep a little extra &long_sleep(10); #mark the secondary as up for the next loop &secondary_up; next; } #get the ppp interface from the pid get_ppp_int_from_pid() $PRIMARY_INT = &get_ppp_int_from_pid($primary_command_pid); #no interface yet, can check anything (may still be coming up) #check on next loop next; } #get the IPs from the PPP interface ($PRIMARY_IP, $PRIMARY_IP_GATEWAY) = &get_ppp_ips($PRIMARY_INT); #check that the IPs are valid, maybe the ppp interface is coming up, but doesn't have #valid IPs yet if ( &check_ipaddress($PRIMARY_IP) && &check_ipaddress($PRIMARY_IP_GATEWAY) ) { #the IPs are valid, lets test it with a ping. Now this is a ppp interface, #maybe even a 56k or slower, we might get packetloss. How do we handle not #switching when packetloss is above the threshold? if ( &CheckPing($PRIMARY_INT, $PRIMARY_IP_GATEWAY) ) { #reset the primary counter $primary_try = 0; #pings were ok, check on next loop next; } else { #pings were not ok, we have the PID, so kill this primary ppp process, and start it again, #and check again on the next loop system("$kill_cmd -9 $primary_command_pid 2>&1 > /dev/null"); sleep(1); #start the primary ppp process $primary_command_pid = &run_command($PRIMARY_COMMAND); #increment the primary try counter $primary_try++; #sleep a little extra &long_sleep(10); next; } } else { #the IPs arent valid yet, what if we get stuck here? #maybe we need a counter for the number of times this has been checked if ( $primary_try >= $MAX_PPP_TRY ) { &syslog("Max attempts ($primary_try) reached for PRIMARY_COMMAND \"$PRIMARY_COMMAND\""); #max attempts reached, kill this process, fallback to the secondary &syslog("Killing PRIMARY_COMMAND PID $primary_command_pid"); system("$kill_cmd -9 $primary_command_pid 2>&1 > /dev/null"); sleep(1); #start the primary ppp process &syslog("Attempting to run SECONDARY_COMMAND \"$SECONDARY_COMMAND\""); $secondary_command_pid = &run_command($SECONDARY_COMMAND); if ( $secondary_command_pid ) { &syslog("SECONDARY_COMMAND \"$SECONDARY_COMMAND\" running with PID $secondary_command_pid"); } #increment the primary try counter $secondary_try++; #sleep a little extra &long_sleep(10); #mark the secondary as up for the next loop &secondary_up; next; } $primary_try++; next; } } else { #the primary command isn't running #so start it again, sleep, and we will check it again on the next loop &syslog("Attempting to run PRIMARY_COMMAND \"$PRIMARY_COMMAND\""); $primary_command_pid = &run_command($PRIMARY_COMMAND); if ( $primary_command_pid ) { &syslog("PRIMARY_COMMAND \"$PRIMARY_COMMAND\" running, got PID $primary_command_pid."); } $primary_try++; #sleep a little so the connection can come up &long_sleep(10); } &primary_up; next; } else # if ( $secondary_up ) { #check the ppp process is still running $secondary_command_pid = &get_pid($SECONDARY_COMMAND); if ( $secondary_command_pid ) { &syslog("SECONDARY_COMMAND \"$SECONDARY_COMMAND\" running, got PID $secondary_command_pid."); #get the ppp interface from the pid get_ppp_int_from_pid() $SECONDARY_INT = &get_ppp_int_from_pid($secondary_command_pid); #we didn't get a ppp interface from the pid file /var/run/pppN.pid if ( $SECONDARY_INT !~ /ppp\d+/i ) { &syslog("Cannot find a ppp interface matching PID $secondary_command_pid yet"); if ( $secondary_try >= $MAX_PPP_TRY ) { &syslog("Max attempts ($secondary_try) reached for SECONDARY_COMMAND \"$SECONDARY_COMMAND\""); #max attempts reached, kill this process, fallback to the secondary &syslog("Killing SECONDARY_COMMAND PID $secondary_command_pid"); system("$kill_cmd -9 $secondary_command_pid 2>&1 > /dev/null"); sleep(1); #start the primary ppp process &syslog("Attempting to run PRIMARY_COMMAND \"$PRIMARY_COMMAND\""); $primary_command_pid = &run_command($PRIMARY_COMMAND); if ( $primary_command_pid ) { &syslog("PRIMARY_COMMAND \"$PRIMARY_COMMAND\" running with PID $primary_command_pid"); } #reset the primary try counter $primary_try = 0; #sleep a little extra &long_sleep(10); #mark the secondary as up for the next loop &primary_up; next; } #increment the counter, and we check to see if ppp is up on the next cycle $secondary_try++; #no interface yet, can check anything (may still be coming up) #check on next loop next; } #get the IPs from the PPP interface ($SECONDARY_IP, $SECONDARY_IP_GATEWAY) = &get_ppp_ips($SECONDARY_INT); #check that the IPs are valid, maybe the ppp interface is coming up, but doesn't have #valid IPs yet if ( &check_ipaddress($SECONDARY_IP) && &check_ipaddress($SECONDARY_IP_GATEWAY) ) { #the IPs are valid, lets test it with a ping. Now this is a ppp interface, #maybe even a 56k or slower, we might get packetloss. How do we handle not #switching when packetloss is above the threshold? if ( &CheckPing($SECONDARY_INT, $SECONDARY_IP_GATEWAY) ) { #reset the secondary counter $secondary_try = 0; ############################################# #the secondary is up, lets see if we can get #the primary back online ############################################# &syslog("Trying to bring PRIMARY_COMMAND \"$PRIMARY_COMMAND\" back up."); #see if the primary command is running for some wierd reason $primary_command_pid = &get_pid($PRIMARY_COMMAND); if ( $primary_command_pid ) { #it's running, thats wierd, lets kill it. &syslog("PRIMARY_COMMAND \"$PRIMARY_COMMAND\" is already running with PID $primary_command_pid (shouldn't be), killing it"); system("$kill_cmd -9 $primary_command_pid 2>&1 > /dev/null"); } #now try to bring it back up &syslog("Attempting to run PRIMARY_COMMAND \"$PRIMARY_COMMAND\""); $primary_command_pid = &run_command($PRIMARY_COMMAND); if ( $primary_command_pid ) { &syslog("PRIMARY_COMMAND \"$PRIMARY_COMMAND\" running, got PID $primary_command_pid."); &syslog("Sleeping 45 seconds to allow the PRIMARY to come back up."); #this is hard coded since we don't want to wait longer &long_sleep(45); #now check which interface it's using. $PRIMARY_INT = &get_ppp_int_from_pid($primary_command_pid); if ( $PRIMARY_INT !~ /ppp\d+/i ) { &syslog("PRIMARY_COMMAND did not come up with a vaild ppp interface in time, continuing to use SECONDARY"); &syslog("Killing PRIMARY_COMMAND PID $primary_command_pid"); system("$kill_cmd -9 $primary_command_pid 2>&1 > /dev/null"); next; } #get the IPs from the PPP interface ($PRIMARY_IP, $PRIMARY_IP_GATEWAY) = &get_ppp_ips($PRIMARY_INT); #check that the IPs are valid, maybe the ppp interface is coming up, but doesn't have #valid IPs yet if ( &check_ipaddress($PRIMARY_IP) && &check_ipaddress($PRIMARY_IP_GATEWAY) ) { #the IPs are valid, lets test it with a ping. Now this is a ppp interface, #maybe even a 56k or slower, we might get packetloss. How do we handle not #switching when packetloss is above the threshold? if ( &CheckPing($PRIMARY_INT, $PRIMARY_IP_GATEWAY) ) { #reset the primary and secondary counters $primary_try = 0; $secondary_try = 0; #add a default route for this interface since #ppp won't overwrite a default route when one exists &syslog("running command: \"$route_add default gw $PRIMARY_IP_GATEWAY dev $PRIMARY_INT\""); system("$route_add default gw $PRIMARY_IP_GATEWAY dev $PRIMARY_INT"); #kill the secondary ppp connection &syslog("Killing SECONDARY_COMMAND PID $secondary_command_pid"); system("$kill_cmd -9 $secondary_command_pid 2>&1 > /dev/null"); &syslog("PRIMARY interface back up on INT $PRIMARY_INT with IP $PRIMARY_IP"); #mark the primary back up &primary_up; #check on the next cycle next; } } } #pings were ok for secondary, check on next loop next; } else { #pings were not ok, we have the PID, so kill this primary ppp process, and start it again, #and check again on the next loop system("$kill_cmd -9 $secondary_command_pid 2>&1 > /dev/null"); sleep(1); #start the primary ppp process $secondary_command_pid = &run_command($SECONDARY_COMMAND); #increment the primary try counter $secondary_try++; #sleep a little extra &long_sleep(10); next; } } else { #the IPs arent valid yet, what if we get stuck here? #maybe we need a counter for the number of times this has been checked if ( $secondary_try >= $MAX_PPP_TRY ) { &syslog("Max attempts ($secondary_try) reached for SECONDARY_COMMAND \"$SECONDARY_COMMAND\""); #max attempts reached, kill this process, fallback to the secondary &syslog("Killing SECONDARY_COMMAND PID $secondary_command_pid"); system("$kill_cmd -9 $secondary_command_pid 2>&1 > /dev/null"); sleep(1); #start the primary ppp process &syslog("Attempting to run PRIMARY_COMMAND \"$PRIMARY_COMMAND\""); $primary_command_pid = &run_command($PRIMARY_COMMAND); if ( $primary_command_pid ) { &syslog("PRIMARY_COMMAND \"$PRIMARY_COMMAND\" running with PID $secondary_command_pid"); } #reset the primary try counter $primary_try = 0; #sleep a little extra &long_sleep(10); #mark the secondary as up for the next loop &secondary_up; next; } $secondary_try++; next; } } else { #the secondary command isn't running #so start it again, sleep, and we will check it again on the next loop &syslog("Attempting to run SECONDARY_COMMAND \"$SECONDARY_COMMAND\""); $secondary_command_pid = &run_command($SECONDARY_COMMAND); if ( $secondary_command_pid ) { &syslog("SECONDARY_COMMAND \"$SECONDARY_COMMAND\" running, got PID $secondary_command_pid."); } $secondary_try++; #sleep a little so the connection can come up &long_sleep(10); } &secondary_up; next; } }#end while loop } #end if ( $both_ppp ) #currently I don't see a reason why someone would need to fallback from PPP to ethernet... if ( $primary_is_ppp && $secondary_is_ethernet) { &syslog("** This script currently does not support PRIMARY_INT set to ppp and"); &syslog("** SECONDARY_INT set to ethernet."); &loop_forever(); } #default action...will we ever get here? &syslog("** Unknown configuration error. Please report this to the author."); &loop_forever(); ##################################### ##################################### # subroutines below ##################################### ##################################### #toggle the primary interface flag on, secondary off sub primary_up() { $primary_up = 1; $secondary_up = 0; } #toggle the secondary interface flag on, primary off sub secondary_up() { $primary_up = 0; $secondary_up = 1; } sub CheckPing { my @pingstats; my @triptimes; my @timeunit; my $result = ""; my $line; my $element; my $GATEWAYDEV = $_[0]; my $GATEWAY = $_[1]; my $command = "$ping_cmd $GATEWAY -q -c 10 -I $GATEWAYDEV"; &syslog("ping command: '$command'") if ($debug_level); @PINGRESULTS = `$command 2> /dev/null`; &syslog("ping done for interface $GATEWAYDEV.") if ($debug_level); foreach $line(@PINGRESULTS) { chomp($line); # get the last line if ( $line =~ /packets\s+transmitted/ ) { @pingstats = split /\s+/, $line; foreach $element(@pingstats) { chomp($element); if ( $element =~ /^100%$/ ) { &syslog("Ping result for $GATEWAYDEV was 100% packet loss."); return 0; } #if 1%-99% match elsif ( $element =~ /[0-9]%$/ && $element !~ /^0%$/ ) { @temp = split/%/, $element; $number = substr($temp[0],-3,3); $number =~ s/[^0-9]//g; $number = int($number); &syslog("Ping result for $GATEWAYDEV was $number\% packet loss."); if ( $number >= int($PACKET_LOSS_LIMIT) ) { &syslog("Results are above the tolerence of $PACKET_LOSS_LIMIT\%."); return 0; } } } } } &syslog("Ping results are within limits.") if ($debug_level); return 1; } # check if a v4 IP is formatted properly #returns 1 if the IP is valid #must be x.x.x.x #octet must be from 0 to 255 #octet can be 0, but not start with 0 ( 019 ) sub check_ipaddress { return $_[0] =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ && $1 >= 0 && $1 <= 255 && $1 !~ /^0\d+$/ && $2 >= 0 && $2 <= 255 && $2 !~ /^0\d+$/ && $3 >= 0 && $3 <= 255 && $3 !~ /^0\d+$/ && $4 >= 0 && $4 <= 255 && $4 !~ /^0\d+$/; } #reads a config file (passed in) and returns a %hash #pointer if the file is read. Returns 0 if file missing. #If the file contains a line "VARIABLE=123" then the hash #will have a key "VARIABLE" and value "123" #usage: %somehash = &read_config("/somedir/somefile.cfg"); # $my_var = $somehash{"VARIABLE"}; sub read_config { local $_; local %return_hash = (); open(THISFILE, $_[0]) || return undef; while() { chomp; local $hash = index($_, "#"); local $eq = index($_, "="); if ($hash != 0 && $eq >= 0) { local $name = substr($_, 0, $eq); local $value = substr($_, $eq+1); $return_hash{"$name"} = "$value"; } } close(THISFILE); return %return_hash; } #takes a passed message, and use "logger" #to send the message to the syslog sub syslog { my $p = $_[1]; my $message = $_[0]; my $date = localtime time; chomp($date); if ( 1 == $use_logger ) { $mess =~ s/\"/\\\"/g; if ( $p =~ /[0-9]/ ) { system("$logger_cmd -t $prog_name -p $p \"$message\" "); } else { system("$logger_cmd -t $prog_name \"$message\""); } if ( $debug_level > 1 ) { print "$date $prog_name: $message\n"; } } else { #no "logger" program, send to STDOUT print "$date $prog_name: $message\n"; } } #use "usleep" to sleep less than a second. sub usleep { system("usleep $_[0] 2> /dev/null"); } #something is wrong ( maybe config ) #so sleep forever in case we were launched by respawn:init sub loop_forever { &syslog("Sleeping forever."); while (1) { &long_sleep(300); } } # returns the running program name as called #without the PATH. /somedir/progname becomes progname sub RealName { my $real_program_name = $0; $real_program_name =~ s/^[\.\/].*\///g; return $real_program_name; } #returns the path to the program which was requested #$_[1] flags wether the program is required sub get_command { my $command = $_[0]; my $required = int($_[1]); chomp($command); my @sys_command = `/usr/bin/which $command 2> /dev/null`; chomp(@sys_command); if ( $sys_command[0] =~ /^\s*$/ && $required ) { &syslog("The program \"$command\" is required, but not found on this system."); &loop_forever(); } return $sys_command[0]; } #check if we can open a file sub check_file { open(THISFILE, $_[0]) || return 0; close(THISFILE); return 1; } #this function is used when the primary is ethernet, and the secondary is ppp. #our primary interface is not getting pings to it's gateway. #keep trying to bring up the secondary, all the while checking to see #if the primary is back up while we are doing it. sub bring_up_secondary() { my @output; my @tmp; my $secondary_command_pid; my $counter = 1; #try forever bring up the secondary, all the while checking if our primary #is back up. while ( 1 ) { #see if the process is still running $secondary_command_pid = &get_pid($SECONDARY_COMMAND); #if we were told this is a JBM Embedded Wavecom wireless modem #then reset it every 3 times in case it might be not responding to AT commands if ( $use_reset_wavecom ) { if ( 0 == ($counter % 3) ) { &syslog("Running system command \"$reset_wavecom_cmd\""); system("$reset_wavecom_cmd"); #the Wavecom modem takes about 5 seconds to come back after a reset sleep(5); } } #run the command, and get the pid if ( !$secondary_command_pid ) { &syslog("Attempt #$counter to bring up ppp with SECONDARY_COMMAND"); $secondary_command_pid = &run_command($SECONDARY_COMMAND); } else { &syslog("SECONDARY_COMMAND \"$SECONDARY_COMMAND\" running, got PID $secondary_command_pid."); #let's now wait about 25 seconds, and then check that our PPP link is up &syslog("Waiting for SECONDARY_COMMAND to come up."); sleep(5); #sometimes PPPD exits because the wavecom modem gets a "NO CARRIER" right away, #so check to see if it's running. If it is, wait another 10 seconds. $secondary_command_pid = &get_pid($SECONDARY_COMMAND); if ( $secondary_command_pid ) { &long_sleep(10); } #get the ppp interface from the pid $SECONDARY_INT = &get_ppp_int_from_pid($secondary_command_pid); #get the PPP interface IP and P-T-P IP address ($SECONDARY_IP, $SECONDARY_IP_GATEWAY) = &get_ppp_ips($SECONDARY_INT); #if we have valid IPs from our PPP interface, then check that it is still #getting traffic through. if ( &check_ipaddress($SECONDARY_IP) && &check_ipaddress($SECONDARY_IP_GATEWAY) ) { #hmmm, our PPP interface is not getting pings....we should restart it if ( &CheckPing($SECONDARY_INT, $SECONDARY_IP_GATEWAY) ) { return 1; } else { #pings not getting through?, kill it and try again? &syslog("Killing SECONDARY_COMMAND PID $secondary_command_pid"); system("$kill_cmd -9 $secondary_command_pid 2>&1 > /dev/null"); } } else { &syslog("SECONDARY_INT doesn't have a valid IP connection yet."); $secondary_command_pid = &get_pid($SECONDARY_COMMAND); if ( !$secondary_command_pid ) { &syslog("SECONDARY_COMMAND is no longer running with PID $secondary_command_pid."); } } } #if we can't get our secondary link up, test the PRIMARY every 2 times so #we can switch back. if ( ( 0 == $counter % 2 ) && &CheckPing($PRIMARY_INT, $PRIMARY_IP_GATEWAY) ) { &syslog("Primary interface $PRIMARY_INT back up."); &syslog("Adding Primary interface $PRIMARY_INT default route."); system("$route_add $primary_default_route"); $secondary_command_pid = &get_pid($SECONDARY_COMMAND); if ( $secondary_command_pid ) { &syslog("Killing SECONDARY $SECONDARY_INT proccess PID $secondary_command_pid"); system("$kill_cmd -9 $secondary_command_pid 2>&1 > /dev/null"); } return 0; } $counter++; } } #return the PID of a running command, this might not work for #all types of running processes sub get_pid() { my $pid; my @output; my $command = $_[0]; chomp($command); my $ps_cmd_option = "auxw"; return 0 if ( $command =~ /^\s*$/ ); #print "COMMAND: $ps_cmd $ps_cmd_option | $grep_cmd \"$command\" | $grep_cmd -v \"$grep_cmd\"\n"; @output = `$ps_cmd $ps_cmd_option | $grep_cmd "$command" | $grep_cmd -v "$grep_cmd" `; chomp(@output); #return 0 if we got no lines, or more than 1. #how do we handle more than 1? return 0 if ( @output > 1 || 0 == @output ); #foreach(@output) {print "output: $_\n";} #output looks like this: #root 3227 0.0 1.1 2488 740 ttyS0 R 16:35 0:00 pppd call sprint my @tmp = split/\s+/, $output[0]; #foreach(@tmp) {my $counter=0; print "tmp[$counter]: $_\n";$counter++;} ($pid = $tmp[1]) =~ s/[^0-9]//g; return $pid if ($pid); return 0; } #run a system command, and then return the PID sub run_command() { my $pid; my @output; my $command = $_[0]; chomp($command); my $ps_cmd_option = "auxw"; return 0 if ( $command =~ /^\s*$/ ); system("$command 2>&1 > /dev/null &"); #sleep a second sleep(1); #print "COMMAND: $ps_cmd $ps_cmd_option | $grep_cmd \"$command\" | $grep_cmd -v \"$grep_cmd\"\n"; @output = `$ps_cmd $ps_cmd_option | $grep_cmd "$command" | $grep_cmd -v "$grep_cmd" `; chomp(@output); #return 0 if we got no lines, or more than 1. #how do we handle more than 1? return 0 if ( @output > 1 || 0 == @output ); #foreach(@output) {print "output: $_\n";} #output looks like this: #root 3227 0.0 1.1 2488 740 ttyS0 R 16:35 0:00 pppd call sprint my @tmp = split/\s+/, $output[0]; #foreach(@tmp) {my $counter=0; print "tmp[$counter]: $_\n";$counter++;} ($pid = $tmp[1]) =~ s/[^0-9]//g; return $pid if ($pid); return 0; } #takes the interface it was passed, and returns the PPP IP and PTP in an array sub get_ppp_ips() { my $interface = $_[0]; my @ip_list = (); my $ip; my $ptp; my @output = `$ifconfig_cmd $interface 2> /dev/null | $grep_cmd \"inet addr\"`; chomp(@output); if ( !@output ) { return 0; } #output looks like this: #0 1 2 3 4 # inet addr:166.153.187.172 P-t-P:66.174.50.42 Mask:255.255.255.255 my @tmp = split/\s+/,$output[0]; ($ip = $tmp[2]) =~ s/[^0-9\.]//g; ($ptp = $tmp[3]) =~ s/[^0-9\.]//g; push(@ip_list,$ip); push(@ip_list,$ptp); return @ip_list; } #this is used to validate an IP address is within a network block range. sub validate_gateway_in_subnet() { my $IP_GATEWAY; my $IP_MASK; my $IP_NETWORK; my $IP_BROADCAST; my $TEST_BROADCAST; my $TEST_NETWORK; ($IP_GATEWAY, $IP_MASK, $IP_NETWORK, $IP_BROADCAST ) = @_; my $ipcalc_options = "$IP_GATEWAY -n $IP_MASK -n -b"; my @output = `$ipcalc_cmd $ipcalc_options 2> /dev/null`; #output will look something like this: #BROADCAST=192.168.0.255 #NETWORK=192.168.0.0 #no output from the data we were passed. #this means ipcalc returned with an error if ( !@output) { return 0; } foreach(@output) { chomp; my @tmp = split/\=/, $_; if ( $tmp[0] =~ /BROADCAST/i) { return 0 if ( $IP_BROADCAST ne $tmp[1] ); } if ( $tmp[0] =~ /NETWORK/i) { return 0 if ( $IP_NETWORK ne $tmp[1] ); } } return 1; } #looks for /var/run/ppp*.pid files and compares them #with the passed pid number. If they match, it returns the pppN #interface. sub get_ppp_int_from_pid() { my $passed_pid = $_[0]; my $empty = ""; my @file_list = `/bin/ls /var/run/ppp*.pid 2> /dev/null`; foreach(@file_list) { chomp; open(PIDFILE,"<$_") || return $empty; $pid = ; close (PIDFILE); chomp($pid); if ( "$pid" eq "$passed_pid" ) { #strip off the /path/to/file ($interface = $_) =~ s/^[\.\/].*\///g; $interface =~ s/\.pid//g; return $interface; } } return $empty; } #a process for updating $0 for log sleep times so a user #can see that the process is still running with ps -auxw sub long_sleep() { my $delay = $_[0]; my $orig_program_name = $0; $delay =~ s/[^0-9]//g; if ( ! int($delay) ) { sleep(1); return; } if ( 5 > int($delay) ) { $0 = $orig_program_name . " : sleeping for $delay second(s)"; sleep($delay); } else { $0 = $orig_program_name . " : sleeping for $delay seconds."; for( my $i=0; $i <= $delay; $i++ ) { my $sleeping = $delay - $i; if ( 0 == $sleeping % 5 ) { $0 = $orig_program_name . " : sleeping for $sleeping seconds."; } sleep(1); } } $0 = $orig_program_name; return; } #check for a pid file, and/or write it sub write_pid() { my $last_pid; my $pid_file = "/var/run/ipfallback.pid"; my $prog_name = &RealName; my @proc = grep(s/^\s*(\d*)\s*\S*\s*\S*\s(\S*).*$/"$1:$2"/,`/bin/ps -e`); if ( -e "$pid_file" ) { open(PIDFILE,"$pid_file"); $last_pid= ; close (PIDFILE); if ( grep(/"$last_pid:$prog_name"/,@proc) ) { syslog("$prog_name already running with PID $last_pid\n"); exit(1); } } open(PIDFILE,">$pid_file"); print PIDFILE $$; close(PIDFILE); } #routine for sending an snmptrap for a DOWN trap state sub send_snmptrap_down() { return if ( ! $use_snmptrap ); my $message = $_[0]; chomp $message; return if ( $message =~ /^\s*$/ ); &syslog("Attempting to send snmp trap : ") if ($debug_level); &syslog(" $snmptrap_down $message") if ($debug_level); system("$snmptrap_down \"$message\" 2>&1 > /dev/null"); } #routine for sending an snmptrap for a UP trap state sub send_snmptrap_up() { return if ( ! $use_snmptrap ); my $message = $_[0]; chomp $message; return if ( $message =~ /^\s*$/ ); &syslog("Attempting to send snmp trap : ") if ($debug_level); &syslog(" $snmptrap_up $message") if ($debug_level); system("$snmptrap_up \"$message\" 2>&1 > /dev/null"); } #routine for removing default routes sub remove_default_routes() { #get default routes and remove them since we are getting ready #to switch back to the PRIMARY, or SECONDARY my $temp = `$route_cmd -n 2> /dev/null`; my @route_table = split/\n/, $temp; foreach(@route_table) { chomp; #turn multiple spaces into just 1 for syslog display $_ =~ s/\s+/ /g; #print "route table: $_\n" if ($debug_level); if ( $_ =~ /^0\.0\.0\.0.*$/ ) { #&syslog("Default route found: \"$_\"") if ($debug_level); my @tmp = split/\s+/, $_; &syslog("Removing default route to gw $tmp[1]") if ($debug_level); system("$route_cmd del default gw $tmp[1]"); } } }