From 3153a1d93cabdc47d019fc7bf8c76bf735598fc6 Mon Sep 17 00:00:00 2001 From: Sukru Senli Date: Thu, 14 Jul 2016 15:56:54 +0200 Subject: [PATCH] multiwan package --- multiwan/Makefile | 47 ++ multiwan/files/etc/config/multiwan | 57 ++ multiwan/files/etc/init.d/multiwan | 25 + multiwan/files/usr/bin/multiwan | 1085 ++++++++++++++++++++++++++++ 4 files changed, 1214 insertions(+) create mode 100644 multiwan/Makefile create mode 100644 multiwan/files/etc/config/multiwan create mode 100755 multiwan/files/etc/init.d/multiwan create mode 100755 multiwan/files/usr/bin/multiwan diff --git a/multiwan/Makefile b/multiwan/Makefile new file mode 100644 index 000000000..f5161cce6 --- /dev/null +++ b/multiwan/Makefile @@ -0,0 +1,47 @@ +# +# Copyright (C) 2010-2012 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=multiwan +PKG_VERSION:=1.0.22 +PKG_RELEASE:=2 + +include $(INCLUDE_DIR)/package.mk + +define Package/multiwan + SECTION:=net + CATEGORY:=Network + DEPENDS:=+ip +iptables +kmod-ipt-conntrack +iptables-mod-conntrack-extra +iptables-mod-ipopt + TITLE:=Simple multi WAN configuration + URL:=ftp://ftp.netlab7.com/ + MAINTAINER:=Craig M. Coffee +endef + +define Package/multiwan/description +An agent script that makes Multi-WAN configuration simple, +easy and manageable. Complete with load balancing, failover and an easy +to manage traffic ruleset. +endef + +define Package/multiwan/conffiles +/etc/config/multiwan +endef + +define Build/Compile +endef + +define Package/multiwan/install + $(CP) ./files/* $(1) +endef + +define Package/multiwan/postinst +[ -n "$${IPKG_INSTROOT}" ] || /etc/init.d/multiwan enable +exit 0 +endef + +$(eval $(call BuildPackage,multiwan)) diff --git a/multiwan/files/etc/config/multiwan b/multiwan/files/etc/config/multiwan new file mode 100644 index 000000000..09d988f16 --- /dev/null +++ b/multiwan/files/etc/config/multiwan @@ -0,0 +1,57 @@ + +config 'multiwan' 'config' + # REMOVE THIS LINE OR PUT TO 1 TO ENABLE MULTIWAN + option 'enabled' '0' + + option 'default_route' 'balancer' + # health_monitor below is defaulted to parallel, and can be set to + # serial to save system resources. + # option 'health_monitor' 'serial' + # option 'debug' '1' + +config 'interface' 'wan' + option 'weight' '10' + option 'health_interval' '10' + option 'icmp_hosts' 'dns' + # icmp_count is defaulted to 1, and can be increased to reduce + # false positives. + # option 'icmp_count' '3' + option 'timeout' '3' + option 'health_fail_retries' '3' + option 'health_recovery_retries' '5' + option 'failover_to' 'wwan' + option 'dns' 'auto' + +config 'interface' 'wwan' + option 'weight' '10' + option 'health_interval' '10' + option 'icmp_hosts' 'gateway' + option 'timeout' '3' + option 'health_fail_retries' '3' + option 'health_recovery_retries' '5' + option 'failover_to' 'balancer' + option 'dns' '208.67.222.222 208.67.220.220' + +#config 'mwanfw' +# option 'src' '192.168.1.0/24' +# option 'dst' 'ftp.netlab7.com' +# option 'proto' 'tcp' +# option 'ports' '21' +# option 'wanrule' 'wan2' + +# VoIP traffic goes through wan +# config 'mwanfw' + # option 'src' '192.168.1.0/24' + # option 'proto' 'udp' + # option 'port_type' 'source-ports' + # option 'ports' '5060,16384:16482' + # option 'wanrule' 'wan' + +#config 'mwanfw' +# option 'src' '192.168.0.3' +# option 'proto' 'icmp' +# option 'wanrule' 'balancer' + +#config 'mwanfw' +# option 'dst' 'www.whatismyip.com' +# option 'wanrule' 'fastbalancer' diff --git a/multiwan/files/etc/init.d/multiwan b/multiwan/files/etc/init.d/multiwan new file mode 100755 index 000000000..368075714 --- /dev/null +++ b/multiwan/files/etc/init.d/multiwan @@ -0,0 +1,25 @@ +#!/bin/sh /etc/rc.common +START=99 +EXTRA_COMMANDS="single" +USE_PROCD=1 + +start_service () { + /usr/bin/multiwan agent & +} + +stop_service () { + sh /usr/bin/multiwan stop +} + +reload_service () { + /usr/bin/multiwan restart & +} + +single () { + /usr/bin/multiwan single & +} + +service_triggers() { + procd_add_reload_trigger multiwan +} + diff --git a/multiwan/files/usr/bin/multiwan b/multiwan/files/usr/bin/multiwan new file mode 100755 index 000000000..5e08a27c7 --- /dev/null +++ b/multiwan/files/usr/bin/multiwan @@ -0,0 +1,1085 @@ +#!/bin/sh + +. /lib/functions.sh +. /lib/functions/network.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 ipaddr gateway + network_get_device ifname ${1} || ifname=x + network_get_ipaddr ipaddr ${1} || ipaddr=x + network_get_gateway gateway ${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 -D PREROUTING -j MultiWan + iptables -t mangle -D FORWARD -j MultiWan + iptables -t mangle -D OUTPUT -j MultiWan + iptables -t mangle -D POSTROUTING -j MultiWan + 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 PREROUTING + iptables -t mangle -F FORWARD + iptables -t mangle -F OUTPUT + iptables -t mangle -F POSTROUTING + 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" ] && network_get_dnsserver dns ${group} + 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 +} + +if_rx_bytes() { + local up=0 + local rx_bytes=0 + json_load "$(devstatus $1)" + json_get_var up up + if [ "$up" == "1" ]; then + json_select statistics + json_get_var rx_bytes rx_bytes + json_select .. + fi + json_close_object + echo "$rx_bytes" +} + +dhcp_renew() { + local pid=$(ps -w | grep udhcpc | grep "$1.pid" | awk '{print$1}') + [ -n "$pid" ] && kill -SIGUSR1 $pid +} + +monitor_wan() { + local ifname ipaddr gateway icmp_hosts_acquire icmp_test_host + local check_test + + . /tmp/.mwan/cache + + local health_method=$(uci_get_state multiwan ${1} health_method "ping") + 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) + local old_rx_bytes="0" + local new_rx_bytes="$(if_rx_bytes $ifname_cur)" + + while :; do + [ "${health_monitor%.*}" = 'parallel' ] && sleep $health_interval + + network_get_device ifname ${1} || ifname=x + network_get_ipaddr ipaddr ${1} || ipaddr=x + network_get_gateway gateway ${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 [ "$health_method" == "stats" ] && [ "$ifname" != "x" -a "$ipaddr" != "x" -a "$gateway" != "x" ]; then + new_rx_bytes="$(if_rx_bytes $ifname)" + if [ "$new_rx_bytes" == "0" ]; then + add_task "$1" fail + elif [ "$new_rx_bytes" == "$old_rx_bytes" ]; then + dhcp_renew $ifname + new_rx_bytes="$(if_rx_bytes $ifname)" + network_get_ipaddr ipaddr ${1} || ipaddr=x + network_get_gateway gateway ${1} || gateway=x + if [ "$ipaddr" = "x" -o "$gateway" = "x" ]; then + add_task "$1" fail + elif [ "$new_rx_bytes" == "$old_rx_bytes" ]; then + add_task "$1" fail + elif [ "$new_rx_bytes" != "0" ]; then + old_rx_bytes=$new_rx_bytes + add_task "$1" pass + fi + else + old_rx_bytes=$new_rx_bytes + add_task "$1" pass + fi + elif [ "$health_method" == "ping" ] && [ "$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" ] && + network_get_dnsserver icmp_hosts_acquire $1 + 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_load "multiwan" +config_get_bool enabled config enabled '1' +[ "$enabled" -gt 0 ] || exit +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