#!/bin/sh

. /etc/functions.sh

silencer() {
    if [ -z "$debug" -o "$debug" == "0" ]; then
	$* > /dev/null 2>&1
    else
	$*
    fi
}

mwnote() {
    logger ${debug:+-s} -p 5 -t multiwan "$1"
}

failover() {
    local failchk=$(query_config failchk $2)
    local recvrychk=$(query_config recvrychk $2)

    local wanid=$(query_config wanid $2)
    local failover_to=$(uci_get_state multiwan ${2} failover_to)
    local failover_to_wanid=$(query_config wanid $failover_to)

    local existing_failover=$(iptables -n -L FW${wanid}MARK -t mangle | echo $(($(wc -l) - 2)))

    add() {

	wan_fail_map=$(echo $wan_fail_map | sed -e "s/${1}\[${failchk}\]//g")
	wan_fail_map="$wan_fail_map${1}[x]"
	wan_recovery_map=$(echo $wan_recovery_map | sed -e "s/${1}\[${recvrychk}\]//g")
	update_cache

	if [ "$existing_failover" == "2" ]; then
	    if [ "$failover_to" != "balancer" -a "$failover_to" != "fastbalancer" -a "$failover_to" != "disable" -a "$failover_to_wanid" != "$wanid" ]; then
		iptables -I FW${wanid}MARK 2 -t mangle -j FW${failover_to_wanid}MARK
	    elif [ "$failover_to" == "balancer" ]; then
		iptables -I FW${wanid}MARK 2 -t mangle -j LoadBalancer
	    elif [ "$failover_to" == "fastbalancer" ]; then
		iptables -I FW${wanid}MARK 2 -t mangle -j FastBalancer
	    fi
	fi
	mwnote "$1 has failed and is currently offline."
    }

    del() {

	wan_recovery_map=$(echo $wan_recovery_map | sed -e "s/${1}\[${recvrychk}\]//g")
	wan_fail_map=$(echo $wan_fail_map | sed -e "s/${1}\[${failchk}\]//g")
	update_cache

	if [ "$existing_failover" == "3" ]; then
	    iptables -D FW${wanid}MARK 2 -t mangle
	fi
	mwnote "$1 has recovered and is back online!"
    }

    case $1 in 
	add) add $2;;
	del) del $2;;
    esac
}

fail_wan() {
    local new_fail_count

    local health_fail_retries=$(uci_get_state multiwan ${1} health_fail_retries)
    local weight=$(uci_get_state multiwan ${1} weight)

    local failchk=$(query_config failchk $1)
    local recvrychk=$(query_config recvrychk $1)
    wan_recovery_map=$(echo $wan_recovery_map | sed -e "s/${1}\[${recvrychk}\]//g")

    if [ -z "$failchk" ]; then
	failchk=1
	wan_fail_map="$wan_fail_map${1}[1]"
    fi

    if [ "$failchk" != "x" ]; then
	new_fail_count=$(($failchk + 1))
	if [ "$new_fail_count" -lt "$health_fail_retries" ]; then
	    wan_fail_map=$(echo $wan_fail_map | sed -e "s/${1}\[${failchk}\]/$1\[${new_fail_count}\]/g")
	else
	    failover add $1
	    refresh_dns
	    if [ "$weight" != "disable" ]; then
		refresh_loadbalancer
	    fi
	fi
    fi
    update_cache
}

recover_wan() {
    local new_fail_count

    local health_recovery_retries=$(uci_get_state multiwan ${1} health_recovery_retries)
    local weight=$(uci_get_state multiwan ${1} weight)

    local failchk=$(query_config failchk $1)
    local recvrychk=$(query_config recvrychk $1)
    local wanid=$(query_config wanid $1)

    if [ ! -z "$failchk" -a "$failchk" != "x" ]; then
	wan_fail_map=$(echo $wan_fail_map | sed -e "s/${1}\[${failchk}\]//g")
	update_cache
    fi

    if [ "$failchk" == "x" ]; then
	if [ -z "$recvrychk" ]; then
	    wan_recovery_map="$wan_recovery_map${1}[1]"
	    update_cache
	    if [ "$health_recovery_retries" == "1" ]; then
		recover_wan $1
	    fi
	else
	    new_recovery_count=$(($recvrychk + 1))
	    if [ "$new_recovery_count" -lt "$health_recovery_retries" ]; then
		wan_recovery_map=$(echo $wan_recovery_map | sed -e "s/${1}\[${recvrychk}\]/$1\[${new_recovery_count}\]/g")
		update_cache
	    else
		failover del $1
		refresh_dns
		if [ "$weight" != "disable" ]; then
		    refresh_loadbalancer
		fi
	    fi
	fi
    fi
}

acquire_wan_data() {
    local check_old_map
    local get_wanid
    local old_ifname
    local old_ipaddr
    local old_gateway

    local ifname=$(uci_get_state network ${1} ifname 'x')
    local ipaddr=$(uci_get_state network ${1} ipaddr 'x')
    local gateway=$(uci_get_state network ${1} gateway 'x')

    check_old_map=$(echo $wan_id_map 2>&1 | grep -o "$1\[")

    if [ -z $check_old_map ]; then
	wancount=$(($wancount + 1))
	if [ $wancount -gt 20 ]; then
	    wancount=20
	    return
	fi
	wan_if_map="$wan_if_map${1}[${ifname}]"
	wan_id_map="$wan_id_map${1}[${wancount}]"
	wan_gw_map="$wan_gw_map${1}[${gateway}]"
	wan_ip_map="$wan_ip_map${1}[${ipaddr}]"
    else
	old_ipaddr=$(query_config ipaddr $1)
	old_gateway=$(query_config gateway $1)
	old_ifname=$(query_config ifname $1)
	get_wanid=$(query_config wanid $1)

	wan_if_map=$(echo $wan_if_map | sed -e "s/${1}\[${old_ifname}\]/$1\[${ifname}\]/g")
	wan_ip_map=$(echo $wan_ip_map | sed -e "s/${1}\[${old_ipaddr}\]/$1\[${ipaddr}\]/g")
	wan_gw_map=$(echo $wan_gw_map | sed -e "s/${1}\[${old_gateway}\]/$1\[${gateway}\]/g")

	if [ "$old_ifname" != "$ifname" ]; then
	    iptables -D MultiWanPreHandler -t mangle -i $old_$ifname -m state --state NEW -j FW${get_wanid}MARK
	    iptables -A MultiWanPreHandler -t mangle -i $ifname -m state --state NEW -j FW${get_wanid}MARK 
	    iptables -D MultiWanPostHandler -t mangle -o $old_$ifname -m mark --mark 0x1 -j FW${get_wanid}MARK
	    iptables -A MultiWanPostHandler -t mangle -o $ifname -m mark --mark 0x1 -j FW${get_wanid}MARK 
	fi 

	if [ "$ifname" != "x" -a "$ipaddr" != "x" -a "$gateway" != "x" ]; then
	    failover del $1
	    iprules_config $get_wanid
	else
	    failover add $1
	fi

	refresh_routes
	refresh_loadbalancer
	refresh_dns
	update_cache
    fi
}

update_cache() {
    if [ ! -d /tmp/.mwan ]; then
	mkdir /tmp/.mwan > /dev/null 2>&1
    fi

    rm /tmp/.mwan/cache > /dev/null 2>&1
    touch /tmp/.mwan/cache

    echo "# Automatically Generated by Multi-WAN Agent Script. Do not modify or remove. #" > /tmp/.mwan/cache
    echo "wan_id_map=\"$wan_id_map\"" >> /tmp/.mwan/cache
    echo "wan_if_map=\"$wan_if_map\"" >> /tmp/.mwan/cache
    echo "wan_ip_map=\"$wan_ip_map\"" >> /tmp/.mwan/cache
    echo "wan_gw_map=\"$wan_gw_map\"" >> /tmp/.mwan/cache
    echo "wan_fail_map=\"$wan_fail_map\"" >> /tmp/.mwan/cache
    echo "wan_recovery_map=\"$wan_recovery_map\"" >> /tmp/.mwan/cache
    echo "wan_monitor_map=\"$wan_monitor_map\"" >> /tmp/.mwan/cache
}

query_config() {
    case $1 in
	ifname) echo $wan_if_map | grep -o "$2\[\w*.*\]" | awk -F "[" '{print $2}' | awk -F "]" '{print $1}';; 
	ipaddr) echo $wan_ip_map | grep -o "$2\[\w*.*\]" | awk -F "[" '{print $2}' | awk -F "]" '{print $1}';;
	gateway) echo $wan_gw_map | grep -o "$2\[\w*.*\]" | awk -F "[" '{print $2}' | awk -F "]" '{print $1}';;
	wanid) echo $wan_id_map | grep -o "$2\[\w*.*\]" | awk -F "[" '{print $2}' | awk -F "]" '{print $1}';;
	failchk) echo $wan_fail_map | grep -o "$2\[\w*.*\]" | awk -F "[" '{print $2}' | awk -F "]" '{print $1}';;
	recvrychk) echo $wan_recovery_map | grep -o "$2\[\w*.*\]" | awk -F "[" '{print $2}' | awk -F "]" '{print $1}';;
	monitor) echo $wan_monitor_map | grep -o "$2\[\w*.*\]" | awk -F "[" '{print $2}' | awk -F "]" '{print $1}';;
	group) echo $wan_id_map | grep -o "\w*\[$2\]" | awk -F "[" '{print $1}';;
    esac
}

mwan_kill() {
    local otherpids=$(ps 2>&1 | grep 'multiwan agent' | grep -v $$ | awk '{print $1}')
    [ -n "$otherpids" ] && kill $otherpids > /dev/null 2>&1
    sleep 2
}

# For system shutdownl: stop
#   A plain stop will leave network in a limp state, without wan access
# stop single: restore to a single wan
# stop restart: restart multiple wan's
stop() {
    mwan_kill
    flush $1

    if [ "$1" == "single" ]; then
	# ifup is quite expensive--do it only when single wan is requested
	echo "## Refreshing Interfaces ##"
	local i=0
	while [ $((i++)) -lt $wancount ]; do 
	    local group=$(query_config group $i)
	    ifup $group >&- 2>&- && sleep 1
	done

	echo "## Unloaded, updating syslog and exiting. ##"
	mwnote "Succesfully Unloaded on $(exec date -R)."
	rm -fr /tmp/.mwan >&- 2>&-
    fi
    ip route flush cache

    if [ "$1" == "restart" ]; then
	echo "## Restarting Multi-WAN. ##"
	mwnote "Reinitializing Multi-WAN Configuration."
	rm -fr /tmp/.mwan >&- 2>&-
	/etc/init.d/multiwan start >&- 2>&-
    fi

    exit
}

clear_rules() {
    local restore_single=$1
    local group 

    iptables -t mangle -F PREROUTING 
    iptables -t mangle -F FORWARD
    iptables -t mangle -F POSTROUTING
    iptables -t mangle -F OUTPUT
    iptables -t mangle -F MultiWan
    iptables -t mangle -X MultiWan
    iptables -t mangle -F MultiWanRules
    iptables -t mangle -X MultiWanRules
    iptables -t mangle -F MultiWanDNS
    iptables -t mangle -X MultiWanDNS
    iptables -t mangle -F MultiWanPreHandler
    iptables -t mangle -X MultiWanPreHandler
    iptables -t mangle -F MultiWanPostHandler
    iptables -t mangle -X MultiWanPostHandler
    iptables -t mangle -F LoadBalancer
    iptables -t mangle -X LoadBalancer
    iptables -t mangle -F FastBalancer
    iptables -t mangle -X FastBalancer
    iptables -t mangle -F MultiWanLoadBalancer
    iptables -t mangle -X MultiWanLoadBalancer

    local i=0
    while [ $((i++)) -lt $wancount ]; do 
	iptables -t mangle -F FW${i}MARK
	iptables -t mangle -X FW${i}MARK
    done

    if [ ! -z "$CHKFORQOS" ]; then
	iptables -t mangle -F MultiWanQoS
	iptables -t mangle -X MultiWanQoS

	i=0
	while [ $((i++)) -lt $wancount ]; do 
	    group=$(query_config group $i)
	    iptables -t mangle -F qos_${group}
	    iptables -t mangle -F qos_${group}_ct
	    iptables -t mangle -X qos_${group}
	    iptables -t mangle -X qos_${group}_ct
	done
    fi

    [ "$restore_single" == 'single' ] &&
	/etc/init.d/qos restart > /dev/null 2>&1
}

qos_init() {
    local ifname
    local queue_count
    local get_wan_tc
    local get_wan_iptables
    local add_qos_iptables
    local add_qos_tc
    local execute
    local iprule
    local qos_if_test

    ifname=$(query_config ifname $1)

    if [ "$ifname" == "x" ]; then
	return
    fi

    qos_if_test=$(echo $qos_if_done | grep $ifname.)

    if [ ! -z "$qos_if_test" ]; then
	return
    fi

    qos_if_done=$(echo ${qos_if_done}.${ifname})

    queue_count=$(tc filter list dev $ifname | tail -n 1 | awk -F " " '{print $10}' | sed "s/0x//g")

    if [ -z "$queue_count" ]; then
	return
    fi

    queue_count=$(($queue_count + 1))

    iptables -t mangle -N qos_${1}
    iptables -t mangle -N qos_${1}_ct

    get_wan_tc=$(tc filter list dev $ifname | grep "0x" | sed -e "s/filter /tc filter add dev $ifname /g" -e "s/pref/prio/g" -e "s/fw//g") 
    get_wan_iptables=$(iptables-save | egrep  '(-A Default )|(-A Default_ct )' | grep -v "MultiWanQoS" | sed -e "s/Default /qos_${1} /g" -e "s/Default_ct /qos_${1}_ct /g" -e "s/-A/iptables -t mangle -A/g")


    local i=0
    while [ $i -lt $queue_count ]; do 
	echo "s/\(0x$i \|0x$i\/0xffffffff\)/0x$(($2 * 10 + $i)) /g" >> /tmp/.mwan/qos.$1.sedfilter
	i=$(($i + 1))
    done

    add_qos_iptables=$(echo "$get_wan_iptables" | sed -f /tmp/.mwan/qos.$1.sedfilter)
    echo "$add_qos_iptables" | while read execute; do ${execute}; done

    rm /tmp/.mwan/qos.$1.sedfilter 
    i=1
    while [ $i -lt $queue_count ]; do 
	echo "s/0x$i /0x${2}${i} fw /g" >> /tmp/.mwan/qos.$1.sedfilter
	i=$(($i + 1))
    done

    add_qos_tc=$(echo "$get_wan_tc" | sed -f /tmp/.mwan/qos.$1.sedfilter)
    echo "$add_qos_tc" | while read execute; do ${execute}; done
    rm /tmp/.mwan/qos.$1.sedfilter

    i=0
    while [ $i -lt $queue_count ]; do
	if [ $i -lt $(($queue_count - 1)) ]; then
	    ip rule add fwmark 0x$(($2 * 10 + $i + 1)) table $(($2 + 170)) prio $(( $2 * 10 + $i + 2))
	fi
	iptables -t mangle -A MultiWanQoS -m mark --mark 0x$(($2 * 10 + $i)) -j qos_${1}
	i=$(($i + 1))
    done
}

mwanrule() {
    local src
    local dst
    local ports
    local proto
    local wanrule

    config_get src $1 src
    config_get dst $1 dst
    config_get port_type $1 port_type 'dports'
    config_get ports $1 ports
    config_get proto $1 proto
    config_get wanrule $1 wanrule

    if [ -z "$wanrule" ]; then
	return
    fi

    if [ "$wanrule" != "balancer" -a "$wanrule" != "fastbalancer" ]; then
	wanrule=$(query_config wanid ${wanrule})
	wanrule="FW${wanrule}MARK"
    elif [ "$wanrule" == "balancer" ]; then
	wanrule="LoadBalancer"
    elif [ "$wanrule" == "fastbalancer" ]; then
	wanrule="FastBalancer"
    fi
    if [ "$dst" == "all" ]; then
	dst=$NULL
    fi
    if [ "$proto" == "all" ]; then
	proto=$NULL
    fi
    if [ "$ports" == "all" ]; then
	ports=$NULL
    fi
    add_rule() {
	if [ "$proto" == "icmp" ]; then
	    ports=$NULL
	fi 
	if [ "$src" == "all" ]; then
	    src=$NULL
	fi
	iptables -t mangle -A MultiWanRules ${src:+-s $src} ${dst:+-d $dst} \
	    -m mark --mark 0x0 ${proto:+-p $proto -m $proto} \
	    ${ports:+-m multiport --$port_type $ports} \
	    -j $wanrule
    }
    if  [ -z "$proto" -a ! -z "$ports" ]; then
	proto=tcp
	add_rule
	proto=udp
	add_rule
	return
    fi
    add_rule
}

refresh_dns() {
    local dns
    local group
    local ipaddr
    local gateway
    local ifname
    local failchk
    local compile_dns
    local dns_server

    iptables -F MultiWanDNS -t mangle

    rm /tmp/resolv.conf.auto
    touch /tmp/resolv.conf.auto

    echo "## Refreshing DNS Resolution and Tables ##"

    local i=0
    while [ $((i++)) -lt $wancount ]; do
	group=$(query_config group $i)
	gateway=$(query_config gateway $group)
	ipaddr=$(query_config ipaddr $group)
	ifname=$(query_config ifname $group)
	failchk=$(query_config failchk $group)

	dns=$(uci_get_state multiwan ${group} dns 'auto')
	[ "$dns" == "auto" ] && dns=$(uci_get_state network ${group} dns)
	dns=$(echo $dns | sed -e "s/ /\n/g")

	if [ ! -z "$dns" -a "$failchk" != "x" -a "$ipaddr" != "x" -a "$gateway" != "x" -a "$ifname" != "x" ]; then
	    echo "$dns" | while read dns_server; do
		iptables -t mangle -A MultiWanDNS -d $dns_server -p tcp --dport 53 -j FW${i}MARK
		iptables -t mangle -A MultiWanDNS -d $dns_server -p udp --dport 53 -j FW${i}MARK

		compile_dns="nameserver $dns_server"
		echo "$compile_dns" >> /tmp/resolv.conf.auto
	    done
	fi
    done

    last_resolv_update=$(ls -l -e /tmp/resolv.conf.auto | awk -F " " '{print $5, $9}')
}

iptables_init() {
    echo "## IPTables Rule Initialization ##"
    local iprule
    local group
    local ifname
    local execute
    local IMQ_NFO
    local default_route_id
    local i

    if [ ! -z "$CHKFORQOS" ]; then
	echo "## QoS Initialization ##"

	/etc/init.d/qos restart > /dev/null 2>&1

	IMQ_NFO=$(iptables -n -L PREROUTING -t mangle -v | grep IMQ | awk -F " " '{print $6,$12}')

	iptables -t mangle -F PREROUTING 
	iptables -t mangle -F FORWARD
	iptables -t mangle -F POSTROUTING
	iptables -t mangle -F OUTPUT

	echo "$IMQ_NFO" | while read execute; do
	    iptables -t mangle -A PREROUTING -i $(echo $execute | awk -F " " '{print $1}') -j IMQ --todev $(echo $execute | awk -F " " '{print $2}')
	done

	iptables -t mangle -N MultiWanQoS

	i=0
	while [ $((i++)) -lt $wancount ]; do 
	    qos_init $(query_config group $i) $i
	done

    fi

    iptables -t mangle -N MultiWan
    iptables -t mangle -N LoadBalancer
    iptables -t mangle -N FastBalancer
    iptables -t mangle -N MultiWanRules
    iptables -t mangle -N MultiWanDNS
    iptables -t mangle -N MultiWanPreHandler
    iptables -t mangle -N MultiWanPostHandler
    iptables -t mangle -N MultiWanLoadBalancer

    echo "## Creating FW Rules ##"
    i=0
    while [ $((i++)) -lt $wancount ]; do 
	iprule=$(($i * 10))
	iptables -t mangle -N FW${i}MARK
	iptables -t mangle -A FW${i}MARK -j MARK --set-mark 0x${iprule}
	iptables -t mangle -A FW${i}MARK -j CONNMARK --save-mark
    done

    iptables -t mangle -A LoadBalancer -j MARK --set-mark 0x1
    iptables -t mangle -A LoadBalancer -j CONNMARK --save-mark

    if [ -z "$CHKFORMODULE" ]; then
	iptables -t mangle -A FastBalancer -j MARK --set-mark 0x2
	iptables -t mangle -A FastBalancer -j CONNMARK --save-mark
    else
	mwnote "Performance load balancer(fastbalanacer) is unavailable due to current kernel limitations."
	iptables -t mangle -A FastBalancer -j MARK --set-mark 0x1
	iptables -t mangle -A FastBalancer -j CONNMARK --save-mark
    fi

    iptables -t mangle -A MultiWan -j CONNMARK --restore-mark
    iptables -t mangle -A MultiWan -j MultiWanPreHandler
    iptables -t mangle -A MultiWan -j MultiWanRules
    iptables -t mangle -A MultiWan -j MultiWanLoadBalancer
    iptables -t mangle -A MultiWan -j MultiWanDNS
    iptables -t mangle -A MultiWan -j MultiWanPostHandler

    iptables -t mangle -I PREROUTING -j MultiWan
    iptables -t mangle -I FORWARD -j MultiWan
    iptables -t mangle -I OUTPUT -j MultiWan
    iptables -t mangle -I POSTROUTING -j MultiWan


    refresh_dns

    config_load "multiwan"
    config_foreach mwanrule mwanfw

    if [ "$default_route" != "balancer" -a "$default_route" != "fastbalancer" ]; then 
	default_route_id=$(query_config wanid $default_route)
	iptables -t mangle -A MultiWanRules -m mark --mark 0x0 -j FW${default_route_id}MARK
    elif [ "$default_route" == "fastbalancer" ]; then
	iptables -t mangle -A MultiWanRules -m mark --mark 0x0 -j FastBalancer
    else
	iptables -t mangle -A MultiWanRules -m mark --mark 0x0 -j LoadBalancer
    fi

    i=0
    while [ $((i++)) -lt $wancount ]; do 
	group=$(query_config group $i)
	ifname=$(query_config ifname $group)
	iptables -t mangle -A MultiWanPreHandler -i $ifname -m state --state NEW -j FW${i}MARK
	iptables -t mangle -A MultiWanPostHandler -o $ifname -m mark --mark 0x1 -j FW${i}MARK
    done

    if [ ! -z "$CHKFORQOS" ]; then
	iptables -t mangle -A MultiWan -j MultiWanQoS
    fi
}

refresh_loadbalancer() {
    local group
    local gateway
    local ifname
    local failchk
    local weight
    local nexthop
    local pre_nexthop_chk
    local rand_probability

    echo "## Refreshing Load Balancer ##"

    ip rule del prio 9 > /dev/null 2>&1 
    ip route flush table 170 > /dev/null 2>&1

    for TABLE in 170; do
	ip route | grep -Ev ^default | while read ROUTE; do
	    ip route add table $TABLE to $ROUTE
	done
    done

    iptables -F MultiWanLoadBalancer -t mangle

    local total_weight=0

    local i=0
    while [ $((i++)) -lt $wancount ]; do 
	group=$(query_config group $i)
	failchk=$(query_config failchk $group)
	gateway=$(query_config gateway $group)
	ifname=$(query_config ifname $group)
	weight=$(uci_get_state multiwan ${group} weight)
	if [ "$gateway" != "x" -a "$ifname" != "x" -a "$failchk" != "x" -a "$weight" != "disable" ]; then
	    total_weight=$(($total_weight + $weight))
	fi
    done

    i=0
    while [ $((i++)) -lt $wancount ]; do 
	group=$(query_config group $i)
	failchk=$(query_config failchk $group)
	gateway=$(query_config gateway $group)
	ifname=$(query_config ifname $group)

	weight=$(uci_get_state multiwan ${group} weight)

	if [ "$gateway" != "x" -a "$ifname" != "x" -a "$failchk" != "x" -a "$weight" != "disable" ]; then
	    nexthop="$nexthop nexthop via $gateway dev $ifname weight $weight"

	    rand_probability=$(($weight * 100 / $total_weight))
	    total_weight=$(($total_weight - $weight))

	    if [ $rand_probability -lt 10 ]; then
		rand_probability="0.0${rand_probability}"
	    elif [ $rand_probability -lt 100 ]; then
		rand_probability="0.${rand_probability}"
	    else
		rand_probability="1.0"
	    fi

	    if [ -z "$CHKFORMODULE" ]; then
		iptables -A MultiWanLoadBalancer -t mangle -m mark --mark 0x2 -m statistic --mode random --probability $rand_probability -j FW${i}MARK
	    fi
	fi

    done

    pre_nexthop_chk=$(echo $nexthop | awk -F "nexthop" '{print NF-1}')
    if [ "$pre_nexthop_chk" == "1" ]; then
	ip route add default via $(echo $nexthop | awk -F " " '{print $3}') dev $(echo $nexthop | awk -F " " '{print $5}') proto static table 170
    elif [ "$pre_nexthop_chk" -gt "1" ]; then
	ip route add proto static table 170 default scope global $nexthop
    fi

    ip rule add fwmark 0x1 table 170 prio 9
    ip route flush cache
}

refresh_routes() {
    local iprule
    local gateway
    local group
    local ifname
    local ipaddr

    echo "## Refreshing Routing Tables ##"

    local i=0
    while [ $((i++)) -lt $wancount ]; do 
	group=$(query_config group $i)
	gateway=$(query_config gateway $group)
	ifname=$(query_config ifname $group)
	ipaddr=$(query_config ipaddr $group)
	ip route flush table $(($i + 170)) > /dev/null 2>&1

	TABLE=$(($i + 170))
	ip route | grep -Ev ^default | while read ROUTE; do
	    ip route add table $TABLE to $ROUTE
	done

	if [ "$gateway" != "x" -a "$ipaddr" != "x" -a "$ifname" != "x" ]; then
	    ip route add default via $gateway table $(($i + 170)) src $ipaddr proto static
	    route add default gw $gateway > /dev/null 2>&1
	fi
    done

    ip route flush cache
}

iprules_config() {
    local iprule
    local group
    local gateway
    local ipaddr

    group=$(query_config group $1)
    gateway=$(query_config gateway $group)
    ipaddr=$(query_config ipaddr $group)

    CHKIPROUTE=$(grep MWAN${1} /etc/iproute2/rt_tables)
    if [ -z "$CHKIPROUTE" ]; then
	echo "$(($1 + 170)) MWAN${1}" >> /etc/iproute2/rt_tables
    fi

    ip rule del prio $(($1 * 10)) > /dev/null 2>&1 
    ip rule del prio $(($1 * 10 + 1)) > /dev/null 2>&1

    if [ "$gateway" != "x" -a "$ipaddr" != "x" ]; then
	ip rule add from $ipaddr table $(($1 + 170)) prio $(($1 * 10))
	ip rule add fwmark 0x$(($1 * 10)) table $(($1 + 170)) prio $(($1 * 10 + 1))
    fi
}

flush() {
    local restore_single=$1
    echo "## Flushing IP Rules & Routes ##"

    ip rule flush > /dev/null 2>&1
    ip rule add lookup main prio 32766 > /dev/null 2>&1
    ip rule add lookup default prio 32767 > /dev/null 2>&1

    ip route flush table 170 > /dev/null

    local i=0
    while [ $((i++)) -lt $wancount ]; do 
	ip route del default > /dev/null 2>&1
	ip route flush table $(($i + 170)) > /dev/null 2>&1
    done

    echo "## Clearing Rules ##"
    clear_rules $restore_single > /dev/null 2>&1

    rm $jobfile > /dev/null 2>&1
}

main_init() {
    local RP_PATH IFACE
    local group
    local health_interval

    echo "## Main Initialization ##"

    mkdir /tmp/.mwan > /dev/null 2>&1

    mwan_kill
    flush

    echo "## IP Rules Initialization ##"

    CHKIPROUTE=$(grep LoadBalancer /etc/iproute2/rt_tables)
    if [ -z "$CHKIPROUTE" ]; then
	echo "#" >> /etc/iproute2/rt_tables
	echo "170 LoadBalancer" >> /etc/iproute2/rt_tables
    fi

    local i=0
    while [ $((i++)) -lt $wancount ]; do 
	iprules_config $i
    done

    refresh_routes
    iptables_init

    refresh_loadbalancer

    RP_PATH=/proc/sys/net/ipv4/conf
    for IFACE in $(ls $RP_PATH); do
	echo 0 > $RP_PATH/$IFACE/rp_filter
    done
    mwnote "Succesfully Initialized on $(date -R)."
    fail_start_check

    while :; do
	schedule_tasks
	do_tasks
    done
}

monitor_wan() {
    local ifname ipaddr gateway icmp_hosts_acquire icmp_test_host
    local check_test

    . /tmp/.mwan/cache

    local timeout=$(uci_get_state multiwan ${1} timeout)
    local icmp_hosts=$(uci_get_state multiwan ${1} icmp_hosts)
    local icmp_count=$(uci_get_state multiwan ${1} icmp_count '1')
    local health_interval=$(uci_get_state multiwan ${1} health_interval)
    local ifname_cur=$(query_config ifname $1)
    local ipaddr_cur=$(query_config ipaddr $1)
    local gateway_cur=$(query_config gateway $1)

    while :; do
	[ "${health_monitor%.*}" = 'parallel' ] && sleep $health_interval

	ifname=$(uci_get_state network ${1} ifname 'x')
	ipaddr=$(uci_get_state network ${1} ipaddr 'x')
	gateway=$(uci_get_state network ${1} gateway 'x')

	if [ "$ifname_cur" != "$ifname" -o "$ipaddr_cur" != "$ipaddr" -o "$gateway_cur" != "$gateway" ]; then
	    add_task "$1" acquire
	    if [ "${health_monitor%.*}" = 'parallel' ]; then
		exit
	    else
		return
	    fi
	else
	    [ "$gateway" != "x" ] && ! ip route | grep -o $gateway >&- 2>&- &&
		add_task route refresh
	fi

	if [ "$icmp_hosts" != "disable" -a "$ifname" != "x" -a "$ipaddr" != "x" -a "$gateway" != "x" ]; then

	    if [ "$icmp_hosts" == "gateway" -o -z "$icmp_hosts" ]; then
		icmp_hosts_acquire=$gateway
	    elif [ "$icmp_hosts" == "dns" ]; then
		icmp_hosts_acquire=$(uci_get_state multiwan $1 dns 'auto')
		[ "$icmp_hosts_acquire" == "auto" ] &&
		    icmp_hosts_acquire=$(uci_get_state network $1 dns)
	    else
		icmp_hosts_acquire=$icmp_hosts
	    fi

	    icmp_hosts=$(echo $icmp_hosts_acquire | sed -e "s/\,/ /g" | sed -e "s/ /\n/g")

	    ping_test() {
		echo "$icmp_hosts" | while read icmp_test_host; do
		    ping -c "$icmp_count" -W $timeout -I $ifname $icmp_test_host 2>&1 | grep -o "round-trip"
		done
	    }

	    check_test=$(ping_test)

	    if [ -z "$check_test" ]; then
		add_task "$1" fail
	    else
		add_task "$1" pass
	    fi       	        

	elif [ "$icmp_hosts" == "disable" ]; then 
	    add_task "$1" pass
	fi

	[ "$health_monitor" = 'serial' ] && {
	    wan_monitor_map=$(echo $wan_monitor_map | sed -e "s/$1\[\w*\]/$1\[$(date +%s)\]/g")
	    update_cache
	    break
	}
    done
}

# Add a task to the $jobfile while ensuring
# no duplicate tasks for the specified group
add_task() {
    local group=$1
    local task=$2
    grep -o "$group.$task" $jobfile >&- 2>&- || echo "$group.$task" >> $jobfile
}

# For health_monitor "parallel", start a background monitor for each group.
# For health_monitor "serial", queue monitor tasks for do_tasks.
schedule_tasks() {
    local group health_interval monitored_last_at current_time diff delay
    local i=0

    get_health_interval() {
	group=$(query_config group $1)
	health_interval=$(uci_get_state multiwan ${group} health_interval 'disable')
	[ "$health_interval" = "disable" ] && health_interval=0
    }

    [ "$health_monitor" = 'parallel' ] && {
	while [ $((i++)) -lt $wancount ]; do
	    get_health_interval $i
	    if [ "$health_interval" -gt 0 ]; then
		monitor_wan $group &
		sleep 1
	    fi
	done
	echo "## Started background monitor_wan ##"
	health_monitor="parallel.started"
    }

    [ "$health_monitor" = 'serial' ] && {
	local monitor_disabled=1

	until [ -f $jobfile ]; do
	    current_time=$(date +%s)
	    delay=$max_interval
	    i=0

	    while [ $((i++)) -lt $wancount ]; do
		get_health_interval $i
		if [ "$health_interval" -gt 0 ]; then
		    monitor_disabled=0

		    monitored_last=$(query_config monitor $group)
		    [ -z "$monitored_last" ] && {
			monitored_last=$current_time
			wan_monitor_map="${wan_monitor_map}${group}[$monitored_last]"
			update_cache
		    }

		    will_monitor_at=$(($monitored_last + $health_interval))
		    diff=$(($will_monitor_at - $current_time))
		    [ $diff -le 0 ] && add_task "$group" 'monitor'

		    delay=$(($delay > $diff ? $diff : $delay))
		fi
	    done

	    [ "$monitor_disabled" -eq 1 ] && {
		# Although health monitors are disabled, still
		# need to check up on iptables rules in do_tasks
		sleep "$iptables_interval"
		break
	    }
	    [ $delay -gt 0 ] && sleep $delay
	done
    }
}

rule_counter=0
# Process each task in the $jobfile in FIFO order
do_tasks() {
    local check_iptables
    local queued_task
    local current_resolv_file

    while :; do

	. /tmp/.mwan/cache

	if [ "$((++rule_counter))" -eq 5 -o "$health_monitor" = 'serial' ]; then

	    check_iptables=$(iptables -n -L MultiWan -t mangle | grep "references" | awk -F "(" '{print $2}' | cut -d " " -f 1)

	    if [ -z "$check_iptables" -o "$check_iptables" -lt 4 ]; then
		mwnote "Netfilter rules appear to of been altered."
		/etc/init.d/multiwan restart
		exit
	    fi

	    current_resolv_file=$(ls -l -e /tmp/resolv.conf.auto | awk -F " " '{print $5, $9}')

	    if [ "$last_resolv_update" != "$current_resolv_file" ]; then
		refresh_dns
	    fi

	    rule_counter=0
	fi

	if [ -f $jobfile ]; then

	    mv $jobfile $jobfile.work

	    while read LINE; do 

		execute_task() {
		    case $2 in 
			fail) fail_wan $1;;
			pass) recover_wan $1;;
			acquire)
			    acquire_wan_data $1
			    [ "${health_monitor%.* }" = 'parallel' ] && {
				monitor_wan $1 &
				echo "## Started background monitor_wan ##"
			    }
			    ;;
			monitor) monitor_wan $1;;
			refresh) refresh_routes;;
			*) echo "## Unknown task command: $2 ##";;
		    esac
		}

		queued_task=$(echo $LINE | awk -F "." '{print $1,$2}')
		execute_task $queued_task
	    done < $jobfile.work

	    rm $jobfile.work
	fi

	if [ "$health_monitor" = 'serial' ]; then
	    break
	else
	    sleep 1
	fi
    done
}

fail_start_check(){ 
    local ipaddr
    local gateway
    local ifname
    local group

    local i=0
    while [ $((i++)) -lt $wancount ]; do 
	group=$(query_config group $i)
	ifname=$(query_config ifname $group)
	ipaddr=$(query_config ipaddr $group)
	gateway=$(query_config gateway $group)

	if [ "$ifname" == "x" -o "$ipaddr" == "x" -o "$gateway" == "x" ]; then
	    failover add $group
	fi
    done
}

wancount=0
max_interval=$(((1<<31) - 1))

config_clear
config_load "multiwan"
config_get default_route	config default_route
config_get health_monitor	config health_monitor
config_get iptables_interval	config iptables_interval '30'
config_get debug		config debug

[ "$health_monitor" = 'serial' ] || health_monitor='parallel'

config_foreach acquire_wan_data interface

update_cache

CHKFORQOS=$(iptables -n -L Default -t mangle 2>&1 | grep "Chain Default")
CHKFORMODULE=$(iptables -m statistic 2>&1 | grep -o "File not found")

jobfile="/tmp/.mwan/jobqueue"

case $1 in
    agent) silencer main_init;;
    stop) silencer stop;;
    restart) silencer stop restart;;
    single) silencer stop single;;
esac