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

START=05

USE_PROCD=1

. /lib/functions/network.sh
[ -r /usr/share/omr/lib/omr-state.sh ] && . /usr/share/omr/lib/omr-state.sh

omr_intf_del() {
	[ -z "$1" ] && return
	uci -q delete openmptcprouter.$1
}

omr_intf_check() {
	[ "$(uci -q get network.$1)" = "" ] && omr_intf_del $1
	uci -q -P /var/state revert openmptcprouter.$1.publicip
	uci -q -P /var/state revert openmptcprouter.$1.publicip6
}

omr_intf_set() {
	local device
	local ifname
	network_get_device ifname $1
	[ -z "$ifname" ] && network_get_physdev ifname $1
	[ -z "$ifname" ] && config_get ifname "$1" device
	[ -z "$ifname" ] && config_get ifname "$1" ifname
	[ -z "$ifname" ] && ifname=$(ifstatus "$1" | jsonfilter -q -e '@["l3_device"]')
	#config_get devicename "$1" _modem_device
	devicename=${ifname}
	config_get type "$1" type

	if [ "$type" != "macvlan" ] && [ -n "$ifname" ] && [ -f /sys/class/net/${ifname}/device/uevent ]; then
		devicepath=$(readlink -f /sys/class/net/${ifname})
		if [ -n "$devicepath" ] && [ "$(echo ${devicepath} | grep virtual)" = "" ] && [ "$(cat /sys/class/net/${ifname}/device/uevent | grep PRODUCT)" != "" ] && [ "$(cat /sys/class/net/${ifname}/device/uevent | grep PCI_SLOT_NAME)" = "" ]; then
			mac=""
			if [ -f /sys/class/net/${ifname}/address ]; then
				mac="$(cat /sys/class/net/${ifname}/address | tr -d '\n')"
			fi
			uci -q set network.$1.modalias="$(cat /sys/class/net/${ifname}/device/uevent | grep MODALIAS | cut -d '=' -f2 | tr -d '\n')-$mac"
			uci -q set network.$1.product="$(cat /sys/class/net/${ifname}/device/uevent | grep PRODUCT | cut -d '=' -f2 | tr -d '\n')"
		elif [ -n "$devicepath" ] && ([ "$(echo ${devicepath} | grep virtual)" != "" ] || [ "$(echo ${devicepath} | grep virtual)" = "" ] && [ "$(cat /sys/class/net/${ifname}/device/uevent | grep PRODUCT)" = "" ] || [ "$(cat /sys/class/net/${ifname}/device/uevent | grep PCI_SLOT_NAME)" != "" ] || [ "$(cat /sys/class/net/${ifname}tmp/device/uevent)" != "" ]); then
			#uci -q delete network.$1.device
			uci -q delete network.$1.modalias
			uci -q delete network.$1.product
		fi
	elif [ "$type" != "macvlan" ] &&  [ -n "$devicename" ] && [ -f /sys/bus/usb-serial/devices/${devicename}/device/uevent ] && [ "$(cat /sys/class/net/${ifname}/device/uevent | grep PRODUCT)" != "" ]; then
		mac=""
		if [ -f /sys/class/net/${ifname}/address ]; then
			mac="$(cat /sys/class/net/${ifname}/address | tr -d '\n')"
		fi
		uci -q set network.$1.modalias="$(cat /sys/bus/usb-serial/devices/${devicename}/device/uevent | grep MODALIAS | cut -d '=' -f2 | tr -d '\n')-$mac"
		uci -q set network.$1.product="$(cat /sys/bus/usb-serial/devices/${devicename}/device/uevent | grep PRODUCT | cut -d '=' -f2 | tr -d '\n')"
	else
		uci -q delete network.$1.modalias
		uci -q delete network.$1.product
	fi
}

omr_set_settings() {
	local device
	local ifname
	local multipath
	# DHCPv6 companion interfaces (<intf>_6) are managed through their parent
	case "$1" in *_6)
		# The companion shares the parent's device and must stay at
		# multipath=off so it never counts as a WAN of its own (#4329);
		# earlier wizard versions copied the parent's value there.
		if [ "$(uci -q get network.$1.proto)" = "dhcpv6" ] && [ -n "$(uci -q get network.${1%_6})" ] && [ "$(uci -q get network.$1.multipath)" != "off" ]; then
			uci -q set network.$1.multipath=off
			[ -n "$(uci -q get openmptcprouter.$1)" ] && uci -q set openmptcprouter.$1.multipath=off
		fi
		return ;;
	esac
	config_get multipath "$1" multipath
	network_get_device ifname $1
	[ -z "$ifname" ] && network_get_physdev ifname $1
	[ -z "$ifname" ] && config_get ifname "$1" device
	[ -z "$ifname" ] && config_get ifname "$1" ifname
	config_get device "$1" device
	config_get proto "$1" proto
	config_get type "$1" type
	config_get ip6addr "$1" ip6addr
	config_get ipv6 "$1" ipv6
	config_get addlatency "$1" addlatency
	[ -z "$multipath" ] || [ "$multipath" = "off" ] && return
	[ "$1" = "omrvpn" ] || [ "$1" = "glorytun" ] && return

	uci -q set openmptcprouter.$1=interface
	uci -q set openmptcprouter.$1.multipath="$multipath"
	config_get disable_ipv6 settings disable_ipv6 "0"
	if [ "$disable_ipv6" = "1" ]; then
		uci -q set network.$1.ipv6=0
	else
		# per-interface wizard choice wins when set (openmptcprouter.<intf>.ipv6)
		ipv6_pref="$(uci -q get openmptcprouter.$1.ipv6)"
		uci -q set network.$1.ipv6="${ipv6_pref:-1}"
	fi
	ipv6="$(uci -q get network.$1.ipv6)"

	[ -z "$addlatency" ] && addlatency=0
	devicename=$(echo "$device" | cut -d'/' -f3)

	ifname=$(ifstatus "$1" | jsonfilter -q -e '@["l3_device"]')
	[ -z "$ifname" ] && config_get ifname "$1" ifname
	[ -n "$(echo $ifname | grep '@')" ] && ifname=$(ifstatus "$1" | jsonfilter -q -e '@["device"]')

	if [ -n "$ifname" ]; then
		if [ "$proto" = "dhcpv6" ] || [ "$ipv6" = "1" ] || [ -n "$ip6addr" ]; then
			# Change interface name for sysctl in case of VLAN (eth0.2 => eth0/2)
			ifnamesys=$(echo $ifname | sed 's:\.:/:')
			sysctl -qw net.ipv6.conf.${ifnamesys}.disable_ipv6=0
			[ -z "$ip6addr" ] && sysctl -qw net.ipv6.conf.${ifnamesys}.accept_ra=1
		else
			ifnamesys=$(echo $ifname | sed 's:\.:/:')
			sysctl -qw net.ipv6.conf.${ifnamesys}.disable_ipv6=1
			sysctl -qw net.ipv6.conf.${ifnamesys}.accept_ra=0
		fi

		if [ "$addlatency" = "0" ] && [ "$(tc qdisc show dev $ifname | grep delay)" != "" ]; then
			tc qdisc del dev ${ifname} root netem >/dev/null 2>&1
		fi
		if [ "$addlatency" != "0" ]; then
			if [ "$(tc qdisc show dev $ifname | grep delay)" = "" ]; then
				tc qdisc add dev ${ifname} root netem delay ${addlatency}ms >/dev/null 2>&1
			elif [ "$(tc qdisc show dev $ifname | awk '/delay/ { print $10 }' | sed 's/ms//')" != "$addlatency" ]; then
				tc qdisc replace dev ${ifname} root netem delay ${addlatency}ms >/dev/null 2>&1
			fi
		fi
	fi
}

# "Disable default gateway" (settings.defaultgw=0): no Internet while the VPN
# is down. The WAN default routes are then only kept in each WAN's own table,
# for what is bound to the WAN, but netifd adds an "iif lo lookup <table>"
# rule for every such table (priority 90000 and up). With no default route
# in the main table, i.e. while the VPN is down and at boot until it is up,
# the router's own traffic went on to these rules and left by a WAN: the
# resolver's queries for the LAN's DNS requests, NTP, update checks. An
# "iif lo unreachable" rule just before them ends that lookup. What is bound
# to a WAN (MPTCP subflows, trackers, 6in4) has its own rules at priority 0,
# and the routes to the VPS are host routes in the main table.
# The rule is only set with a VPN: with a proxy alone the main table never
# has a default route, and the router's own traffic, the proxy's own
# connections included, needs these rules. Nor when a VPS is given by name:
# it must be resolved before the VPN can come up.
# The VPS addresses go on to netifd's rules (goto an anchor at 90000, which
# netifd doesn't use as it adds the ifindex): the routes to the VPS are only
# set once a WAN tracker is up, and the VPN must be able to connect before.
OMR_DEFAULTGW_RULE_PREF=89999

_omr_defaultgw_wan_device() {
	local multipath device
	config_get multipath "$1" multipath
	case "$multipath" in
		on|master|backup) ;;
		*) return ;;
	esac
	network_get_device device "$1"
	[ -z "$device" ] && network_get_physdev device "$1"
	[ -n "$device" ] && omr_defaultgw_wan_devices="$omr_defaultgw_wan_devices $device"
}

_omr_defaultgw_vps() {
	local disabled ip
	config_get_bool disabled "$1" disabled 0
	[ "$disabled" = "1" ] && return
	config_get ip "$1" ip
	for ip in $ip; do
		# An address has only hex digits, dots and colons
		case "$ip" in
			*[!0-9A-Fa-f.:]*) omr_defaultgw_vps_name="$ip" ;;
			*) omr_defaultgw_vps_ips="$omr_defaultgw_vps_ips $ip" ;;
		esac
	done
}

# Remove the main-table default routes whose devices are all WAN devices
# (each WAN's own route and the balanced/backup multipath ones); the VPN's
# routes, 6in4's included, are left alone. omr-tracker only removes them when
# the VPN goes down, so the option had no effect when set during an outage.
_omr_defaultgw_purge() {
	local fam="$1"
	ip -"$fam" -o route show default 2>/dev/null | awk -v wans="$omr_defaultgw_wan_devices" '
		BEGIN { n = split(wans, w, " "); for (i = 1; i <= n; i++) wan[w[i]] = 1 }
		$1 != "default" { next }
		{
			metric = ""; via = ""; dev = ""; ndev = 0; multi = 0
			for (i = 2; i < NF; i++) {
				if ($i == "metric") metric = $(i + 1)
				else if ($i == "nexthop") multi = 1
				else if ($i == "via" && !multi) via = $(i + 1)
				else if ($i == "dev") {
					if (!($(i + 1) in wan)) next
					if (!multi) dev = $(i + 1)
					ndev++
				}
			}
			if (ndev) print metric, (multi ? "-" : via), (multi ? "-" : dev)
		}' | while read -r metric via dev; do
		if [ "$dev" = "-" ]; then
			ip -"$fam" route del default ${metric:+metric "$metric"} >/dev/null 2>&1
		else
			ip -"$fam" route del default ${via:+via "$via"} dev "$dev" ${metric:+metric "$metric"} >/dev/null 2>&1
		fi
	done
}

omr_defaultgw() {
	local block=0 fam previous
	local omr_defaultgw_wan_devices="" omr_defaultgw_vps_name="" omr_defaultgw_vps_ips="" ip

	previous="$(omr_state_get openmptcprouter.omr.defaultgw)"
	if [ "$(uci -q get openmptcprouter.settings.defaultgw)" = "0" ]; then
		config_load network
		config_foreach _omr_defaultgw_wan_device interface
		_omr_defaultgw_purge 4
		_omr_defaultgw_purge 6
		if [ "$(uci -q get openmptcprouter.settings.vpn)" != "none" ] && [ "$(uci -q get network.omrvpn.disabled)" != "1" ]; then
			config_load openmptcprouter
			config_foreach _omr_defaultgw_vps server
			if [ -z "$omr_defaultgw_vps_name" ]; then
				block=1
			else
				logger -t "OMR-defaultgw" "VPS $omr_defaultgw_vps_name is set by name: the router's own traffic is not blocked while the VPN is down"
			fi
		fi
		[ "$previous" = "0" ] || omr_state_set openmptcprouter omr defaultgw 0
	elif [ "$previous" = "0" ]; then
		# Set again while the VPN may be down: the mptcp init puts the WAN
		# default routes back in the main table
		omr_state_set openmptcprouter omr defaultgw 1
		/etc/init.d/mptcp reload >/dev/null 2>&1 &
	fi

	for fam in 4 6; do
		while ip -"$fam" rule del pref $((OMR_DEFAULTGW_RULE_PREF - 1)) >/dev/null 2>&1; do :; done
		if [ "$block" = "1" ]; then
			[ -n "$(ip -"$fam" rule show pref $((OMR_DEFAULTGW_RULE_PREF + 1)) iif lo 2>/dev/null)" ] || \
				ip -"$fam" rule add pref $((OMR_DEFAULTGW_RULE_PREF + 1)) iif lo lookup main >/dev/null 2>&1
			for ip in $omr_defaultgw_vps_ips; do
				case "$fam:$ip" in
					4:*:*|6:*.*) continue ;;
					6:*:*|4:*) ;;
					*) continue ;;
				esac
				ip -"$fam" rule add pref $((OMR_DEFAULTGW_RULE_PREF - 1)) iif lo to "$ip" goto $((OMR_DEFAULTGW_RULE_PREF + 1)) >/dev/null 2>&1
			done
			[ -n "$(ip -"$fam" rule show pref $OMR_DEFAULTGW_RULE_PREF iif lo 2>/dev/null)" ] || \
				ip -"$fam" rule add pref $OMR_DEFAULTGW_RULE_PREF iif lo unreachable >/dev/null 2>&1
		else
			while ip -"$fam" rule del pref $OMR_DEFAULTGW_RULE_PREF iif lo unreachable >/dev/null 2>&1; do :; done
			while ip -"$fam" rule del pref $((OMR_DEFAULTGW_RULE_PREF + 1)) iif lo lookup main >/dev/null 2>&1; do :; done
		fi
	done
}

start_service() {
	local scaling_min_freq scaling_max_freq scaling_governor

	omr_defaultgw

	config_load openmptcprouter
	config_foreach omr_intf_check interface
	config_load network
	config_foreach omr_set_settings interface
	uci -q commit network
	uci -q -P /var/state revert openmptcprouter.omr.vpn
	uci -q commit openmptcprouter

	[ -d /sys/devices/system/cpu/cpufreq/policy0 ] && {
		config_load openmptcprouter
		config_get scaling_min_freq settings scaling_min_freq
		[ -n "$scaling_min_freq" ] && {
			for c in $(ls -d /sys/devices/system/cpu/cpufreq/policy[0-9]*); do
				echo $scaling_min_freq > $c/scaling_min_freq
			done
		}
		config_get scaling_max_freq settings scaling_max_freq
		[ -n "$scaling_max_freq" ] && {
			for c in $(ls -d /sys/devices/system/cpu/cpufreq/policy[0-9]*); do
				echo $scaling_max_freq > $c/scaling_max_freq
			done
		}
		config_get scaling_governor settings scaling_governor
		[ -n "$scaling_governor" ] && {
			for c in $(ls -d /sys/devices/system/cpu/cpufreq/policy[0-9]*); do
				echo $scaling_governor > $c/scaling_governor
			done
		}
	}
	# remove sysctl already defined in /etc/sysctl.d/
	# This start_service() also runs on every "network" reload trigger (see
	# service_triggers below), which fires very often on a multi-WAN OMR box.
	# Only actually rewrite the files (busybox sed -i does a temp-file+rename
	# on every call) when one of the duplicate keys is really present, instead
	# of unconditionally touching flash on every single reload.
	sysctl_dup_pattern='tcp_fin_timeout|tcp_keepalive_time|nf_conntrack_max|tcp_syn_retries|tcp_fastopen|tcp_retries2|tcp_retries1|ip_default_ttl'
	grep -qE "$sysctl_dup_pattern" /etc/sysctl.conf 2>/dev/null && \
		sed -i -e '/tcp_fin_timeout/d' -e '/tcp_keepalive_time/d' -e '/nf_conntrack_max/d' -e '/tcp_syn_retries/d' -e '/tcp_fastopen/d' -e '/tcp_retries2/d' -e '/tcp_retries1/d' -e '/ip_default_ttl/d' /etc/sysctl.conf
	grep -qE "$sysctl_dup_pattern" /etc/sysctl.d/10-default.conf 2>/dev/null && \
		sed -i -e '/tcp_fin_timeout/d' -e '/tcp_keepalive_time/d' -e '/nf_conntrack_max/d' -e '/tcp_syn_retries/d' -e '/tcp_fastopen/d' -e '/tcp_retries2/d' -e '/tcp_retries1/d' -e '/ip_default_ttl/d' /etc/sysctl.d/10-default.conf

	if [ "$(uci -q get openmptcprouter.settings.country)" = "china" ] && [ -n "$(uci -q get dhcp.@dnsmasq[0].server | grep '127.0.0.1#5353')" ]; then
		uci -q batch <<-EOF > /dev/null
			del_list dhcp.@dnsmasq[0].server='127.0.0.1#5353'
			add_list dhcp.@dnsmasq[0].server='114.114.114.114'
			set dhcp.@dnsmasq[0].dnsseccheckunsigned='0'
			delete dhcp.@dnsmasq[0].dnssec
			commit dhcp
		EOF
	elif [ "$(uci -q get openmptcprouter.settings.country)" = "world" ] && [ -n "$(uci -q get dhcp.@dnsmasq[0].server | grep '114.114.114.114')" ]; then
		uci -q batch <<-EOF > /dev/null
			add_list dhcp.@dnsmasq[0].server='127.0.0.1#5353'
			del_list dhcp.@dnsmasq[0].server='114.114.114.114'
			commit dhcp
		EOF
	fi

	if [ "$(uci -q get openmptcprouter.settings.sipalg)" = "0" ]; then
		uci -q batch <<-EOF >/dev/null
			set firewall.zone_lan.auto_helper='0'
			set firewall.zone_wan.auto_helper='0'
			set firewall.zone_vpn.auto_helper='0'
			commit firewall
		EOF
		# This runs on every network reload: wait and unload only what is
		# still loaded.
		if [ -d /sys/module/nf_nat_sip ]; then
			rmmod nf_nat_sip >/dev/null 2>&1
			sleep 2
		fi
		[ -d /sys/module/nf_conntrack_sip ] && rmmod nf_conntrack_sip >/dev/null 2>&1
	else
		uci -q batch <<-EOF >/dev/null
			set firewall.zone_lan.auto_helper='1'
			set firewall.zone_wan.auto_helper='1'
			set firewall.zone_vpn.auto_helper='1'
			commit firewall
		EOF
		if [ ! -d /sys/module/nf_nat_sip ]; then
			modprobe -q nf_conntrack_sip >/dev/null 2>&1
			sleep 2
			modprobe -q nf_nat_sip >/dev/null 2>&1
		fi
	fi

	if [ "$(uci -q get rpcd.@rpcd[0].socket)" != "/var/run/ubus/ubus.sock" ]; then
		uci -q batch <<-EOF >/dev/null
			set rpcd.@rpcd[0].socket='/var/run/ubus/ubus.sock'
			commit rpcd
		EOF
		/etc/init.d/rpcd restart >/dev/null 2>&1
	fi
	# "Disable ModemManager" is disable_modemmanager; modemmanager=0 is
	# the older name of the same choice
	if [ "$(pgrep ModemManager)" = "" ] && [ -f /etc/init.d/modemmanager ] && [ -n "$(uci -q show network | grep modemmanager)" ] && \
	   [ "$(uci -q get openmptcprouter.settings.disable_modemmanager)" != "1" ] && [ "$(uci -q get openmptcprouter.settings.modemmanager)" != "0" ]; then
		logger -t "OMR" "Can't find ModemManager, start it..."
		/etc/init.d/modemmanager restart
	fi
}

stop_service() {
	config_load network
	config_foreach omr_intf_set interface
	uci -q commit network
	uci -q commit openmptcprouter
}

reload_service() {
	rc_procd start_service
	return 0
}

service_triggers() {
	procd_add_reload_trigger "network"
}