OpenWrt Forum Archive

Topic: [howto] Failover Capability script between two wan (DSL and 3G).

The content of this topic has been archived between 20 Apr 2018 and 22 Apr 2018. There are no obvious gaps in this topic, but there may still be some posts missing at the end.

Hello,



Problem:
Internet connection from my DSL provider sometimes collapses. So, I am using 3G modem (best option for me) for a buck-up.
I want to configure my router to automatic switches between DSL and 3G.
When DSL connection is collapsed it switch to 3G but when is working again it switch back to DSL. But it's one condition: 3G connection cannot be used (because of costs) when DSL is working.

I tried multwan but unfortunately it isn't good for me. 3G modem is always connected to Internet and there is traffic on ppp0 interface (UDP, SIP eg. from VoIP devices) even if DSL connection it set up as a default route.

I wrote simple and ugly script solving my problem:


/etc/init.d/failover

#!/bin/sh /etc/rc.common
START=90
start() {
    /usr/bin/failover.sh &
}

stop() {
    PID=`ps | awk '/failover.sh/ {print $1}'`
    kill -9 $PID
}

/usr/bin/failover.sh

#!/bin/sh 

INTERVAL=1
PACKETS=1
HOST="8.8.4.4"
WAN1=wan
WAN2=wan2
USINGWAN=1;
LOG="/root/failover.log"

echo "`date`: Failover script started." >> $LOG
while sleep $INTERVAL
do
        RET=`ping -w 2 -c $PACKETS $HOST 2>/dev/null | awk '/packets received/ {print $4}'`
        if [ "$RET" -ne "$PACKETS" ]; then
                if [ "$USINGWAN" = "1" ]; then
                        ifup $WAN2
                        USINGWAN=2
                        echo "`date`: Changed active WAN port to 3G modem!" >> $LOG
                fi
        else
            if [ "$USINGWAN" = "2" ]; then
                    ifdown $WAN2
                    echo "nameserver 8.8.8.8" > /etc/resolv.conf
                    USINGWAN=1
                    echo "`date`: Changed active WAN port to DSL connection!" >> $LOG
                fi
        fi 
done;

And add new route to 8.8.4.4 via DSL connection
Edit /etc/config/network add at the end:

config 'route'
    option 'interface' 'wan'
    option 'target' '8.8.4.4'
    option 'netmask' '255.255.255.255'

Now make this files executable:
> chmod +x /etc/init.d/failover
> chmod +x /usr/bin/failover.sh

Enable and start the script
> /etc/init.d/failover enable
> /etc/init.d/failover start

Script saves log file in root's directory (/root/failover.log).

For me it usually take about 15-20 seconds to switch to 3G modem and about 5 seconds to back to DSL.

regards,
mateuszkj

nice script!

which router where you usign?

i'm also looking for this kind of flavour: xDSL + 3G, with any kind of "failover"

thanks!
t

Thanks smile

It was Tp-Link TL-WR1043ND. A year ego it was the cheapest router I found with USB, Gigabit Ethernet and has OpenWRT support.

Works nicely :)

But the log file could grow a bit after a while, maybe piping the messages to "logger" would be safer on smaller embedded devices?

Something like: echo "bla, bla..." | logger

Exellent, it could be easily modified and some other useful features could be also added.
I would add the static route directly in the script, so we can change the monitored host, etc dynamicaly.

Could you recommend a cheap 3G usb modem that is well supported and works good (model, chipser, driver used, etc.)?

Thanks for the idea. I modified and cleaned this up (IMHO) to suit my purposes. Note that, in my topology, $WAN1 is configured by DHCP, so no need for "#echo "nameserver $DEFAULT_GATEWAY" > /etc/resolv.conf". Modified failover.sh:

#!/bin/sh
# !!! This script assumes that $WAN1 is available when first started !!!

WAN1=wan
WAN2=ppp0

INTERVAL=2
PACKETS=1
logger="logger -p daemon.info `date`: $0 $@"

$logger "started"

# If your N/W topology changes, need to update $DEFAULT_GATEWAY
# for new topology. Do this: uci delete network.wan.gateway

DEFAULT_GATEWAY=`uci show network.wan.gateway 2>/dev/null | awk -F = '{print $2}'`
WAN1_IF=`uci show network.$WAN1.ifname | awk -F = '{print $2}'`
WAN2_IF=`uci show network.$WAN2.ifname | awk -F = '{print $2}'`

ifdown $WAN2
ifup $WAN1
USINGWAN=1

GATEWAY=
WAN1_IP=
COUNTER=0
# Need to wait for $WAN1 interface up, to establish $WAN1 $GATEWAY
while [ $WAN1_IP == ""] || [ $GATEWAY == "" ]; do
    $logger "Waiting for interface $WAN1 IP"
    WAN1_IP=`ifconfig $WAN1_IF | awk '{if($1=="inet")print $2}' | awk -F : '{print $2}'`
    GATEWAY=`route -n | grep 'G[ \t]' | awk '{print $2}'`
    let COUNTER=COUNTER+1
    if [ $COUNTER -gt 9 ]; then
        # Looks like WAN1 is AWOL, set GATEWAY, enable WAN2
        $logger "Interface $WAN1 is down, enabling interface $WAN2"
        GATEWAY=$DEFAULT_GATEWAY
        ifup $WAN2
        USINGWAN=2
        break
    fi
    sleep $INTERVAL
done

sleep $INTERVAL

WAN2_IP=`ifconfig | grep P-t-P: | awk -F : '{print $2}' | awk '{print $1}'`
WAN2_GW=`ifconfig | grep P-t-P: | awk -F : '{print $3}' | awk '{print $1}'`

# Assume that very first time script run -ever, that $WAN1 is available
# to set $DEFAULT_GATEWAY for subsequent reboots when $WAN1 is not available
if [ "$DEFAULT_GATEWAY" == "" ]; then
    $logger "Setting default gateway to $GATEWAY"
    uci set network.wan.gateway=$GATEWAY
fi

$logger "GATEWAY: $GATEWAY, DEFAULT_GATEWAY: $DEFAULT_GATEWAY, WAN1_IF: $WAN1_IF, WAN1_IP: $WAN1_IP, WAN2_IF: $WAN2_IF, WAN2_IP: $WAN2_IP"

while sleep $INTERVAL
do
    RET=`ping -w 2 -c $PACKETS $GATEWAY 2>/dev/null | awk '/packets received/ {print $4}'`
    #$logger "GATEWAY: $GATEWAY, PACKETS: $PACKETS, RET: $RET"
    if [ "$RET" != "$PACKETS" ]; then
        if [ "$USINGWAN" == "1" ]; then
            $logger "Changing active WAN interface to $WAN2!"
            ifup $WAN2
            USINGWAN=2
        fi
    else
        if [ "$USINGWAN" == "2" ]; then
            $logger "Changing active WAN interface to $WAN1!"
            ifdown $WAN2
            #echo "nameserver $DEFAULT_GATEWAY" > /etc/resolv.conf
            USINGWAN=1
        fi
    fi
done;

am also soliciting ideas on how to do:

/etc/init.d/failover start when 3g dongle inserted

and

/etc/init.d/failover stop when 3g dongle removed

assume, using hotplug.d

Very nice scripts! I have a bit of a problem though. My toplogy is much like rossb, with my private router fed by DHCP from a upstream ADSL capable router and a 3G back-up. However, the ADSL capable router sometimes loose internet connection but with LAN still is "alive", rendering the WAN1 active. This leads to that failover back to WAN1 from WAN2(3G) only happends when I disconnect the connection to the upstream ADSL router from my private router. Is it possible to modify either script with this taken into account?   

Thanks!

(Last edited by Lennong on 26 Jan 2012, 09:52)

This is my Failover Script, working together with a cable-modem (dhcp with changing gateway-ip´s) and 3G HSUPA Stick.
It is pinging the Gateway-IP and two Hosts in the Internet.
WAN2 is going online if both Hosts or the Gateway is offline.
The Gateway-IP (WAN1) is found automatically and can be offline at systemstart.
Simple Instructions to install on a TPLINK WR1043ND with a Huawei E353 (3.se) can be found on my page http://christian1980nrw.de/wordpress/
Thanks for trying and improving my code.
Christian

#!/bin/sh

WAN1=wan
WAN2=wwan
# UMTS-Modem to send reset-command
MODEM=/dev/ttyUSB2
# can be /dev/ttyUSB0 too, open terminal and enter cat /dev/ttyUSB0
# open other terminal and enter echo -e "ATZ\r" > /dev/ttyUSB0
# If Modem answers "OK", its the correct port.

# Example: My Providers DNS Server  as external Pinghost (should be a IP Adress because maybe WAN1 is already offline at System-Start, so DNS ist not working and Route to host cannot be set)
PINGHOST=80.69.100.174
# Example: External Provider DNS Server (can be a hostname)
PINGHOST2=dns.hosteurope.de
# Both Offline --> Switching to WAN2

INTERVAL=2
ROUTERPACKETS=1
PINGHOST1PACKETS=1
PINGHOST2PACKETS=1

logger="logger -p daemon.info `date`: $0 $@"

WAN1_IF=`uci get network.$WAN1.ifname`

ifdown $WAN2
ifup $WAN1
USINGWAN=1
sleep $INTERVAL

GATEWAY=`route -n | grep $WAN1_IF | grep -v 255.* | awk '{print $2}'`
if [ "$GATEWAY" != "" ]; then
route add -net $PINGHOST netmask 255.255.255.255 gw $GATEWAY dev $WAN1_IF
route add -net $PINGHOST2 netmask 255.255.255.255 gw $GATEWAY dev $WAN1_IF
$logger "FAILOVER SCRIPT started. Gateway: $GATEWAY, WAN1_IF: $WAN1_IF"
else
$logger "FAILOVER SCRIPT started. WAN1 Gateway not found. Maybe offline. WAN1_IF: $WAN1_IF"
fi
. /usr/lib/ddns/dynamic_dns_functions.sh # deactivate if u dont need dyndns-support
while sleep $INTERVAL
do
PINGGATEWAY=`ping -w 2 -c $ROUTERPACKETS $GATEWAY 2>/dev/null | awk '/packets received/ {print $4}'`
PINGING=`ping -w 2 -c $PINGHOST1PACKETS $PINGHOST 2>/dev/null | awk '/packets received/ {print $4}'`
PINGING2=`ping -w 2 -c $PINGHOST2PACKETS $PINGHOST2 2>/dev/null | awk '/packets received/ {print $4}'`
if [[ "$PINGGATEWAY" == "$ROUTERPACKETS" || "$TESTGATEWAY" == "" ]]; then
    if [[ "$PINGING" != "$PINGHOST1PACKETS" && "$PINGING2" != "$PINGHOST2PACKETS" ]]; then
    if  [ "$USINGWAN" == "1" ]; then
        $logger "IP $PINGHOST and IP $PINGHOST2 down. Activating $WAN2 and refreshing Gateway-IP $GATEWAY."
        # Reset UMTS-Modem
        echo -e "ATZ\r" > $MODEM
        ifup $WAN2
        USINGWAN=2
        GATEWAY=""
        route delete $PINGHOST
        route delete $PINGHOST2
        start_daemon_for_all_ddns_sections "wan" # deactivate if u dont need dyndns-support
    fi
    else
    if [ "$USINGWAN" == "2" ]; then
        $logger "Changing active WAN interface to $WAN1! and refreshing Gateway-IP $GATEWAY."
        ifdown $WAN2
        ifup $WAN1
        USINGWAN=1
        GATEWAY=""
        #Routes got deleted by ifdown / ifup commands, so route delete not necessary.
        start_daemon_for_all_ddns_sections "wan" # deactivate if u dont need dyndns-support
    fi
    fi
else
        if [ "$USINGWAN" == "1" ]; then
        $logger "Gateway $GATEWAY down. Activating $WAN2 and refreshing Gateway-IP $GATEWAY."
        # Reset UMTS-Modem
        echo -e "ATZ\r" > $MODEM
        ifup $WAN2
        USINGWAN=2
        GATEWAY=""
        route delete $PINGHOST
        route delete $PINGHOST2
        start_daemon_for_all_ddns_sections "wan" # deactivate if u dont need dyndns-support
    fi
fi
# GET WAN1 GATEWAY (IP CAN BE CHANGED SOMETIMES BY PROVIDERS DHCP) & PREPARE PINGHOST-ROUTES FOR PINGING, NEVER OVER WAN2 GATEWAY
TESTGATEWAY=`route -n | grep $WAN1_IF | grep -v 255.* | awk '{print $2}'`
if [[ "$TESTGATEWAY" != "$GATEWAY" && "$TESTGATEWAY" != "" ]]; then
GATEWAY=`route -n | grep $WAN1_IF | grep -v 255.* | awk '{print $2}'`
route add -net $PINGHOST netmask 255.255.255.255 gw $GATEWAY dev $WAN1_IF
route add -net $PINGHOST2 netmask 255.255.255.255 gw $GATEWAY dev $WAN1_IF
$logger "FAILOVER $WAN1 Gateway is now: $GATEWAY"
fi
done;

(Last edited by christian1980nrw on 24 Jul 2012, 11:30)

Some suggestions for improvement:

WAN1_IF=`uci show network.$WAN1.ifname | awk -F = '{print $2}'`

to

WAN1_IF=`uci get network.$WAN1.ifname`

and

GATEWAY=`route -n | grep $WAN1_IF | grep -v 255.* | awk '{print $2}'`

to

GATEWAY=`route -n | awk "/^0\.0\.0\.0 .+ $WAN1_IF\$/ { print \$2 }"`

or even just

GATEWAY=`uci -P/var/state get network.$WAN1.gateway`

I'll also move this to "howtos & Documentation" as it does not belong into the whiterussian forum.

or even just

GATEWAY=`uci -P/var/state get network.$WAN1.gateway`

--> this is not possible because the Gateway is set by dhcp.
So uci will say: Entry not found

Made some changes . Script seems to work perfect now.
I only have problems with the UMTS-Stick.
After a reboot the stick works fine.
Any Ideas?

Jul 23 16:49:57 smtp daemon.info root: Mon Jul 23 16:47:58 CEST 2012: /etc/failover.sh Gateway 78.94.176.1 down. Activating wwan and refreshing Gateway-IP 78.94.176.1.
Jul 23 16:49:57 smtp daemon.info root: Mon Jul 23 16:47:58 CEST 2012: /etc/failover.sh FAILOVER wan Gateway is now: 78.94.176.1
Jul 23 16:49:59 smtp daemon.notice pppd[21531]: pppd 2.4.5 started by root, uid 0
Jul 23 16:50:00 smtp local2.info chat[21538]: abort on (BUSY)
Jul 23 16:50:00 smtp local2.info chat[21538]: abort on (NO CARRIER)
Jul 23 16:50:00 smtp local2.info chat[21538]: abort on (ERROR)
Jul 23 16:50:00 smtp local2.info chat[21538]: report (CONNECT)
Jul 23 16:50:00 smtp local2.info chat[21538]: timeout set to 10 seconds
Jul 23 16:50:00 smtp local2.info chat[21538]: send (AT&F^M)
Jul 23 16:50:10 smtp local2.info chat[21538]: alarm
Jul 23 16:50:10 smtp local2.info chat[21538]:  -- write timed out
Jul 23 16:50:10 smtp local2.err chat[21538]: Failed
Jul 23 16:50:10 smtp daemon.err pppd[21531]: Connect script failed

EDIT:
Im sending a reset-command to the modem now. Script updated. Maybe this helps.

(Last edited by christian1980nrw on 24 Jul 2012, 07:30)

christian1980nrw wrote:

or even just

GATEWAY=`uci -P/var/state get network.$WAN1.gateway`

--> this is not possible because the Gateway is set by dhcp.
So uci will say: Entry not found

The backfire dhcp client will put the active gateway into a uci state var (therfore -P/var/state).
In trunk this state var does not exist anymore, you can obtain the gateway with:

. /lib/functions/network.sh
network_get_gateway GATEWAY "$WAN1"

...added instant Dyndns-Update to the script.

Have Fun!
Christian

can anyone make a howto for loadbalancing dual wan....

The script which is posted on your blog is outdated and doesn't work, i think you should update to the newest version there as well smile


And on my TL-WR1043ND the script doesn't work, i can start it but when i pull out the DSL-cable, the router reboots (10.03.1) and after a reboot, the script doesn't run anymore and both 'wan' and 'wwan' interfaces are shut down. Any ideas?

(Last edited by Thomymaster on 17 Aug 2012, 15:14)

My failover solution (wired WAN + 3G dongle):

#!/bin/sh
touch /bin/wan_failover.sh
chmod 755 /bin/wan_failover.sh

echo '#!/bin/sh

. /etc/functions.sh
include /lib/network
scan_interfaces

config_get WAN wan ifname
config_get WAN_GATEWAY wan gateway
config_get WAN2 wan2 ifname

TARGET_IP=8.8.8.8

if ! ping -q -c 1 -W 10 -I $WAN $WAN_GATEWAY > /dev/null; then
 if ! ping -q -c 1 -W 10 -I $WAN2 $TARGET_IP > /dev/null; then
  ifup wan2
 fi
else
 if ping -q -c 1 -W 10 -I $WAN2 $TARGET_IP > /dev/null; then
  ifdown wan2
  ifup wan
 fi
fi' > /bin/wan_failover.sh

/etc/init.d/cron stop
echo "*/2 * * * * /bin/wan_failover.sh" >> /etc/crontabs/root
/etc/init.d/cron enable
/etc/init.d/cron start

fixed:
if ping -q -c 1 -W 10 -I $WAN2 $TARGET_IP > /dev/null; then
  ifdown wan2
fi

to
if ping -q -c 1 -W 10 -I $WAN2 $TARGET_IP > /dev/null; then
  ifdown wan2
  ifup wan
fi

(Last edited by botch on 30 Nov 2012, 13:01)

Hrmpf. I want to try the failover.sh from posting #6, but after starting /etc/init.d/failover start I get:

~# /etc/init.d/failover start
root@station:~# sh: ==: argument expected
sh: : unknown operand

show yours /etc/init.d/failover and /usr/bin/failover.sh

Thats exactly that from here. I think I have another problem:
To access my VDSL-Modem at the WAN-interface I've got some more settings in /etc/network:

config 'interface' 'wan'
        option 'ifname' 'eth1'
        option '_orig_ifname' 'eth1'
        option '_orig_bridge' 'false'
        option 'proto' 'pppoe'
        option 'username' '1@de.de'
        option 'password' '1'

config 'interface' 'modem'
        option 'proto' 'static'
        option 'ifname' 'eth1'
        option 'ipaddr' '192.168.150.5'
        option 'netmask' '255.255.255.248'

This is what sh -x /usr/bin/failover.sh puts out:

[...]
+ logger -p daemon.info Mon Sep 16 19:58:20 CEST 2013: /usr/bin/failover.sh Waiting for interface wan IP
+ + awkawk {if($1=="inet")print $2} -F
 : {print $2}
+ ifconfig eth1
+ WAN1_IP=192.168.150.5
+ route -n
+ awk {print $2}
+ grep G[ \t]
+ GATEWAY=
+ let COUNTER=COUNTER+1
+ [ 1 -gt 9 ]
+ sleep 2
+ [ 192.168.150.5 == ]
sh: ==: argument expected
+ [ ==  ]
sh: : unknown operand
+ sleep 2
[...]

I think the script has to suit to my circumstances, perhaps you can help?

(Last edited by naturelle on 16 Sep 2013, 19:35)

it has one typo:

# Need to wait for $WAN1 interface up, to establish $WAN1 $GATEWAY
while [ $WAN1_IP == ""] || [ $GATEWAY == "" ]; do

it misses a space:

# Need to wait for $WAN1 interface up, to establish $WAN1 $GATEWAY
while [ $WAN1_IP == "" ] || [ $GATEWAY == "" ]; do

I don't think that the missing blank is the problem.

I tried it before i posted it wink
sh: ==: argument expected
this is triggered by the missing blank

Ok. But after some testing I got:

~# sh -x /usr/bin/failover.sh 
+ WAN1=wan
+ WAN2=ppp0
+ INTERVAL=2
+ PACKETS=1
+ date
+ logger=logger -p daemon.info Tue Sep 17 00:28:26 CEST 2013: /usr/bin/failover.sh 
+ logger -p daemon.info Tue Sep 17 00:28:26 CEST 2013: /usr/bin/failover.sh started
+ awk -F = {print $2}
+ uci show network.wan.gateway
+ DEFAULT_GATEWAY=interface
eth1
eth1
false
pppoe
xxxx@xxxx.de
xxxx
+ awk -F = {print $2}
+ uci show network.wan.ifname
+ WAN1_IF=eth1
+ awk -F = {print $2}
+ uci show network.ppp0.ifname
+ WAN2_IF=ppp0
+ ifdown ppp0
+ ifup wan
+ USINGWAN=1
+ GATEWAY=
+ WAN1_IP=
+ COUNTER=0
+ [ ==  ]
sh: : unknown operand
+ [ ==  ]
sh: : unknown operand
+ sleep 2
+ + awk -F : {print $2}
grep P-t-P:
+ awk {print $1}
+ ifconfig
+ WAN2_IP=
+ + awkawk -F {print $1} :
 {print $3}
+ grep P-t-P:
+ ifconfig
+ WAN2_GW=
+ [ interface
eth1
eth1
false
pppoe
xxxx@xxxx.de
xxxx ==  ]
+ logger -p daemon.info Tue Sep 17 00:28:26 CEST 2013: /usr/bin/failover.sh GATEWAY: , DEFAULT_GATEWAY: interface
eth1
eth1
false
pppoe
xxxx@xxxx.de
xxxx, WAN1_IF: eth1, WAN1_IP: , WAN2_IF: ppp0, WAN2_IP: 
+ sleep 2
^C

Does not look really better. Next problem is that /dev/ttyUSB0 and 1 are not generated if stick is connectec to USB while booting the router hmm

argh should pay more attention hmm

while [ $WAN1_IP == ""] || [ $GATEWAY == "" ]; do

should be:

while [ "$WAN1_IP" == "" ] || [ "$GATEWAY" == "" ]; do

otherwise if the values are zero length, it will throw an error. So it was missing one blank and 4 double quotes.
Because this was incorrect it had troubles.

also you can hold the script till the tty device is availlable.

 while [ ! -e "/dev/ttyUSB0" ]; do sleep 5; done

this snippet will wait till the file exists
or you could write something to wait for the network devices.

(Last edited by FriedZombie on 17 Sep 2013, 00:14)