#!/bin/sh /etc/rc.common
# Copyright 2020-2023 MOSSDeF, Stan Grishin (stangri@melmac.ca)
# shellcheck disable=SC2018,SC2019,SC3043,SC3057,SC3060

# sysctl net.ipv4.conf.default.rp_filter=1
# sysctl net.ipv4.conf.all.rp_filter=1

# shellcheck disable=SC2034
START=94
# shellcheck disable=SC2034
USE_PROCD=1

#!/bin/sh
# Copyright 2023 MOSSDeF, Stan Grishin (stangri@melmac.ca)
# shellcheck disable=SC2018,SC2019,SC2034,SC3043,SC3057,SC3060

readonly packageName='pbr'
readonly PKG_VERSION='dev-test'
readonly serviceName="$packageName $PKG_VERSION"
readonly serviceTrapSignals='exit SIGHUP SIGQUIT SIGKILL'
readonly packageConfigFile="/etc/config/${packageName}"
readonly packageLockFile="/var/run/${packageName}.lock"
readonly dnsmasqFileDefault="/var/dnsmasq.d/${packageName}"
readonly _OK_='\033[0;32m\xe2\x9c\x93\033[0m'
readonly _FAIL_='\033[0;31m\xe2\x9c\x97\033[0m'
readonly __OK__='\033[0;32m[\xe2\x9c\x93]\033[0m'
readonly __FAIL__='\033[0;31m[\xe2\x9c\x97]\033[0m'
readonly _ERROR_='\033[0;31mERROR\033[0m'
readonly _WARNING_='\033[0;33mWARNING\033[0m'
readonly ip_full='/usr/libexec/ip-full'
# shellcheck disable=SC2155
readonly ip_bin="$(command -v ip)"
readonly ipTablePrefix='pbr'
# shellcheck disable=SC2155
readonly iptables="$(command -v iptables)"
# shellcheck disable=SC2155
readonly ip6tables="$(command -v ip6tables)"
# shellcheck disable=SC2155
readonly ipset="$(command -v ipset)"
readonly ipsPrefix='pbr'
readonly iptPrefix='PBR'
# shellcheck disable=SC2155
readonly agh="$(command -v AdGuardHome)"
readonly aghConfigFile='/etc/adguardhome.yaml'
readonly aghIpsetFile="/var/run/${packageName}.adguardhome.ipsets"
# shellcheck disable=SC2155
readonly nft="$(command -v nft)"
readonly nftIPv4Flag='ip'
readonly nftIPv6Flag='ip6'
readonly nftTempFile="/var/run/${packageName}.nft"
readonly nftPermFile="/usr/share/nftables.d/ruleset-post/30-${packageName}.nft"
readonly nftPrefix='pbr'
readonly nftTable='fw4'
readonly chainsList='forward input output postrouting prerouting'
readonly ssConfigFile='/etc/shadowsocks'
readonly torConfigFile='/etc/tor/torrc'
readonly xrayIfacePrefix='xray_'

# package config options
procd_boot_timeout=
enabled=
fw_mask=
icmp_interface=
ignored_interface=
ipv6_enabled=
nft_file_support=
nft_user_set_policy=
nft_user_set_counter=
procd_boot_delay=
procd_reload_delay=
procd_lan_interface=
procd_wan_ignore_status=
procd_wan_interface=
procd_wan6_interface=
resolver_set=
resolver_instance=
rule_create_option=
secure_reload=
strict_enforcement=
supported_interface=
verbosity=
wan_ip_rules_priority=
wan_mark=
nft_set_auto_merge=
nft_set_counter=
nft_set_flags_interval=
nft_set_flags_timeout=
nft_set_flags_gc_interval=
nft_set_policy=
nft_set_timeout=

# run-time
gatewaySummary=
errorSummary=
warningSummary=
wanIface4=
wanIface6=
dnsmasqFile=
dnsmasqFileList=
ifaceMark=
ifaceTableID=
ifacePriority=
ifacesAll=
ifacesSupported=
firewallWanZone=
wanGW4=
wanGW6=
serviceStartTrigger=
processPolicyError=
processPolicyWarning=
resolver_set_supported=
policy_routing_nft_prev_param4=
policy_routing_nft_prev_param6=
nft_set_params=
torDnsPort=
torTrafficPort=

# shellcheck disable=SC1091
. /lib/functions.sh
# shellcheck disable=SC1091
. /lib/functions/network.sh
# shellcheck disable=SC1091
. /usr/share/libubox/jshn.sh

output_ok() { output 1 "$_OK_"; output 2 "$__OK__\\n"; }
output_okn() { output 1 "$_OK_\\n"; output 2 "$__OK__\\n"; }
output_fail() { output 1 "$_FAIL_"; output 2 "$__FAIL__\\n"; }
output_failn() { output 1 "$_FAIL_\\n"; output 2 "$__FAIL__\\n"; }
# shellcheck disable=SC2317
str_replace() { printf "%b" "$1" | sed -e "s/$(printf "%b" "$2")/$(printf "%b" "$3")/g"; }
str_replace() { echo "${1//$2/$3}"; }
str_contains() { [ -n "$1" ] && [ -n "$2" ] && [ "${1//$2}" != "$1" ]; }
str_contains_word() { echo "$1" | grep -q -w "$2"; }
str_to_lower() { echo "$1" | tr 'A-Z' 'a-z'; }
str_to_upper() { echo "$1" | tr 'a-z' 'A-Z'; }
str_extras_to_underscore() { echo "$1" | tr '[\. ~`!@#$%^&*()\+/,<>?//;:]' '_'; }
str_extras_to_space() { echo "$1" | tr ';{}' ' '; }
debug() { local i j; for i in "$@"; do eval "j=\$$i"; echo "${i}: ${j} "; done; }
quiet_mode() {
	case "$1" in
		on) verbosity=0;;
		off) verbosity="$(uci_get "$packageName" 'config' 'verbosity' '2')";;
	esac
}
output() {
# Target verbosity level with the first parameter being an integer
	is_integer() {
		case "$1" in
			(*[!0123456789]*) return 1;;
			('')              return 1;;
			(*)               return 0;;
		esac
	}
	local msg memmsg logmsg text
	local sharedMemoryOutput="/dev/shm/$packageName-output"
	if [ -z "$verbosity" ] && [ -n "$packageName" ]; then
		verbosity="$(uci_get "$packageName" 'config' 'verbosity' '2')"
	fi
	if [ $# -ne 1 ] && is_integer "$1"; then
		if [ $((verbosity & $1)) -gt 0 ] || [ "$verbosity" = "$1" ]; then shift; text="$*"; else return 0; fi
	fi
	text="${text:-$*}";
	[ -t 1 ] && printf "%b" "$text"
	msg="${text//$serviceName /service }";
	if [ "$(printf "%b" "$msg" | wc -l)" -gt 0 ]; then
		[ -s "$sharedMemoryOutput" ] && memmsg="$(cat "$sharedMemoryOutput")"
		logmsg="$(printf "%b" "${memmsg}${msg}" | sed 's/\x1b\[[0-9;]*m//g')"
		logger -t "${packageName:-service} [$$]" "$(printf "%b" "$logmsg")"
		rm -f "$sharedMemoryOutput"
	else
		printf "%b" "$msg" >> "$sharedMemoryOutput"
	fi
}
_build_ifaces_all() { ifacesAll="${ifacesAll}${1} "; }
_build_ifaces_supported() { is_supported_interface "$1" && ! str_contains "$ifacesSupported" "$1" && ifacesSupported="${ifacesSupported}${1} "; }
pbr_find_iface() {
	local iface i param="$2"
	if [ -n "$procd_wan_interface" ] && [ "$param" = 'wan' ]; then
		iface="$procd_wan_interface"
	elif [ -n "$procd_wan6_interface" ] && [ "$param" = 'wan6' ]; then
		iface="$procd_wan6_interface"
	else
		"network_find_${param}" iface
		is_tunnel "$iface" && unset iface
		if [ -z "$iface" ]; then
			for i in $ifacesAll; do
				if "is_${param}" "$i"; then break; else unset i; fi
			done
		fi
	fi
	eval "$1"='${iface:-$i}'
}
pbr_get_gateway() {
	local iface="$2" dev="$3" gw
	network_get_gateway gw "$iface" true
	if [ -z "$gw" ] || [ "$gw" = '0.0.0.0' ]; then
#		gw="$(ubus call "network.interface.${iface}" status | jsonfilter -e "@.route[0].nexthop")"
		gw="$($ip_bin -4 a list dev "$dev" 2>/dev/null | grep inet | awk '{print $2}' | awk -F "/" '{print $1}')"
	fi
	eval "$1"='$gw'
}
pbr_get_gateway6() {
	local iface="$2" dev="$3" gw
	network_get_gateway6 gw "$iface" true
	if [ -z "$gw" ] || [ "$gw" = '::/0' ] || [ "$gw" = '::0/0' ] || [ "$gw" = '::' ]; then
		gw="$($ip_bin -6 a list dev "$dev" 2>/dev/null | grep inet6 | grep 'scope global' | awk '{print $2}')"
	fi
	eval "$1"='$gw'
}

# shellcheck disable=SC2016
is_bad_user_file_nft_call() { grep -q '"\$nft" list' "$1" || grep '"\$nft" -f' "$1";}
is_config_enabled() {
	_check_config() { local en; config_get_bool en "$1" 'enabled' 1; [ "$en" -gt 0 ] && _cfg_enabled=0; }
	local cfg="$1" _cfg_enabled=1
	[ -n "$1" ] || return 1
	config_load "$packageName"
	config_foreach _check_config "$cfg"
	return "$_cfg_enabled"
}
uci_get_device() { uci_get 'network' "$1" 'device' || uci_get 'network' "$1" 'dev'; }
uci_get_protocol() { uci_get 'network' "$1" 'proto'; }
is_default_dev() { [ "$1" = "$($ip_bin -4 r | grep -m1 'dev' | grep -Eso 'dev [^ ]*' | awk '{print $2}')" ]; }
is_domain() { ! is_ipv6 "$1" && str_contains "$1" '[a-zA-Z]'; }
is_dslite() { local p; network_get_protocol p "$1"; [ "${p:0:6}" = "dslite" ]; }
is_family_mismatch() { ( is_ipv4_netmask "${1//!}" && is_ipv6 "${2//!}" ) || ( is_ipv6 "${1//!}" && is_ipv4_netmask "${2//!}" ); }
is_greater() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; }
is_greater_or_equal() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" = "$2"; }
is_ignored_interface() { str_contains_word "$ignored_interface" "$1"; }
is_ignore_target() { [ "$(str_to_lower "$1")" = 'ignore' ]; }
is_installed() { grep -q "Provides: ${1}" /usr/lib/opkg/status; }
is_integer() {
	case "$1" in
		(*[!0123456789]*) return 1;;
		('')              return 1;;
		(*)               return 0;;
	esac
}
is_ipset_type_supported() { ipset help hash:"$1" >/dev/null 2>&1; }
is_nft_mode() { [ -x "$nft" ] && ! str_contains "$resolver_set" 'ipset' && "$nft" list chains inet | grep -q "${nftPrefix}_prerouting"; }
is_ipv4() { expr "$1" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; }
is_ipv6() { ! is_mac_address "$1" && str_contains "$1" ':'; }
is_ipv6_global() { [ "${1:0:4}" = '2001' ]; }
is_ipv6_link_local() { [ "${1:0:4}" = 'fe80' ]; }
is_ipv6_unique_local() { [ "${1:0:2}" = 'fc' ] || [ "${1:0:2}" = 'fd' ]; }
is_list() { str_contains "$1" ',' || str_contains "$1" ' '; }
is_ipv4_netmask() { local ip="${1%/*}"; [ "$ip" != "$1" ] && is_ipv4 "$ip"; }
is_lan() { local d; network_get_device d "$1"; str_contains "$d" 'br-lan'; }
is_l2tp() { local p; network_get_protocol p "$1"; [ "${p:0:4}" = "l2tp" ]; }
is_mac_address() { expr "$1" : '[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]$' >/dev/null; }
is_netifd_table() { local iface="$1"; [ "$(uci_get 'network' "$iface" 'ip4table')" = "${packageName}_${iface%6}" ]; }
is_oc() { local p; network_get_protocol p "$1"; [ "${p:0:11}" = "openconnect" ]; }
is_ovpn() { local d; uci_get_device d "$1"; [ "${d:0:3}" = "tun" ] || [ "${d:0:3}" = "tap" ] || [ -f "/sys/devices/virtual/net/${d}/tun_flags" ]; }
is_ovpn_valid() { local dev_net dev_ovpn; uci_get_device dev_net "$1"; dev_ovpn="$(uci_get 'openvpn' "$1" 'dev')"; [ -n "$dev_net" ] && [ -n "$dev_ovpn" ] && [ "$dev_net" = "$dev_ovpn" ]; }
is_phys_dev() { [ "${1:0:1}" = "@" ] && ip l show | grep -E -q "^\\d+\\W+${1:1}"; }
is_present() { command -v "$1" >/dev/null 2>&1; }
is_service_running() { if is_nft_mode; then is_service_running_nft; else is_service_running_iptables; fi; }
is_service_running_iptables() { [ -x "$iptables" ] && "$iptables" -t mangle -L | grep -q "${iptPrefix}_PREROUTING" >/dev/null 2>&1; }
is_service_running_nft() { [ -x "$nft" ] && [ -n "$(get_mark_nft_chains)" ]; }
is_supported_iface_dev() { local n dev; for n in $ifacesSupported; do network_get_device dev "$n"; [ "$1" = "$dev" ] && return 0; done; return 1; }
is_supported_protocol() { grep -o '^[^#]*' /etc/protocols | grep -w -v '0' | grep . | awk '{print $1}' | grep -q "$1"; }
is_pptp() { local p; network_get_protocol p "$1"; [ "${p:0:4}" = "pptp" ]; }
is_softether() { local d; network_get_device d "$1"; [ "${d:0:4}" = "vpn_" ]; }
is_supported_interface() { is_lan "$1" && return 1; str_contains_word "$supported_interface" "$1" || { ! is_ignored_interface "$1" && { is_wan "$1" || is_wan6 "$1" || is_tunnel "$1"; }; } || is_ignore_target "$1" || is_xray "$1"; }
is_tailscale() { local d; network_get_device d "$1"; [ "${d:0:9}" = "tailscale" ]; }
is_tor() { [ "$(str_to_lower "$1")" = "tor" ]; }
is_tor_running() {
	local ret=0
	is_ignored_interface 'tor' && return 1
	[ -s "$torConfigFile" ] || return 1
	json_load "$(ubus call service list "{ 'name': 'tor' }")" >/dev/null || return 1
	json_select 'tor' >/dev/null || return 1
	json_select 'instances' >/dev/null || return 1
	json_select 'instance1' >/dev/null || return 1
	json_get_var ret 'running' >/dev/null || return 1
	json_cleanup
	if [ "$ret" = "0" ]; then return 1; else return 0; fi
}
is_tunnel() { is_dslite "$1" || is_l2tp "$1" || is_oc "$1" || is_ovpn "$1" || is_pptp "$1" || is_softether "$1" || is_tailscale "$1" || is_tor "$1" || is_wg "$1"; }
is_url() { is_url_file "$1" || is_url_dl "$1"; }
is_url_dl() { is_url_ftp "$1" || is_url_http "$1" || is_url_https "$1"; }
is_url_file() { [ "$1" != "${1#file://}" ];}
is_url_ftp() { [ "$1" != "${1#ftp://}" ];}
is_url_http() { [ "$1" != "${1#http://}" ];}
is_url_https() { [ "$1" != "${1#https://}" ];}
is_wan() { [ "$1" = "$wanIface4" ] || { [ "${1##wan}" != "$1" ] && [ "${1##wan6}" = "$1" ]; } || [ "${1%%wan}" != "$1" ]; }
is_wan6() { [ -n "$wanIface6" ] && [ "$1" = "$wanIface6" ] || [ "${1/#wan6}" != "$1" ] || [ "${1/%wan6}" != "$1" ]; }
is_wg() { local p lp; network_get_protocol p "$1"; uci_get_listen_port lp "$1"; [ -z "$lp" ] && [ "${p:0:9}" = "wireguard" ]; }
is_xray() { [ -n "$(get_xray_traffic_port "$1")" ]; }
dnsmasq_kill() { killall -q -s HUP dnsmasq; }
dnsmasq_restart() { output 3 'Restarting dnsmasq '; if /etc/init.d/dnsmasq restart >/dev/null 2>&1; then output_okn; else output_failn; fi; }
# shellcheck disable=SC2155
get_ss_traffic_ports() { local i="$(jsonfilter -i "$ssConfigFile" -q -e "@.inbounds[*].port")"; echo "${i:-443}"; }
# shellcheck disable=SC2155
get_tor_dns_port() { local i="$(grep -m1 DNSPort "$torConfigFile" | awk -F: '{print $2}')"; echo "${i:-9053}"; }
# shellcheck disable=SC2155
get_tor_traffic_port() { local i="$(grep -m1 TransPort "$torConfigFile" | awk -F: '{print $2}')"; echo "${i:-9040}"; }
get_xray_traffic_port() { local i="${1//$xrayIfacePrefix}"; [ "$i" = "$1" ] && unset i; echo "$i"; }
get_rt_tables_id() { local iface="$1"; grep "${ipTablePrefix}_${iface}\$" '/etc/iproute2/rt_tables' | awk '{print $1;}'; }
get_rt_tables_next_id() { echo "$(($(sort -r -n '/etc/iproute2/rt_tables' | grep -o -E -m 1 "^[0-9]+")+1))"; }
get_rt_tables_non_pbr_next_id() { echo "$(($(grep -v "${ipTablePrefix}_" '/etc/iproute2/rt_tables' | sort -r -n  | grep -o -E -m 1 "^[0-9]+")+1))"; }
# shellcheck disable=SC2016
resolveip_to_ipt() { resolveip "$@" | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d'; }
resolveip_to_ipt4() { resolveip_to_ipt -4 "$@"; }
resolveip_to_ipt6() { [ -n "$ipv6_enabled" ] && resolveip_to_ipt -6 "$@"; }
# shellcheck disable=SC2016
resolveip_to_nftset() { resolveip "$@" | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; }
resolveip_to_nftset4() { resolveip_to_nftset -4 "$@"; }
resolveip_to_nftset6() { [ -n "$ipv6_enabled" ] && resolveip_to_nftset -6 "$@"; }
# shellcheck disable=SC2016
ipv4_leases_to_nftset() { [ -s '/tmp/dhcp.leases' ] || return 1; grep "$1" '/tmp/dhcp.leases' | awk '{print $3}' | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; }
# shellcheck disable=SC2016
ipv6_leases_to_nftset() { [ -s '/tmp/hosts/odhcpd' ] || return 1; grep -v '^#' '/tmp/hosts/odhcpd' | grep "$1" | awk '{print $1}' | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; }
# shellcheck disable=SC3037
ports_to_nftset() { echo -ne "$1"; }
get_mark_ipt_chains() { [ -n "$(command -v iptables-save)" ] && iptables-save | grep ":${iptPrefix}_MARK_" | awk '{ print $1 }' | sed 's/://'; }
get_mark_nft_chains() { [ -x "$nft" ] && "$nft" list table inet "$nftTable" 2>/dev/null | grep chain | grep "${nftPrefix}_mark_" | awk '{ print $2 }'; }
get_ipsets() { [ -x "$(command -v ipset)" ] && ipset list | grep "${ipsPrefix}_" | awk '{ print $2 }'; }
get_nft_sets() { [ -x "$nft" ] && "$nft" list table inet "$nftTable" 2>/dev/null | grep 'set' | grep "${nftPrefix}_" | awk '{ print $2 }'; }
__ubus_get() { ubus call service list "{ 'name': '$packageName' }" | jsonfilter -e "$1"; }
ubus_get_status() { __ubus_get "@.${packageName}.instances.main.data.status.${1}"; }
ubus_get_interface() { __ubus_get "@.${packageName}.instances.main.data.gateways[@.name='${1}']${2:+.$2}"; }
ubus_get_gateways() { __ubus_get "@.${packageName}.instances.main.data.gateways"; }
uci_get_device() {
	local __tmp
	__tmp="$(uci_get 'network' "$2" 'device')"
	[ -z "$__tmp" ] && unset "$1" && return 1
	eval "$1=$__tmp"
}
uci_get_listen_port() {
	local __tmp
	__tmp="$(uci_get 'network' "$2" 'listen_port')"
	[ -z "$__tmp" ] && unset "$1" && return 1
	eval "$1=$__tmp"
}

# luci app specific
is_enabled() { uci_get "$1" 'config' 'enabled'; }
is_running_iptables() { iptables -t mangle -L | grep -q PBR_PREROUTING >/dev/null 2>&1; }
is_running_nft_file() { [ -s "$nftPermFile" ]; }
is_running_nft() { "$nft" list table inet fw4 | grep chain | grep -q pbr_mark_ >/dev/null 2>&1; }
is_running() { is_running_iptables || is_running_nft; }
check_ipset() { { [ -n "$ipset" ] && "$ipset" help hash:net; } >/dev/null 2>&1; }
check_nft() { [ -n "$nft" ]; }
check_agh() { [ -n "$agh" ] && [ -s "$aghConfigFile" ]; }
check_dnsmasq() { command -v dnsmasq >/dev/null 2>&1; }
check_unbound() { command -v unbound >/dev/null 2>&1; }
check_agh_ipset() {
	check_ipset || return 1
	check_agh || return 1
	is_greater_or_equal "$($agh --version | sed 's|AdGuard Home, version v\(.*\)|\1|' | sed 's|-.*||')" '0.107.13'
}
check_dnsmasq_ipset() {
	local o;
	check_ipset || return 1
	check_dnsmasq || return 1
	o="$(dnsmasq -v 2>/dev/null)"
	! echo "$o" | grep -q 'no-ipset' && echo "$o" | grep -q 'ipset'
}
check_dnsmasq_nftset() {
	local o;
	check_nft || return 1
	check_dnsmasq || return 1
	o="$(dnsmasq -v 2>/dev/null)"
	! echo "$o" | grep -q 'no-nftset' && echo "$o" | grep -q 'nftset'
}
print_json_bool() { json_init; json_add_boolean "$1" "$2"; json_dump; json_cleanup; }
print_json_string() { json_init; json_add_string "$1" "$2"; json_dump; json_cleanup; }
opkg_get_version() { grep -m1 -A1 "Package: $1$" '/usr/lib/opkg/status' | grep -m1 'Version: ' | sed 's|Version: \(.*\)|\1|'; }

if type extra_command >/dev/null 2>&1; then
	extra_command 'status' "Generates output required to troubleshoot routing issues
		Use '-d' option for more detailed output
		Use '-p' option to automatically upload data under VPR paste.ee account
			WARNING: while paste.ee uploads are unlisted, they are still publicly available
		List domain names after options to include their lookup in report"
	extra_command 'version' 'Show version information'
	extra_command 'on_firewall_reload' '	Run service on firewall reload'
	extra_command 'on_interface_reload' '	Run service on indicated interface reload'
else
# shellcheck disable=SC2034
	EXTRA_COMMANDS='on_firewall_reload on_interface_reload status version'
# shellcheck disable=SC2034
	EXTRA_HELP="	status	Generates output required to troubleshoot routing issues
		Use '-d' option for more detailed output
		Use '-p' option to automatically upload data under VPR paste.ee account
			WARNING: while paste.ee uploads are unlisted, they are still publicly available
		List domain names after options to include their lookup in report"
fi

get_text() {
	local r
	case "$1" in
		errorConfigValidation) r="Config ($packageConfigFile) validation failure!";;
		errorNoIpFull) r="ip-full binary cannot be found!";;
		errorNoIptables) r="iptables binary cannot be found!";;
		errorNoIpset) r="Resolver set support (${resolver_set}) requires ipset, but ipset binary cannot be found!";;
		errorNoNft) r="Resolver set support (${resolver_set}) requires nftables, but nft binary cannot be found!";;
		errorResolverNotSupported) r="Resolver set (${resolver_set}) is not supported on this system!";;
		errorServiceDisabled) r="The ${packageName} service is currently disabled!";;
		errorNoWanGateway) r="The ${serviceName} service failed to discover WAN gateway!";;
		errorIpsetNameTooLong) r="The ipset name '%s' is longer than allowed 31 characters!";;
		errorNftsetNameTooLong) r="The nft set name '%s' is longer than allowed 255 characters!";;
		errorUnexpectedExit) r="Unexpected exit or service termination: '%s'!";;
		errorPolicyNoSrcDest) r="Policy '%s' has no source/destination parameters!";;
		errorPolicyNoInterface) r="Policy '%s' has no assigned interface!";;
		errorPolicyUnknownInterface) r="Policy '%s' has an unknown interface!";;
		errorPolicyProcessCMD) r="'%s'!";;
		errorFailedSetup) r="Failed to set up '%s'!";;
		errorFailedReload) r="Failed to reload '%s'!";;
		errorUserFileNotFound) r="Custom user file '%s' not found or empty!";;
		errorUserFileSyntax) r="Syntax error in custom user file '%s'!";;
		errorUserFileRunning) r="Error running custom user file '%s'!";;
		errorUserFileNoCurl) r="Use of 'curl' is detected in custom user file '%s', but 'curl' isn't installed!";;
		errorNoGateways) r="Failed to set up any gateway!";;
		errorResolver) r="Resolver '%s'!";;
		errorPolicyProcessNoIpv6) r="Skipping IPv6 policy '%s' as IPv6 support is disabled!";;
		errorPolicyProcessUnknownFwmark) r="Unknown packet mark for interface '%s'!";;
		errorPolicyProcessMismatchFamily) r="Mismatched IP family between in policy '%s'!";;
		errorPolicyProcessUnknownProtocol) r="Unknown protocol in policy '%s'!";;
		errorPolicyProcessInsertionFailed) r="Insertion failed for both IPv4 and IPv6 for policy '%s'!";;
		errorPolicyProcessInsertionFailedIpv4) r="Insertion failed for IPv4 for policy '%s'!";;
		errorInterfaceRoutingEmptyValues) r="Received empty tid/mark or interface name when setting up routing!";;
		errorFailedToResolve) r="Failed to resolve '%s'!";;
		errorNftFileInstall) r="Failed to install fw4 nft file '%s'!";;
		errorDownloadUrlNoHttps) r="Failed to download '%s', HTTPS is not supported!";;
		errorDownloadUrl) r="Failed to download '%s'!";;
		errorNoDownloadWithSecureReload) r="Policy '%s' refers to URL which can't be downloaded in 'secure_reload' mode!";;
		errorFileSchemaRequiresCurl) r="The file:// schema requires curl, but it's not detected on this system!";;
		warningInvalidOVPNConfig) r="Invalid OpenVPN config for '%s' interface.";;
		warningResolverNotSupported) r="Resolver set (${resolver_set}) is not supported on this system.";;
		warningAGHVersionTooLow) r="Installed AdGuardHome ('%s') doesn't support 'ipset_file' option.";;
		warningPolicyProcessCMD) r="'%s'";;
		warningTorUnsetParams) r="Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'.";;
		warningTorUnsetProto) r="Please unset 'proto' or set 'proto' to 'all' for policy '%s'.";;
		warningTorUnsetChainIpt) r="Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'.";;
		warningTorUnsetChainNft) r="Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'.";;
		warningOutdatedWebUIApp) r="The WebUI application is outdated (version %s), please update it.";;
		warningBadNftCallsInUserFile) r="Incompatible nft calls detected in user include file, disabling fw4 nft file support.";;
		warningDnsmasqInstanceNoConfdir) r="Dnsmasq instance (%s) targeted in settings, but it doesn't have its own confdir.";;
	esac
	echo "$r"
}

process_url() {
	local url="$1"
	local dl_command dl_https_supported dl_temp_file
# TODO: check for FILE schema and missing curl
	if is_present 'curl'; then
		dl_command="curl --silent --insecure"
		dl_flag="-o"
	elif is_present '/usr/libexec/wget-ssl'; then
		dl_command="/usr/libexec/wget-ssl --no-check-certificate -q"
		dl_flag="-O"
	elif is_present wget && wget --version 2>/dev/null | grep -q "+https"; then
		dl_command="wget --no-check-certificate -q"
		dl_flag="-O"
	else
		dl_command="uclient-fetch --no-check-certificate -q"
		dl_flag="-O"
	fi
	if curl --version 2>/dev/null | grep -q "https" \
		|| wget --version 2>/dev/null | grep -q "+https" \
		|| grep -q "libustream-mbedtls" /usr/lib/opkg/status \
		|| grep -q "libustream-openssl" /usr/lib/opkg/status \
		|| grep -q "libustream-wolfssl" /usr/lib/opkg/status; then
		dl_https_supported=1
	else
		unset dl_https_supported
	fi
	while [ -z "$dl_temp_file" ] || [ -e "$dl_temp_file" ]; do
		dl_temp_file="$(mktemp -u -q -t ${packageName}_tmp.XXXXXXXX)"
	done
	if is_url_file "$url" && ! is_present 'curl'; then
		state add 'errorSummary' 'errorFileSchemaRequiresCurl' "$url"
	elif is_url_https "$url" && [ -z "$dl_https_supported" ]; then
		state add 'errorSummary' 'errorDownloadUrlNoHttps' "$url"
	elif $dl_command "$url" "$dl_flag" "$dl_temp_file" 2>/dev/null; then
		sed 'N;s/\n/ /;s/\s\+/ /g;' "$dl_temp_file"
	else
		state add 'errorSummary' 'errorDownloadUrl' "$url"
	fi
	rm -f "$dl_temp_file"
}

load_package_config() {
	_check_user_files_for_bad_nft_calls() {
		local cfg="$1"
		local en path
		config_get_bool en   "$cfg" 'enabled' 1
		config_get      path "$cfg" 'path'
		[ "$en" -eq 0 ] && return 0
		[ -z "$path" ] && return 0
		[ -s "$path" ] || return 0
		is_bad_user_file_nft_call "$path" && user_file_check_result='bad'
	}
	local param="$1"
	local user_file_check_result i
	config_load "$packageName"
	config_get_bool enabled                   'config' 'enabled' '0'
	config_get      fw_mask                   'config' 'fw_mask' 'ff0000'
	config_get      icmp_interface            'config' 'icmp_interface'
	config_get      ignored_interface         'config' 'ignored_interface'
	config_get_bool ipv6_enabled              'config' 'ipv6_enabled' '0'
	config_get_bool nft_file_support          'config' 'nft_file_support' '1'
	config_get_bool nft_set_auto_merge        'config' 'nft_set_auto_merge' '1'
	config_get_bool nft_set_counter           'config' 'nft_set_counter' '1'
	config_get_bool nft_set_flags_interval    'config' 'nft_set_flags_interval' '1'
	config_get_bool nft_set_flags_timeout     'config' 'nft_set_flags_timeout' '0'
	config_get      nft_set_gc_interval       'config' 'nft_set_gc_interval'
	config_get      nft_set_policy            'config' 'nft_set_policy' 'performance'
	config_get      nft_set_timeout           'config' 'nft_set_timeout'
	config_get      resolver_set              'config' 'resolver_set'
	config_get      resolver_instance         'config' 'resolver_instance' '*'
	config_get      rule_create_option        'config' 'rule_create_option' 'add'
	config_get_bool secure_reload             'config' 'secure_reload' '0'
	config_get_bool strict_enforcement        'config' 'strict_enforcement' '1'
	config_get      supported_interface       'config' 'supported_interface'
	config_get      verbosity                 'config' 'verbosity' '2'
	config_get      procd_boot_delay          'config' 'procd_boot_delay' '0'
	config_get      procd_boot_timeout        'config' 'procd_boot_timeout' '30'
	config_get      procd_lan_interface       'config' 'procd_lan_interface'
	config_get      procd_wan_ignore_status   'config' 'procd_wan_ignore_status' '0'
	config_get      procd_wan_interface       'config' 'procd_wan_interface'
	config_get      procd_wan6_interface      'config' 'procd_wan6_interface'
	config_get      wan_ip_rules_priority     'config' 'wan_ip_rules_priority' '30000'
	config_get      wan_mark                  'config' 'wan_mark' '010000'
	fw_mask="0x${fw_mask}"
	wan_mark="0x${wan_mark}"
	[ -n "$ipv6_enabled" ] && [ "$ipv6_enabled" -eq 0 ] && unset ipv6_enabled
	[ -n "$nft_file_support" ] && [ "$nft_file_support" -eq 0 ] && unset nft_file_support
	[ -n "$nft_user_set_counter" ] && [ "$nft_user_set_counter" -eq 0 ] && unset nft_user_set_counter
	[ -n "$secure_reload" ] && [ "$secure_reload" -eq 0 ] && unset secure_reload
	config_foreach _check_user_files_for_bad_nft_calls 'include'
	[ -n "$user_file_check_result" ] && unset nft_file_support
	[ -n "$nft_file_support" ] && unset secure_reload
	is_config_enabled 'include' && unset secure_reload
	if is_nft_mode; then
		fw_maskXor="$(printf '%#x' "$((fw_mask ^ 0xffffffff))")"
		fw_maskXor="${fw_maskXor:-0xff00ffff}"
	else
		case $rule_create_option in
			insert|-i|-I) rule_create_option='-I';;
			add|-a|-A|*) rule_create_option='-A';;
		esac
	fi

	[ "$nft_set_auto_merge" != '1' ]     && unset nft_set_auto_merge
	[ "$nft_set_counter" != '1' ]        && unset nft_set_counter
	[ "$nft_set_flags_interval" != '1' ] && unset nft_set_flags_interval
	[ "$nft_set_flags_timeout" != '1' ]  && unset nft_set_flags_timeout
	[ -z "${nft_set_flags_timeout}${nft_set_timeout}" ] && unset nft_set_gc_interval
	local nft_set_flags
	if [ -n "${nft_set_flags_interval}${nft_set_flags_timeout}" ]; then
		[ -n "$nft_set_flags_interval" ] && nft_set_flags='flags interval'
		if [ -n "$nft_set_flags_timeout" ]; then
			if [ -n "$nft_set_flags" ]; then
				nft_set_flags="${nft_set_flags}, timeout"
			else
				nft_set_flags='flags timeout'
			fi
		fi
	fi
	nft_set_params=" \
		${nft_set_auto_merge:+ auto-merge;} \
		${nft_set_counter:+ counter;} \
		${nft_set_flags:+ $nft_set_flags;} \
		${nft_set_gc_interval:+ gc_interval "$nft_set_gc_interval";} \
		${nft_set_policy:+ policy "$nft_set_policy";} \
		${nft_set_timeout:+ timeout "$nft_set_timeout";} \
		"

	resolver 'check_support' && resolver 'configure_instances'
}

load_environment() {
	local param="$1" validation_result="$2"
	load_package_config "$param"
	case "$param" in
		on_start)
			if [ "$enabled" -eq 0 ]; then
				state add 'errorSummary' 'errorServiceDisabled'
				return 1
			fi
			if [ -n "$validation_result" ] && [ "$validation_result" != '0' ]; then
				output "${_ERROR_}: The $packageName config validation failed!\\n"
				output "Please check if the '$packageConfigFile' contains correct values for config options.\\n"
				state add 'errorSummary' 'errorConfigValidation'
				return 1
			fi
			if [ ! -x "$ip_bin" ]; then
				state add 'errorSummary' 'errorNoIpFull'
				return 1
			fi
			if is_nft_mode; then
				if [ "$(uci_get 'firewall' 'defaults' 'auto_includes')" = '0' ]; then
					uci_remove 'firewall' 'defaults' 'auto_includes'
					uci_commit firewall
				fi
			else
				if [ -z "$iptables" ] || [ ! -x "$iptables" ]; then
					state add 'errorSummary' 'errorNoIptables'
					return 1
				fi
			fi
		;;
		on_stop)
			:
		;;
	esac
	load_network "$param"
}

load_network() {
	_find_firewall_wan_zone() { [ "$(uci_get 'firewall' "$1" 'name')" = "wan" ] && firewallWanZone="$1"; }
	local i param="$1"
	if [ -z "$ifacesSupported" ]; then
		config_load 'firewall'
		config_foreach _find_firewall_wan_zone 'zone'
		for i in $(uci_get 'firewall' "$firewallWanZone" 'network'); do
			is_supported_interface "$i" && ! str_contains "$ifacesSupported" "$1" && ifacesSupported="${ifacesSupported}${i} "
			if ! is_tunnel "$i"; then
				is_wan  "$i" && wanIface4="$i"
				[ -n "$ipv6_enabled" ] && is_wan6 "$i" && wanIface6="$i"
			fi
		done
		config_load 'network'
		[ -z "$ifacesAll" ] && config_foreach _build_ifaces_all 'interface'
		config_foreach _build_ifaces_supported 'interface'
	fi
	[ -z "$wanIface4" ] && pbr_find_iface wanIface4 'wan'
	[ -n "$wanIface4" ] && [ -z "$wanGW4" ] && network_get_gateway wanGW4 "$wanIface4"
	if [ -n "$ipv6_enabled" ]; then
		[ -z "$wanIface6" ] && pbr_find_iface wanIface6 'wan6'
		[ -n "$wanIface6" ] && [ -z "$wanGW6" ] && network_get_gateway6 wanGW6 "$wanIface6"
	fi
	case "$param" in
		on_boot|on_start)
			[ -n "$wanIface4" ] && output 2 "Found wan interface (${param}): $wanIface4 \\n"
			[ -n "$wanGW4" ] && output 2 "Found wan gateway (${param}): $wanGW4 \\n"
			[ -n "$wanIface6" ] && output 2 "Found wan6 interface (${param}): $wanIface6 \\n"
			[ -n "$wanGW6" ] && output 2 "Found wan6 gateway (${param}): $wanGW6 \\n"
		;;
	esac
	wanGW="${wanGW4:-$wanGW6}"
}

is_wan_up() {
	local sleepCount='1' param="$1"
	load_network "$param"
	[ "$procd_wan_ignore_status" -eq '0' ] || return 0
	[ "$param" = 'on_boot' ] || procd_boot_timeout='1'
	while [ -z "$wanGW" ] ; do
		load_network "$param"
		if [ $((sleepCount)) -gt $((procd_boot_timeout)) ] || [ -n "$wanGW" ]; then break; fi
		output "$serviceName waiting for ${procd_wan_interface:-wan} gateway...\\n"
		sleep 1
		network_flush_cache
		sleepCount=$((sleepCount+1))
	done
	if [ -n "$wanGW" ]; then
		return 0
	else
		state add 'errorSummary' 'errorNoWanGateway'
		return 1
	fi
}

# shellcheck disable=SC2086
ipt4() {
	local d
	[ -x "$iptables" ] || return 1
	for d in "${*//-A/-D}" "${*//-I/-D}" "${*//-N/-F}" "${*//-N/-X}"; do 
		[ "$d" != "$*" ] && "$iptables" $d >/dev/null 2>&1
	done
	d="$*"; "$iptables" $d >/dev/null 2>&1
}

# shellcheck disable=SC2086
ipt6() {
	local d
	[ -n "$ipv6_enabled" ] || return 0
	[ -x "$ip6tables" ] || return 1
	for d in "${*//-A/-D}" "${*//-I/-D}" "${*//-N/-F}" "${*//-N/-X}"; do 
		[ "$d" != "$*" ] && "$ip6tables" $d >/dev/null 2>&1
	done
	d="$*"
	"$ip6tables" $d >/dev/null 2>&1
}

# shellcheck disable=SC2086
ipt() {
	local d failFlagIpv4=1 failFlagIpv6=1
	[ -x "$iptables" ] || return 1
	for d in "${*//-A/-D}" "${*//-I/-D}" "${*//-N/-F}" "${*//-N/-X}"; do 
		if [ "$d" != "$*" ]; then
			"$iptables" $d >/dev/null 2>&1
			if [ -x "$ip6tables" ]; then
				"$ip6tables" $d >/dev/null 2>&1
			fi
		fi
	done
	d="$*"; "$iptables" $d >/dev/null 2>&1 && failFlagIpv4=0;
	if [ -n "$ipv6_enabled" ] && [ -x "$ip6tables" ]; then
		"$ip6tables" $d >/dev/null 2>&1 && failFlagIpv6=0
	fi
	[ "$failFlagIpv4" -eq 0 ] || [ "$failFlagIpv6" -eq 0 ]
}

# shellcheck disable=SC2086
ips4() { [ -x "$ipset" ] && "$ipset" "$@" >/dev/null 2>&1; }
ips6() { [ -x "$ipset" ] && { if [ -n "$ipv6_enabled" ] && [ -n "$*" ]; then "$ipset" "$@" >/dev/null 2>&1; else return 1; fi; }; }
ips() {
	local command="$1" iface="$2" target="${3:-dst}" type="${4:-ip}" uid="$5" comment="$6" param="$7" mark="$7"
	local ipset4 ipset6 i
	local ipv4_error=1 ipv6_error=1
	ipset4="${ipsPrefix}${iface:+_$iface}_4${target:+_$target}${type:+_$type}${uid:+_$uid}"
	ipset6="${ipsPrefix}${iface:+_$iface}_6${target:+_$target}${type:+_$type}${uid:+_$uid}"

	[ -x "$ipset" ] || return 1

	if [ "${#ipset4}" -gt 31 ]; then 
		state add 'errorSummary' 'errorIpsetNameTooLong' "$ipset4"
		return 1
	fi

	case "$command" in
		add)
			ips4 -q -! add "$ipset4" ["$param"] comment "$comment" && ipv4_error=0
			ips6 -q -! add "$ipset6" ["$param"] comment "$comment" && ipv6_error=0
		;;
		add_agh_element)
			[ -n "$ipv6_enabled" ] || unset ipset6
			echo "${param}/${ipset4}${ipset6:+,$ipset6}" >> "$aghIpsetFile" && ipv4_error=0
		;;
		add_dnsmasq_element)
			[ -n "$ipv6_enabled" ] || unset ipset6
			# shellcheck disable=SC2086
			echo "ipset=/${param}/${ipset4}${ipset6:+,$ipset6} # $comment" | tee -a $dnsmasqFileList >/dev/null 2>&1 && ipv4_error=0
		;;
		create)
			ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0
			ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv6_error=0
		;;
		create_agh_set)
			ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0
			ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv6_error=0
		;;
		create_dnsmasq_set)
			ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0
			ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv6_error=0
		;;
		create_user_set)
			case "$type" in
				ip|net)
					ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0
					ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv6_error=0
					case "$target" in
						dst)
							ipt4 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" dst -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0
							ipt6 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" dst -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0
						;;
						src)
							ipt4 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0
							ipt6 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0
					;;
					esac
				;;
				mac)
					ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0
					ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv4_error=0
					ipt4 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0
					ipt6 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0
				;;
				esac
		;;
		delete|destroy)
			ips4 -q -! destroy "$ipset4" && ipv4_error=0
			ips6 -q -! destroy "$ipset6" && ipv6_error=0
		;;
		delete_user_set)
			ips4 -q -! destroy "$ipset4" && ipv4_error=0
			ips6 -q -! destroy "$ipset6" family inet6 && ipv6_error=0
			case "$type" in
				ip|net)
					case "$target" in
						dst)
							ipt4 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" dst -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0
							ipt6 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" dst -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0
						;;
						src)
							ipt4 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0
							ipt6 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0
					;;
					esac
				;;
				mac)
					ipt4 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0
					ipt6 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0
				;;
				esac
		;;
		flush|flush_user_set)
			ips4 -q -! flush "$ipset4" && ipv4_error=0
			ips6 -q -! flush "$ipset6" && ipv6_error=0
		;;
	esac
	if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then
		return 0
	else
		return 1
	fi
}

nft_call() { [ -x "$nft" ] && "$nft" "$@" >/dev/null 2>&1; }
nft_file() {
	local i
	[ -x "$nft" ] || return 1
	case "$1" in
		add|add_command)
			[ -n "$nft_file_support" ] || return 1
			shift
			grep -q "$*" "$nftTempFile" || echo "$*" >> "$nftTempFile"
		;;
		create)
			rm -f "$nftTempFile" "$nftPermFile"
			for i in "$nftTempFile" "$nftPermFile"; do 
				mkdir -p "${i%/*}"
			done
			[ -n "$nft_file_support" ] || return 1
			{ echo '#!/usr/sbin/nft -f'; echo ''; } > "$nftTempFile"
		;;
		delete|rm|remove)
			rm -f "$nftTempFile" "$nftPermFile"
		;;
		enabled)
			[ -n "$nft_file_support" ] && return 0 || return 1
		;;
		exists)
			[ -s "$nftPermFile" ] && return 0 || return 1
		;;
		install)
			[ -n "$nft_file_support" ] || return 1
			[ -s "$nftTempFile" ] || return 1
			output "Installing fw4 nft file "
			if nft_call -c -f "$nftTempFile" && \
				cp -f "$nftTempFile" "$nftPermFile"; then
				output_okn
			else
				state add 'errorSummary' 'errorNftFileInstall' "$nftTempFile"
				output_failn
			fi
		;;
	esac
}
nft() { [ -x "$nft" ] && [ -n "$*" ] && { nft_file 'add_command' "$@" || "$nft" "$@" >/dev/null 2>&1;} }
nft4() { nft "$@"; }
nft6() { [ -n "$ipv6_enabled" ] || return 0; nft "$@"; }
nftset() {
	local command="$1" iface="$2" target="${3:-dst}" type="${4:-ip}" uid="$5" comment="$6" param="$7" mark="$7"
	local nftset4 nftset6 i param4 param6
	local ipv4_error=1 ipv6_error=1
	nftset4="${nftPrefix}${iface:+_$iface}_4${target:+_$target}${type:+_$type}${uid:+_$uid}"
	nftset6="${nftPrefix}${iface:+_$iface}_6${target:+_$target}${type:+_$type}${uid:+_$uid}"

	[ -x "$nft" ] || return 1

	if [ "${#nftset4}" -gt 255 ]; then 
		state add 'errorSummary' 'errorNftsetNameTooLong' "$nftset4"
		return 1
	fi

	case "$command" in
		add)
			if is_mac_address "$param" || is_list "$param"; then
				nft4 add element inet "$nftTable" "$nftset4" "{ $param }" && ipv4_error=0
				nft6 add element inet "$nftTable" "$nftset6" "{ $param }" && ipv6_error=0
			elif is_ipv4_netmask "$param" || is_ipv4 "$param"; then
				nft4 add element inet "$nftTable" "$nftset4" "{ $param }" && ipv4_error=0
			elif is_ipv6 "$param"; then
				nft6 add element inet "$nftTable" "$nftset6" "{ $param }" && ipv6_error=0
			else
				if [ "$target" = 'src' ]; then
					param4="$(ipv4_leases_to_nftset "$param")"
					param6="$(ipv6_leases_to_nftset "$param")"
				fi
				[ -z "$param4" ] && param4="$(resolveip_to_nftset4 "$param")"
				[ -z "$param6" ] && param6="$(resolveip_to_nftset6 "$param")"
				if [ -z "$param4" ] && [ -z "$param6" ]; then
					state add 'errorSummary' 'errorFailedToResolve' "$param"
				else
					[ -n "$param4" ] && nft4 add element inet "$nftTable" "$nftset4" "{ $param4 }" && ipv4_error=0
					[ -n "$param6" ] && nft6 add element inet "$nftTable" "$nftset6" "{ $param6 }" && ipv6_error=0
				fi
			fi
		;;
		add_dnsmasq_element)
			[ -n "$ipv6_enabled" ] || unset nftset6
			# shellcheck disable=SC2086
			echo "nftset=/${param}/4#inet#${nftTable}#${nftset4}${nftset6:+,6#inet#${nftTable}#$nftset6} # $comment" | tee -a $dnsmasqFileList >/dev/null 2>&1 && ipv4_error=0
		;;
		create)
			case "$type" in
				ip|net)
					nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; $nft_set_params comment \"$comment\";}" && ipv4_error=0
					nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0
				;;
				mac)
					nft4 add set inet "$nftTable" "$nftset4" "{ type ether_addr; $nft_set_params comment \"$comment\"; }" && ipv4_error=0
					nft6 add set inet "$nftTable" "$nftset6" "{ type ether_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0
				;;
				esac
		;;
		create_dnsmasq_set)
			nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; $nft_set_params comment \"$comment\"; }" && ipv4_error=0
			nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0
		;;
		create_user_set)
			case "$type" in
				ip|net)
					nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; $nft_set_params comment \"$comment\"; }" && ipv4_error=0
					nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0
					case "$target" in
						dst)
							nft4 add rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv4Flag" daddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0
							nft6 add rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv6Flag" daddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0
						;;
						src)
							nft4 add rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv4Flag" saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0
							nft6 add rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv6Flag" saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0
						;;
					esac
					;;
				mac)
					nft4 add set inet "$nftTable" "$nftset4" "{ type ether_addr; $nft_set_params comment \"$comment\"; }" && ipv4_error=0
					nft6 add set inet "$nftTable" "$nftset6" "{ type ether_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0
					nft4 add rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0
					nft6 add rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0
					;;
				esac
		;;
		delete|destroy)
			nft_call delete set inet "$nftTable" "$nftset4" && ipv4_error=0
			nft_call delete set inet "$nftTable" "$nftset6" && ipv6_error=0
		;;
		delete_user_set)
			nft_call delete set inet "$nftTable" "$nftset4" && ipv4_error=0
			nft_call delete set inet "$nftTable" "$nftset6" && ipv6_error=0
			case "$type" in
				ip|net)
					case "$target" in
						dst)
							nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv4Flag" daddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0
							nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv6Flag" daddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0
						;;
						src)
							nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv4Flag" saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0
							nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv6Flag" saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0
						;;
					esac
					;;
				mac)
					nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0
					nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0
					;;
				esac
		;;
		flush|flush_user_set)
			nft_call flush set inet "$nftTable" "$nftset4" && ipv4_error=0
			nft_call flush set inet "$nftTable" "$nftset6" && ipv6_error=0
		;;
	esac
# nft6 returns true if IPv6 support is not enabled
	[ -z "$ipv6_enabled" ] && ipv6_error='1'
	if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then
		return 0
	else
		return 1
	fi
}

cleanup_rt_tables() { sed -i "/${ipTablePrefix}_/d" '/etc/iproute2/rt_tables'; sync; }

cleanup_main_chains() {
	local i
	for i in $chainsList; do
		i="$(str_to_lower "$i")"
		nft_call flush chain inet "$nftTable" "${nftPrefix}_${i}"
	done
	for i in $chainsList; do
		i="$(str_to_upper "$i")"
		ipt -t mangle -D "${i}" -m mark --mark "0x0/${fw_mask}" -j "${iptPrefix}_${i}"
		ipt -t mangle -F "${iptPrefix}_${i}"
		ipt -t mangle -X "${iptPrefix}_${i}"
	done
}

cleanup_marking_chains() {
	local i
	for i in $(get_mark_nft_chains); do
		nft_call flush chain inet "$nftTable" "$i"
		nft_call delete chain inet "$nftTable" "$i"
	done
	for i in $(get_mark_ipt_chains); do
		ipt -t mangle -F "$i"
		ipt -t mangle -X "$i"
	done
}

cleanup_sets() {
	local i
	for i in $(get_nft_sets); do
		nft_call flush set inet "$nftTable" "$i"
		nft_call delete set inet "$nftTable" "$i"
	done
	for i in $(get_ipsets); do
		ipset -q -! flush "$i" >/dev/null 2>&1
		ipset -q -! destroy "$i" >/dev/null 2>&1
	done
}

state() {
	local action="$1" param="$2" value="${3//#/_}"
	shift 3
# shellcheck disable=SC2124
	local extras="$@"
	local line error_id error_extra label
	case "$action" in
		add)
			line="$(eval echo "\$$param")"
			eval "$param"='${line:+$line#}${value}${extras:+ $extras}'
		;;
		json)
			json_init
			json_add_object "$packageName"
			case "$param" in
				errorSummary)
					json_add_array 'errors';;
				warningSummary)
					json_add_array 'warnings';;
			esac
			if [ -n "$(eval echo "\$$param")" ]; then
				while read -r line; do
					if str_contains "$line" ' '; then
						error_id="${line% *}"
						error_extra="${line#* }"
					else
						error_id="$line"
					fi
					json_add_object
					json_add_string 'id' "$error_id"
					json_add_string 'extra' "$error_extra"
					json_close_object
				done <<EOF
$(eval echo "\$$param" | tr \# \\n)
EOF
			fi
			json_close_array
			json_close_object
			json_dump
		;;
		print)
			[ -z "$(eval echo "\$$param")" ] && return 0
			case "$param" in
				errorSummary)
					label="${_ERROR_}:";;
				warningSummary)
					label="${_WARNING_}:";;
			esac
				while read -r line; do
					if str_contains "$line" ' '; then
						error_id="${line% *}"
						error_extra="${line#* }"
						printf "%b $(get_text "$error_id")\\n" "$label" "$error_extra"
					else
						error_id="$line"
						printf "%b $(get_text "$error_id")\\n" "$label"
					fi
				done <<EOF
$(eval echo "\$$param" | tr \# \\n)
EOF
		;;
		set)
			eval "$param"='${value}${extras:+ $extras}'
		;;
	esac
}

_resolver_dnsmasq_confdir() {
	local cfg="$1"
	local confdir
	[ -z "$(uci_get 'dhcp' "$cfg")" ] && return 1;
	config_get confdir "$1" 'confdir'
	if [ -z "$confdir" ] && [ "$resolver_instance" != "*" ]; then
		state add 'warningSummary' 'warningDnsmasqInstanceNoConfdir' "$cfg"
	fi
	if [ -n "$confdir" ] && ! str_contains "$dnsmasqFileList" "$confdir"; then
		dnsmasqFile="${confdir}/${packageName}"
		dnsmasqFileList="${dnsmasqFileList:+$dnsmasqFileList }${dnsmasqFile}"
	fi
}

resolver() {
	local agh_version
	local param="$1"
	shift

	if [ "$param" = 'cleanup_all' ]; then
		sed -i "/ipset_file: ${aghIpsetFile}/d" "$aghConfigFile" >/dev/null 2>&1
		rm -f "$aghIpsetFile"
		local dfl
		for dfl in $dnsmasqFileList; do
			rm -f "$dfl"
		done
		return 0
	fi

	case "$resolver_set" in
		''|none)
			case "$param" in
				add_resolver_element) return 1;;
				create_resolver_set) return 1;;
				check_support) return 0;;
				cleanup) return 0;;
				configure) return 0;;
				init) return 0;;
				init_end) return 0;;
				kill) return 0;;
				reload) return 0;;
				restart) return 0;;
				compare_hash) return 0;;
				store_hash) return 0;;
			esac
		;;
		adguardhome.ipset)
			case "$param" in
				add_resolver_element)
					[ -n "$resolver_set_supported" ] && ips 'add_agh_element' "$@";;
				create_resolver_set)
					[ -n "$resolver_set_supported" ] && ips 'create_agh_set' "$@";;
				check_support)
					if [ ! -x "$ipset" ]; then
						state add 'errorSummary' 'errorNoIpset'
						return 1
					fi
					if [ -n "$agh" ] && [ -s "$aghConfigFile" ]; then
						agh_version="$($agh --version | sed 's|AdGuard Home, version v\(.*\)|\1|' | sed 's|-.*||')"
						if is_greater_or_equal "$agh_version" '0.107.13'; then
							resolver_set_supported='true'
							return 0
						else
							state add 'warningSummary' 'warningAGHVersionTooLow' "$agh_version"
							return 1
						fi
					else
						state add 'warningSummary' 'warningResolverNotSupported'
						return 1
					fi
				;;
				cleanup)
					[ -z "$resolver_set_supported" ] && return 0
					rm -f "$aghIpsetFile"
					sed -i "/ipset_file: ${aghIpsetFile}/d" "$aghConfigFile" >/dev/null 2>&1
					;;
				configure)
					[ -z "$resolver_set_supported" ] && return 1
					mkdir -p "${aghIpsetFile%/*}"
					touch "$aghIpsetFile"
					sed -i '/ipset_file/d' "$aghConfigFile" >/dev/null 2>&1
					sed -i "/  ipset:/a \ \ ipset_file: $aghIpsetFile" "$aghConfigFile"
				;;
				init) :;;
				init_end) :;;
				kill)
					[ -n "$resolver_set_supported" ] && [ -n "$agh" ] && killall -q -s HUP "$agh";;
				reload)
					[ -z "$resolver_set_supported" ] && return 1
					output 3 'Reloading adguardhome '
					if /etc/init.d/adguardhome reload >/dev/null 2>&1; then
						output_okn
						return 0
					else
						output_failn
						return 1
					fi
				;;
				restart)
					[ -z "$resolver_set_supported" ] && return 1
					output 3 'Restarting adguardhome '
					if /etc/init.d/adguardhome restart >/dev/null 2>&1; then
						output_okn
						return 0
					else
						output_failn
						return 1
					fi
				;;
				compare_hash)
					[ -z "$resolver_set_supported" ] && return 1
					local resolverNewHash
					if [ -s "$aghIpsetFile" ]; then
						resolverNewHash="$(md5sum "$aghIpsetFile" | awk '{ print $1; }')"
					fi
					[ "$resolverNewHash" != "$resolverStoredHash" ]
				;;
				store_hash)
					[ -s "$aghIpsetFile" ] && resolverStoredHash="$(md5sum "$aghIpsetFile" | awk '{ print $1; }')";;
			esac
		;;
		dnsmasq.ipset)
			case "$param" in
				add_resolver_element)
					[ -n "$resolver_set_supported" ] && ips 'add_dnsmasq_element' "$@";;
				create_resolver_set)
					[ -n "$resolver_set_supported" ] && ips 'create_dnsmasq_set' "$@";;
				check_support)
					if [ ! -x "$ipset" ]; then
						state add 'errorSummary' 'errorNoIpset'
						return 1
					fi
					if ! dnsmasq -v 2>/dev/null | grep -q 'no-ipset' && dnsmasq -v 2>/dev/null | grep -q 'ipset'; then
						resolver_set_supported='true'
						return 0
					else
						state add 'warningSummary' 'warningResolverNotSupported'
						return 1
					fi
				;;
				cleanup)
					if [ -n "$resolver_set_supported" ]; then
						local dfl
						for dfl in $dnsmasqFileList; do
							rm -f "$dfl"
						done
					fi
				;;
				configure)
					if [ -n "$resolver_set_supported" ]; then
						local dfl
						for dfl in $dnsmasqFileList; do
							mkdir -p "${dfl%/*}"
							chmod -R 660 "${dfl%/*}"
							chown -R root:dnsmasq "${dfl%/*}"
							touch "$dfl"
							chmod 660 "$dfl"
							chown root:dnsmasq "$dfl"
						done
					fi
				;;
				configure_instances)
					config_load 'dhcp'
					if [ "$resolver_instance" = "*" ]; then
						config_foreach _resolver_dnsmasq_confdir 'dnsmasq'
						dnsmasqFile="${dnsmasqFile:-$dnsmasqFileDefault}"
						str_contains "$dnsmasqFileList" "$dnsmasqFileDefault" || \
							dnsmasqFileList="${dnsmasqFileList:+$dnsmasqFileList }${dnsmasqFileDefault}"
					else
						for i in $resolver_instance; do
							_resolver_dnsmasq_confdir "@dnsmasq[$i]" \
							|| _resolver_dnsmasq_confdir "$i"
						done
						dnsmasqFile="${dnsmasqFile:-$dnsmasqFileDefault}"
						str_contains "$dnsmasqFileList" "$dnsmasqFileDefault" || \
							dnsmasqFileList="${dnsmasqFileList:-$dnsmasqFileDefault}"
					fi
				;;
				init) :;;
				init_end) :;;
				kill)
					[ -n "$resolver_set_supported" ] && killall -q -s HUP dnsmasq;;
				reload)
					[ -z "$resolver_set_supported" ] && return 1
					output 3 'Reloading dnsmasq '
					if /etc/init.d/dnsmasq reload >/dev/null 2>&1; then
						output_okn
						return 0
					else
						output_failn
						return 1
					fi
				;;
				restart)
					[ -z "$resolver_set_supported" ] && return 1
					output 3 'Restarting dnsmasq '
					if /etc/init.d/dnsmasq restart >/dev/null 2>&1; then
						output_okn
						return 0
					else
						output_failn
						return 1
					fi
				;;
				compare_hash)
					[ -z "$resolver_set_supported" ] && return 1
					local resolverNewHash
					if [ -s "$dnsmasqFile" ]; then
						resolverNewHash="$(md5sum "$dnsmasqFile" | awk '{ print $1; }')"
					fi
					[ "$resolverNewHash" != "$resolverStoredHash" ]
				;;
				store_hash)
					[ -s "$dnsmasqFile" ] && resolverStoredHash="$(md5sum "$dnsmasqFile" | awk '{ print $1; }')";;
			esac
		;;
		dnsmasq.nftset)
			case "$param" in
				add_resolver_element)
					[ -n "$resolver_set_supported" ] && nftset 'add_dnsmasq_element' "$@";;
				create_resolver_set)
					[ -n "$resolver_set_supported" ] && nftset 'create_dnsmasq_set' "$@";;
				check_support)
					if [ ! -x "$nft" ]; then
						state add 'errorSummary' 'errorNoNft'
						return 1
					fi
					if ! dnsmasq -v 2>/dev/null | grep -q 'no-nftset' && dnsmasq -v 2>/dev/null | grep -q 'nftset'; then
						resolver_set_supported='true'
						return 0
					else
						state add 'warningSummary' 'warningResolverNotSupported'
						return 1
					fi
				;;
				cleanup)
					if [ -n "$resolver_set_supported" ]; then
						local dfl
						for dfl in $dnsmasqFileList; do
							rm -f "$dfl"
						done
					fi
				;;
				configure)
					if [ -n "$resolver_set_supported" ]; then
						local dfl
						for dfl in $dnsmasqFileList; do
							mkdir -p "${dfl%/*}"
							chmod -R 660 "${dfl%/*}"
							chown -R root:dnsmasq "${dfl%/*}"
							touch "$dfl"
							chmod 660 "$dfl"
							chown root:dnsmasq "$dfl"
						done
					fi
				;;
				configure_instances)
					config_load 'dhcp'
					if [ "$resolver_instance" = "*" ]; then
						config_foreach _resolver_dnsmasq_confdir 'dnsmasq'
						dnsmasqFile="${dnsmasqFile:-$dnsmasqFileDefault}"
						str_contains "$dnsmasqFileList" "$dnsmasqFileDefault" || \
							dnsmasqFileList="${dnsmasqFileList:+$dnsmasqFileList }${dnsmasqFileDefault}"
					else
						for i in $resolver_instance; do
							_resolver_dnsmasq_confdir "@dnsmasq[$i]" \
							|| _resolver_dnsmasq_confdir "$i"
						done
						dnsmasqFile="${dnsmasqFile:-$dnsmasqFileDefault}"
						str_contains "$dnsmasqFileList" "$dnsmasqFileDefault" || \
							dnsmasqFileList="${dnsmasqFileList:-$dnsmasqFileDefault}"
					fi
				;;
				init) :;;
				init_end) :;;
				kill)
					[ -n "$resolver_set_supported" ] && killall -q -s HUP dnsmasq;;
				reload)
					[ -z "$resolver_set_supported" ] && return 1
					output 3 'Reloading dnsmasq '
					if /etc/init.d/dnsmasq reload >/dev/null 2>&1; then
						output_okn
						return 0
					else
						output_failn
						return 1
					fi
				;;
				restart)
					[ -z "$resolver_set_supported" ] && return 1
					output 3 'Restarting dnsmasq '
					if /etc/init.d/dnsmasq restart >/dev/null 2>&1; then
						output_okn
						return 0
					else
						output_failn
						return 1
					fi
				;;
				compare_hash)
					[ -z "$resolver_set_supported" ] && return 1
					local resolverNewHash
					if [ -s "$dnsmasqFile" ]; then
						resolverNewHash="$(md5sum "$dnsmasqFile" | awk '{ print $1; }')"
					fi
					[ "$resolverNewHash" != "$resolverStoredHash" ]
				;;
				store_hash)
					[ -s "$dnsmasqFile" ] && resolverStoredHash="$(md5sum "$dnsmasqFile" | awk '{ print $1; }')";;
			esac
		;;
		unbound.ipset)
			case "$param" in
				add_resolver_element) :;;
				create_resolver_set) :;;
				check_support) :;;
				cleanup) :;;
				configure) :;;
				init) :;;
				init_end) :;;
				kill) :;;
				reload) :;;
				restart) :;;
				compare_hash) :;;
				store_hash) :;;
			esac
		;;
		unbound.nftset)
			case "$param" in
				add_resolver_element) :;;
				create_resolver_set) :;;
				check_support) :;;
				cleanup) :;;
				configure) :;;
				init) :;;
				init_end) :;;
				kill) :;;
				reload) :;;
				restart) :;;
				compare_hash) :;;
				store_hash) :;;
			esac
		;;
	esac
}

trap_process() {
	output "\\n"
	output "Unexpected exit or service termination: '${1}'!\\n"
	state add 'errorSummary' 'errorUnexpectedExit' "$1"
	traffic_killswitch 'remove'
}

traffic_killswitch() {
	local s=0
	case "$1" in
		insert)
			local lan_subnet wan_device wan6_device
			[ -n "$secure_reload" ] || return 0
			nft_file 'enabled' && return 0
			for i in $serviceTrapSignals; do
# shellcheck disable=SC2064
				trap "trap_process $i" "$i"
			done
			output 3 'Activating traffic killswitch '
			network_get_subnet lan_subnet "${procd_lan_interface:-lan}"
			network_get_physdev wan_device "${wanIface4:-wan}"
			network_get_physdev wan6_device "${wanIface6:-wan6}"
			if is_nft_mode; then
				nft_call add chain inet "$nftTable" "${nftPrefix}_killswitch" '{ type filter hook forward priority 0; policy accept; }' || s=1
				nft_call add rule inet "$nftTable" "${nftPrefix}_killswitch" oifname "$wan_device"  "$nftIPv4Flag" saddr "$lan_subnet" counter reject || s=1
				nft_call add rule inet "$nftTable" "${nftPrefix}_killswitch" oifname "$wan6_device" "$nftIPv6Flag" saddr "$lan_subnet" counter reject
			else
				ipt -N "${iptPrefix}_KILLSWITCH" || s=1
				ipt -A "${iptPrefix}_KILLSWITCH" -s "$lan_subnet" -o "$wan_device" -j REJECT || s=1
				ipt -A "${iptPrefix}_KILLSWITCH" -s "$lan_subnet" -o "$wan6_device" -j REJECT
				ipt -I FORWARD -j "${iptPrefix}_KILLSWITCH" || s=1
			fi
			if [ "$s" -eq 0 ]; then
				output_okn
			else
				output_failn
			fi
		;;
		remove)
			if [ -n "$secure_reload" ] && ! nft_file 'enabled'; then
				output 3 'Deactivating traffic killswitch '
			fi
			if is_nft_mode; then
				nft_call flush chain inet "$nftTable" "${nftPrefix}_killswitch" || s=1
				nft_call delete chain inet "$nftTable" "${nftPrefix}_killswitch" || s=1
			else
				ipt -D FORWARD -j "${iptPrefix}_KILLSWITCH" || s=1
				ipt -F "${iptPrefix}_KILLSWITCH" || s=1
				ipt -X "${iptPrefix}_KILLSWITCH" || s=1
			fi
			if [ -n "$secure_reload" ] && ! nft_file 'enabled'; then
				if [ "$s" -eq 0 ]; then
					output_okn
				else
					output_failn
				fi
			fi
# shellcheck disable=SC2086
			trap - $serviceTrapSignals
		;;
	esac
}

policy_routing() { if is_nft_mode; then policy_routing_nft "$@"; else policy_routing_iptables "$@"; fi; }
policy_routing_iptables() {
	local mark param4 param6 i negation value dest4 dest6 ipInsertOption="-A"
	local ip4error='1' ip6error='1'
	local name="$1" iface="$2" laddr="$3" lport="$4" raddr="$5" rport="$6" proto chain uid="$9"
	proto="$(str_to_lower "$7")"
	chain="$(str_to_upper "$8")"
	chain="${chain:-PREROUTING}"
	mark=$(eval echo "\$mark_${iface//-/_}")

	if [ -n "$ipv6_enabled" ] && { is_ipv6 "$laddr" || is_ipv6 "$raddr"; }; then
		processPolicyError='true'
		state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name"
		return 1
	fi

	if is_tor "$iface"; then
		return 1
	elif is_xray "$iface"; then
		unset rport
		[ -z "$lport" ] && lport='0-52,54-65535'
		proto='tcp udp'
		dest4="-j TPROXY --on-ip 0.0.0.0 --on-port $(get_xray_traffic_port "$iface")"
		dest6="-j TPROXY --on-ip :: --on-port $(get_xray_traffic_port "$iface")"
	elif [ -n "$mark" ]; then
		dest4="-g ${iptPrefix}_MARK_${mark}"
		dest6="-g ${iptPrefix}_MARK_${mark}"
	elif [ "$iface" = "ignore" ]; then
		dest4="-j RETURN"
		dest6="-j RETURN"
	else
		processPolicyError='true'
		state add 'errorSummary' 'errorPolicyProcessUnknownFwmark' "$iface"
		return 1
	fi

	if is_family_mismatch "$laddr" "$raddr"; then 
		processPolicyError='true'
		state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$laddr' '$raddr'"
		return 1
	fi

	if [ -z "$proto" ]; then
		if [ -n "${lport}${rport}" ]; then 
			proto='tcp udp'
		else
			proto='all'
		fi
	fi

	for i in $proto; do
		if [ "$i" = 'all' ]; then
			param4="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest4"
			param6="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest6"
		elif ! is_supported_protocol "$i"; then
			processPolicyError='true'
			state add 'errorSummary' 'errorPolicyProcessUnknownProtocol' "${name}: '$i'"
			return 1
		else
			param4="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest4 -p $i"
			param6="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest6 -p $i"
		fi

		if [ -n "$laddr" ]; then
			if [ "${laddr:0:1}" = "!" ]; then
				negation='!'; value="${laddr:1}"
			else
				unset negation; value="$laddr";
			fi
			if is_phys_dev "$value"; then
				param4="$param4 ${negation:+$negation }-m physdev --physdev-in ${value:1}"
				param6="$param6 ${negation:+$negation }-m physdev --physdev-in ${value:1}"
			elif is_ipv4_netmask "$value"; then
				local target='src' type='net'
				if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $laddr" && \
					ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $laddr" "$value"; then
					param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target"
					param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target"
				else
					param4="$param4 ${negation:+$negation }-s $value"
					param6="$param6 ${negation:+$negation }-s $value"
				fi
			elif is_mac_address "$value"; then
				local target='src' type='mac'
				if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $laddr" && \
					ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $laddr" "$value"; then
					param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target"
					param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target"
				else
					param4="$param4 -m mac ${negation:+$negation }--mac-source $value"
					param6="$param6 -m mac ${negation:+$negation }--mac-source $value"
				fi
			else
				local target='src' type='ip'
				if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $laddr" && \
					ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $laddr" "$value"; then
					param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target"
					param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target"
				else
					local resolvedIP4 resolvedIP6
					resolvedIP4="$(resolveip_to_ipt4 "$value")"
					resolvedIP6="$(resolveip_to_ipt6 "$value")"
					if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then
						state add 'errorSummary' 'errorFailedToResolve' "$value"
					fi
					param4="$param4 ${negation:+$negation }-s $resolvedIP4"
					param6="$param6 ${negation:+$negation }-s $resolvedIP6"
				fi
			fi
		fi

		if [ -n "$lport" ]; then
			if [ "${lport:0:1}" = "!" ]; then
				negation='!'; value="${lport:1}"
			else
				unset negation; value="$lport";
			fi
			param4="$param4 -m multiport ${negation:+$negation }--sport ${value//-/:}"
			param6="$param6 -m multiport ${negation:+$negation }--sport ${value//-/:}"
		fi

		if [ -n "$raddr" ]; then 
			if [ "${raddr:0:1}" = "!" ]; then
				negation='!'; value="${raddr:1}"
			else
				unset negation; value="$raddr";
			fi
			if is_ipv4_netmask "$value"; then
				local target='dst' type='net'
				if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $raddr" && \
					ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $raddr" "$value"; then
					param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target"
					param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target"
				else
					param4="$param4 ${negation:+$negation }-d ${value}"
					param6="$param6 ${negation:+$negation }-d ${value}"
				fi
			elif is_domain "$value"; then
				local target='dst' type='ip'
				if resolver 'create_resolver_set' "$iface" "$target" "$type" "$uid" "${name}: $raddr" && \
					resolver 'add_resolver_element' "$iface" "$target" "$type" "$uid" "${name}: $raddr" "$value"; then
					param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target"
					param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target"
				elif ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $raddr" && \
					ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $raddr" "$value"; then
					param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target"
					param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target"
				else
					local resolvedIP4 resolvedIP6
					resolvedIP4="$(resolveip_to_ipt4 "$value")"
					resolvedIP6="$(resolveip_to_ipt6 "$value")"
					if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then
						state add 'errorSummary' 'errorFailedToResolve' "$value"
					fi
					param4="$param4 ${negation:+$negation }-d $resolvedIP4"
					param6="$param6 ${negation:+$negation }-d $resolvedIP6"
				fi
			else
				local target='dst' type='ip'
				if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $raddr" && \
					ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $raddr" "$value"; then
					param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target"
					param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target"
				else
					param4="$param4 ${negation:+$negation }-d ${value}"
					param6="$param6 ${negation:+$negation }-d ${value}"
				fi
			fi
		fi

		if [ -n "$rport" ]; then
			if [ "${rport:0:1}" = "!" ]; then
				negation='!'; value="${rport:1}"
			else
				unset negation; value="$rport";
			fi
			param4="$param4 -m multiport ${negation:+$negation }--dport ${value//-/:}"
			param6="$param6 -m multiport ${negation:+$negation }--dport ${value//-/:}"
		fi

		if [ -n "$name" ]; then
			param4="$param4 -m comment --comment $(str_extras_to_underscore "$name")"
			param6="$param6 -m comment --comment $(str_extras_to_underscore "$name")"
		fi

		local ipv4_error='0' ipv6_error='0'
		if [ "$param4" = "$param6" ]; then
			ipt4 "$param4" || ipv4_error='1'
		else
			ipt4 "$param4" || ipv4_error='1'
			ipt6 "$param6" || ipv6_error='1'
		fi

	if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then
		processPolicyError='true'
		state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name"
		state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param4"
		state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param6"
		logger -t "$packageName" "ERROR: iptables $param4"
		logger -t "$packageName" "ERROR: iptables $param6"
	elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then
		processPolicyError='true'
		state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name"
		state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param4"
		logger -t "$packageName" "ERROR: iptables $param4"
	fi

	done
}
policy_routing_nft() {
	local mark i nftInsertOption='add'
	local param4 param6 proto_i negation value dest4 dest6	
	local name="$1" iface="$2" src_addr="$3" src_port="$4" dest_addr="$5" dest_port="$6" proto chain uid="$9"
	proto="$(str_to_lower "$7")"
	chain="$(str_to_lower "$8")"
	chain="${chain:-prerouting}"
	mark=$(eval echo "\$mark_${iface//-/_}")

	if [ -z "$ipv6_enabled" ] && { is_ipv6 "$src_addr" || is_ipv6 "$dest_addr"; }; then
		processPolicyError='true'
		state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name"
		return 1
	fi

	if is_tor "$iface"; then
		unset dest_port
		unset proto
	elif is_xray "$iface"; then
		unset dest_port
		[ -z "$src_port" ] && src_port='0-65535'
		dest4="tproxy $nftIPv4Flag to: $(get_xray_traffic_port "$iface") accept"
		dest6="tproxy $nftIPv6Flag to: $(get_xray_traffic_port "$iface") accept"
	elif [ -n "$mark" ]; then
		dest4="goto ${nftPrefix}_mark_${mark}"
		dest6="goto ${nftPrefix}_mark_${mark}"
	elif [ "$iface" = "ignore" ]; then
		dest4="return"
		dest6="return"
	else
		processPolicyError='true'
		state add 'errorSummary' 'errorPolicyProcessUnknownFwmark' "$iface"
		return 1
	fi

	if is_family_mismatch "$src_addr" "$dest_addr"; then 
		processPolicyError='true'
		state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_addr'"
		return 1
	fi

	if [ -z "$proto" ]; then
		if [ -n "${src_port}${dest_port}" ]; then 
			proto='tcp udp'
		else
			proto='all'
		fi
	fi

	for proto_i in $proto; do
		unset param4
		unset param6
		if [ "$proto_i" = 'all' ]; then
			unset proto_i
		elif ! is_supported_protocol "$proto_i"; then
			processPolicyError='true'
			state add 'errorSummary' 'errorPolicyProcessUnknownProtocol' "${name}: '$proto_i'"
			return 1
		fi

		if [ -n "$src_addr" ]; then
			if [ "${src_addr:0:1}" = "!" ]; then
				negation='!='; value="${src_addr:1}"
			else
				unset negation; value="$src_addr";
			fi
			if is_phys_dev "$value"; then
				param4="${param4:+$param4 }iifname ${negation:+$negation }${value:1}"
				param6="${param6:+$param6 }iifname ${negation:+$negation }${value:1}"
			elif is_mac_address "$value"; then
				local target='src' type='mac'
				if nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \
					nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then
					param4="${param4:+$param4 }ether saddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}"
					param6="${param6:+$param6 }ether saddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}"
				else
					param4="${param4:+$param4 }ether saddr ${negation:+$negation }${value}"
					param6="${param6:+$param6 }ether saddr ${negation:+$negation }${value}"
				fi
			else
				local target='src' type='ip'
				if nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \
					nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then
					param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}"
					param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}"
				else
					param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }${value}"
					param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }${value}"
				fi
			fi
		fi

		if [ -n "$dest_addr" ]; then 
			if [ "${dest_addr:0:1}" = "!" ]; then
				negation='!='; value="${dest_addr:1}"
			else
				unset negation; value="$dest_addr";
			fi
			if is_phys_dev "$value"; then
				param4="${param4:+$param4 }oifname ${negation:+$negation }${value:1}"
				param6="${param6:+$param6 }oifname ${negation:+$negation }${value:1}"
			elif is_domain "$value"; then
				local target='dst' type='ip'
				if resolver 'create_resolver_set' "$iface" "$target" "$type" "$uid" "$name" && \
					resolver 'add_resolver_element' "$iface" "$target" "$type" "$uid" "$name" "$value"; then
					param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}"
					param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}"
				elif nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \
					nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then
					param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}"
					param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}"
				else
					local resolvedIP4 resolvedIP6
					resolvedIP4="$(resolveip_to_nftset4 "$value")"
					resolvedIP6="$(resolveip_to_nftset6 "$value")"
					if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then
						state add 'errorSummary' 'errorFailedToResolve' "$value"
					fi
					param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }{ $resolvedIP4 }"
					param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }{ $resolvedIP6 }"
				fi
			else
				local target='dst' type='ip'
				if nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \
					nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then
					param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}"
					param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}"
				else
					param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }${value}"
					param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }${value}"
				fi
			fi
		fi

		if [ -n "$src_port" ]; then
			if [ "${src_port:0:1}" = "!" ]; then
				negation='!='; value="${src_port:1}"
			else
				unset negation; value="$src_port";
			fi
			param4="${param4:+$param4 }${proto_i:+$proto_i }sport ${negation:+$negation }{$(ports_to_nftset "$value")}"
			param6="${param6:+$param6 }${proto_i:+$proto_i }sport ${negation:+$negation }{$(ports_to_nftset "$value")}"
		fi

		if [ -n "$dest_port" ]; then
			if [ "${dest_port:0:1}" = "!" ]; then
				negation='!='; value="${dest_port:1}"
			else
				unset negation; value="$dest_port";
			fi
			param4="${param4:+$param4 }${proto_i:+$proto_i }dport ${negation:+$negation }{$(ports_to_nftset "$value")}"
			param6="${param6:+$param6 }${proto_i:+$proto_i }dport ${negation:+$negation }{$(ports_to_nftset "$value")}"
		fi

		if is_tor "$iface"; then
			local dest_udp_53 dest_tcp_80 dest_udp_80 dest_tcp_443 dest_udp_443
			local ipv4_error='0' ipv6_error='0'
			local dest_i dest4 dest6
			param4="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} dstnat meta nfproto ipv4 $param4"
			param6="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} dstnat meta nfproto ipv6 $param6"
			dest_udp_53="udp dport 53 counter redirect to :${torDnsPort} comment 'Tor-DNS-UDP'"
			dest_tcp_80="tcp dport 80 counter redirect to :${torTrafficPort} comment 'Tor-HTTP-TCP'"
			dest_udp_80="udp dport 80 counter redirect to :${torTrafficPort} comment 'Tor-HTTP-UDP'"
			dest_tcp_443="tcp dport 443 counter redirect to :${torTrafficPort} comment 'Tor-HTTPS-TCP'"
			dest_udp_443="udp dport 443 counter redirect to :${torTrafficPort} comment 'Tor-HTTPS-UDP'"
			for dest_i in dest_udp_53 dest_tcp_80 dest_udp_80 dest_tcp_443 dest_udp_443; do
				eval "dest4=\$$dest_i"
				eval "dest6=\$$dest_i"
				nft4 "$param4" "$dest4" || ipv4_error='1'
				nft6 "$param6" "$dest6" || ipv6_error='1'
				if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then
					processPolicyError='true'
					state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name"
					state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4 $dest4"
					state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param6 $dest6"
					logger -t "$packageName" "ERROR: nft $param4 $dest4"
					logger -t "$packageName" "ERROR: nft $param6 $dest6"
				elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then
					processPolicyError='true'
					state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name"
					state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4 $dest4"
					logger -t "$packageName" "ERROR: nft $param4 $dest4"
				fi
			done
		else
			param4="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} $param4 $dest4 comment \"$name\""
			param6="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} $param6 $dest6 comment \"$name\""
			local ipv4_error='0' ipv6_error='0'
			if [ "$policy_routing_nft_prev_param4" != "$param4" ]; then
				nft4 "$param4" || ipv4_error='1'
				policy_routing_nft_prev_param4="$param4"
			fi
			if [ "$policy_routing_nft_prev_param6" != "$param6" ] && \
				[ "$param4" != "$param6" ]; then
				nft6 "$param6" || ipv6_error='1'
				policy_routing_nft_prev_param6="$param6"
			fi

			if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then
				processPolicyError='true'
				state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name"
				state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4"
				state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param6"
				logger -t "$packageName" "ERROR: nft $param4"
				logger -t "$packageName" "ERROR: nft $param6"
			elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then
				processPolicyError='true'
				state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name"
				state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4"
				logger -t "$packageName" "ERROR: nft $param4"
			fi
		fi
	done
}

policy_process() {
	local i j uid="$9"
	if [ -z "$uid" ]; then # first non-recursive call
		[ "$enabled" -gt 0 ] || return 0
		unset processPolicyError
		uid="$1"
		if is_nft_mode; then
			chain="$(str_to_lower "$chain")"
		else
			chain="$(str_to_upper "$chain")"
		fi
		proto="$(str_to_lower "$proto")"
		[ "$proto" = 'auto' ] && unset proto
		[ "$proto" = 'all' ] && unset proto
		output 2 "Routing '$name' via $interface "
		if [ -z "${src_addr}${src_port}${dest_addr}${dest_port}" ]; then
			state add 'errorSummary' 'errorPolicyNoSrcDest' "$name"
			output_fail; return 1;
		fi
		if [ -z "$interface" ]; then
			state add 'errorSummary' 'errorPolicyNoInterface' "$name"
			output_fail; return 1;
		fi
		if ! is_supported_interface "$interface"; then
			state add 'errorSummary' 'errorPolicyUnknownInterface' "$name"
			output_fail; return 1;
		fi
		src_port="${src_port//  / }"; src_port="${src_port// /,}"; src_port="${src_port//,\!/ !}"; 
		dest_port="${dest_port//  / }"; dest_port="${dest_port// /,}"; dest_port="${dest_port//,\!/ !}";
		policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"
		if [ -n "$processPolicyError" ]; then
			output_fail
		else
			output_ok
		fi
	else # recursive call, get options from passed variables
		local name="$1" interface="$2" src_addr="$3" src_port="$4" dest_addr="$5" dest_port="$6" proto="$7" chain="$8"
		if str_contains "$src_addr" '[ ;\{\}]'; then
			for i in $(str_extras_to_space "$src_addr"); do [ -n "$i" ] && policy_process "$name" "$interface" "$i" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"; done
		elif str_contains "$src_port" '[ ;\{\}]'; then
			for i in $(str_extras_to_space "$src_port"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$i" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"; done
		elif str_contains "$dest_addr" '[ ;\{\}]'; then
			for i in $(str_extras_to_space "$dest_addr"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$i" "$dest_port" "$proto" "$chain" "$uid"; done
		elif str_contains "$dest_port" '[ ;\{\}]'; then
			for i in $(str_extras_to_space "$dest_port"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$i" "$proto" "$chain" "$uid"; done
		elif str_contains "$proto" '[ ;\{\}]'; then
			for i in $(str_extras_to_space "$proto"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$i" "$chain" "$uid"; done
		else
			if [ -n "$secure_reload" ] && { is_url_dl "$src_addr" || is_url_dl "$dest_addr"; }; then
				state add 'errorSummary' 'errorNoDownloadWithSecureReload' "$name"
			elif is_url "$src_addr"; then
				src_addr="$(process_url "$src_addr")"
				[ -n "$src_addr" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"
			elif is_url "$dest_addr"; then
				dest_addr="$(process_url "$dest_addr")"
				[ -n "$dest_addr" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"
			else
				policy_routing "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"
			fi
		fi
	fi
}

interface_routing() {
	local action="$1" tid="$2" mark="$3" iface="$4" gw4="$5" dev="$6" gw6="$7" dev6="$8" priority="$9"
	local dscp s=0 i ipv4_error=1 ipv6_error=1
	if [ -z "$tid" ] || [ -z "$mark" ] || [ -z "$iface" ]; then
		state add 'errorSummary' 'errorInterfaceRoutingEmptyValues'
		return 1
	fi
	case "$action" in
		create)
			if is_netifd_table "$iface"; then
				ipv4_error=0
				$ip_bin rule del table "$tid" >/dev/null 2>&1
				$ip_bin -4 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1
				if is_nft_mode; then
					nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1 
					nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} counter mark set mark and ${fw_maskXor} xor ${mark}" || ipv4_error=1
					nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" || ipv4_error=1
				else
					ipt -t mangle -N "${iptPrefix}_MARK_${mark}" || ipv4_error=1
					ipt -t mangle -A "${iptPrefix}_MARK_${mark}" -j MARK --set-xmark "${mark}/${fw_mask}" || ipv4_error=1
					ipt -t mangle -A "${iptPrefix}_MARK_${mark}" -j RETURN || ipv4_error=1
				fi
				if [ -n "$ipv6_enabled" ]; then
					ipv6_error=0
					$ip_bin -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1
				fi
			else
				if ! grep -q "$tid ${ipTablePrefix}_${iface}" '/etc/iproute2/rt_tables'; then
					sed -i "/${ipTablePrefix}_${iface}/d" '/etc/iproute2/rt_tables'
					sync
					echo "$tid ${ipTablePrefix}_${iface}" >> '/etc/iproute2/rt_tables'
					sync
				fi
				$ip_bin rule del table "$tid" >/dev/null 2>&1
				$ip_bin route flush table "$tid" >/dev/null 2>&1
				if [ -n "$gw4" ] || [ "$strict_enforcement" -ne 0 ]; then
					ipv4_error=0
					if [ -z "$gw4" ]; then
						$ip_bin -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1
					else
						$ip_bin -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1
					fi
# shellcheck disable=SC2086
					while read -r i; do
						i="$(echo "$i" | sed 's/ linkdown$//')"
						i="$(echo "$i" | sed 's/ onlink$//')"
						idev="$(echo "$i" | grep -Eso 'dev [^ ]*' | awk '{print $2}')"
						if ! is_supported_iface_dev "$idev"; then
							$ip_bin -4 route add $i table "$tid" >/dev/null 2>&1 || ipv4_error=1
						fi
					done << EOF
					$($ip_bin -4 route list table main)
EOF
					$ip_bin -4 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1
					if is_nft_mode; then
						nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1 
						nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} counter mark set mark and ${fw_maskXor} xor ${mark}" || ipv4_error=1
						nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" || ipv4_error=1
					else
						ipt -t mangle -N "${iptPrefix}_MARK_${mark}" || ipv4_error=1
						ipt -t mangle -A "${iptPrefix}_MARK_${mark}" -j MARK --set-xmark "${mark}/${fw_mask}" || ipv4_error=1
						ipt -t mangle -A "${iptPrefix}_MARK_${mark}" -j RETURN || ipv4_error=1
					fi
				fi
				if [ -n "$ipv6_enabled" ]; then
					ipv6_error=0
					if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ "$strict_enforcement" -ne 0 ]; then
						if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then
							$ip_bin -6 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv6_error=1
						elif $ip_bin -6 route list table main | grep -q " dev $dev6 "; then
							$ip_bin -6 route add default via "$gw6" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1
							while read -r i; do
								i="$(echo "$i" | sed 's/ linkdown$//')"
								i="$(echo "$i" | sed 's/ onlink$//')"
								# shellcheck disable=SC2086
								$ip_bin -6 route add $i table "$tid" >/dev/null 2>&1 || ipv6_error=1
							done << EOF
							$($ip_bin -6 route list table main | grep " dev $dev6 ")
EOF
						else
							$ip_bin -6 route add "$($ip_bin -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1
							$ip_bin -6 route add default dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1
						fi
					fi
					$ip_bin -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" >/dev/null 2>&1 || ipv6_error=1
				fi
			fi
			if [ "$ipv4_error" -eq 0 ] || [ "$ipv6_error" -eq 0 ]; then
				dscp="$(uci_get "$packageName" 'config' "${iface}_dscp")"
				if is_nft_mode; then
					if [ "${dscp:-0}" -ge 1 ] && [ "${dscp:-0}" -le 63 ]; then
						nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv4Flag} dscp ${dscp} goto ${nftPrefix}_mark_${mark}" || s=1
						if [ -n "$ipv6_enabled" ]; then
							nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv6Flag} dscp ${dscp} goto ${nftPrefix}_mark_${mark}" || s=1
						fi
					fi
					if [ "$iface" = "$icmp_interface" ]; then
						nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv4Flag} protocol icmp goto ${nftPrefix}_mark_${mark}" || s=1
						if [ -n "$ipv6_enabled" ]; then
							nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv6Flag} protocol icmp goto ${nftPrefix}_mark_${mark}" || s=1
						fi
					fi
				else
					if [ "${dscp:-0}" -ge 1 ] && [ "${dscp:-0}" -le 63 ]; then
						ipt -t mangle -I "${iptPrefix}_PREROUTING" -m dscp --dscp "${dscp}" -g "${iptPrefix}_MARK_${mark}" || s=1
					fi
					if [ "$iface" = "$icmp_interface" ]; then
						ipt -t mangle -I "${iptPrefix}_OUTPUT" -p icmp -g "${iptPrefix}_MARK_${mark}" || s=1
					fi
				fi
			else
				s=1
			fi
			return "$s"
		;;
		create_user_set)
			if is_nft_mode; then
				nftset 'create_user_set' "$iface" 'dst' 'ip' 'user' '' "$mark" || s=1
				nftset 'create_user_set' "$iface" 'src' 'ip' 'user' '' "$mark" || s=1
				nftset 'create_user_set' "$iface" 'src' 'mac' 'user' '' "$mark" || s=1
			else
				ips 'create_user_set' "$iface" 'dst' 'ip' 'user' '' "$mark" || s=1
				ips 'create_user_set' "$iface" 'dst' 'net' 'user' '' "$mark" || s=1
				ips 'create_user_set' "$iface" 'src' 'ip' 'user' '' "$mark" || s=1
				ips 'create_user_set' "$iface" 'src' 'net' 'user' '' "$mark" || s=1
				ips 'create_user_set' "$iface" 'src' 'mac' 'user' '' "$mark" || s=1
			fi
			return "$s"
		;;
		delete|destroy)
			$ip_bin rule del table "$tid" >/dev/null 2>&1
			if ! is_netifd_table "$iface"; then
				$ip_bin route flush table "$tid" >/dev/null 2>&1
				sed -i "/${ipTablePrefix}_${iface}\$/d" '/etc/iproute2/rt_tables'
				sync
			fi
			return "$s"
		;;
		reload_interface)
			is_netifd_table "$iface" && return 0;
			ipv4_error=0
			$ip_bin rule del table "$tid" >/dev/null 2>&1
			if ! is_netifd_table "$iface"; then
				$ip_bin route flush table "$tid" >/dev/null 2>&1
			fi
			if [ -n "$gw4" ] || [ "$strict_enforcement" -ne 0 ]; then
				if [ -z "$gw4" ]; then
					$ip_bin -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1
				else
					$ip_bin -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1
				fi
				$ip_bin rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1
			fi
			if [ -n "$ipv6_enabled" ]; then
				ipv6_error=0
				if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ "$strict_enforcement" -ne 0 ]; then
					if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then
						$ip_bin -6 route add unreachable default table "$tid" || ipv6_error=1
					elif $ip_bin -6 route list table main | grep -q " dev $dev6 "; then
						while read -r i; do
							# shellcheck disable=SC2086
							$ip_bin -6 route add $i table "$tid" >/dev/null 2>&1 || ipv6_error=1
						done << EOF
						$($ip_bin -6 route list table main | grep " dev $dev6 ")
EOF
					else
						$ip_bin -6 route add "$($ip_bin -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1
						$ip_bin -6 route add default dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1
					fi
				fi
				$ip_bin -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1
			fi
			if [ "$ipv4_error" -eq 0 ] || [ "$ipv6_error" -eq 0 ]; then
				s=0
			else
				s=1
			fi
			return "$s"
		;;
	esac
}

json_add_gateway() {
	local action="$1" tid="$2" mark="$3" iface="$4" gw4="$5" dev4="$6" gw6="$7" dev6="$8" priority="$9" default="${10}"
	json_add_object ''
	json_add_string 'name' "$iface"
	json_add_string 'device_ipv4' "$dev4"
	json_add_string 'gateway_ipv4' "$gw4"
	json_add_string 'device_ipv6' "$dev6"
	json_add_string 'gateway_ipv6' "$gw6"
	if [ -n "$default" ]; then
		json_add_boolean 'default' '1'
	else
		json_add_boolean 'default' '0'
	fi
	json_add_string 'action' "$action"
	json_add_string 'table_id' "$tid"
	json_add_string 'mark' "$mark"
	json_add_string 'priority' "$priority"
	json_close_object
}

interface_process() {
	local gw4 gw6 dev dev6 s=0 dscp iface="$1" action="$2" reloadedIface="$3"
	local displayText dispDev dispGw4 dispGw6 dispStatus

	if [ "$iface" = 'all' ] && [ "$action" = 'prepare' ]; then
		config_load 'network'
		ifaceMark="$(printf '0x%06x' "$wan_mark")"
		ifacePriority="$wan_ip_rules_priority"
		unset ifaceTableID
		return 0
	fi

	if [ "$iface" = 'tor' ]; then 
		case "$action" in
			create|reload)
				torDnsPort="$(get_tor_dns_port)"
				torTrafficPort="$(get_tor_traffic_port)"
				displayText="${iface}/53->${torDnsPort}/80,443->${torTrafficPort}"
				gatewaySummary="${gatewaySummary}${displayText}\\n"
				;;
			destroy)
				;;
		esac
		return 0
	fi

	is_supported_interface "$iface" || return 0
	is_wan6 "$iface" && return 0
	[ $((ifaceMark)) -gt $((fw_mask)) ] && return 1

	if is_ovpn "$iface" && ! is_ovpn_valid "$iface"; then
		: || state add 'warningSummary' 'warningInvalidOVPNConfig' "$iface"
	fi

	network_get_device dev "$iface"
	[ -z "$dev" ] && network_get_physdev dev "$iface"
	if is_wan "$iface" && [ -n "$wanIface6" ] && str_contains "$wanIface6" "$iface"; then
		network_get_device dev6 "$wanIface6"
		[ -z "$dev6" ] && network_get_physdev dev6 "$wanIface6"
	fi

	[ -z "$dev6" ] && dev6="$dev"
	[ -z "$ifaceMark" ] && ifaceMark="$(printf '0x%06x' "$wan_mark")"
	[ -z "$ifacePriority" ] && ifacePriority="$wan_ip_rules_priority"

	if [ "$action" = 'pre-init' ]; then
		pbr_get_gateway6 gw6 "$iface" "$dev6"
		[ -n "$gw6" ] && ipv6_enabled=1
		[ -z "$ifaceTableID" ] && ifaceTableID="$(get_rt_tables_non_pbr_next_id)"
		eval "pre_init_mark_${iface//-/_}"='$ifaceMark'
		eval "pre_init_priority_${iface//-/_}"='$ifacePriority'
		eval "pre_init_tid_${iface//-/_}"='$ifaceTableID'
		ifaceMark="$(printf '0x%06x' $((ifaceMark + wan_mark)))"
		ifacePriority="$((ifacePriority + 1))"
		ifaceTableID="$((ifaceTableID + 1))"
		return 0
	fi

# TODO: if interfaces are started out of order the rt_tables ID may be incorrect
# use the expected_tid_${iface} ???
	ifaceTableID="$(get_rt_tables_id "$iface")"
	[ -z "$ifaceTableID" ] && ifaceTableID="$(get_rt_tables_next_id)"
	eval "mark_${iface//-/_}"='$ifaceMark'
	eval "tid_${iface//-/_}"='$ifaceTableID'
	pbr_get_gateway gw4 "$iface" "$dev"
	pbr_get_gateway6 gw6 "$iface" "$dev6"
	dispGw4="${gw4:-0.0.0.0}"
	dispGw6="${gw6:-::/0}"
	[ "$iface" != "$dev" ] && dispDev="$dev"
	if is_default_dev "$dev"; then
		[ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__"
	fi
	displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}"

	case "$action" in
		create)
			output 2 "Setting up routing for '$displayText' "
			if interface_routing 'create' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority"; then
				json_add_gateway 'create' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority" "$dispStatus"
				gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\\n"
				output_ok
			else
				state add 'errorSummary' 'errorFailedSetup' "$displayText"
				output_fail
			fi
		;;
		create_user_set)
			interface_routing 'create_user_set' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority"
		;;
		destroy)
			displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}"
			output 2 "Removing routing for '$displayText' "
			interface_routing 'destroy' "${ifaceTableID}" "${ifaceMark}" "${iface}"
			output_ok
		;;
		reload)
			gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\\n"
		;;
		reload_interface)
			if [ "$iface" = "$reloadedIface" ]; then
				output 2 "Reloading routing for '$displayText' "
				if interface_routing 'reload_interface' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority"; then
					json_add_gateway 'reload_interface' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority" "$dispStatus"
					gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\\n"
					output_ok
				else
					state add 'errorSummary' 'errorFailedReload' "$displayText"
					output_fail
				fi
			else
				gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\\n"
			fi
		;;
	esac
#	ifaceTableID="$((ifaceTableID + 1))"
	ifaceMark="$(printf '0x%06x' $((ifaceMark + wan_mark)))"
	ifacePriority="$((ifacePriority + 1))"
	return $s
}

user_file_process() {
	local shellBin="${SHELL:-/bin/ash}"
	[ "$enabled" -gt 0 ] || return 0
	if [ ! -s "$path" ]; then
		state add 'errorSummary' 'errorUserFileNotFound' "$path"
		output_fail
		return 1
	fi
	if ! $shellBin -n "$path"; then
		state add 'errorSummary' 'errorUserFileSyntax' "$path"
		output_fail
		return 1
	fi
	output 2 "Running $path "
# shellcheck disable=SC1090
	if ! . "$path"; then
		state add 'errorSummary' 'errorUserFileRunning' "$path"
		if grep -q -w 'curl' "$path" && ! is_present 'curl'; then
			state add 'errorSummary' 'errorUserFileNoCurl' "$path"
		fi
		output_fail
		return 1
	else
		output_ok
		return 0
	fi
}

boot() {
	local procd_boot_delay
	config_load "$packageName"
	config_get procd_boot_delay 'config' 'procd_boot_delay' '0'
	nft_file 'delete'
	ubus -t 30 wait_for network.interface 2>/dev/null
	{ is_integer "$procd_boot_delay" && sleep "$procd_boot_delay"; \
		rc_procd start_service 'on_boot' && service_started 'on_boot'; } &
}

on_firewall_reload() { 
	if [ ! -e "$packageLockFile" ]; then
		logger -t "$packageName" "Reload on firewall action aborted: service is stopped."
		return 0
	else
		if nft_file 'exists'; then
			logger -t "$packageName" "Reusing the fw4 nft file."
		else
			rc_procd start_service 'on_firewall_reload' "$1"
		fi
	fi
}

on_interface_reload() { 
	if [ ! -e "$packageLockFile" ]; then
		logger -t "$packageName" "Reload on interface change aborted: service is stopped."
		return 0
	else
		rc_procd start_service 'on_interface_reload' "$1"
	fi
}

start_service() {
	local resolverStoredHash resolverNewHash i param="$1" reloadedIface

	load_environment "${param:-on_start}" "$(load_validate_config)" || return 1
	is_wan_up "$param" || return 1

	interface_process 'all' 'prepare'
	config_foreach interface_process 'interface' 'pre-init'

	case "$param" in
		on_boot)
			serviceStartTrigger='on_start'
		;;
		on_firewall_reload)
			serviceStartTrigger='on_start'
		;;
		on_interface_reload)
			reloadedIface="$2"
			local tid pre_init_tid
			tid="$(get_rt_tables_id "$reloadedIface")"
			pre_init_tid="$(eval echo "\$pre_init_tid_${reloadedIface//-/_}")"
			if [ "$tid" = "$pre_init_tid" ]; then
#				logger -t "$packageName" "Updated interface $reloadedIface TID: ${tid}; Pre-Init TID: ${pre_init_tid}. Reloading..."
				serviceStartTrigger='on_interface_reload'
			else
#				logger -t "$packageName" "Updated interface $reloadedIface TID: ${tid}; Pre-Init TID: ${pre_init_tid}. Restarting..."
				serviceStartTrigger='on_start'
				unset reloadedIface
			fi
#			if is_ovpn "$reloadedIface"; then
#				logger -t "$packageName" "Updated interface is an OpenVPN tunnel, restarting."
#				serviceStartTrigger='on_start'
#				unset reloadedIface
#			else
#				serviceStartTrigger='on_interface_reload'
#			fi
		;;
		on_reload)
			serviceStartTrigger='on_reload'
		;;
		on_restart)
			serviceStartTrigger='on_start'
		;;
	esac

	if [ -n "$reloadedIface" ] && ! is_supported_interface "$reloadedIface"; then
		return 0
	fi

	if [ -n "$(ubus_get_status error)" ] || [ -n "$(ubus_get_status warning)" ]; then
		serviceStartTrigger='on_start'
		unset reloadedIface
	elif ! is_service_running; then
		serviceStartTrigger='on_start'
		unset reloadedIface
	elif [ -z "$(ubus_get_status gateways)" ]; then
		serviceStartTrigger='on_start'
		unset reloadedIface
#	elif [ "$serviceStartTrigger" = 'on_interface_reload' ] && \
#			 [ -z "$(ubus_get_interface "$reloadedIface" 'gateway_ipv4')" ] && \
#			 [ -z "$(ubus_get_interface "$reloadedIface" 'gateway_ipv6')" ]; then
#		serviceStartTrigger='on_start'
#		unset reloadedIface
	else
		serviceStartTrigger="${serviceStartTrigger:-on_start}"
	fi

	procd_open_instance 'main'
	procd_set_param command /bin/true
	procd_set_param stdout 1
	procd_set_param stderr 1
	procd_open_data

	case $serviceStartTrigger in
		on_interface_reload)
			output 1 "Reloading Interface: $reloadedIface "
			json_add_array 'gateways'
			interface_process 'all' 'prepare'
			config_foreach interface_process 'interface' 'reload_interface' "$reloadedIface"
			json_close_array
			output 1 '\n'
		;;
		on_reload)
			traffic_killswitch 'insert'
			resolver 'store_hash'
			resolver 'cleanup_all'
			resolver 'configure'
			resolver 'init'
			cleanup_main_chains
			cleanup_sets
			nft_file 'create'
			if ! is_nft_mode; then
				for i in $chainsList; do
					i="$(str_to_upper "$i")"
					ipt -t mangle -N "${iptPrefix}_${i}"
					ipt -t mangle "$rule_create_option" "$i" -m mark --mark "0x0/${fw_mask}" -j "${iptPrefix}_${i}"
				done
			fi
			json_add_array 'gateways'
			interface_process 'all' 'prepare'
			config_foreach interface_process 'interface' 'reload'
			interface_process 'tor' 'destroy'
			is_tor_running && interface_process 'tor' 'reload'
			json_close_array
			if is_config_enabled 'policy'; then
				output 1 'Processing policies '
				config_load "$packageName"
				config_foreach load_validate_policy 'policy' policy_process
				output 1 '\n'
			fi
			if is_config_enabled 'include'; then
				interface_process 'all' 'prepare'
				config_foreach interface_process 'interface' 'create_user_set'
				output 1 'Processing user file(s) '
				config_load "$packageName"
				config_foreach load_validate_include 'include' user_file_process
				output 1 '\n'
			fi
			nft_file 'install'
			resolver 'init_end'
			! nft_file 'exists' && resolver 'compare_hash' && resolver 'restart'
			traffic_killswitch 'remove'
		;;
		on_start|*)
			traffic_killswitch 'insert'
			resolver 'store_hash'
			resolver 'cleanup_all'
			resolver 'configure'
			resolver 'init'
			cleanup_main_chains
			cleanup_sets
			cleanup_marking_chains
			cleanup_rt_tables
			nft_file 'create'
			if ! is_nft_mode; then
				for i in $chainsList; do
					i="$(str_to_upper "$i")"
					ipt -t mangle -N "${iptPrefix}_${i}"
					ipt -t mangle "$rule_create_option" "$i" -m mark --mark "0x0/${fw_mask}" -j "${iptPrefix}_${i}"
				done
			fi
			output 1 'Processing interfaces '
			json_add_array 'gateways'
			interface_process 'all' 'prepare'
			config_foreach interface_process 'interface' 'create'
			interface_process 'tor' 'destroy'
			is_tor_running && interface_process 'tor' 'create'
			json_close_array
			ip route flush cache
			output 1 '\n'
			if is_config_enabled 'policy'; then
				output 1 'Processing policies '
				config_load "$packageName"
				config_foreach load_validate_policy 'policy' policy_process
				output 1 '\n'
			fi
			if is_config_enabled 'include'; then
				interface_process 'all' 'prepare'
				config_foreach interface_process 'interface' 'create_user_set'
				output 1 'Processing user file(s) '
				config_load "$packageName"
				config_foreach load_validate_include 'include' user_file_process
				output 1 '\n'
			fi
			nft_file 'install'
			resolver 'init_end'
			! nft_file 'exists' && resolver 'compare_hash' && resolver 'restart'
			traffic_killswitch 'remove'
		;;
	esac

	if [ -z "$gatewaySummary" ]; then
		state add 'errorSummary' 'errorNoGateways'
	fi
	json_add_object 'status'
	[ -n "$gatewaySummary" ] && json_add_string 'gateways' "$gatewaySummary"
	[ -n "$errorSummary" ] && json_add_string 'errors' "$errorSummary"
	[ -n "$warningSummary" ] && json_add_string 'warnings' "$warningSummary"
	if [ "$strict_enforcement" -ne 0 ] && str_contains "$gatewaySummary" '0.0.0.0'; then
		json_add_string 'mode' 'strict'
	fi
	json_close_object
	procd_close_data
	procd_close_instance
}

service_started() {
	if nft_file 'exists'; then
		procd_set_config_changed firewall
		if nft_file 'exists'; then
			resolver 'compare_hash' && resolver 'restart'
			[ -n "$gatewaySummary" ] && output "$serviceName (fw4 nft file mode) started with gateways:\\n${gatewaySummary}"
		else
			output "$serviceName FAILED TO START in fw4 nft file mode!!!"
			output "Check the output of nft -c -f $nftTempFile"
		fi
	elif is_nft_mode; then
		[ -n "$gatewaySummary" ] && output "$serviceName (nft mode) started with gateways:\\n${gatewaySummary}"
	else
		[ -n "$gatewaySummary" ] && output "$serviceName (iptables mode) started with gateways:\\n${gatewaySummary}"
	fi
	state print 'errorSummary'
	state print 'warningSummary'
	touch "$packageLockFile"
	if [ -n "$errorSummary" ]; then
		return 2
	elif [ -n "$warningSummary" ]; then
		return 1
	else
		return 0
	fi
}

service_triggers() {
	local n
	load_environment 'on_triggers'
# shellcheck disable=SC2034
	PROCD_RELOAD_DELAY=$(( procd_reload_delay * 1000 ))
	procd_open_validate
		load_validate_config
		load_validate_policy
		load_validate_include
	procd_close_validate
	procd_open_trigger
		procd_add_config_trigger "config.change" 'openvpn' "/etc/init.d/${packageName}" reload 'on_openvpn_change'
		procd_add_config_trigger "config.change" "${packageName}" /etc/init.d/${packageName} reload
		for n in $ifacesSupported; do 
			procd_add_interface_trigger "interface.*" "$n" /etc/init.d/${packageName} on_interface_reload "$n"
		done
	procd_close_trigger
#	procd_add_raw_trigger "interface.*.up" 4000 "/etc/init.d/${packageName}" restart 'on_interface_up'
	if [ "$serviceStartTrigger" = 'on_start' ]; then
		output 3 "$serviceName monitoring interfaces: ${ifacesSupported}\\n"
	fi
}

stop_service() {
	local i nft_file_mode
	load_environment 'on_stop'
	! is_service_running && [ "$(get_rt_tables_next_id)" = "$(get_rt_tables_non_pbr_next_id)" ] && return 0
	[ "$1" = 'quiet' ] && quiet_mode 'on'
	traffic_killswitch 'insert'
	if nft_file 'exists'; then
		nft_file_mode=1
	fi
	nft_file 'delete'
	cleanup_main_chains
	cleanup_sets
	cleanup_marking_chains
	output 1 'Resetting interfaces '
	config_load 'network'
	config_foreach interface_process 'interface' 'destroy'
	interface_process 'tor' 'destroy'
	cleanup_rt_tables
	output 1 "\\n"
	ip route flush cache
	unset ifaceMark
	unset ifaceTableID
	resolver 'store_hash'
	resolver 'cleanup_all'
	resolver 'compare_hash' && resolver 'restart'
	traffic_killswitch 'remove'
	if [ "$enabled" -ne 0 ]; then
		if [ -n "$nft_file_mode" ]; then
			output "$serviceName (fw4 nft file mode) stopped "; output_okn;
		elif is_nft_mode; then
			output "$serviceName (nft mode) stopped "; output_okn;
		else
			output "$serviceName (iptables mode) stopped "; output_okn;
		fi
	fi
	rm -f "$packageLockFile"
}

version() { echo "$PKG_VERSION"; }

status_service() {
	local _SEPARATOR_='============================================================'
	load_environment 'on_status'
	if is_nft_mode; then
		status_service_nft "$@"
	else
		status_service_iptables "$@"
	fi
}

status_service_nft() {
	local i dev dev6 wan_tid

	json_load "$(ubus call system board)"; json_select release; json_get_var dist distribution; json_get_var vers version
	if [ -n "$wanIface4" ]; then
		network_get_gateway wanGW4 "$wanIface4"
		network_get_device dev "$wanIface4"
	fi
	if [ -n "$wanIface6" ]; then
		network_get_device dev6 "$wanIface6"
		wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}')
		[ "$wanGW6" = "default" ] && wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}')
	fi
	while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done
	[ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support"
	status="$serviceName running on $dist $vers."
	[ -n "$wanIface4" ] && status="$status WAN (IPv4): ${wanIface4}/${dev}/${wanGW4:-0.0.0.0}."
	[ -n "$wanIface6" ] && status="$status WAN (IPv6): ${wanIface6}/${dev6}/${wanGW6:-::/0}."

	echo "$_SEPARATOR_"
	echo "$packageName - environment"
	echo "$status"
	echo "$_SEPARATOR_"
	dnsmasq --version 2>/dev/null | sed '/^$/,$d'
	if nft_file 'exists'; then
		echo "$_SEPARATOR_"
		echo "$packageName fw4 nft file: $nftPermFile"
		sed '1d;2d;' "$nftPermFile"
	fi
	echo "$_SEPARATOR_"
	echo "$packageName chains - policies"
	for i in forward input output prerouting postrouting; do
		"$nft" -a list table inet "$nftTable" | sed -n "/chain ${nftPrefix}_${i} {/,/\t}/p"
	done
	echo "$_SEPARATOR_"
	echo "$packageName chains - marking"
	for i in $(get_mark_nft_chains); do
		"$nft" -a list table inet "$nftTable" | sed -n "/chain ${i} {/,/\t}/p"
	done
	echo "$_SEPARATOR_"
	echo "$packageName nft sets"
	for i in $(get_nft_sets); do
		"$nft" -a list table inet "$nftTable" | sed -n "/set ${i} {/,/\t}/p"
	done
	if [ -s "$dnsmasqFile" ]; then
		echo "$_SEPARATOR_"
		echo "dnsmasq sets"
		cat "$dnsmasqFile"
	fi
#	echo "$_SEPARATOR_"
#	ip rule list | grep "${packageName}_"
	echo "$_SEPARATOR_"
	tableCount="$(grep -c "${packageName}_" /etc/iproute2/rt_tables)" || tableCount=0
	wan_tid=$(($(get_rt_tables_next_id)-tableCount))
	i=0; while [ $i -lt "$tableCount" ]; do 
		echo "IPv4 table $((wan_tid + i)) route: $($ip_bin -4 route show table $((wan_tid + i)) | grep default)"
		echo "IPv4 table $((wan_tid + i)) rule(s):"
		$ip_bin -4 rule list table "$((wan_tid + i))"
		if [ -n "$ipv6_enabled" ]; then
			echo "IPv6 table $((wan_tid + i)) route: $($ip_bin -6 route show table $((wan_tid + i)) | grep default)"
			echo "IPv6 table $((wan_tid + i)) rule(s):"
			$ip_bin -6 route show table $((wan_tid + i))
		fi
		i=$((i + 1))
	done
}

status_service_iptables() {
	local dist vers out id s param status set_d set_p tableCount i=0 dev dev6 j wan_tid

	json_load "$(ubus call system board)"; json_select release; json_get_var dist distribution; json_get_var vers version
	if [ -n "$wanIface4" ]; then
		network_get_gateway wanGW4 "$wanIface4"
		network_get_device dev "$wanIface4"
	fi
	if [ -n "$wanIface6" ]; then
		network_get_device dev6 "$wanIface6"
		wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}')
		[ "$wanGW6" = "default" ] && wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}')
	fi
	while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done
	[ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support"
	status="$serviceName running on $dist $vers."
	[ -n "$wanIface4" ] && status="$status WAN (IPv4): ${wanIface4}/${dev}/${wanGW4:-0.0.0.0}."
	[ -n "$wanIface6" ] && status="$status WAN (IPv6): ${wanIface6}/${dev6}/${wanGW6:-::/0}."
	{
		echo "$status"
		echo "$_SEPARATOR_"
		dnsmasq --version 2>/dev/null | sed '/^$/,$d'
		if [ -n "$1" ]; then
			echo "$_SEPARATOR_"
			echo "Resolving domains"
			for i in $1; do
				echo "$i: $(resolveip "$i" | tr '\n' ' ')"
			done
		fi

		echo "$_SEPARATOR_"
		echo "Routes/IP Rules"
		tableCount="$(grep -c "${packageName}_" /etc/iproute2/rt_tables)" || tableCount=0
		if [ -n "$set_d" ]; then route; else route | grep '^default'; fi
		if [ -n "$set_d" ]; then ip rule list; fi
		wan_tid=$(($(get_rt_tables_next_id)-tableCount))
		i=0; while [ $i -lt "$tableCount" ]; do 
			echo "IPv4 table $((wan_tid + i)) route: $($ip_bin -4 route show table $((wan_tid + i)) | grep default)"
			echo "IPv4 table $((wan_tid + i)) rule(s):"
			$ip_bin -4 rule list table "$((wan_tid + i))"
			i=$((i + 1))
		done

		if [ -n "$ipv6_enabled" ]; then
			i=0; while [ $i -lt "$tableCount" ]; do
				$ip_bin -6 route show table $((wan_tid + i)) | while read -r param; do
					echo "IPv6 Table $((wan_tid + i)): $param"
				done
				i=$((i + 1))
			done
		fi

		for j in Mangle NAT; do
			if [ -z "$set_d" ]; then
				for i in $chainsList; do
					i="$(str_to_upper "$i")"
					if iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_${i}" >/dev/null 2>&1; then
						echo "$_SEPARATOR_"
						echo "$j IP Table: $i"
						iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_${i}"
						if [ -n "$ipv6_enabled" ]; then
							echo "$_SEPARATOR_"
							echo "$j IPv6 Table: $i"
							iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_${i}"
						fi
					fi
				done
			else
				echo "$_SEPARATOR_"
				echo "$j IP Table"
				iptables -L -t "$(str_to_lower $j)"
				if [ -n "$ipv6_enabled" ]; then
					echo "$_SEPARATOR_"
					echo "$j IPv6 Table"
					iptables -L -t "$(str_to_lower $j)"
				fi
			fi
			i=0; ifaceMark="$wan_mark";
			while [ $i -lt "$tableCount" ]; do
				if iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_MARK_${ifaceMark}" >/dev/null 2>&1; then
					echo "$_SEPARATOR_"
					echo "$j IP Table MARK Chain: ${iptPrefix}_MARK_${ifaceMark}"
					iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_MARK_${ifaceMark}"
					ifaceMark="$(printf '0x%06x' $((ifaceMark + wan_mark)))";
				fi
				i=$((i + 1))
			done
		done

		echo "$_SEPARATOR_"
		echo "Current ipsets"
		ipset save
		if [ -s "$dnsmasqFile" ]; then
			echo "$_SEPARATOR_"
			echo "DNSMASQ sets"
			cat "$dnsmasqFile"
		fi
		if [ -s "$aghIpsetFile" ]; then
			echo "$_SEPARATOR_"
			echo "AdGuardHome sets"
			cat "$aghIpsetFile"
		fi
		echo "$_SEPARATOR_"
	} | tee -a /var/${packageName}-support
	if [ -n "$set_p" ]; then
		printf "%b" "Pasting to paste.ee... "
		if is_present 'curl' && is_installed 'libopenssl' && is_installed 'ca-bundle'; then
			json_init; json_add_string 'description' "${packageName}-support"
			json_add_array 'sections'; json_add_object '0'
			json_add_string 'name' "$(uci_get 'system' '@system[0]' 'hostname')"
			json_add_string 'contents' "$(cat /var/${packageName}-support)"
			json_close_object; json_close_array; payload=$(json_dump)
			out=$(curl -s -k "https://api.paste.ee/v1/pastes" -X "POST" -H "Content-Type: application/json" -H "X-Auth-Token:uVOJt6pNqjcEWu7qiuUuuxWQafpHhwMvNEBviRV2B" -d "$payload")
			json_load "$out"; json_get_var id id; json_get_var s success
			[ "$s" = "1" ] && printf "%b" "https://paste.ee/p/$id $__OK__\\n" || printf "%b" "$__FAIL__\\n"
			[ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support"
		else
			printf "%b" "${__FAIL__}\\n"
			printf "%b" "${_ERROR_}: The curl, libopenssl or ca-bundle packages were not found!\\nRun 'opkg update; opkg install curl libopenssl ca-bundle' to install them.\\n"
		fi
	else
		printf "%b" "Your support details have been logged to '/var/${packageName}-support'. $__OK__\\n"
	fi
}

# shellcheck disable=SC2120
load_validate_config() {
	uci_load_validate "$packageName" "$packageName" "$1" "${2}${3:+ $3}" \
		'enabled:bool:0' \
		'strict_enforcement:bool:1' \
		'secure_reload:bool:0' \
		'ipv6_enabled:bool:0' \
		'resolver_set:or("", "none", "dnsmasq.ipset", "dnsmasq.nftset")' \
		'resolver_instance:list(or(integer, string)):*' \
		'verbosity:range(0,2):2' \
		'wan_mark:regex("[A-Fa-f0-9]{8}"):010000' \
		'fw_mask:regex("[A-Fa-f0-9]{8}"):ff0000' \
		'icmp_interface:or("", tor, uci("network", "@interface"))' \
		'ignored_interface:list(or(tor, uci("network", "@interface")))' \
		'supported_interface:list(or(ignore, tor, regex("xray_.*"), uci("network", "@interface")))' \
		'procd_boot_delay:integer:0' \
		'procd_boot_timeout:integer:30' \
		'procd_lan_interface:string' \
		'procd_reload_delay:integer:0' \
		'procd_wan_ignore_status:bool:0' \
		'procd_wan_interface:string' \
		'procd_wan6_interface:string' \
		'wan_ip_rules_priority:uinteger:30000' \
		'rule_create_option:or("", add, insert):add' \
		'webui_supported_protocol:list(string)' \
		'nft_file_support:bool:1'\
		'nft_set_auto_merge:bool:1'\
		'nft_set_counter:bool:1'\
		'nft_set_flags_interval:bool:1'\
		'nft_set_flags_timeout:bool:0'\
		'nft_set_gc_interval:or("", string)'\
		'nft_set_policy:or("", memory, performance):performance'\
		'nft_set_timeout:or("", string)'
}

# shellcheck disable=SC2120
load_validate_policy() {
	local name
	local enabled
	local interface
	local proto
	local chain
	local src_addr
	local src_port
	local dest_addr
	local dest_port
	uci_load_validate "$packageName" 'policy' "$1" "${2}${3:+ $3}" \
		'name:string:Untitled' \
		'enabled:bool:1' \
		'interface:or("ignore", "tor", regex("xray_.*"), uci("network", "@interface")):wan' \
		'proto:or(string)' \
		'chain:or("", "forward", "input", "output", "prerouting", "postrouting", "FORWARD", "INPUT", "OUTPUT", "PREROUTING", "POSTROUTING"):prerouting' \
		'src_addr:list(neg(or(host,network,macaddr,string)))' \
		'src_port:list(neg(or(portrange,string)))' \
		'dest_addr:list(neg(or(host,network,string)))' \
		'dest_port:list(neg(or(portrange,string)))'
}

# shellcheck disable=SC2120
load_validate_include() {
	local path=
	local enabled=
	uci_load_validate "$packageName" 'include' "$1" "${2}${3:+ $3}" \
		'path:file' \
		'enabled:bool:0'
}
