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

START=50

USE_PROCD=1

. /lib/functions/network.sh
. /usr/lib/mptcp/init_helpers.sh

# Was sourced from /usr/lib/unbound/iptools.sh, which only unbound-daemon
# ships: without unbound the "." aborted the whole script (no MPTCP setup at
# all), so the one function used here is kept as its own copy.
valid_subnet4() {
	case "$1" in
		169"."254"."*) echo "not" ;;
		127"."*) echo "not" ;;
		0"."*) echo "not" ;;
		255"."*) echo "not" ;;
		25[0-4]"."[0-9]*) echo "ok" ;;
		2[0-4][0-9]"."[0-9]*) echo "ok" ;;
		1[0-9][0-9]"."[0-9]*) echo "ok" ;;
		[0-9][0-9]"."[0-9]*) echo "ok" ;;
		[0-9]"."[0-9]*) echo "ok" ;;
		*) echo "not" ;;
	esac
}

# Most subflows and accepted ADD_ADDR per connection: x86_64 routers
# aggregate up to 16 WANs, the others 8
case "$(uname -m)" in
	x86_64) MPTCP_LIMIT_MAX=16 ;;
	*) MPTCP_LIMIT_MAX=8 ;;
esac

# An MPTCP limit as the kernel takes it: a number from 0 to $3 (default
# MPTCP_LIMIT_MAX), $2 when $1 is not a number
_mptcp_limit() {
	local max="${3:-${MPTCP_LIMIT_MAX:-8}}"
	case "$1" in
		''|*[!0-9]*) echo "$2" ;;
		?|??) if [ "$1" -gt "$max" ]; then echo "$max"; else echo "$1"; fi ;;
		*) echo "$max" ;;
	esac
}

# Persist the sysctl $1=$2 in zzz_openmptcprouter.conf. procd reloads mptcp on
# every interface event, and "sed -i" rewrote the file on flash each time,
# the value unchanged: it is only edited when a line of that key differs.
_sysctl_conf_set() {
	local conf=/etc/sysctl.d/zzz_openmptcprouter.conf
	if grep -q "^$1=" "$conf" 2>/dev/null && ! grep "^$1=" "$conf" | grep -qvx "$1=$2"; then
		return 0
	fi
	if grep -q "$1" "$conf" 2>/dev/null; then
		sed -i "s:^$1=[0-1]*:$1=$2:" "$conf"
	else
		echo "$1=$2" >> "$conf"
	fi
}

restart_scheduler_dependent_services() {
	local proxy vpn
	proxy="$(uci -q get openmptcprouter.settings.proxy)"
	case "$proxy" in
		shadowsocks) /etc/init.d/shadowsocks-libev restart >/dev/null 2>&1 ;;
		shadowsocks-rust|shadowsocks-go) /etc/init.d/shadowsocks-rust restart >/dev/null 2>&1 ;;
		v2ray*) /etc/init.d/v2ray restart >/dev/null 2>&1 ;;
		xray*) /etc/init.d/xray restart >/dev/null 2>&1 ;;
	esac

	vpn="$(uci -q get openmptcprouter.settings.vpn)"
	case "$vpn" in
		openvpn) /etc/init.d/openvpn restart >/dev/null 2>&1 ;;
		openvpn_bonding)
			/etc/init.d/openvpn restart >/dev/null 2>&1
			/etc/init.d/openvpnbonding restart >/dev/null 2>&1
			;;
		glorytun_tcp) /etc/init.d/glorytun restart >/dev/null 2>&1 ;;
	esac
	logger -t "MPTCP" "Scheduler changed, restarted proxy=${proxy:-none} vpn=${vpn:-none} (vpn restart skipped unless TCP-based)"
}

# Register the BPF scheduler $1 when /usr/share/bpf/scheduler has an object
# for it, unless that object is what is registered already.
#
# This ran on every global reload, and procd reloads mptcp on every interface
# event: all schedulers were unregistered and the selected one registered
# again, so the verifier ran each time and, until it was done, the configured
# scheduler did not exist. The connections opened in that window got the
# default scheduler for their whole life (mptcp_init_sched()). It is now only
# done when the scheduler is not registered, when another one was selected
# or when its object changed (an upgrade).
_mptcp_bpf_scheduler() {
	local sched="$1" pin_dir="/sys/fs/bpf/mptcp" state="/var/state/mptcp-bpf-scheduler"
	local obj="/usr/share/bpf/scheduler/mptcp_${1}.o" stamp="" pin reg_err
	[ -f "$obj" ] && stamp="$sched $(md5sum "$obj" 2>/dev/null | cut -d' ' -f1)"
	if [ -n "$stamp" ] && [ "$(cat "$state" 2>/dev/null)" = "$stamp" ]; then
		case " $(cat /proc/sys/net/mptcp/available_schedulers 2>/dev/null) " in
			*" $sched "*) return 0 ;;
		esac
	fi
	mkdir -p "$pin_dir"
	# Deleting the pinned link is what unregisters a scheduler; bpftool's
	# "unregister" takes a map id or name, never a pinned path, so
	# `unregister pinned "$pin"` exited 255 on a usage error and the
	# `&& rm -f` behind it never ran. Pins then accumulated, every
	# scheduler ever selected stayed registered, and re-registering
	# a rebuilt object failed with "File exists" -- silently, since
	# the output was discarded, so the router kept running the OLD
	# program after an upgrade shipped a fixed one.
	for pin in "$pin_dir"/*; do
		[ -e "$pin" ] || continue
		rm -f "$pin"
	done
	rm -f "$state"
	[ -n "$stamp" ] || return 0
	mkdir -p "${state%/*}"
	if reg_err="$(bpftool struct_ops register "$obj" "$pin_dir/" 2>&1)"; then
		echo "$stamp" > "$state"
	else
		logger -t "MPTCP" "Failed to register BPF scheduler $obj: $reg_err"
		# When a scheduler of that name is still registered (bpftool can't
		# unregister one on every kernel), it is used as it is: retrying on
		# every reload only repeated this error.
		case " $(cat /proc/sys/net/mptcp/available_schedulers 2>/dev/null) " in
			*" $sched "*) echo "$stamp" > "$state" ;;
		esac
	fi
}

global_multipath_settings() {
	local multipath mptcp_path_manager mptcp_scheduler mptcp_subflows mptcp_debug mptcp_add_addr_accepted congestion mptcp_checksum mptcp_syn_retries mptcp_fullmesh_num_subflows mptcp_fullmesh_create_on_err mptcp_ndiffports_num_subflows mptcp_rr_cwnd_limited mptcp_rr_num_segments
	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_subflows globals mptcp_subflows "3"
	config_get mptcp_add_addr_accepted globals mptcp_add_addr_accepted "1"
	config_get mptcp_add_addr_timeout globals mptcp_add_addr_timeout "120"
	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
	config_get mptcp_rr_cwnd_limited globals mptcp_rr_cwnd_limited
	config_get mptcp_rr_num_segments globals mptcp_rr_num_segments
	config_get mptcp_version globals mptcp_version "0"
	config_get mptcp_stale_loss_cnt globals mptcp_stale_loss_cnt "4"
	# Fallback matches the uci-defaults / LuCI form default (in-kernel PM):
	# an absent value must NOT resolve to userspace PM, or a config save
	# that drops this option (see issue #4349) silently disables in-kernel
	# fullmesh subflow creation and breaks multi-WAN bonding.
	config_get mptcp_pm_type globals mptcp_pm_type "0"
	config_get mptcp_allow_join_initial_addr_port globals mptcp_allow_join_initial_addr_port "1"
	config_get mptcp_close_timeout globals mptcp_close_timeout "60"
	# 0 as in uci-defaults and the LuCI form: OMR keeps MPTCP on behind a
	# middlebox that drops its options rather than disabling it for an hour
	config_get mptcp_blackhole_timeout globals mptcp_blackhole_timeout "0"
	config_get mptcp_syn_retrans_before_tcp_fallback globals mptcp_syn_retrans_before_tcp_fallback "2"
	
	[ "$multipath" = "enable" ] && multipath_status=1

	# Global MPTCP configuration
	if [ -f /proc/sys/net/mptcp/mptcp_enabled ]; then
		sysctl -qew net.mptcp.mptcp_enabled="$multipath_status"
		_sysctl_conf_set net.mptcp.mptcp_enabled "$multipath_status"
		[ -z "$mptcp_path_manager" ] || sysctl -qew net.mptcp.mptcp_path_manager="$mptcp_path_manager"
		if [ -n "$mptcp_scheduler" ]; then
			local prev_sched="$(cat /proc/sys/net/mptcp/mptcp_scheduler 2>/dev/null)"
			sysctl -qew net.mptcp.mptcp_scheduler="$mptcp_scheduler"
			# Restart dependents only when the live scheduler actually changed
			# (a rejected value leaves it as it was).
			local new_sched="$(cat /proc/sys/net/mptcp/mptcp_scheduler 2>/dev/null)"
			[ -n "$prev_sched" ] && [ -n "$new_sched" ] && [ "$prev_sched" != "$new_sched" ] && restart_scheduler_dependent_services
		fi
		[ -z "$mptcp_checksum" ] || sysctl -qew net.mptcp.mptcp_checksum="$mptcp_checksum"
		[ -z "$mptcp_debug" ] || sysctl -qew net.mptcp.mptcp_debug="$mptcp_debug"
		[ -z "$mptcp_syn_retries" ] || sysctl -qew net.mptcp.mptcp_syn_retries="$mptcp_syn_retries"
		[ -z "$mptcp_fullmesh_num_subflows" ] || sysctl -qew /sys/module/mptcp_fullmesh/parameters/num_subflows="$mptcp_fullmesh_num_subflows"
		[ -z "$mptcp_fullmesh_create_on_err" ] || sysctl -qew /sys/module/mptcp_fullmesh/parameters/create_on_err="$mptcp_fullmesh_create_on_err"
		[ -z "$mptcp_ndiffports_num_subflows" ] || sysctl -qew /sys/module/mptcp_ndiffports/parameters/num_subflows="$mptcp_ndiffports_num_subflows"
		[ -z "$mptcp_rr_cwnd_limited" ] || sysctl -qew /sys/module/mptcp_rr/parameters/cwnd_limited="$mptcp_rr_cwnd_limited"
		[ -z "$mptcp_rr_num_segments" ] || sysctl -qew /sys/module/mptcp_rr/parameters/num_segments="$mptcp_rr_num_segments"
		[ -z "$mptcp_version" ] || sysctl -qew net.mptcp.mptcp_version="$mptcp_version"
	elif [ -f /proc/sys/net/mptcp/enabled ]; then
		sysctl -qew net.mptcp.enabled="$multipath_status"
		_sysctl_conf_set net.mptcp.enabled "$multipath_status"
		# The kernel refuses the whole command when one limit is above its
		# maximum, so a single value too high left both limits at whatever
		# they were. That maximum is 8 on a kernel without the 64 limits
		# backport: retry within it there.
		mptcp_add_addr_accepted="$(_mptcp_limit "$mptcp_add_addr_accepted" 1)"
		mptcp_subflows="$(_mptcp_limit "$mptcp_subflows" 3)"
		ip mptcp limits set add_addr_accepted $mptcp_add_addr_accepted subflows $mptcp_subflows >/dev/null 2>&1 || \
			ip mptcp limits set add_addr_accepted "$(_mptcp_limit "$mptcp_add_addr_accepted" 1 8)" subflows "$(_mptcp_limit "$mptcp_subflows" 3 8)" >/dev/null 2>&1
		# The subflow limit decides how many socket buffers one MPTCP connection
		# can hold open at once, and they all draw on the same net.ipv4.tcp_mem
		# pool, so re-run the sizing now that the limit is known. Cheap and
		# idempotent: it only rewrites its sysctl.d file when values change.
		[ -x /bin/omr-net-mem ] && /bin/omr-net-mem --quiet >/dev/null 2>&1
		#[ -z "$mptcp_debug" ] || sysctl -qew net.mptcp.mptcp_debug="$mptcp_debug"
		[ -z "$mptcp_add_addr_timeout" ] || sysctl -qew net.mptcp.add_addr_timeout="$mptcp_add_addr_timeout"
		[ -z "$mptcp_checksum" ] || sysctl -qew net.mptcp.checksum_enabled="$mptcp_checksum"
		[ -z "$mptcp_stale_loss_cnt" ] || sysctl -qew net.mptcp.stale_loss_cnt="$mptcp_stale_loss_cnt"
		[ -z "$mptcp_pm_type" ] || sysctl -qew net.mptcp.pm_type="$mptcp_pm_type"
		# net.mptcp.path_manager and pm_type are two views of the same
		# setting: the kernel sets one when the other is written. pm_type
		# chooses between "kernel" and "userspace" (its absent value must
		# stay in-kernel, #4349), so path_manager is only written for another
		# path manager the kernel registers (a BPF one).
		if [ -n "$mptcp_path_manager" ] && [ -f /proc/sys/net/mptcp/path_manager ]; then
			case "$mptcp_path_manager" in
				kernel|userspace) ;;
				*)
					case " $(cat /proc/sys/net/mptcp/available_path_managers 2>/dev/null) " in
						*" $mptcp_path_manager "*) sysctl -qew net.mptcp.path_manager="$mptcp_path_manager" ;;
						*) logger -t "MPTCP" "Path manager '$mptcp_path_manager' is not available (kernel offers: $(cat /proc/sys/net/mptcp/available_path_managers 2>/dev/null))" ;;
					esac
					;;
			esac
		fi
		[ -z "$mptcp_allow_join_initial_addr_port" ] || sysctl -qew net.mptcp.allow_join_initial_addr_port="$mptcp_allow_join_initial_addr_port"
		[ -z "$mptcp_close_timeout" ] || sysctl -qew net.mptcp.close_timeout="$mptcp_close_timeout"
		[ -z "$mptcp_syn_retrans_before_tcp_fallback" ] || sysctl -qew net.mptcp.syn_retrans_before_tcp_fallback="$mptcp_syn_retrans_before_tcp_fallback"
		[ -z "$mptcp_blackhole_timeout" ] || sysctl -qew net.mptcp.blackhole_timeout="$mptcp_blackhole_timeout"
		if [ -n "$mptcp_scheduler" ]; then
			local sched="${mptcp_scheduler#mptcp_}"; sched="${sched%.o}"
			local prev_sched="$(cat /proc/sys/net/mptcp/scheduler 2>/dev/null)"
			# The name becomes the path of the object registered below, so
			# only a plain name is taken: with "/" and ".." in it (the rpcd
			# API wrote any string) it could point outside of
			# /usr/share/bpf/scheduler.
			case "$sched" in
				''|*[!A-Za-z0-9_]*)
					logger -t "MPTCP" "Invalid scheduler name '$mptcp_scheduler', using 'default'"
					sched="default"
					;;
			esac
			[ -d /usr/share/bpf/scheduler ] && _mptcp_bpf_scheduler "$sched"
			# Only a scheduler the kernel lists can be set. A legacy name left
			# in uci ('blest' from the v0.9x defaults is not a 6.x scheduler)
			# or a BPF object that failed to register makes the sysctl fail;
			# fall back to 'default' rather than silently keeping whatever was
			# live. The restart decision below compares what was live before
			# with what is live after: comparing against the requested name
			# restarted the proxy on every single reload while the request
			# kept failing (27 "Scheduler changed" restarts in one test run).
			local avail_sched="$(cat /proc/sys/net/mptcp/available_schedulers 2>/dev/null)"
			if [ -n "$avail_sched" ]; then
				case " $avail_sched " in
					*" $sched "*) ;;
					*)
						logger -t "MPTCP" "Scheduler '$sched' is not available (kernel offers: $avail_sched), using 'default'"
						sched="default"
						;;
				esac
			fi
			sysctl -qew net.mptcp.scheduler="$sched" >/dev/null 2>&1
			local new_sched="$(cat /proc/sys/net/mptcp/scheduler 2>/dev/null)"
			[ -n "$prev_sched" ] && [ -n "$new_sched" ] && [ "$prev_sched" != "$new_sched" ] && restart_scheduler_dependent_services
		fi
	fi
	[ -z "$congestion" ] || sysctl -qew net.ipv4.tcp_congestion_control="$congestion"
}

interface_macaddr_count() {
	local intf="$1"
	local dmacaddr="$2"
	config_get macaddr "$intf" macaddr
	[ "$macaddr" = "$dmacaddr" ] && [ "${intf%.*}" = "$intf" ] && nbmac=$((nbmac+1))
}

# procd reloads this init on every interface event. A "uci commit" writes
# the whole pending delta of the config, other processes' staged changes
# included (openmptcprouter-vps's uncommitted token went to flash on each
# reload), so an option is only set when it differs, and a config is only
# committed when this run changed it.
_mptcp_uci_set() {
	[ "$(uci -q get "$1")" = "$2" ] && return 0
	uci -q set "$1=$2"
	_mptcp_uci_changed="$_mptcp_uci_changed ${1%%.*}"
}

_mptcp_uci_delete() {
	uci -q get "$1" >/dev/null || return 0
	uci -q delete "$1"
	_mptcp_uci_changed="$_mptcp_uci_changed ${1%%.*}"
}

_mptcp_uci_commit() {
	case " $_mptcp_uci_changed " in
		*" $1 "*) uci -q commit "$1" ;;
	esac
}

interface_max_metric() {
	local config="$1"
	case "$1" in
		omrvpn)
			_mptcp_uci_set network.${config}.metric 1500
			_mptcp_uci_set network.${config}.ip4table 1500
			_mptcp_uci_set network.${config}.ip6table 61500
			_mptcp_uci_set openmptcprouter.${config}.metric 1500
			;;
		omr6in4)
			_mptcp_uci_set network.${config}.metric 1201
			_mptcp_uci_set network.${config}.ip4table 1201
			_mptcp_uci_set network.${config}.ip6table 61201
			_mptcp_uci_set openmptcprouter.${config}.metric 1201
			;;
		lan)
			# only when needed: this runs on every reload, sed -i writes flash
			grep -q '50 lan' /etc/iproute2/rt_tables 2>/dev/null && \
				sed -i 's:50 lan:9999 lan:g' /etc/iproute2/rt_tables
			;;
		loopback)
			;;
		*)
			# Called with the openmptcprouter config loaded, where the routing
			# table id lives in "metric" (network.<iface>.ip4table is the same
			# value in the network config): reading ip4table here always got an
			# empty string, so every WAN raised a shell "out of range" error and
			# count stayed 0. Interfaces added afterwards then restarted metric
			# allocation from 1 in interface_multipath_settings and collided with
			# the routing tables, route metrics and MPTCP endpoint ids of the
			# existing ones.
			config_get metric "$config" metric
			case "$metric" in
				''|*[!0-9]*) return 0 ;;
			esac
			if [ "$metric" -gt "$count" ] && [ "$metric" -lt "1000" ]; then
				count=$metric
			fi
			;;
	esac
}

# sqm-scripts (cake / htb+fq_codel / hfsc set up by /usr/lib/sqm) and
# qos-scripts own the root qdisc of the devices they shape. Replacing it with
# fq below silently deleted the egress shaper on every "mptcp reload <device>"
# (tracker status change, IP change, ifup hotplug) while SQM's state file still
# claimed it was running, so SQM never re-applied it until its next restart:
# LuCI and the wizard showed SQM enabled, tc showed plain fq (#4329).
# Leave the root qdisc alone whenever another shaper manages this device.
_root_qdisc_managed_elsewhere() {
	local dev="$1" config="$2" state_dir sec
	[ -n "$dev" ] || return 1
	state_dir="$(. /etc/sqm/sqm.conf 2>/dev/null; echo "${SQM_STATE_DIR:-/var/run/sqm}")"
	[ -f "${state_dir}/${dev}.state" ] && return 0
	# an enabled sqm queue on this device: named after the interface by the
	# wizard, or an anonymous section created from the LuCI SQM page
	for sec in $(uci -q show sqm 2>/dev/null | sed -n "s/^sqm\.\([^.]*\)\.interface='${dev}'\$/\1/p"); do
		[ "$(uci -q get sqm.${sec}.enabled)" = "1" ] && return 0
	done
	[ "$(uci -q get qos.${config}.enabled)" = "1" ] && return 0
	# omr-quota shapes an interface whose quota is exceeded with a tbf root
	# qdisc (throttle action, daily-budget speed limit); nothing else in OMR
	# installs tbf. Replacing it with fq lifted the quota's speed limit until
	# the daemon's next poll -- on every ifup hotplug / tracker status change.
	case "$(tc qdisc show dev "$dev" root 2>/dev/null)" in
		"qdisc tbf "*) return 0 ;;
	esac
	return 1
}

# Delete the pref 0 rules of the routing table $2 ("-4"/"-6" in $1) other
# than "from $3" and "oif $4", the two added below. The rules of a previous
# address or device of the WAN were never removed: the delete looked for
# " <id> " in "ip rule show", whose lines end with "lookup <id>", so every new
# address (each IPv6 prefix change) left one more rule behind. Only pref 0
# is looked at: netifd's rules for the same table and the fwmark ones are not
# ours, and deleting them by table alone took those too.
_prune_wan_rules() {
	local family="$1" table="$2" from="$3" dev="$4" sel
	ip $family rule show pref 0 table "$table" 2>/dev/null | \
		awk -v t="$table" '$1 == "0:" && $(NF-1) == "lookup" && $NF == t {
			# "[detached]" (device gone) is shown, not part of the selector
			$1 = ""; $(NF-1) = ""; $NF = ""
			for (i = 2; i < NF - 1; i++) if ($i ~ /^\[/) $i = ""
			print }' | \
		while read -r sel; do
			[ -n "$from" ] && [ "$sel" = "from $from" ] && continue
			[ "$sel" = "from all oif $dev" ] && continue
			ip $family rule del pref 0 $sel table "$table" >/dev/null 2>&1
		done
}

# Delete the routes of the routing table $2 ("-4"/"-6" in $1) that were added
# here for the device $3 and are not among the ones added again below ($4...),
# the network and gateway routes of a previous address. This replaces
# "ip route flush <id>", which took the id for a destination: it flushed
# <id>.0.0.0/8 from the main table and never touched the WAN's own. Only routes
# added without a protocol (proto boot) on that device are looked at, so
# netifd's routes and omr-tracker's "unreachable default" are kept, and
# add_route puts the ones of the uci route sections back afterwards. A route
# deleted although it is wanted (another spelling of the same prefix) is only
# missing until the "ip route replace" that follows.
_prune_wan_routes() {
	local family="$1" table="$2" dev="$3" dst rest keep
	shift 3
	ip $family route show table "$table" dev "$dev" proto boot 2>/dev/null | \
		while read -r dst rest; do
			[ -n "$dst" ] || continue
			for keep in default "$@"; do
				keep="${keep%/32}"; keep="${keep%/128}"
				[ "$keep" = "$dst" ] && continue 2
			done
			ip $family route del "$dst" dev "$dev" proto boot table "$table" >/dev/null 2>&1
		done
}

# config_foreach callback: device_section is the "config device" section of
# the device $2
_find_device_section() {
	local name
	config_get name "$1" name
	[ -n "$name" ] && [ "$name" = "$2" ] && device_section="$1"
}

# config_foreach callback: _multipath_dev_found is set when the interface $1,
# not $3, is in MPTCP (on, master or backup) on the device $2
_check_multipath_device() {
	local sec="$1" dev="$2" self="$3" mode disabled sdev
	[ "$sec" = "$self" ] && return 0
	[ -n "$_multipath_dev_found" ] && return 0
	case "$sec" in *_6) return 0 ;; esac
	config_get disabled "$sec" disabled
	[ "$disabled" = "1" ] && return 0
	config_get mode "$sec" multipath
	[ -n "$mode" ] || mode="$(uci -q get openmptcprouter.${sec}.multipath)"
	case "$mode" in on|master|backup) ;; *) return 0 ;; esac
	network_get_device sdev "$sec"
	[ -z "$sdev" ] && network_get_physdev sdev "$sec"
	[ -z "$sdev" ] && sdev=$(ifstatus "$sec" | jsonfilter -q -e '@["l3_device"]')
	[ -z "$sdev" ] && config_get sdev "$sec" ifname
	case "$sdev" in *@*) sdev=$(ifstatus "$sec" | jsonfilter -q -e '@["device"]') ;; esac
	[ -n "$sdev" ] && [ "$sdev" = "$dev" ] && _multipath_dev_found=1
	return 0
}

# Whether another interface than $2 has MPTCP on the device $1. A second
# interface on a WAN's device (a wan6 on @wan2, a static alias) is "off"
# itself, and turning MPTCP off for it removed the WAN's endpoints on every
# reload: until the WAN's own turn put them back, or for good when the WAN
# came first.
_device_used_by_multipath() {
	_multipath_dev_found=""
	config_foreach _check_multipath_device interface "$1" "$2"
	[ -n "$_multipath_dev_found" ]
}

interface_multipath_settings() {
	local mode iface proto metric ip4table qdisc device_section
	local config="$1"
	local intf="$2"
	# DHCPv6 companion interfaces (<intf>_6) share the parent's device:
	# processing them here would clobber the parent's multipath state
	case "$config" in *_6) return 0 ;; esac
	local enabled uci_route
	uci_route=$(uci -q get openmptcprouter.settings.uci_route)

	network_get_device iface $config
	[ -z "$iface" ] && network_get_physdev iface $config
	[ -z "$iface" ] && iface=$(ifstatus "$config" | jsonfilter -q -e '@["l3_device"]')
	[ -z "$iface" ] && config_get iface "$config" ifname
	[ -n "$intf" ] && [ "$iface" != "$intf" ] && return 0

	config_get enabled "$config" auto "1"
	config_get txqueuelen "$config" txqueuelen
	config_get qdisc "$config" qdisc
	case "$iface" in *@*) iface=$(ifstatus "$config" | jsonfilter -q -e '@["device"]') ;; esac
	# LuCI's device dialog writes the qdisc, the TX queue length and the
	# offloads to the "config device" section of the device: only the
	# interface's own options were read, so those were never applied and the
	# queue length was put back to 1000 over the one netifd had set. The
	# device's value wins, the interface's is the fallback.
	device_section=""
	[ -n "$iface" ] && config_foreach _find_device_section device "$iface"
	[ -n "$device_section" ] && {
		config_get txqueuelen "$device_section" txqueuelen "$txqueuelen"
		config_get qdisc "$device_section" qdisc "$qdisc"
	}
	if [ "$(uci -q get openmptcprouter.${config}.metric)" = "" ] || [ "$(uci -q get openmptcprouter.${config}.metric)" = "1" ]; then
		count=$((count+1))
		metric=$count
	else
		metric="$(uci -q get openmptcprouter.${config}.metric)"
	fi
	[ "$metric" -gt 1000 ] && [ "${config}" != "omrvpn" ] && [ "${config}" != "omr6in4" ] && [ "${config}" != "loopback" ] && metric=$((metric-1000))
	if [ "${config}" = "lan" ] || [ "$(uci -q get network.${config}.ip4table)" = "lan" ] || [ "$(uci -q get network.${config}.ip4table)" = "9999" ]; then
		metric="9999"
	elif [ "${config}" = "omrvpn" ]; then
		metric="1500"
	elif [ "${config}" = "omr6in4" ]; then
		metric="1201"
#	elif [ "$(uci -q get network.${config}.multipath)" = "off" ]; then
#		metric="9998"
	fi
	config_set "$config" metric $metric
	_mptcp_uci_set network.${config}.metric "$metric"
	_mptcp_uci_set network.${config}.ip4table "$metric"
	_mptcp_uci_set network.${config}.ip6table "6$metric"
	_mptcp_uci_set openmptcprouter.${config}.metric "$metric"
	config_get mode "$config" multipath
	config_get disabled "$config" disabled
	[ "$disabled" = "1" ] && mode="off"
	id=$metric
	ip4table=$metric
	ip6table=6$metric
	[ -n "$iface" ] && {
		local offload val
		for offload in gro gso lro ufo tso; do
			config_get val "$config" "$offload"
			[ -n "$device_section" ] && config_get val "$device_section" "$offload" "$val"
			[ "$val" = "1" ] && ethtool -K $iface $offload on >/dev/null 2>&1
			[ "$val" = "0" ] && ethtool -K $iface $offload off >/dev/null 2>&1
		done
	}
	[ "$mode" = "" ] && {
		# Migration path for configs that only ever had the mode in the
		# openmptcprouter copy. Log it: it also fires when something dropped
		# network.<iface>.multipath behind the user's back, in which case the
		# interface silently comes back in its previous mode (#4368).
		mode="$(uci -q get openmptcprouter.${config}.multipath)"
		[ -n "$mode" ] && {
			logger -t "MPTCP" "No multipath setting for $config, restoring $mode from openmptcprouter config"
			_mptcp_uci_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"
		_mptcp_uci_set network.${config}.multipath "$mode"
		_mptcp_uci_set openmptcprouter.${config}.multipath "$mode"
	}
	[ "$mode" != "off" ] && {
		# LRO, TSO and UFO break MPTCP packets scheduling, always disable them on multipath interfaces
		[ -n "$iface" ] && {
			for offload in lro tso ufo; do
				ethtool -K $iface $offload off >/dev/null 2>&1
			done
		}
		[ -n "$mptcpintf" ] && mptcpintf="$mptcpintf $iface"
		[ -z "$mptcpintf" ] && mptcpintf="$iface"
		[ -z "$mptcpmintf" ] && mptcpmintf="$config"
		_mptcp_uci_set network.${config}.defaultroute 0
		_mptcp_uci_set network.${config}.peerdns 0
		_mptcp_uci_delete network.${config}.dns
		echo '' > /etc/resolv.conf >/dev/null 2>&1
	}
	[ "$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"
			_mptcp_uci_set network.${config}.multipath "on"
			_mptcp_uci_set openmptcprouter.${config}.multipath "on"
		else
			master="$config"
		fi
	}
	_mptcp_uci_set openmptcprouter.${config} "interface"
	_mptcp_uci_set openmptcprouter.${config}.multipath "$mode"

	config_get macaddr "$config" macaddr ""
	[ -n "$macaddr" ] && {
		nbmac=0
		config_foreach interface_macaddr_count interface $macaddr
		[ "$nbmac" != "1" ] && {
			logger -t "MPTCP" "Duplicate MAC saved, removing"
			_mptcp_uci_delete network.${config}.macaddr
		}
	}

	[ "$enabled" = "0" ] && return 0
	[ -n "$intf" ] && [ "$iface" != "$intf" ] && return 0
	[ -z "$iface" ] && return 0
	#[ "$config" = "omrvpn" ] && return 0
	[ "$config" = "omrvpn" ] && mode="off"
	#[ -n "$(ifconfig | grep $iface)" ] || return 0
	ip link show dev "$iface" >/dev/null 2>&1 || return 0
	case "$iface" in *_dev*) return 0 ;; esac
	case "$iface" in if*) return 0 ;; esac
	[ "$iface" = "lo" ] && return 0
	status=`ifstatus $config | jsonfilter -q -e "@.up" | tr -d "\n"`
	[ "$status" = "false" ] && return 0

	
	#echo "îface: $iface"
	
	#[ "$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
		# ipaddr can be a list in CIDR notation, with no netmask: network
		# stayed empty and the WAN got no table route, rule or qdisc
		ipaddr="${ipaddr%% *}"
		case "$ipaddr" in */*)
			[ -z "$netmask" ] && netmask="${ipaddr#*/}"
			ipaddr="${ipaddr%%/*}"
		;; esac
		[ -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;}'`
	elif [ "$proto" != "gre" ]; then
		network_get_ipaddr ipaddr $config
		[ -z "$ipaddr" ] && ipaddr=$(ip -4 addr show dev $iface | awk '/inet / {print $2; exit}' | cut -d/ -f1 | tr -d "\n")
		[ -z "$ipaddr" ] && ipaddr=$(uci -q get "network.$config.ipaddr" | 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=$(ip -4 r list dev "$iface" | awk '/via/ {print $3;exit}' | 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 }')
			[ "$gateway" = "*" ] && gateway=""
		fi
		network_get_subnet netmask $config
		[ -n "$netmask" ] && [ "$(echo $netmask | grep '/')" != "" ] && netmask=""
		[ -z "$netmask" ] && netmask=$(ip -4 addr show dev $iface | awk '/peer/ {print $4; exit}' | cut -d/ -f2 | tr -d "\n")
		[ -z "$netmask" ] && netmask=$(ip -4 addr show dev $iface | awk '/inet / {print $2;exit}' | cut -d/ -f2 | tr -d "\n")
		[ -z "$netmask" ] && netmask=$(uci -q get "network.$config.netmask")
		[ -z "$netmask" ] && [ -n "$ipaddr" ] && [ -n "$gateway" ] && \
			[ "$(echo $ipaddr | cut -d. -f1-3)" = "$(echo $gateway | cut -d. -f1-3)" ] && netmask="24"
		[ -n "$ipaddr" ] && ipaddr=$(echo $ipaddr | cut -d/ -f1 | 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_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
	fi

	if [ -n "$gateway" ] && [ -n "$network" ]; then
		if [ "$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=${ip4table}
				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=${ip4table}
				set network.${config}_route_default.created=mptcp
				commit network
			EOF
		else
			_prune_wan_rules -4 "$ip4table" "$ipaddr" "$iface"
			_prune_wan_routes -4 "$ip4table" "$iface" "$network/$netmask" "$gateway"
			[ -n "$ipaddr" ] && [ -z "$(ip rule show from $ipaddr table $ip4table)" ] && ip rule add from $ipaddr table $ip4table pref 0
			[ -z "$(ip rule show oif $iface table $ip4table)" ] && ip rule add oif $iface table $ip4table pref 0
			ip route replace $network/$netmask dev $iface scope link metric $id $initcwrwnd >/dev/null 2>&1
			ip route replace $network/$netmask dev $iface scope link table $ip4table $initcwrwnd >/dev/null 2>&1
			ip route replace $gateway dev $iface table $ip4table $initcwrwnd >/dev/null 2>&1
			ip route replace default via $gateway dev $iface table $ip4table $initcwrwnd >/dev/null 2>&1
			[ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ] && {
				ip route replace $gateway dev $iface metric $id $initcwrwnd >/dev/null 2>&1
				ip route replace default via $gateway dev $iface metric $id $initcwrwnd >/dev/null 2>&1
			}
			#ip route flush cache $id >/dev/null 2>&1
		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 >/dev/null 2>&1
		#}
		if [ "$txqueuelen" != "" ]; then
			#ifconfig $iface txqueuelen $txqueuelen > /dev/null 2>&1
			ip link set dev $iface txqueuelen $txqueuelen > /dev/null 2>&1
		else
			#ifconfig $iface txqueuelen 1000 > /dev/null 2>&1
			ip link set dev $iface txqueuelen 1000 > /dev/null 2>&1
		fi
		_root_qdisc_managed_elsewhere "$iface" "$config" || tc qdisc replace dev $iface root ${qdisc:-fq} > /dev/null 2>&1
	fi
	if [ -z "$gateway" ] && [ -n "$network" ]; then
		if [ "$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=${ip4table}
				set network.${config}_route.created=mptcp
				delete network.${config}_route_default
				commit network
			EOF
		else
			_prune_wan_rules -4 "$ip4table" "$ipaddr" "$iface"
			[ -n "$ipaddr" ] && [ -z "$(ip rule show from $ipaddr table $ip4table)" ] && ip rule add from $ipaddr table $ip4table pref 0
			[ -z "$(ip rule show oif $iface table $ip4table)" ] && ip rule add oif $iface table $ip4table pref 0
			ip route replace $network/$netmask dev $iface scope link metric $id $initcwrwnd >/dev/null 2>&1
			ip route replace $network/$netmask dev $iface scope link table $ip4table $initcwrwnd >/dev/null 2>&1
			#ip route flush cache $id >/dev/null 2>&1
		fi

		if [ "$txqueuelen" != "" ]; then
			#ifconfig $iface txqueuelen $txqueuelen > /dev/null 2>&1
			ip link set dev $iface txqueuelen $txqueuelen > /dev/null 2>&1
		else
			#ifconfig $iface txqueuelen 1000 > /dev/null 2>&1
			ip link set dev $iface txqueuelen 1000 > /dev/null 2>&1
		fi
		_root_qdisc_managed_elsewhere "$iface" "$config" || tc qdisc replace dev $iface root ${qdisc:-fq} > /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 "$ipaddr6" | cut -d/ -f1 | tr -d "\n"`
			netmask6=`ipcalc -p $ipaddr6 | sed -n '/PREFIX=/{;s/.*=//;s/ .*//;p;}'`
			network6=`ipcalc -n $ip6addr | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'`
		fi
		if [ -z "$ip6addr" ] || [ -z "$gateway6" ]; then
			[ -z "$ip6addr" ] && network_get_ipaddr6 ip6addr $config
			[ -z "$ip6addr" ] && ip6addr=$(ip -6 addr show dev $iface | grep -v 'scope link' | awk '/inet6/ {print $2; exit}' | cut -d/ -f1 | tr -d "\n")
			[ -z "$gateway6" ] && network_get_gateway6 gateway6 $config true
			# Only an on-link host route can name the gateway: netifd also adds
			# the interface's own prefix as a "proto static" route (on a DHCP
			# WAN, for its <name>_6 DHCPv6 companion), and the target of that
			# one is the network address of the prefix, not a router.
			[ -z "$gateway6" ] && gateway6=$(ip -6 r list dev $iface | grep -v default | awk '/proto static/ && !/ via / && $1 !~ /\// {print $1; exit}' | 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")
			[ -z "$gateway6" ] && gateway6=$(ubus call network.interface.${config}_6 status 2>/dev/null | jsonfilter -q -l 1 -e '@.inactive.route[@.target="::"].nexthop' | tr -d "\n")
			[ -z "$gateway6" ] && gateway6=$(ubus call network.interface.${config}_6 status 2>/dev/null | jsonfilter -q -l 1 -e '@.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
			#	echo "ipv6 not ok"
			#	gateway6=$(ubus call network.interface.${config}_6 status 2>/dev/null | jsonfilter -q -l 1 -e '@.inactive.route[@.target="::"].nexthop' | tr -d "\n")
			#fi
			gateway6=$(echo $gateway6 | cut -d/ -f1 | tr -d "\n")
			netmask6=$(ubus call network.interface.$config status | jsonfilter -q -l 1 -e '@["ipv6-prefix"][0].mask' | tr -d "\n")
			network6=$(ubus call network.interface.$config status | jsonfilter -q -l 1 -e '@["ipv6-prefix"][0].address' | tr -d "\n")
			[ -z "$netmask6" ] && [ -n "$ip6addr" ] && netmask6=$(ip -6 addr show dev $iface | grep $ip6addr | awk '/inet6/ {print $2; exit}' | cut -d/ -f2 | tr -d "\n")
			[ -z "$network6" ] && [ -n "$ip6addr" ] && [ -n "$netmask6" ] && network6=`/usr/sbin/ipcalc -n ${ip6addr}/${netmask6} | sed -n '/NETWORK=/{;s/.*=//;s/ .*//;p;}'`
		fi
		if [ "$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
		fi
		if [ -n "$gateway6" ] && [ -n "$network6" ]; then
			#echo "gateway6: $gateway6 - network6: $network6 -> ok"
			if [ "$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=${ip6table}
					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=${ip6table}
					set network.${config}_route6_default.created=mptcp
					commit network
				EOF
			else
				_prune_wan_rules -6 "$ip6table" "$ip6addr" "$iface"
				_prune_wan_routes -6 "$ip6table" "$iface" "$network6/$netmask6" "$gateway6"
				[ -n "$ip6addr" ] && ip -6 rule add from $ip6addr table $ip6table pref 0 >/dev/null 2>&1
				[ -z "$(ip -6 rule show pref 0 table $ip6table oif $iface)" ] && ip -6 rule add oif $iface table $ip6table pref 0
				ip -6 route replace $network6/$netmask6 dev $iface scope link metric 6$id $initcwrwnd >/dev/null 2>&1
				ip -6 route replace $network6/$netmask6 dev $iface scope link table $ip6table $initcwrwnd >/dev/null 2>&1
				ip -6 route replace $gateway6 dev $iface table $ip6table $initcwrwnd >/dev/null 2>&1
				ip -6 route replace default via $gateway6 dev $iface table $ip6table $initcwrwnd >/dev/null 2>&1
				[ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ] && {
					ip -6 route replace $gateway6 dev $iface metric 6$id $initcwrwnd >/dev/null 2>&1
					ip -6 route replace default via $gateway6 dev $iface metric 6$id $initcwrwnd >/dev/null 2>&1
				}
				#ip -6 route flush cache 6$id >/dev/null 2>&1
			fi

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

	if [ "$(uci -q get openmptcprouter.settings.force_multipath)" != "0" ] && [ -n "$(ip a show dev $iface | grep inet)" ]; then
		local tracker_state
		tracker_state="$(uci -q -P /var/state get openmptcprouter.${config}.state)"
		if [ "$tracker_state" = "down" ] && [ -n "$mode" ] && [ "$mode" != "off" ]; then
			# omr-tracker still considers this interface down: it only flips state
			# to "up" once its recovery counter (tries_up) is satisfied, and
			# post-tracking's 002-error has already taken the endpoint out of
			# MPTCP. This reload comes from netifd's own ifup/ifupdate/link-up
			# event, which knows nothing about that check, so leave the endpoint
			# alone instead of putting the interface back into MPTCP: adding it
			# back - even as backup - makes the kernel keep opening subflows over
			# a link OMR knows is dead, and on a flapping modem that re-add ran on
			# every ifup (discussion #4368). 003-up adds the endpoint back in the
			# configured mode as soon as the recovery is confirmed (issue #4355).
			logger -t "MPTCP" "$iface tracker state is down, not adding it to MPTCP ($(multipath $iface))"
		elif ([ "$mode" = "master" ] || [ "$mode" = "on" ]) && { [ -z "$(multipath $iface | grep default)" ] || ! multipath -i "$iface" "$id"; }; then
			logger -t "MPTCP" "Set $iface to $mode from $(multipath $iface)"
			multipath "$iface" "on" "$id"
		elif ([ "$mode" = "off" ] || [ -z "$mode" ]) && [ -z "$(multipath $iface | grep deactivated)" ] && \
			! _device_used_by_multipath "$iface" "$config"; then
			logger -t "MPTCP" "Set $iface to $mode from $(multipath $iface)"
			multipath "$iface" "$mode" "$id"
		elif [ "$mode" = "backup" ] && { [ -z "$(multipath $iface | grep backup)" ] || ! multipath -i "$iface" "$id"; }; then
			logger -t "MPTCP" "Set $iface to $mode from $(multipath $iface)"
			multipath "$iface" "$mode" "$id"
		fi
	fi

}

load_interfaces() {
	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 multipath "$1" multipath ""
	[ -z "$multipath" ] && multipath="$(uci -q get openmptcprouter.$1.multipath)"
	[ "$multipath" != "off" ] && [ "$multipath" != "" ] && interfaces=" ${ifname} ${interfaces}"
}

set_multipath() {
	local iface mp
	for iface in /sys/class/net/*; do
		iface="${iface##*/}"
		case " $mptcpintf " in *" $iface "*) continue ;; esac
		mp=$(multipath "$iface" 2>/dev/null)
		[ "$iface" = "bonding_master" ] && continue
		[ -z "$mp" ] && continue
		case "$mp" in *deactivated*) continue ;; esac
		logger -t "MPTCP" "Disabling MPTCP on interface $iface not found in enabled multipath list"
		multipath "$iface" off >/dev/null 2>&1
	done
}

add_route() {
	config_get disabled "$1" disabled
	[ "$disabled" = "1" ] && return 0
	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" type
	[ -n "$type" ] && routeset="$routeset type $type"
	config_get table "$1" table
	[ -n "$table" ] && routeset="$routeset table $table"
	config_get interface "$1" interface
	iface=$(ifstatus "$interface" | jsonfilter -q -e '@["l3_device"]')
	case "$iface" in *@*) iface=$(ifstatus "$interface" | jsonfilter -q -e '@["device"]') ;; esac
	[ -n "$iface" ] && routeset="$routeset dev $iface"
	logger -t "MPTCP" "Add route $routeset"
	[ -n "$routeset" ] && {
		ip route replace ${routeset} >/dev/null 2>&1
	}
}

add_route6() {
	config_get disabled "$1" disabled
	[ "$disabled" = "1" ] && return 0
	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" type
	[ -n "$type" ] && routeset="$routeset type $type"
	config_get table "$1" table
	[ -n "$table" ] && routeset="$routeset table $table"
	config_get interface "$1" interface
	iface=$(ifstatus "$interface" | jsonfilter -q -e '@["l3_device"]')
	case "$iface" in *@*) iface=$(ifstatus "$interface" | jsonfilter -q -e '@["device"]') ;; esac
	[ -n "$iface" ] && routeset="$routeset dev $iface"
	logger -t "MPTCP" "Add IPv6 route $routeset"
	[ -n "$routeset" ] && {
		ip -6 route replace ${routeset} >/dev/null 2>&1
	}
}

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

start_service() {
	local intf=$1
	local id intfmaster
	. /lib/functions.sh
	. /lib/functions/network.sh
	#[ -n "$intf" ] && multipath "${intf}" off >/dev/null 2>&1
	[ -z "$intf" ] && global_multipath_settings

	local board_name
	board_name=$(ubus call system board | jsonfilter -e '@.board_name')

	case "$board_name" in *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
		}
	;; esac

	mptcpintf=""
	mptcpmintf=""
	master=""
	count=0
	_mptcp_uci_changed=""
	config_load openmptcprouter
	config_foreach interface_max_metric interface
	
	initcwrwnd=""
	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
	#}
	if [ "$(uci -q get network.globals.mptcp_disable_initial_config)" != "1" ]; then
		config_foreach interface_multipath_settings interface $intf
		config_foreach add_route route
		config_foreach add_route6 route6
		[ -z "$intf" ] && set_multipath
	
		# If the master interface is disabled, another one is set as master.
		# Not when no interface is set as master: the interfaces then share
		# the balancing routes evenly, instead of 100 to 1 for a master.
		if [ "$master" = "" ] && [ -z "$intf" ]; then
			intfmaster="$mptcpmintf"
			[ "$intfmaster" != "" ] && mptcp_has_master_multipath && {
				logger -t "MPTCP" "Master multipath interface disabled, setting it to $intfmaster"
				_mptcp_uci_set network.${intfmaster}.multipath "master"
				_mptcp_uci_set openmptcprouter.${intfmaster}.multipath "master"
			}
		fi
	fi
	_mptcp_uci_commit network
	_mptcp_uci_commit openmptcprouter
	case "$board_name" in
		*raspberry*) case "$board_name" in *4-model-b*|*5*) ;; *)
			ethtool --offload eth0 rx off tx off > /dev/null 2>&1
		;; esac ;;
	esac
	case "$board_name" in *[Rr]2[Ss]*)
		ethtool -K eth0 rx off tx off > /dev/null 2>&1
		ethtool -K eth1 rx off tx off > /dev/null 2>&1
	;; esac
}

stop_service() {
	local pin_dir="/sys/fs/bpf/mptcp"
	# Deleting the pinned link unregisters the scheduler, as in
	# global_multipath_settings: "bpftool struct_ops unregister" takes a map
	# id or name, not a pinned path, so it failed and the pins were kept.
	for pin in "$pin_dir"/*; do
		[ -e "$pin" ] || continue
		rm -f "$pin"
	done
	rm -f /var/state/mptcp-bpf-scheduler
}

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

service_triggers_load_interface() { ifaces="${ifaces}${1} "; }

service_triggers() {
	local ifaces n
	config_load network
	config_foreach service_triggers_load_interface 'interface';
	procd_add_reload_trigger "network"
	procd_open_trigger
	for n in $ifaces; do 
		network_get_device iface "$n"
		[ -z "$iface" ] && network_get_physdev iface "$n"
		[ -z "$iface" ] && iface=$(ifstatus "$n" | jsonfilter -q -e '@["l3_device"]')
		[ -z "$iface" ] && config_get iface "$n" ifname
		[ -z "$iface" ] && continue
		procd_add_reload_interface_trigger "$n"
		procd_add_interface_trigger "interface.*" "$n" /etc/init.d/mptcp reload "$iface"
	done
	procd_close_trigger
}
