#!/bin/sh

. /lib/mcast/common.sh
. /lib/functions/network.sh

include /lib/network

CONFFILE=
PROG_EXE=/usr/sbin/mcproxy
PROG_PARAMS=
PROG_PARAMS_SEPARATOR=:
readonly WANPORT="$(jsonfilter -i /etc/board.json -e @.network.wan.device)"

snooping_bridges=


__device_is_bridge() {
	local device="$2"
	local devsec__="$(uci show network | grep -F ".name='$device'" | cut -d'.' -f2)"
	local sectype="$(uci -q get network.$devsec__)"
	local devtype="$(uci -q get network.$devsec__.type)"
	[ "$sectype" != "device" -o "$devtype" != "bridge" ] && return 1
	eval "$1=$devsec__"
}

device_is_bridge() {
	local device="$1"
	local devsec=
	__device_is_bridge devsec "$device" || return 1
}

device_ports() {
	local device="$1"
	local devsec=

	if __device_is_bridge devsec "$device"; then
		echo "$(uci get network.$devsec.ports)"
	else
		echo "$device"
	fi
}

device_has_ip() {
	local protocol="$1"
	local device="$2"

	# Read the openwrt interface for the device.
	# Device can have multiple logical interfaces like wan and wan6
	# but same l3 device
	local ifaces=$(ubus call network.interface dump | jsonfilter -e "@.interface[@.device='$device'].interface")
	for iface in $ifaces; do
		local ip=
		case "$protocol" in
			"igmp") network_get_ipaddr ip "$iface" ;;
			"mld") network_get_ipaddr6 ip "$iface" ;;
		esac
		[ -n "$ip" ] && return
	done

	return 1
}

config_mcproxy_interfaces() {
	local protocol="$1"
	local upstreams="$2"
	local downstreams="$3"
	local exceptions="$4"

	if [ -z "$upstreams" ] || [ -z "$downstreams" ]; then
		return 1
	fi

	local str_up=""
	for upstream in $upstreams; do
		device_has_ip "$protocol" "$upstream" || continue
		str_up="$str_up \"$upstream\""
	done
	[ -z "$str_up" ] && return 1

	local str_down=""
	for downstream in $downstreams; do
		device_has_ip "$protocol" "$downstream" || continue
		str_down="$str_down \"$downstream\""
	done
	[ -z "$str_down" ] && return 1

	echo -e "pinstance main:$str_up ==>$str_down;\n" >> $CONFFILE

	local filter=""
	for excp in $exceptions; do
		case $excp in
		*/*)
			if [ "$protocol" == "igmp" ]; then
				ip_start="$(ipcalc.sh $excp | grep IP | awk '{print substr($0,4)}')"
				ip_end="$(ipcalc.sh $excp | grep BROADCAST | awk '{print substr($0,11)}')"
				filter="$filter ($ip_start - $ip_end | *)"
			elif [ "$protocol" == "mld" ]; then
				output="$(sipcalc $excp | grep "Network range" -A 1 | cut -d'-' -f2 | tr '\n' ' ')"
				ip_start="$(echo $output | awk '{print $1}')"
				ip_end="$(echo $output | awk '{print $2}')"
				filter="$filter ($ip_start - $ip_end | *)"
			fi
			;;
		*)
			filter="$filter ($excp | *)"
			;;
		esac
	done

	for upstream in $str_up; do
		echo "pinstance main upstream $upstream in blacklist table{$filter };" >> $CONFFILE
		echo "pinstance main upstream $upstream out blacklist table{$filter };" >> $CONFFILE
	done

	for downstream in $str_down; do
		echo "pinstance main downstream $downstream in blacklist table{$filter };" >> $CONFFILE
		echo "pinstance main downstream $downstream out blacklist table{$filter };" >> $CONFFILE
	done
}

config_sysfs_mcast_snooping() {
	local downstreams="$1"
	local snooping="$2"

	for downstream in $downstreams; do
		if device_is_bridge "$downstream"; then
			echo 0 > /sys/class/net/$downstream/bridge/multicast_snooping
			echo $snooping > /sys/class/net/$downstream/bridge/multicast_snooping
		fi
	done
}

config_sysfs_mcast_fastleave() {
	local downstreams="$1"
	local fastleave="$2"
	local prt

	for downstream in $downstreams; do
		for prt in $(device_ports $downstream); do
			if [ -f  /sys/class/net/$prt/brport/multicast_fast_leave ]; then
				echo $fastleave > /sys/class/net/$prt/brport/multicast_fast_leave
			fi
		done
	done
}

config_sysfs_mcast_version() {
	local protocol="$1"
	local interfaces="$2"
	local version="$3"

	for iface in $interfaces; do
		echo $version > /sys/class/net/$iface/bridge/multicast_"$protocol"_version
	done
}

config_sysfs_mcast_robustness() {
	local interfaces="$1"
	local robustness="$2"

	for iface in $interfaces; do
		echo $robustness > /sys/class/net/$iface/bridge/multicast_last_member_count
	done
}

config_sysfs_mcast_query_interval() {
	local interfaces="$1"
	local query_interval="$2"

	for iface in $interfaces; do
		echo $query_interval > /sys/class/net/$iface/bridge/multicast_query_interval
	done
}

config_sysfs_mcast_q_resp_interval() {
	local interfaces="$1"
	local q_resp_interval="$2"

	for iface in $interfaces; do
		echo $q_resp_interval > /sys/class/net/$iface/bridge/multicast_query_response_interval
	done
}

config_sysfs_mcast_last_mem_q_int() {
	local interfaces="$1"
	local last_mem_q_int="$2"

	for iface in $interfaces; do
		echo $last_mem_q_int > /sys/class/net/$iface/bridge/multicast_last_member_interval
	done
}

config_sysfs_mcast_flood() {
	local downstreams=$1
	local mcast_mode=$2
	local prt

	local mcast_flood=
	if [ $mcast_mode == "2" ]; then # disable mcast flood
		mcast_flood=0
	else
		mcast_flood=1
	fi

	for downstream in $downstreams; do
		for prt in $(device_ports $downstream); do
			if [ -f  /sys/class/net/$prt/brport/multicast_flood ]; then
				echo $mcast_flood > /sys/class/net/$prt/brport/multicast_flood
			fi
		done
	done
}

config_snooping_mode() {
	local interfaces="$1"
	local snooping="$2"

	# snooping_mode:
	#  0 - snooping is disabled
	#  1 - multicast flood is enabled
	#  2 - multicast flood is disabled
	[ -z "$snooping_mode" ] && snooping_mode=2

	if [ "$snooping_mode" == 0 ]; then
		config_sysfs_mcast_snooping "$interfaces" 0
	else
		config_sysfs_mcast_snooping "$interfaces" 1
	fi

	config_sysfs_mcast_flood "$interfaces" "$snooping_mode"
}

config_mcproxy_instance() {
	local protocol="$1"
	local version="$2"

	local robustness=
	local query_interval=
	local q_resp_interval=
	local last_mem_q_int=
	local fast_leave=0
	local exceptions=
	local upstreams=
	local downstreams=
	local mcast_mode=2 # default value 2 is for blocking mode

	CONFFILE=/var/etc/mcproxy_"$protocol".conf
	rm -f $CONFFILE
	touch $CONFFILE

	if [ "$protocol" == "igmp" ]; then
		case "$version" in
			[1-3])
				echo -e "protocol IGMPv${version};\n" >> $CONFFILE
				;;
			*)
				echo -e "protocol IGMPv2;\n" >> $CONFFILE
				;;
		esac

		robustness=$igmp_p_robustness
		query_interval=$igmp_query_interval
		q_resp_interval=$igmp_q_resp_interval
		last_mem_q_int=$igmp_last_mem_q_int
		fast_leave=$igmp_fast_leave
		exceptions=$igmp_p_exceptions

		upstreams=$igmp_p_up_interfaces
		downstreams=$igmp_p_down_interfaces
		mcast_mode=$igmp_p_mode

	elif [ "$protocol" == "mld" ]; then
		case "$version" in
			[1-2])
				echo -e "protocol MLDv${version};\n" >> $CONFFILE
				;;
			*)
				echo -e "protocol MLDv2;\n" >> $CONFFILE
				;;
		esac

		robustness=$mld_p_robustness
		query_interval=$mld_query_interval
		q_resp_interval=$mld_q_resp_interval
		last_mem_q_int=$mld_last_mem_q_int
		fast_leave=$mld_fast_leave
		exceptions=$mld_p_exceptions

		upstreams=$mld_p_up_interfaces
		downstreams=$mld_p_down_interfaces
		mcast_mode=$mld_p_mode
	fi

	[ -n "$max_groups" ] && echo -e "max_groups $max_groups;" >> $CONFFILE
	[ -n "$robustness" ] && echo -e "rv $robustness;" >> $CONFFILE
	[ -n "$query_interval" ] && echo -e "qi $query_interval;" >> $CONFFILE
	[ -n "$q_resp_interval" ] && echo -e "qri $q_resp_interval;" >> $CONFFILE
	[ -n "$last_mem_q_int" ] && echo -e "lmqi $last_mem_q_int;" >> $CONFFILE
	[ -n "$fast_leave" ] && echo -e "fastleave $fast_leave;\n" >> $CONFFILE

	config_mcproxy_interfaces "$protocol" "$upstreams" "$downstreams" "$exceptions" || return

	# for snooping to work we should enable it on the bridge, doing it from
	# here instead of from inside network config
	if [ "$downstreams" != "$snooping_bridges" ]; then
		if [ "$mcast_mode" == "0" ]; then
			config_sysfs_mcast_snooping "$downstreams" 0
		else
			config_sysfs_mcast_snooping "$downstreams" 1
		fi

		[ -n $fast_leave ] &&
			config_sysfs_mcast_fastleave "$downstreams" "$fast_leave"
		config_sysfs_mcast_flood "$downstreams" "$mcast_mode"

		for iface in $downstreams; do
			if device_is_bridge "$iface"; then
				if [ "${protocol}" = "mld" ]; then
					ebtables -t broute -I mcsnooping -s Unicast -d 33:33:00:00:00:00/ff:ff:00:00:00:00 --logical-in ${iface} ! -i "${WANPORT}+" --mld-snooping
				elif [ "${protocol}" = "igmp" ]; then
					ebtables -t broute -I mcsnooping -s Unicast -d 01:00:5e:00:00:00/ff:ff:ff:00:00:00 --logical-in ${iface} ! -i "${WANPORT}+" --igmp-snooping
				fi
			else
				if [ "${protocol}" = "mld" ]; then
					ebtables -t broute -I mcsnooping -s Unicast -d 33:33:00:00:00:00/ff:ff:00:00:00:00 -i ${iface} --mld-snooping
				elif [ "${protocol}" = "igmp" ]; then
					ebtables -t broute -I mcsnooping -s Unicast -d 01:00:5e:00:00:00/ff:ff:ff:00:00:00 -i ${iface} --igmp-snooping
				fi
			fi
		done
	fi

	PROG_PARAMS="${PROG_PARAMS} -f ${CONFFILE}${PROG_PARAMS_SEPARATOR}"
}

disable_snooping_iface() {
	local iface="$(uci -q get network.$1.name)"
	config_sysfs_mcast_snooping "$iface" 0
}

disable_snooping() {
	config_load network
	config_foreach disable_snooping_iface device

	ebtables --concurrent -t broute -F mcsnooping

	# Clean up populated /procfs entries
	rmmod ebt_igmpsnooping 2> /dev/null #Loaded on demand
	rmmod ebt_mldsnooping 2> /dev/null #Loaded on demand
}

config_snooping() {
	local protocol="$1"

	local version=
	local robustness=
	local query_interval=
	local q_resp_interval=
	local last_mem_q_int=
	local fast_leave=0
	local snooping_mode=
	local interfaces=

	local HZ=100

	local all_interfaces=
	if [ "$protocol" == "igmp" ]; then
		all_interfaces=$igmp_s_iface
	elif [ "$protocol" == "mld" ]; then
		all_interfaces=$mld_s_iface
	fi
	for iface in $all_interfaces; do
		device_is_bridge "$iface" || continue
		interfaces="$interfaces $iface"
	done
	[ -z "$interfaces" ] && return
	snooping_bridges="$interfaces"

	if [ "$protocol" == "igmp" ]; then
		case "$igmp_s_version" in
			[1-3])
				version="$igmp_s_version"
				;;
			*)
				version="2"
				;;
		esac

		robustness=$igmp_s_robustness
		query_interval=$(( igmp_s_query_interval * HZ ))
		q_resp_interval=$(( igmp_s_q_resp_interval * HZ / 10 ))
		last_mem_q_int=$(( igmp_s_last_mem_q_int * HZ / 10 ))
		fast_leave=$igmp_s_fast_leave
		snooping_mode=$igmp_s_mode
	elif [ "$protocol" == "mld" ]; then
		case "$mld_s_version" in
			[1-2])
				version="$mld_s_version"
				;;
			*)
				version="2"
				;;
		esac

		robustness=$mld_s_robustness
		query_interval=$(( mld_s_query_interval * HZ ))
		q_resp_interval=$(( mld_s_q_resp_interval * HZ / 10 ))
		last_mem_q_int=$(( mld_s_last_mem_q_int * HZ / 10 ))
		fast_leave=$mld_s_fast_leave
		snooping_mode=$mld_s_mode
	fi

	config_snooping_mode "$interfaces" "$snooping_mode"

	[ -n "$version" ] && config_sysfs_mcast_version "$protocol" "$interfaces" "$version"
	[ -n "$robustness" ] && config_sysfs_mcast_robustness "$interfaces" "$robustness"
	[ -n "$query_interval" ] && config_sysfs_mcast_query_interval "$interfaces" "$query_interval"
	[ -n "$q_resp_interval" ] && config_sysfs_mcast_q_resp_interval "$interfaces" "$q_resp_interval"
	[ -n "$last_mem_q_int" ] && config_sysfs_mcast_last_mem_q_int "$interfaces" "$last_mem_q_int"
	[ -n "$fast_leave" ] && config_sysfs_mcast_fastleave "$interfaces" "$fast_leave"

	for iface in $snooping_bridges; do
		if [ "${protocol}" = "mld" ]; then
			ebtables -t broute -I mcsnooping -s Unicast -d 33:33:00:00:00:00/ff:ff:00:00:00:00 --logical-in ${iface} ! -i "${WANPORT}+" --mld-snooping
		elif [ "${protocol}" = "igmp" ]; then
			ebtables -t broute -I mcsnooping -s Unicast -d 01:00:5e:00:00:00/ff:ff:ff:00:00:00 --logical-in ${iface} ! -i "${WANPORT}+" --igmp-snooping
		fi
	done
}

config_mcproxy() {
	disable_snooping
	if [ "$igmp_p_enable" == "1" ]; then
		config_mcproxy_instance igmp "$igmp_p_version"
	fi
	if [ "$igmp_s_enable" == "1" ]; then
		config_snooping igmp "$igmp_s_version"
	fi

	if [ "$mld_p_enable" == "1" ]; then
		config_mcproxy_instance mld "$mld_p_version"
	fi
	if [ "$mld_s_enable" == "1" ]; then
		config_snooping mld "$mld_s_version"
	fi
}

setup_mcast_mode() {
	:
}

configure_mcast() {
	config_global_params "set_max_groups_and_sources"

	read_mcast_snooping_params
	read_mcast_proxy_params
	config_mcproxy
}

validate_params() {
	[ -n "${PROG_PARAMS}" ] && return 0

	return 1
}
