#!/bin/sh

. /lib/functions.sh

MAPDIR="/etc/multiap/"
AL_BRIDGE=${AL_BRIDGE-"br-lan"}
PRIMARY_VID=${PRIMARY_VID-1}
MAP_BH_FILE="/var/run/multiap/multiap.backhaul"

### Traffic Separation ###

ts_dbg() {
	logger -t traffic_separation $@
}

ts_sub() {
	ts_usage() {
		cat <<EOF
Usage: $0 [create|reload]
Traffic Separation related functions.
create vid - create vlan configuration with vlan_id
reload     - reload network with new configuration
EOF
	exit 1
	}

	_dhcp_cleanup() {
		vid=$1

		[ -n "$(uci -q get dhcp.guest${vid})" ] && {
			uci -q delete dhcp.guest${vid}
			uci commit dhcp
			/etc/init.d/dnsmasq reload &
		}
	}

	_firewall_cleanup() {
		vid=$1

		guest_teardown() {
				local section=$1
				local config=$2
				local val=$3
				local option=$4

				config_get name "$section" "$option"

				[ "$val" != "$name" ] && continue

				uci delete ${config}.${section}
				uci commit ${config}
			}

			[ -n "$(uci -q get firewall.guest${vid}_dns)" ] && {
				uci -q delete firewall.guest${vid}_dns
				uci commit firewall
			}

			[ -n "$(uci -q get firewall.guest${vid}_dhcp)" ] && {
				uci -q delete firewall.guest${vid}_dhcp
				uci commit firewall
			}

			[ -n "$(uci -q get firewall.guest${vid}_ping)" ] && {
				uci -q delete firewall.guest${vid}_ping
				uci commit firewall
			}

			config_load firewall
			config_foreach guest_teardown forwarding "firewall" guest${vid} "src" #delete firewall section with name = guest${vid}
			config_load firewall
			config_foreach guest_teardown zone "firewall" guest${vid} "name" #delete firewall section with name = guest${vid}
	}

	_network_cleanup() {
		vid=$1

		[ -n "$(uci -q get network.vlan${vid})" ] && {
			uci -q delete network.vlan${vid}
			uci commit network
		}

		[ -n "$(uci -q get network.guest${vid})" ] && {
			uci -q delete network.guest${vid}
			uci commit network
		}
	}

	_guest_cleanup() {
		vid=$1
		_dhcp_cleanup $vid
		_firewall_cleanup $vid
		_network_cleanup $vid
	}

	net_get_albridge_ip() {
		local section=$1

		config_get device "$section" device

		[ "${AL_BRIDGE}" = "$device" -o "${AL_BRIDGE}.${PRIMARY_VID}" = "$device" ] && {
			ipaddr="$(uci -q get network.${section}.ipaddr)"
			netmask="$(uci -q get network.${section}.netmask)"
			break;
		}
	}

	# parse map ports
	# if "all" or "*" is provided in ports list, then apply tags to all
	# ports in network ports list
	map_get_ports() {
		local name=$1
		local br_dev="${AL_BRIDGE/-/_}"
		local all_ports=$(uci -q get network.${br_dev}.ports)
		local map_ports=$(uci -q get mapagent.agent.map_port)

		for port in $map_ports ; do
			if [ "$port" = "all" -o "$port" = "*" ]; then
				echo "$all_ports"
				return
			fi
		done
		echo "$map_ports"
	}

	ts_create() {
		local brcm_setup="$(uci -q get mapagent.agent.brcm_setup)"

		ebtables -t broute -D BROUTING -p 802_1Q  --vlan-encap 888e -j DROP
		ebtables -t broute -I BROUTING 1 -p 802_1Q  --vlan-encap 888e -j DROP

		_dhcp_setup() {
			local name=$1

			[ -n "$(uci -q get dhcp.${name})" ] && return

			uci -q set dhcp.${name}=dhcp
			uci -q set dhcp.${name}.interface="${name}"
			uci -q set dhcp.${name}.start="100"
			uci -q set dhcp.${name}.limit="150"
			uci -q set dhcp.${name}.leasetime="1h"
			uci -q set dhcp.${name}.dhcpv4="server"
			uci -q set dhcp.${name}.dhcpv6="server"
			uci -q set dhcp.${name}.ra="server"
			uci -q set dhcp.${name}.ra_slaac="1"
			uci -q add_list dhcp.${name}.ra_flags="managed-config"
			uci -q add_list dhcp.${name}.ra_flags="other-config"

			uci -q commit dhcp
		}


		_firewall_setup() {
			local name=$1
			local network=$1
			local vid=$2
			local exists=0
			local diff=0
			local subnet=$(($vid % 256))
			local ipaddr="192.168.${subnet}.1"

			config_foreach net_get_albridge_ip interface $PRIMARY_VID

			# replace third octet of the ip with $subnet
			ipaddr=$(echo $ipaddr | sed "s/\(\([0-9]\{1,3\}\.\)\{2\}\)[0-9]\{1,3\}/\1${subnet}/")

			config_load firewall

			_process_zone() {
				local section=$1
				local new_name=$2
				local val=$3
				local name

				config_get name $section $val

				[ "$name" == "$new_name" ] && exists=1
			}

			config_foreach _process_zone zone $name "name"
			[ "$exists" == "0" ] && {
				uci -q add firewall zone
				uci -q set firewall.@zone[-1].name="$name"
				uci -q add_list firewall.@zone[-1].network="$network"
				uci -q set firewall.@zone[-1].input='DROP'
				uci -q set firewall.@zone[-1].output='ACCEPT'
				uci -q set firewall.@zone[-1].forward='DROP'
				diff=1
			}

			exists=0
			config_foreach _process_zone forwarding $name "src"
			[ "$exists" == "0" ] && {
				uci -q add firewall forwarding
				uci -q set firewall.@forwarding[-1].src="$name"
				uci -q set firewall.@forwarding[-1].dest="wan"
				diff=1
			}

			[ -z "$(uci -q get firewall.${name}_dns)" ] && {
				uci set firewall.${name}_dns=rule
				uci set firewall.${name}_dns.name="Allow-DNS-${name}"
				uci set firewall.${name}_dns.src="${name}"
				uci set firewall.${name}_dns.dest_port="53"
				uci set firewall.${name}_dns.proto="tcp udp"
				uci set firewall.${name}_dns.target="ACCEPT"
				diff=1
			}

			[ -z "$(uci -q get firewall.${name}_dhcp)" ] && {
				uci set firewall.${name}_dhcp=rule
				uci set firewall.${name}_dhcp.name="Allow-DHCP-${name}"
				uci set firewall.${name}_dhcp.src="${name}"
				uci set firewall.${name}_dhcp.src_port="68"
				uci set firewall.${name}_dhcp.dest_port="67"
				uci set firewall.${name}_dhcp.proto="udp"
				uci set firewall.${name}_dhcp.family="ipv4"
				uci set firewall.${name}_dhcp.target="ACCEPT"
				diff=1
			}

			[ -z "$(uci -q get firewall.${name}_ping)" ] && {
				uci set firewall.${name}_ping=rule
				uci set firewall.${name}_ping.name="Allow-Ping-${name}"
				uci set firewall.${name}_ping.src="${name}"
				uci set firewall.${name}_ping.dest_ip="${ipaddr}"
				uci set firewall.${name}_ping.proto="icmp"
				uci set firewall.${name}_ping.icmp_type="echo-request"
				uci set firewall.${name}_ping.family="ipv4"
				uci set firewall.${name}_ping.target="ACCEPT"
				diff=1
			}

			[ $diff -eq 1 ] && uci -q commit firewall
		}

		_net_setup() {
			net_check_for_vlan() {
				local section=$1
				local vid=$2

				config_get vlan "$section" vlan

				[ "$vid" = "$vlan" ] && {
					echo "1"
					break
				}
			}

			net_check_for_network() {
				local section=$1
				local vid=$2

				config_get device "$section" device

				[ "${AL_BRIDGE}.$vid" = "$device" ] && {
					echo "1"
					break
				}
			}

			find_vid_interface() {
				local section="$1"
				local mapc_vid="$2"
				local multi_ap_vid=""
				local prefix=""

				# Check if this section has multi_ap_vid option
				config_get multi_ap_vid "$section" multi_ap_vid

				# If multi_ap_vid value matches mapcontroller vid
				if [ -n "$multi_ap_vid" -a "$multi_ap_vid" = "$mapc_vid" ]; then
					# Try to get device option
					config_get device "$section" device

					if [ -n "$device" ]; then
						# ignore if device is a bridge device
						[ "${device:0:3}" == "br-" ] && return 0

						netif_device="$device"
					else
						config_get proto "$section" proto
						case $proto in
							6in4|6rd|6to4|map) prefix="$proto" ;;
							dslite) prefix="ds" ;;
							gre) prefix="gre4" ;;
							gretap) prefix="gre4t" ;;
							grev6) prefix="gre6" ;;
							grev6tap) prefix="gre6t" ;;
							*) return 0 ;;
						esac
						netif_device="${prefix}-${section}"
						tunnel_if=1
					fi

					return 0
				fi
			}

			local vid=$1
			diff=""

			config_load network

			local name="vlan${vid}"
			local br_dev="${AL_BRIDGE/-/_}"
			local tag=":t"
			local self_flags="untagged"
			local map_ports="$(map_get_ports)"

			# add bridge-vlan section for $vid if doesnt exist
			exists=$(config_foreach net_check_for_vlan bridge-vlan $vid)
			[ -z "$exists" ] && {
				uci -q set network.${name}="bridge-vlan"
				uci -q set network.${name}.name="${name}"
				uci -q set network.${name}.device="$AL_BRIDGE"
				uci -q set network.${name}.vlan="$vid"
			}

			# guest vids are trunked
			[ "${vid}" = "${PRIMARY_VID}" ] && {
#				self_flags="untagged pvid"
				uci -q set network.${name}.flags="untagged pvid"
				tag=":*"
			}
			uci -q set network.${name}.local='1'

			uci -q delete network.${name}.ports

			# Handle extra network interfaces that should be part of the segregated VLAN
			local netif_device=""
			config_foreach find_vid_interface interface $vid

			# support tagging for all map_port
			for port in $map_ports ; do
				uci -q get network.${name}.ports | grep -q "${port}" && {
					uci -q del_list network.${name}.ports="${port}"
				}
				uci -q add_list network.${name}.ports="${port}${tag}"
			done

			# tag devices assigned to netif_device
			[ -n "$netif_device" ] && {
				uci -q del_list network.${name}.ports="${netif_device}:*"
				uci -q add_list network.${name}.ports="${netif_device}:*"
				uci -q commit network
				diff="1"
				return
			}

			# non-map ports add PVID egress untagged
			[ "${vid}" = "${PRIMARY_VID}" ] && {
				for port in $(uci -q get network.${br_dev}.ports) ; do
					uci -q get network.${name}.ports | grep -q "${port}" && continue
					uci -q add_list network.${name}.ports="${port}"
				done
			}

			uci -q commit network
			diff="1"

			# add interface sections for subinterface if doesnt exist
			exists=$(config_foreach net_check_for_network interface $vid)
			[ -z "$exists" -a "$PRIMARY_VID" != "$vid" -a -x "/usr/sbin/mapcontroller" ] && {
				local name="guest${vid}"
				local br_dev="${AL_BRIDGE/-/_}"
				local tag=":t"
				local self_flags="untagged"
				local subnet=$(($vid % 256))

				netmask="255.255.255.0"
				ipaddr="192.168.1.1"

				config_load network
				config_foreach net_get_albridge_ip interface $PRIMARY_VID

				# replace third octet of the ip with $subnet
				ipaddr=$(echo $ipaddr | sed "s/\(\([0-9]\{1,3\}\.\)\{2\}\)[0-9]\{1,3\}/\1${subnet}/")

				uci -q set network.${name}="interface"
				uci -q set network.${name}.device="${AL_BRIDGE}.$vid"
				uci -q set network.${name}.ipaddr="$ipaddr"
				uci -q set network.${name}.netmask="$netmask"
				uci -q set network.${name}.proto="static"

				uci -q commit network
				diff="1"
			}
		}

		vid=$1

		[ -n "$vid" ] || {
			cat <<EOF
VID required to configure.
EOF
			exit 1
		}

		logger -t vlan "setup ts vid $vid"
		local tunnel_if=0
		_net_setup ${vid}

		[ -x "/usr/sbin/mapcontroller" -a "$PRIMARY_VID" != "$vid" -a $tunnel_if -eq 0 ] && {
			_dhcp_setup guest${vid}
			_firewall_setup guest${vid} $vid
		}

		[ "$brcm_setup" == "1" ] && {
			# Disable pktfwd here and flush FlowCache rules
			echo 0 > /proc/pktfwd_dhd/enable
			echo 0 > /proc/pktfwd_wl/enable
			fcctl flush
		}
	}

	ts_reload() {
		/etc/init.d/dnsmasq reload &
		ubus call uci commit '{"config":"network"}'
		/etc/init.d/firewall reload &
	}

	# maintain VIDs passed as args in network config, remove rest
	ts_keep() {
		local al_bridge=$(uci -q get mapagent.agent.al_bridge)
		restart=""

		[ "$al_bridge" = "" ] && al_bridge="br-lan"

		bridge_vlan_teardown() {
			local section=$1
			shift
			local bridge=$1
			shift
			local keep="$@"

			config_get device "$section" device

			[ "$bridge" != "$device" ] && continue

			config_get vlan "$section" vlan

			for i in $@; do
				if [ "$i" -eq "$vlan" ] ; then
					return
				fi
			done

			restart="1"
			_guest_cleanup $vlan
		}

		config_load network
		config_foreach bridge_vlan_teardown bridge-vlan $al_bridge $@

		if [ "$restart" = "1" ]; then
			uci commit network
			ts_dbg "trigger network restart"
			/etc/init.d/network restart
		fi
	}

	ts_cleanup() {
		local brcm_setup="$(uci -q get mapagent.agent.brcm_setup)"

		ebtables -t broute -D BROUTING -p 802_1Q  --vlan-encap 888e -j DROP

		local vid=$1
		restart=""

		[ -n "$vid" ] && _guest_cleanup $vid

		[ "$brcm_setup" == "1" ] && {
			# enable pktfwd again and flush FlowCache rules
			echo 1 > /proc/pktfwd_dhd/enable
			echo 1 > /proc/pktfwd_wl/enable
			echo 0 > /proc/pktfwd_dhd/enable
			echo 0 > /proc/pktfwd_wl/enable
			echo 1 > /proc/pktfwd_dhd/enable
			echo 1 > /proc/pktfwd_wl/enable
			fcctl flush
		}
	}

	ts_isolate_wds() {
		local action=$1 # add/del
		local ifname=$2 # guest fbss name
		local wds=$3 # 4addr interface name or prefix+
		local br_dev="${AL_BRIDGE/-/_}"
		local map_ports="$(map_get_ports)"
		local all_ports=$(uci -q get network.${br_dev}.ports)

		[ -z "$wds" -o -z "$ifname" ] && return

		ebtables -D FORWARD -i $ifname -o $wds -j DROP > /dev/null 2>&1
		ebtables -D FORWARD -i $wds -o $ifname -j DROP > /dev/null 2>&1
		ebtables -D FORWARD -i $wds -o $wds -p 802_1Q --vlan-id ! $PRIMARY_VID -j DROP > /dev/null 2>&1

		# must not drop traffic to upstream
		json_load_file "$MAP_BH_FILE" 2>/dev/null
		json_get_var bk_ifname ifname
		json_cleanup

		# isolate eth map ports
		for port in $all_ports ; do
			ebtables -D FORWARD -i $wds -o $port -p 802_1Q --vlan-id ! $PRIMARY_VID -j DROP > /dev/null 2>&1
			ebtables -D FORWARD -i $port -o $wds -p 802_1Q --vlan-id ! $PRIMARY_VID -j DROP  > /dev/null 2>&1
		done

		[ "$action" != "add" ] && return

		for port in $map_ports ; do
			[ "$port" = "$bk_ifname" ] && continue
			ebtables -A FORWARD -i $wds -o $port -p 802_1Q --vlan-id ! $PRIMARY_VID -j DROP > /dev/null 2>&1
			ebtables -A FORWARD -i $port -o $wds -p 802_1Q --vlan-id ! $PRIMARY_VID -j DROP > /dev/null 2>&1
		done

		ebtables -A FORWARD -i $ifname -o $wds -j DROP > /dev/null 2>&1
		ebtables -A FORWARD -i $wds -o $ifname -j DROP > /dev/null 2>&1
		ebtables -A FORWARD -i $wds -o $wds -p 802_1Q --vlan-id ! $PRIMARY_VID -j DROP > /dev/null 2>&1
        }

	ts_isolate_ap() {
		ts_isolate_radio() {
			local section=$1
			local action=$2
			local band=$3
			local vid=$4
			local ifname=$5

			config_get apband "$section" band
			[ "$band" = "$apband" ] && continue

			config_get apvid "$section" vid
			[ "$vid" != "$apvid" ] && continue

			config_get apifname "$section" ifname
			[ "$ifname" = "$apifname" ] && continue # this should never happen

			ebtables -D FORWARD -i $ifname -o $apifname -j DROP > /dev/null 2>&1

			[ "$action" != "add" ] && continue

			ebtables -A FORWARD -i $ifname -o $apifname -j DROP > /dev/null 2>&1
		}
		local action=$1
		shift
		local pvid=$1 # pvid
		shift
		local vid=$1 # vid
		shift
		local band=$1 # band
		shift
		local ifname=$1 # guest fbss name
		shift
		local br_dev="${AL_BRIDGE/-/_}"
		local map_ports="$(map_get_ports)"
		local all_ports=$(uci -q get network.${br_dev}.ports)

		[ -z "$pvid" -o -z "$ifname" ] && return

		config_load mapagent
		config_foreach ts_isolate_radio ap $action $band $vid $ifname

		# must not drop traffic to upstream
		json_load_file "$MAP_BH_FILE" 2>/dev/null
		json_get_var bk_ifname ifname
		json_cleanup

		# isolate eth map ports
		for port in $all_ports ; do
			ebtables -D FORWARD -i $ifname -o $port -j DROP > /dev/null 2>&1
			ebtables -D FORWARD -i $port -o $ifname -j DROP  > /dev/null 2>&1
		done

		[ "$action" != "add" ] && return

		for port in $map_ports ; do
			[ "$port" = "$bk_ifname" ] && continue
			ebtables -A FORWARD -i $ifname -o $port -j DROP > /dev/null 2>&1
			ebtables -A FORWARD -i $port -o $ifname -j DROP > /dev/null 2>&1
		done
	}

	local func=$1
	shift

	case "$func" in
		create) ts_dbg "create $@"; ts_create $@;;
		keep) ts_dbg "keep $@"; ts_keep $@;;
		reload) ts_dbg "reload $@"; ts_reload $@;;
		cleanup) ts_dbg "cleanup $@"; ts_cleanup $@;;
		isolate_ap) ts_dbg "isolate_ap $@"; ts_isolate_ap $@;;
		isolate_wds) ts_dbg "isolate_wds $@"; ts_isolate_wds $@;;
		--help|help) ts_usage;;
		*) ts_usage; exit 1;;
	esac
}
