#!/bin/sh

mwan3_basic_iptables()
{
	if ! iptables -S mwan3_rules -t mangle &> /dev/null; then
		iptables -N mwan3_rules -t mangle
	fi

	if ! iptables -S mwan3_interfaces -t mangle &> /dev/null; then
		iptables -N mwan3_interfaces -t mangle
	fi

	if ! iptables -S mwan3_default -t mangle &> /dev/null; then
		iptables -N mwan3_default -t mangle
	fi

	if ! iptables -S mwan3_pre -t mangle &> /dev/null; then
		iptables -N mwan3_pre -t mangle
		iptables -A mwan3_pre -t mangle -j CONNMARK --restore-mark --nfmask 65280 --ctmask 65280
		iptables -A mwan3_pre -t mangle -m mark --mark 0/65280 -j mwan3_default
		iptables -A mwan3_pre -t mangle -m mark --mark 0/65280 -j mwan3_interfaces
		iptables -A mwan3_pre -t mangle -m mark --mark 0/65280 -j mwan3_rules
		iptables -A mwan3_pre -t mangle -m mark --mark 0/65280 -j MARK --set-xmark 32512/65280
	fi

	if ! iptables -S mwan3_post -t mangle &> /dev/null; then
		iptables -N mwan3_post -t mangle
		iptables -A mwan3_post -t mangle -m mark --mark 32768/32768 -j MARK --set-xmark 0/32768
		iptables -A mwan3_post -t mangle -j CONNMARK --save-mark --nfmask 65280 --ctmask 65280
	fi

	if ! iptables -S PREROUTING -t mangle | grep mwan3_pre &> /dev/null; then
		iptables -A PREROUTING -t mangle -j mwan3_pre
	fi

	if ! iptables -S INPUT -t mangle | grep mwan3_post &> /dev/null; then
		iptables -A INPUT -t mangle -j mwan3_post
	fi

	if ! iptables -S OUTPUT -t mangle | grep mwan3_pre &> /dev/null; then
		iptables -A OUTPUT -t mangle -j mwan3_pre
	fi

	if ! iptables -S POSTROUTING -t mangle | grep mwan3_post &> /dev/null; then
		iptables -A POSTROUTING -t mangle -j mwan3_post
	fi
}

mwan3_interface_iptables()
{
	local connected
	
	connected=$(ip -4 route list dev $DEVICE | awk '{print $1}' | egrep -m 1 '[0-9]{1,3}(\.[0-9]{1,3}){3}')

	if [ -n "$connected" ]; then
		iptables -D mwan3_pre -t mangle -i $DEVICE ! -s $connected -m mark ! --mark 32512/65280 -j MARK --set-xmark $((($interface_number*256)+32768))/65280 &> /dev/null
		iptables -D mwan3_post -t mangle -o $DEVICE ! -d $connected -m mark ! --mark 32512/65280 -j MARK --set-xmark $(($interface_number*256))/65280 &> /dev/null

		if [ $ACTION == "ifup" ]; then
			iptables -I mwan3_pre 2 -t mangle -i $DEVICE ! -s $connected -m mark ! --mark 32512/65280 -j MARK --set-xmark $((($interface_number*256)+32768))/65280 &> /dev/null
			iptables -I mwan3_post 1 -t mangle -o $DEVICE ! -d $connected -m mark ! --mark 32512/65280 -j MARK --set-xmark $(($interface_number*256))/65280 &> /dev/null
		fi
	else
		iptables -D mwan3_pre -t mangle -i $DEVICE -m mark ! --mark 32512/65280 -j MARK --set-xmark $((($interface_number*256)+32768))/65280 &> /dev/null
		iptables -D mwan3_post -t mangle -o $DEVICE -m mark ! --mark 32512/65280 -j MARK --set-xmark $(($interface_number*256))/65280 &> /dev/null

		if [ $ACTION == "ifup" ]; then
			iptables -I mwan3_pre 2 -t mangle -i $DEVICE -m mark ! --mark 32512/65280 -j MARK --set-xmark $((($interface_number*256)+32768))/65280 &> /dev/null
			iptables -I mwan3_post 1 -t mangle -o $DEVICE -m mark ! --mark 32512/65280 -j MARK --set-xmark $(($interface_number*256))/65280 &> /dev/null
		fi
	fi
}

mwan3_connected_iptables()
{
	if iptables -S mwan3_default -t mangle &> /dev/null; then
		local connected_networks

		iptables -F mwan3_default -t mangle
		iptables -A mwan3_default -t mangle -d 224.0.0.0/3 -m mark --mark 0/65280 -j MARK --set-xmark 32512/65280

		for connected_networks in $(ip -4 route list table local dev lo scope host | awk '{print $2}' | egrep '[0-9]{1,3}(\.[0-9]{1,3}){3}'); do
			iptables -A mwan3_default -t mangle -d $connected_networks -m mark --mark 0/65280 -j MARK --set-xmark 32512/65280 &> /dev/null
		done

		for connected_networks in $(ip -4 route | awk '{print $1}' | egrep '[0-9]{1,3}(\.[0-9]{1,3}){3}'); do
			iptables -A mwan3_default -t mangle -d $connected_networks -m mark --mark 0/65280 -j MARK --set-xmark 32512/65280 &> /dev/null
		done
	fi
}

mwan3_interface_routes_and_rules()
{
	local link_address loopback_address reroute

	config_get reroute $INTERFACE reroute 0

	ip -4 route flush table $(($interface_number+1000))
	[ $ACTION == "ifup" ] && ip -4 route add table $(($interface_number+1000)) default $route_args

	while [ -n "$(ip -4 rule list | awk '$1 == ("'$(($interface_number+1000)):'")')" ]; do
		ip -4 rule del pref $(($interface_number+1000))
	done

	[ $ACTION == "ifup" ] && ip -4 rule add pref $(($interface_number+1000)) fwmark $(($interface_number*256))/65280 table $(($interface_number+1000))

	if [ $ACTION == "ifup" -a $reroute != "1" ]; then
		iptables -N mwan3_$INTERFACE -t mangle &> /dev/null
		iptables -D mwan3_interfaces -t mangle -j mwan3_$INTERFACE &> /dev/null
		iptables -F mwan3_$INTERFACE -t mangle

		for link_address in $(ip -4 route list table local dev $DEVICE | grep ^local | sed 's/.*local \([^ ]*\) .*/\1/') ; do	
			iptables -A mwan3_$INTERFACE -t mangle -s $link_address -j MARK --set-xmark $(($interface_number*256))/65280
		done

		iptables -A mwan3_interfaces -t mangle -j mwan3_$INTERFACE
	fi

	[ $ACTION == "ifdown" ] && iptables -X mwan3_$INTERFACE -t mangle &> /dev/null
}

mwan3_policy_routes_and_rules()
{
	local member iface iface_test member_weight policy_counter

	mwan3_get_weight_member()
	{
		iface=$(uci show -P /var/state network | awk -F'=' '$2 == ("'$2'")' | grep ifname | grep -v orig_ifname | awk -F'.' '{ print $2 }')
		if [ $(echo "$iface" | wc -w) != "1" ]; then
			for iface_test in $iface; do
				[ $(cat /etc/config/network | awk '/config interface \x27$iface_test\x27/' RS= | grep ifname | grep -q @; echo $?) == "1" ] && {
					iface=$iface_test
					break
				}
			done
		fi

		for member in $(uci get mwan3.$1.use_member); do
			if [ $(uci get -P /var/state mwan3.$member.interface) == "$iface" ]; then
				member_weight=$(uci get -P /var/state mwan3.$member.weight)
			fi
		done
	}

	iptables -F mwan3_rules -t mangle

	config_foreach mwan3_policy_rules policy
	config_foreach mwan3_rules_iptables rule
}

mwan3_policy_rules()
{
	local policy

	policy_counter=$(($policy_counter+1))
	policy=$1

	if [ "$policy_counter" -lt 85 ]; then
		eval mwp$1=$(($policy_counter+15))

		config_list_foreach $1 use_member mwan3_policy_routes

		while [ -n "$(ip -4 rule list | awk '$1 == ("'$(($policy_counter+1015)):'")')" ]; do
			ip -4 rule del pref $(($policy_counter+1015)) &> /dev/null
		done

		if [ -n "$(ip -4 route list table $(($policy_counter+1015)))" ]; then
			ip -4 rule add pref $(($policy_counter+1015)) fwmark $((($policy_counter+15)*256))/65280 table $(($policy_counter+1015)) &> /dev/null
		fi
	else
		logger -t mwan3 -p info "Maximum number of 84 policies reached. Omitting policy $policy_counter $1"
	fi
}

mwan3_policy_routes()
{
	local other_route_args metric other_device weight

	if [ $(uci get -P /var/state mwan3.$1.interface) == "$INTERFACE" ]; then
		config_get metric $1 metric 1
		config_get weight $1 weight 1

		other_route_args=$(ip -4 route list table $(($policy_counter+1015)) | awk '/default  metric ('$metric')/ {flag=1;next} /default/{flag=0} flag { print }' | awk '$1 == "nexthop"' | grep -v "dev $DEVICE " | sort -u)

		if [ -z "$other_route_args" ]; then
			other_device=$(ip -4 route list table $(($policy_counter+1015)) | grep -v "dev $DEVICE " | grep "metric $metric " | sed 's/.*dev \([^ ]*\) .*/\1/' | grep -v "^default")

			if [ -n "$other_device" ]; then
				mwan3_get_weight_member $policy $other_device
				other_route_args=$(ip -4 route list table $(($policy_counter+1015)) dev $other_device | grep "metric $metric " | sed 's/.*via \([^ ]*\) .*/\1/' | grep -v "^default")
				[ -n "$other_route_args" ] && other_route_args="via $other_route_args"
				other_route_args="nexthop $other_route_args dev $other_device weight $member_weight"
			fi
		fi

		ip -4 route del table $(($policy_counter+1015)) metric $metric &> /dev/null

		[ $ACTION == "ifdown" ] && ip -4 route add table $(($policy_counter+1015)) metric $metric default $other_route_args &> /dev/null
		[ $ACTION == "ifup" ] && ip -4 route add table $(($policy_counter+1015)) metric $metric default $route_args weight $weight $other_route_args &> /dev/null
	fi
}

mwan3_rules_iptables()
{
	local probability src_ip src_port dest_ip dest_port use_policy equalize equalize_devices rulenumber nf_mark iftotalweight lowest_metric

	config_get proto $1 proto
	config_get src_ip $1 src_ip 0.0.0.0/0
	config_get src_port $1 src_port 0:65535
	config_get dest_ip $1 dest_ip 0.0.0.0/0
	config_get dest_port $1 dest_port 0:65535
	config_get use_policy $1 use_policy
	config_get_bool equalize $1 equalize 0

	mwan3_add_rule_iptables()
	{
		case $proto in
			icmp)
			iptables -I mwan3_rules $rulenumber -t mangle -p $proto -s $src_ip -d $dest_ip -m mark --mark 0/65280 $probability -j MARK --set-xmark $(($nf_mark*256))/65280 &> /dev/null
			;;
			tcp|udp)
			iptables -I mwan3_rules $rulenumber -t mangle -p $proto -s $src_ip -d $dest_ip -m multiport --sports $src_port -m multiport --dports $dest_port -m mark --mark 0/65280 $probability -j MARK --set-xmark $(($nf_mark*256))/65280 &> /dev/null
			;;
			*)
			iptables -I mwan3_rules $rulenumber -t mangle -s $src_ip -d $dest_ip -m mark --mark 0/65280 $probability -j MARK --set-xmark $(($nf_mark*256))/65280 &> /dev/null
			;;
		esac
	}

	if [ "$use_policy" == "default" ]; then
		nf_mark=127
	else
		eval nf_mark='$'mwp"$use_policy"
	fi

	if [ -n "$nf_mark" ]; then
		rulenumber=$(($(iptables -S mwan3_rules -t mangle | wc -l)))
		lowest_metric=$(ip -4 route list table $(($nf_mark+1000)) | grep -m 1 default | egrep -o 'metric [0-9]{1,9}' | awk '{print $2}')
		equalize_devices=$(ip -4 route list table $(($nf_mark+1000)) | awk '/default  metric ('$lowest_metric')/ {flag=1;next} /default/{flag=0} flag { print }' | sed 's/.*dev \([^ ]*\) .*/\1/')

		if [ -n "$equalize_devices" -a "$equalize" -eq 1 ]; then
			iftotalweight=0

			for device in $equalize_devices; do
				mwan3_get_weight_member $use_policy $device
				iftotalweight=$(($member_weight+$iftotalweight))
				eval nf_mark='$'mwif"$iface"
				probability=$(($member_weight*1000/$iftotalweight))

				if [ "$probability" -lt 10 ]; then
					probability="0.00$probability"
					elif [ $probability -lt 100 ]; then
					probability="0.0$probability"
					elif [ $probability -lt 1000 ]; then
					probability="0.$probability"
				else
					probability="1"
				fi

				probability="-m statistic --mode random --probability $probability"
				mwan3_add_rule_iptables
			done
		else
			mwan3_add_rule_iptables
		fi
	fi
}

mwan3_loopback_route_and_rule()
{
	loopback_address=$(ip -4 route list table local dev lo scope host | sed 's/.*src \([^ ]*\) .*/\1/' | egrep '[0-9]{1,3}(\.[0-9]{1,3}){3}' | egrep -m 1 -v '^127.')

	while [ -n "$(ip -4 rule list | awk '$1 == "1000:"')" ]; do
		ip -4 rule del pref 1000
	done

	ip -4 route flush table 1000

	if [ -n "$loopback_address" -a $ACTION == "ifup" ]; then
		ip -4 route add table 1000 default via $loopback_address src $loopback_address
		ip -4 rule add pref 1000 fwmark 0/65280 table 1000
	fi
}

mwan3_track()
{
	local track_ips reliability count timeout interval down up

	mwan3_list_track_ips()
	{
		track_ips="$1 $track_ips"
	}
	config_list_foreach $INTERFACE track_ip mwan3_list_track_ips

	if [ -n "$track_ips" ]; then
		config_get reliability $INTERFACE reliability 1
		config_get count $INTERFACE count 1
		config_get timeout $INTERFACE timeout 4
		config_get interval $INTERFACE interval 10
		config_get down $INTERFACE down 5
		config_get up $INTERFACE up 5

		[ -x /usr/sbin/mwan3track ] && /usr/sbin/mwan3track $INTERFACE $DEVICE $reliability $count $timeout $interval $down $up $track_ips &
	fi
}

mwan3_ifupdown()
{
	[ -n "$DEVICE" ] || exit 0
	[ -n "$INTERFACE" ] || exit 0
	[ $(uci get -P /var/state mwan3.$INTERFACE.enabled) == "1" ] || return 0
	[ "$(ip -4 route list dev $DEVICE default)" ] || return 0

	while [ "$(pgrep -f -o hotplug-call)" -ne $$ ]; do
		sleep 1
	done

	local interface_counter interface_number route_args

	config_load mwan3

	mwan3_get_interfaces()
	{
		interface_counter=$(($interface_counter+1))
		eval mwif$1=$interface_counter
		[ "$1" == "$INTERFACE" ] && interface_number=$interface_counter
	}
	config_foreach mwan3_get_interfaces interface

	[ "$interface_counter" -le "15" ] || exit 0

	logger -t mwan3 -p info "$ACTION interface $INTERFACE ($DEVICE)"

	route_args=$(ip -4 route list dev $DEVICE default | sed 's/.*via \([^ ]*\) .*/\1/' | grep -v "^default")
	[ -n "$route_args" ] && route_args="via $route_args"
	route_args="nexthop $route_args dev $DEVICE"

	mwan3_basic_iptables
	mwan3_interface_iptables
	mwan3_interface_routes_and_rules
	mwan3_policy_routes_and_rules
	mwan3_loopback_route_and_rule

	[ $ACTION == "ifup" ] && mwan3_track
}

case "$ACTION" in
	ifup|ifdown)
		mwan3_ifupdown
		mwan3_connected_iptables
	;;
esac
