#!/bin/sh /etc/rc.common
# Copyright (C) 2018 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
# Released under GPL 3. See LICENSE for the full terms.

START=90

USE_PROCD=1

. /usr/lib/unbound/iptools.sh
. /lib/functions/network.sh

global_multipath_settings() {
	local multipath mptcp_path_manager mptcp_schdeduler mptcp_debug congestion mptcp_checksum mptcp_syn_retries mptcp_fullmesh_num_subflows mptcp_fullmesh_create_on_err mptcp_ndiffports_num_subflows
	local multipath_status=0
	config_load network
	config_get multipath globals multipath
	config_get mptcp_path_manager globals mptcp_path_manager
	config_get mptcp_scheduler globals mptcp_scheduler
	config_get mptcp_debug globals mptcp_debug
	config_get congestion globals congestion
	config_get mptcp_checksum globals mptcp_checksum
	config_get mptcp_syn_retries globals mptcp_syn_retries
	config_get mptcp_fullmesh_num_subflows globals mptcp_fullmesh_num_subflows
	config_get mptcp_fullmesh_create_on_err globals mptcp_fullmesh_create_on_err
	config_get mptcp_ndiffports_num_subflows globals mptcp_ndiffports_num_subflows
	
	[ "$multipath" = "enable" ] && multipath_status=1

	# Global MPTCP configuration
	sysctl -qw net.mptcp.mptcp_enabled="$multipath_status"
	[ -z "$mptcp_path_manager" ] || sysctl -qw net.mptcp.mptcp_path_manager="$mptcp_path_manager"
	[ -z "$mptcp_scheduler" ] || sysctl -qw net.mptcp.mptcp_scheduler="$mptcp_scheduler"
	[ -z "$congestion" ] || sysctl -qw net.ipv4.tcp_congestion_control="$congestion"
	[ -z "$mptcp_checksum" ] || sysctl -qw net.mptcp.mptcp_checksum="$mptcp_checksum"
	[ -z "$mptcp_debug" ] || sysctl -qw net.mptcp.mptcp_debug="$mptcp_debug"
	[ -z "$mptcp_syn_retries" ] || sysctl -qw net.mptcp.mptcp_syn_retries="$mptcp_syn_retries"
	[ -z "$mptcp_fullmesh_num_subflows" ] || sysctl -qw /sys/module/mptcp_fullmesh/parameters/num_subflows="$mptcp_fullmesh_num_subflows"
	[ -z "$mptcp_fullmesh_create_on_err" ] || sysctl -qw /sys/module/mptcp_fullmesh/parameters/create_on_err="$mptcp_fullmesh_create_on_err"
	[ -z "$mptcp_ndiffports_num_subflows" ] || sysctl -qw /sys/module/mptcp_ndiffports/parameters/num_subflows="$mptcp_ndiffports_num_subflows"
}

interface_multipath_settings() {
	local mode iface proto
	local config="$1"
	local intf="$2"
	local enabled

	config_get enabled "$config" auto "1"
	config_get iface "$config" ifname
	[ -z "$iface" ] && iface=$(ifstatus "$config" | jsonfilter -q -e '@["l3_device"]')
	count=$(($count+1))
	id=$count
	config_set "$config" metric $count
	uci -q set network.${config}.metric=$count
	uci -q set openmptcprouter.${config}.metric=$count
	config_get mode "$config" multipath
	[ "$mode" = "" ] && {
		mode="$(uci -q get openmptcprouter.${config}.multipath)"
		[ -n "$mode" ] && uci -q set network.${config}.multipath="$mode"
	}
	[ "$mode" = "" ] && {
		[ "$config" = "lan" ] && mode="off"
		[ "$config" = "omrvpn" ] && mode="off"
		[ "$config" = "omr6in4" ] && mode="off"
		[ "$mode" = "" ] && mode="off"
		logger -t "MPTCP" "Multipath not defined for $config set to $mode"
		uci -q set network.${config}.multipath="$mode"
		uci -q set openmptcprouter.${config}.multipath="$mode"
	}
	[ "$mode" != "off" ] && {
		[ -n "$mptcpintf" ] && mptcpintf="$mptcpintf $iface"
		[ -z "$mptcpintf" ] && mptcpintf="$iface"
		uci -q set network.${config}.defaultroute=0
		uci -q set network.${config}.peerdns=0
	}
	[ "$mode" = "master" ] && {
		# Force that only one interface is master
		if [ "$master" != "" ]; then
			logger -t "MPTCP" "Multipath master already set, disable master for $config"
			mode="on"
			config_set "$config" multipath "on"
			uci -q set network.${config}.multipath="on"
			uci -q set openmptcprouter.${config}.multipath="on"
		else
			master="$config"
		fi
	}
	uci -q set openmptcprouter.${config}.multipath="$mode"

	[ "$enabled" = "0" ] && return 0
	[ -n "$intf" ] && [ "$iface" != "$intf" ] && return 0
	[ -z "$iface" ] && return 0
	[ "$config" = "omrvpn" ] && return 0
	[ -n "$(ifconfig | grep $iface)" ] || return 0
	[ "$(echo $iface | grep _dev)" != "" ] && return 0
	multipath "$iface" "$mode"
	#[ "$mode" = "off" ] && {
	#	ip rule del table $id > /dev/null 2>&1
	#	ip route flush $id > /dev/null 2>&1
	#	return 1
	#}

	# IPv4 Updates:
	local ipaddr
	local gateway
	local network
	local netmask
	local proto
	config_get proto $config proto
	if [ "$proto" = "static" ]; then
		config_get ipaddr $config ipaddr
		config_get gateway $config gateway
		config_get netmask $config netmask
		[ -n "$ipaddr" ] && [ -n "$netmask" ] && netmask=`ipcalc.sh $ipaddr $netmask | sed -n '/PREFIX=/{;s/.*=//;s/ .*//;p;}'`
		[ -n "$îpaddr" ] && [ -n "$netmask" ] && network=`ipcalc.sh $ipaddr $netmask | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'`
	else
		network_get_ipaddr ipaddr $config
		[ -z "$ipaddr" ] && ipaddr=$(ip -4 addr show dev $iface | grep inet | awk '{print $2}' | cut -d/ -f1 | tr -d "\n")
		network_get_gateway gateway $config true
		[ -z "$gateway" ] && gateway=$(ip -4 r list dev $iface | grep -v default | awk '/proto static/ {print $1}' | tr -d "\n")
		[ -z "$gateway" ] && gateway=$(uci -q get "network.$config.gateway")
		[ -z "$gateway" ] && gateway=$(ubus call network.interface.$config status | jsonfilter -q -l 1 -e '@.inactive.route[@.target="0.0.0.0"].nexthop' | tr -d "\n")
		if [ -z "$gateway" ] || [ "$( valid_subnet4 $gateway )" != "ok" ]; then
			gateway=$(ubus call network.interface.$config status | jsonfilter -q -l 1 -e '@.route[@.target="0.0.0.0"].nexthop' | tr -d "\n")
		fi
		if [ -z "$gateway" ] || [ "$( valid_subnet4 $gateway )" != "ok" ]; then 
			gateway=$(ubus call network.interface.${config}_4 status 2>/dev/null | jsonfilter -q -l 1 -e '@.inactive.route[@.target="0.0.0.0"].nexthop' | tr -d "\n")
		fi
		if [ -z "$gateway" ] || [ "$( valid_subnet4 $gateway )" != "ok" ]; then 
			gateway=$(ubus call network.interface.${config}_4 status 2>/dev/null | jsonfilter -q -l 1 -e '@.route[@.target="0.0.0.0"].nexthop' | tr -d "\n")
		fi
		if [ -z "$gateway" ] || [ "$( valid_subnet4 $gateway )" != "ok" ]; then
			gateway=$(traceroute -m1 -i $iface 8.8.8.8 2>/dev/null | awk 'FNR==2{ print $2 }')
		fi
		network_get_subnet netmask $config
		[ -n "$netmask" ] && [ "$(echo $netmask | grep '/')" != "" ] && netmask=""
		[ -z "$netmask" ] && netmask=$(ip -4 addr show dev $iface | grep peer | awk '{print $4}' | cut -d/ -f2 | tr -d "\n")
		[ -z "$netmask" ] && netmask=$(ip -4 addr show dev $iface | grep inet | awk '{print $2}' | cut -d/ -f2 | tr -d "\n")
		[ -n "$ipaddr" ] && [ -n "$netmask" ] && netmask=`ipcalc.sh $ipaddr $netmask | sed -n '/PREFIX=/{;s/.*=//;s/ .*//;p;}'`
		[ -n "$ipaddr" ] && [ -n "$netmask" ] && network=`ipcalc.sh $ipaddr $netmask | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'`
	fi
	if [ "$(uci -q get openmptcprouter.settings.uci_route)" = "1" ]; then
		uci -q batch <<-EOF >/dev/null
			delete network.${config}_rule
			delete network.${config}_route
			delete network.${config}_route_default
			commit network
		EOF
	else
		ip rule del table $id > /dev/null 2>&1
		ip route flush $id > /dev/null 2>&1
	fi

	if [ -n "$gateway" ] && [ -n "$network" ]; then
		if [ "$(uci -q get openmptcprouter.settings.uci_route)" = "1" ]; then
			uci -q batch <<-EOF >/dev/null
				delete network.${config}_rule
				set network.${config}_rule=rule
				set network.${config}_rule.lookup=${id}
				set network.${config}_rule.priority=0
				set network.${config}_rule.src="${ipaddr}/32"
				set network.${config}_rule.created=mptcp
				delete network.${config}_route
				set network.${config}_route=route
				set network.${config}_route.interface=${config}
				set network.${config}_route.target=${network}
				set network.${config}_route.netmask=${netmask}
				set network.${config}_route.table=${id}
				set network.${config}_route.created=mptcp
				delete network.${config}_route_default
				set network.${config}_route_default=route
				set network.${config}_route_default.interface=${config}
				set network.${config}_route_default.target='0.0.0.0'
				set network.${config}_route_default.netmask='0.0.0.0'
				set network.${config}_route_default.gateway=$gateway
				set network.${config}_route_default.table=${id}
				set network.${config}_route_default.created=mptcp
				commit network
			EOF
		else
			#echo "Add routes for $ipaddr table $id"
			ip rule add from $ipaddr table $id pref 0
			ip route replace $network/$netmask dev $iface scope link metric $id
			ip route replace $network/$netmask dev $iface scope link table $id
			ip route replace default via $gateway dev $iface table $id
			ip route replace default via $gateway dev $iface metric $id
			#ip route flush $id
		fi

		#config_get mode "$config" multipath ""
		#[ "$mode" = "" ] && mode="$(uci -q get openmptcprouter.${config}.multipath)"
		[ "$mode" = "master" ] && {
			#echo "ip route replace default via $gateway dev $iface"
			ip route replace default via $gateway dev $iface
		}
		[ "$mode" = "off" ] && {
			ifconfig $iface txqueuelen 50 > /dev/null 2>&1
		} || {
			ifconfig $iface txqueuelen 100 > /dev/null 2>&1
		}
	fi
	if [ "$(uci -q get openmptcprouter.settings.disable_ipv6)" != "1" ] && [ "$config" != "omr6in4" ]; then
		# IPv6 Updates:
		local ip6addr
		local ipaddr6
		local gateway6
		local network6
		local netmask6
		config_get ipaddr6 $config ip6addr
		config_get gateway6 $config ip6gw
		if [ -n "$ipaddr6" ]; then
			ip6addr=`echo $ip6addr | cut -d/ -f1`
			netmask6=`ipcalc $ipaddr6 | sed -n '/PREFIX=/{;s/.*=//;s/ .*//;p;}'`
			network6=`ipcalc $ip6addr | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'`
			if [ -z "$ip6addr" ] || [ -z "$network6" ]; then
				ip6addr=$(ip -6 addr show dev $iface | grep -v 'scope link' | grep inet6 | awk '{print $2}' | cut -d/ -f1 | tr -d "\n")
				gateway6=$(ip -6 r list dev $iface | grep -v default | awk '/proto static/ {print $1}' | tr -d "\n")
				[ -z "$gateway6" ] && gateway6=$(uci -q get "network.$config.ip6gw")
				[ -z "$gateway6" ] && gateway6=$(ubus call network.interface.$config status | jsonfilter -q -l 1 -e '@.inactive.route[@.target="::"].nexthop' | tr -d "\n")
				if [ -z "$gateway6" ] || [ "$( valid_subnet6 $gateway6 )" != "ok" ]; then 
					gateway6=$(ubus call network.interface.$config status | jsonfilter -q -l 1 -e '@.route[@.target="::"].nexthop' | tr -d "\n")
				fi
				if [ -z "$gateway6" ] || [ "$( valid_subnet6 $gateway6 )" != "ok" ]; then
					gateway6=$(ubus call network.interface.${config}_6 status 2>/dev/null | jsonfilter -q -l 1 -e '@.inactive.route[@.target="::"].nexthop' | tr -d "\n")
				fi
				netmask6=$(ip -6 addr show dev $iface | grep -v 'scope link' | grep inet6 | awk '{print $2}' | cut -d/ -f2 | tr -d "\n")
				network6=`ipcalc $ip6addr | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'`
			fi
		fi
		if [ "$(uci -q get openmptcprouter.settings.uci_route)" = "1" ]; then
			uci -q batch <<-EOF >/dev/null
				delete network.${config}_rule6
				delete network.${config}_route6
				delete network.${config}_route6_default
				commit network
			EOF
		else
			ip -6 rule del table 6$id > /dev/null 2>&1
			ip -6 route flush 6$id > /dev/null 2>&1
		fi
		if [ -n "$ip6addr" ] && [ -n "$gateway6" ] && [ -n "$network6" ]; then
			if [ "$(uci -q get openmptcprouter.settings.uci_route)" = "1" ]; then
				uci -q batch <<-EOF >/dev/null
					delete network.${config}_rule6
					set network.${config}_rule6=rule6
					set network.${config}_rule6.lookup=6${id}
					set network.${config}_rule6.priority=0
					set network.${config}_rule6.src="${ipaddr6}/127"
					set network.${config}_rule6.created=mptcp
					delete network.${config}_route6
					set network.${config}_route6=route6
					set network.${config}_route6.interface=${config}
					set network.${config}_route6.target=${network6}/${netmask6}
					set network.${config}_route6.table=6${id}
					set network.${config}_route6.created=mptcp
					delete network.${config}_route6_default
					set network.${config}_route6_default=route6
					set network.${config}_route6_default.interface=${config}
					set network.${config}_route6_default.target='::'
					set network.${config}_route6_default.gateway=$gateway6
					set network.${config}_route6_default.table=6${id}
					set network.${config}_route6_default.created=mptcp
					commit network
				EOF
			else
				ip -6 rule add from $ip6addr table 6$id pref 0
				ip -6 route replace $network6/$netmask6 dev $iface scope link table 6$id
				ip -6 route replace default via $gateway6 dev $iface table 6$id
				ip -6 route replace default via $gateway6 dev $iface metric $id
				ip -6 route flush 6$id
			fi

			#config_get mode "$config" multipath "off"
			[ "$mode" = "master" ] && {
				ip -6 route replace default via $gateway6 dev $iface
			}
			#[ "$mode" = "off" ] && {
			#	ifconfig $iface txqueuelen 50 > /dev/null 2>&1
			#} || {
			#	ifconfig $iface txqueuelen 100 > /dev/null 2>&1
			#}
		fi
	fi
}

load_interfaces() {
	config_get ifname "$1" ifname
	config_get multipath "$1" multipath ""
	[ -z "$multipath" ] && multipath="$(uci -q get openmptcprouter.$1.multipath)"
	[ "$multipath" != "off" ] && [ "$multipath" != "" ] && interfaces=" ${ifname} ${interfaces}"
}

set_multipath() {
	ls -1 /sys/class/net/ | while read iface; do
		exist=0
		for ifacemptcp in $mptcpintf; do
			if [ "$iface" = "$ifacemptcp" ]; then
				exist=1
			fi
		done
		[ "$exist" = "0" ] && multipath $iface off
	done
}

add_route() {
	config_get target "$1" target
	routeset="$target"
	config_get netmask "$1" netmask
	[ -n "$target" ] && [ -n "$netmask" ] && {
		netmask=`ipcalc.sh $target $netmask | sed -n '/PREFIX=/{;s/.*=//;s/ .*//;p;}'`
		network=`ipcalc.sh $target $netmask | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'`
		[ -n "$netmask" ] && [ "$target" = "$network" ] && routeset="$routeset/$netmask"
	}
	config_get gateway "$1" gateway
	[ -n "$gateway" ] && routeset="$routeset via $gateway"
	config_get metric "$1" metric
	[ -n "$metric" ] && routeset="$routeset metric $metric"
	config_get mtu "$1" mtu
	[ -n "$mtu" ] && routeset="$routeset mtu $mtu"
	config_get type "$1"
	[ -n "$type" ] && routeset="$routeset type $type"
	config_get interface "$1" interface
	iface=$(ifstatus "$interface" | jsonfilter -q -e '@["l3_device"]')
	routeset="$routeset dev $iface"
	logger -t "MPTCP" "Add route $routeset"
	ip route replace $routeset
}

add_route6() {
	config_get target "$1" target
	routeset="$target"
	config_get gateway "$1" gateway
	[ -n "$gateway" ] && routeset="$routeset via $gateway"
	config_get metric "$1" metric
	[ -n "$metric" ] && routeset="$routeset metric $metric"
	config_get mtu "$1" mtu
	[ -n "$mtu" ] && routeset="$routeset mtu $mtu"
	config_get type "$1"
	[ -n "$type" ] && routeset="$routeset type $type"
	config_get interface "$1" interface
	iface=$(ifstatus "$interface" | jsonfilter -q -e '@["l3_device"]')
	routeset="$routeset dev $iface"
	logger -t "MPTCP" "Add IPv6 route $routeset"
	ip -6 route replace $routeset
}

remove() {
	logger -t "MPTCP" "Remove network.$1"
	uci -q delete network.$1
}

start_service() {
	local intf=$1
	local id count intfmaster
	. /lib/functions.sh
	. /lib/functions/network.sh
	global_multipath_settings

	[ -n "$(ubus call system board | jsonfilter -e '@.board_name' | grep '3-model-b')" ] && [ "$(ip link show eth0 | grep UP)" = "" ] && {
		# RPI 3 workaround no network at boot
		ethtool eth0 > /dev/null 2>&1
		ethtool -s eth0 autoneg off > /dev/null 2>&1
		ip link set eth0 up > /dev/null 2>&1
		ethtool -s eth0 autoneg on > /dev/null 2>&1
	}

	mptcpintf=""
	master=""
	config_load network
	#config_foreach remove route
	#config_foreach remove route6
	#config_foreach remove rule
	#config_foreach remove rule6
	[ -z "$intf" ] && [ -n "$(uci -q get network.@route[-1])" ] && {
	#	logger -t "MPTCP" "Flush main table"
	#	ip route flush table main
	#	ip -6 route flush table main
		logger -t "MPTCP" "Flush route cache"
		ip route flush cache
		ip -6 route flush cache
	}
	config_foreach interface_multipath_settings interface $intf
	set_multipath
	config_foreach add_route route
	config_foreach add_route route6
	# If no master is defined, one interface is defined as master
	if [ "$master" = "" ] && [ "$intf" = "" ]; then
		intfmaster="$(echo $mptcpintf | cut -d' ' -f1 | tr -d '\n')"
		[ "$intfmaster" != "" ] && {
			logger -t "MPTCP" "No master multipath defined, setting it to $intfmaster"
			uci -q set network.${intfmaster}.multipath="master"
			uci -q set openmptcprouter.${intfmaster}.multipath="master"
		}
	fi
	uci -q commit network
	uci -q commit openmptcprouter
	[ -n "$(ubus call system board | jsonfilter -e '@.board_name' | grep raspberry)" ] && [ -z "$(ubus call system board | jsonfilter -e '@.board_name' | grep '4-model-b')" ] && {
		ethtool --offload eth0 rx off tx off > /dev/null 2>&1
	}
}

reload_service() {
	rc_procd start_service "$@"
	return 0
}

add_interface_trigger() {
	local interface ignore

	config_get interface "$1" interface
	config_get_bool ignore "$1" ignore 0

	[ -n "$interface" -a $ignore -eq 0 ] && procd_add_interface_trigger "interface.*" "$interface" /etc/init.d/mptcp reload
}

service_triggers() {
	procd_add_reload_trigger "network"
	config_load network
	config_foreach add_interface_trigger interface
}
