pbr: update to 1.1.8-32
authorStan Grishin <[email protected]>
Fri, 1 Aug 2025 01:04:35 +0000 (01:04 +0000)
committerStan Grishin <[email protected]>
Fri, 1 Aug 2025 18:31:34 +0000 (13:31 -0500)
Makefile:
  - add SPDX-Identifier-License
  - update Copyright

README:
  - add basic README with the link to full documentation

Config:
  - add debug_dnsmasq
  - add procd_boot_trigger_delay

Init Script:
  - move extra_command calls high up for visibility
  - bump packageCompat to sync with luci app
  - implement support for debug_dnsmasq to dump dnsmasq debug into $packageDebugFile
  - create $runningStatusFile json-file allowing more verbose errors/warnings messages
  - replaced `state add` calls with json add calls to store errors/warnings messages
  - remove no longer needed errorSummary, warningSummary
  - ensure environment is only loaded once per run via $load_environment_flag
  - bugfix: update is_{host,hostname,domain,ipv4,mac_address} functions to properly sort policy entries
  - bugfix: change references to melmac.net to melmac.ca
  - add some new error/warning messages
  - add delay before service is started on boot via procd_boot_trigger_delay
  - bugfix: add logic to identify unknown policy entries instead of silently failing on them
  - store error/warning messages as json objects in ubus data for luci app
  - update load_validate_config with debug_dnsmasq and procd_boot_trigger_delay entries

Signed-off-by: Stan Grishin <[email protected]>
net/pbr/Makefile
net/pbr/README.md
net/pbr/files/etc/config/pbr
net/pbr/files/etc/init.d/pbr

index 3ac7c54eac06b3d3164366e7fe71cd2c87c0c003..856d83ded4c4614477bbe7f3002dd6c898172f35 100644 (file)
@@ -1,11 +1,11 @@
-# Copyright 2017-2024 MOSSDeF, Stan Grishin ([email protected]).
-# This is free software, licensed under AGPL-3.0-or-later.
+# SPDX-License-Identifier: AGPL-3.0-or-later
+# Copyright 2017-2025 MOSSDeF, Stan Grishin ([email protected]).
 
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=pbr
 PKG_VERSION:=1.1.8
-PKG_RELEASE:=16
+PKG_RELEASE:=32
 PKG_LICENSE:=AGPL-3.0-or-later
 PKG_MAINTAINER:=Stan Grishin <[email protected]>
 
index 98feae65df58ff1ecf3fc22e842860126e095d0c..3a65c6b2257cbd7b6d0451f182ac399e126be40c 100644 (file)
@@ -1,4 +1,18 @@
-# README
+# Policy-Based Routing (pbr)
 
-Documentation for this project is available at [https://docs.openwrt.melmac.net/pbr/](https://docs.openwrt.melmac.net/pbr/).
+[![OpenWrt](https://img.shields.io/badge/OpenWrt-Compatible-blueviolet)](https://openwrt.org)
+[![Web UI](https://img.shields.io/badge/Web_UI-Available-blue)](https://docs.openwrt.melmac.ca/pbr/)
+[![License](https://img.shields.io/badge/License-GPL--3.0-lightgrey)](https://github.com/stangri/pbr/blob/master/LICENSE)
 
+Flexible policy-based routing (PBR) framework for OpenWrt.  
+Allows routing specific traffic (by IP, MAC, port, protocol, or domain) through a specific WAN, VPN, or tunnel.
+
+## Features
+
+- Route by IP, MAC, port, or domain name
+- Works with WAN, VPNs (WireGuard, OpenVPN), or tunnels
+- Lightweight shell-based implementation
+- Optional Web UI integration via LuCI
+
+**Full documentation:**  
+[https://docs.openwrt.melmac.ca/pbr/](https://docs.openwrt.melmac.ca/pbr/)
index f6d50ebd4fbc7e6205c91fbb4e0047b377669f00..8bf686f063555b0edc8c92b2b0849c8e4b67c174 100644 (file)
@@ -1,4 +1,5 @@
 config pbr 'config'
+       option debug_dnsmasq '0'
        option enabled '0'
        option verbosity '2'
        option strict_enforcement '1'
@@ -8,6 +9,7 @@ config pbr 'config'
        list ignored_interface 'vpnserver'
        option boot_timeout '30'
        option rule_create_option 'add'
+       option procd_boot_trigger_delay '5000'
        option procd_reload_delay '1'
        option webui_show_ignore_target '0'
        option nft_rule_counter '0'
index 4efae5cb3fd1610e56ee82dfaa7df91d65cf78e9..dcf64d1483f2639912acf46cb4392dda4149237b 100755 (executable)
@@ -12,21 +12,44 @@ USE_PROCD=1
 
 [ -n "${IPKG_INSTROOT}" ] && return 0
 
+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 PBR 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 PBR 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
+
 readonly packageName='pbr'
 readonly PKG_VERSION='dev-test'
-readonly packageCompat='11'
+readonly packageCompat='14'
 readonly serviceName="$packageName $PKG_VERSION"
 readonly packageConfigFile="/etc/config/${packageName}"
+readonly packageDebugFile="/var/run/${packageName}.debug"
 readonly packageLockFile="/var/run/${packageName}.lock"
 readonly dnsmasqFileDefault="/var/run/${packageName}.dnsmasq"
+readonly runningStatusFile="/dev/shm/${packageName}.status.json"
+readonly runningStatusFileLock="/var/lock/${packageName}.lock"
 readonly _OK_='\033[0;32m\xe2\x9c\x93\033[0m'
 readonly __OK__='\033[0;32m[\xe2\x9c\x93]\033[0m'
 readonly _OKB_='\033[1;34m\xe2\x9c\x93\033[0m'
 readonly __OKB__='\033[1;34m[\xe2\x9c\x93]\033[0m'
 readonly _FAIL_='\033[0;31m\xe2\x9c\x97\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 _ERROR_='\033[0;31mERROR:\033[0m'
+readonly _WARNING_='\033[0;33mWARNING:\033[0m'
 readonly ip_full='/usr/libexec/ip-full'
 # shellcheck disable=SC2155
 readonly ipTablePrefix="$packageName"
@@ -47,6 +70,7 @@ readonly xrayIfacePrefix='xray_'
 readonly rtTablesFile='/etc/iproute2/rt_tables'
 
 # package config options
+debug_dnsmasq=
 enabled=
 fw_mask=
 icmp_interface=
@@ -54,6 +78,7 @@ ignored_interface=
 ipv6_enabled=
 nft_user_set_policy=
 nft_user_set_counter=
+procd_boot_trigger_delay=
 procd_reload_delay=
 procd_lan_device=
 procd_wan_interface=
@@ -76,10 +101,9 @@ nft_set_policy=
 nft_set_timeout=
 
 # run-time
+load_environment_flag=
 aghConfigFile='/etc/AdGuardHome/AdGuardHome.yaml'
 gatewaySummary=
-errorSummary=
-warningSummary=
 wanIface4=
 wanIface6=
 dnsmasqFileList=
@@ -143,6 +167,8 @@ output_okb() { output 1 "$_OKB_"; output 2 "$__OKB__\n"; }
 output_okbn() { output 1 "$_OKB_\n"; output 2 "$__OKB__\n"; }
 output_fail() { output 1 "$_FAIL_"; output 2 "$__FAIL__\n"; }
 output_failn() { output 1 "$_FAIL_\n"; output 2 "$__FAIL__\n"; }
+output_error() { output "${_ERROR_} $*!\n"; }
+output_warning() { output "${_WARNING_} $*!\n"; }
 quiet_mode() {
        case "$1" in
                on) verbosity=0;;
@@ -215,7 +241,9 @@ uci_get_device() {
 uci_get_protocol() { uci_get 'network' "$1" 'proto'; }
 is_default_dev() { [ "$1" = "$(ip -4 r | grep -m1 'dev' | grep -Eso 'dev [^ ]*' | awk '{print $2}')" ]; }
 is_disabled_interface() { [ "$(uci_get 'network' "$1" 'disabled')" = '1' ]; }
-is_domain(){ echo "$1" | grep -qE '^([a-zA-Z0-9][a-zA-Z0-9-]{0,61}\.)*[a-zA-Z]{2,}$'; }
+is_host() { echo "$1" | grep -qE '^[a-zA-Z0-9][a-zA-Z0-9_-]{0,61}[a-zA-Z0-9]$|^[a-zA-Z0-9]$'; }
+is_hostname() { echo "$1" | grep -qE '^([a-zA-Z0-9]([a-zA-Z0-9_-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'; }
+is_domain() { ! is_ipv4 "$1" && ! is_mac_address_bad_notation "$1" && { is_host "$1" || is_hostname "$1"; }; }
 is_dslite() { local p; network_get_protocol p "$1"; [ "${p:0:6}" = "dslite" ]; }
 is_family_mismatch() { ( is_ipv4 "${1//!}" && is_ipv6 "${2//!}" ) || ( is_ipv6 "${1//!}" && is_ipv4 "${2//!}" ); }
 is_greater() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; }
@@ -223,7 +251,7 @@ is_greater_or_equal() { test "$(printf '%s\n' "$@" | sort -V | head -n '1')" = "
 is_ignored_interface() { str_contains_word "$ignored_interface" "$1"; }
 is_ignore_target() { [ "$(str_to_lower "$1")" = 'ignore' ]; }
 is_integer() { case "$1" in ''|*[!0-9]*) return 1;; esac; }
-is_ipv4() { expr "${1%/*}" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; }
+is_ipv4() { echo "$1" | grep -qE '^((25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(/(3[0-2]|[12]?[0-9]))?$'; }
 is_ipv6() { ! is_mac_address "$1" && str_contains "$1" ':'; }
 is_ipv6_global_scope() { [ "${1:0:4}" = '2001' ]; }
 is_ipv6_local_scope() { is_ipv6_local_link "$1" || is_ipv6_local_unique "$1"; }
@@ -232,7 +260,8 @@ is_ipv6_local_unique() { [ "${1:0:2}" = 'fc' ] || [ "${1:0:2}" = 'fd' ]; }
 is_list() { str_contains "$1" ',' || str_contains "$1" ' '; }
 is_lan() { local d; network_get_device d "$1"; str_contains "$procd_lan_device" "$d"; }
 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_mac_address() { echo "$1" | grep -qE '^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$'; }
+is_mac_address_bad_notation() { echo "$1" | grep -qE '^([0-9A-Fa-f]{2}-){5}([0-9A-Fa-f]{2})$'; }
 is_negated() { [ "${1:0:1}" = '!' ]; }
 is_netifd_table() { grep -q "ip.table.*$1" /etc/config/network; }
 is_netifd_table_interface() { local iface="$1"; [ "$(uci_get 'network' "$iface" 'ip4table')" = "${packageName}_${iface%6}" ]; }
@@ -311,47 +340,33 @@ check_dnsmasq_nftset() {
        check_nft || return 1
        check_dnsmasq || return 1
        o="$(dnsmasq -v 2>/dev/null)"
+       [ -n "$debug_dnsmasq" ] && { 
+               echo " $(date) dnsmasq output dump:";
+# shellcheck disable=SC3003
+               echo  "${o%$'\n'$'\n'This*}"; 
+               echo '-------------------------'; 
+       } >> "$packageDebugFile"
        ! 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; }
 try() {
        if ! "$@" >/dev/null 2>&1; then
-               state add 'errorSummary' 'errorTryFailed' "$*"
+               json add error 'errorTryFailed' "$*"
                return 1
        fi
 }
 
-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!";;
                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!";;
+               errorServiceDisabled) r="The ${packageName} service is currently disabled";;
                errorNoWanGateway) r="The ${serviceName} service failed to discover WAN gateway!";;
                errorNoWanInterface) r="The %s interface not found, you need to set the 'pbr.config.procd_wan_interface' option!";;
-               errorNoWanInterfaceHint) r="Refer to https://docs.openwrt.melmac.net/pbr/#procd_wan_interface.";;
+               errorNoWanInterfaceHint) r="Refer to https://docs.openwrt.melmac.ca/pbr/#procd_wan_interface.";;
                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!";;
@@ -374,6 +389,7 @@ get_text() {
                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'!";;
+               errorPolicyProcessUnknownEntry) r="Unknown entry in policy '%s'!";;
                errorInterfaceRoutingEmptyValues) r="Received empty tid/mark or interface name when setting up routing!";;
                errorFailedToResolve) r="Failed to resolve '%s'!";;
                errorTryFailed) r="Command failed: %s";;
@@ -387,6 +403,7 @@ get_text() {
                errorDefaultFw4ChainMissing) r="Default fw4 chain '%s' is missing!";;
                errorRequiredBinaryMissing) r="Required binary '%s' is missing!";;
                errorInterfaceRoutingUnknownDevType) r="Unknown IPv6 Link type for device '%s'!";;
+               errorUplinkDown) r="Uplink/WAN interface is still down, increase value of 'procd_boot_trigger_delay' option";;
                warningInvalidOVPNConfig) r="Invalid OpenVPN config for '%s' interface.";;
                warningResolverNotSupported) r="Resolver set (${resolver_set}) is not supported on this system.";;
                warningPolicyProcessCMD) r="'%s'";;
@@ -397,6 +414,7 @@ get_text() {
                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.";;
                warningDhcpLanForce) r="Please set 'dhcp.%s.force=1' to speed up service start-up.";;
+               *) r="Unknown error '%s'!";;
        esac
        echo "$r"
 }
@@ -428,13 +446,13 @@ process_url() {
                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"
+               json add error 'errorFileSchemaRequiresCurl' "$url"
        elif is_url_https "$url" && [ -z "$dl_https_supported" ]; then
-               state add 'errorSummary' 'errorDownloadUrlNoHttps' "$url"
+               json add error '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"
+               json add error 'errorDownloadUrl' "$url"
        fi
        rm -f "$dl_temp_file"
 }
@@ -443,6 +461,7 @@ load_package_config() {
        local param="$1"
        local user_file_check_result i
        config_load "$packageName"
+       config_get_bool debug_dnsmasq             'config' 'debug_dnsmasq' '0'
        config_get_bool enabled                   'config' 'enabled' '0'
        config_get      fw_mask                   'config' 'fw_mask' 'ff0000'
        config_get      icmp_interface            'config' 'icmp_interface'
@@ -461,12 +480,14 @@ load_package_config() {
        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_reload_delay        'config' 'procd_reload_delay' '0'
+       config_get      procd_boot_trigger_delay  'config' 'procd_boot_trigger_delay' '5000'
        config_get      procd_lan_device          'config' 'procd_lan_device'  'br-lan'
+       config_get      procd_reload_delay        'config' 'procd_reload_delay' '0'
        config_get      procd_wan_interface       'config' 'procd_wan_interface'  'wan'
        config_get      procd_wan6_interface      'config' 'procd_wan6_interface' 'wan6'
        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}"
        if [ -x "$agh" ] && [ ! -s "$aghConfigFile" ]; then
@@ -477,6 +498,10 @@ load_package_config() {
        fw_maskXor="$(printf '%#x' "$((fw_mask ^ 0xffffffff))")"
        fw_maskXor="${fw_maskXor:-0xff00ffff}"
 
+       is_integer "$procd_boot_trigger_delay" || procd_boot_trigger_delay='5000'
+       [ "$procd_boot_trigger_delay" -lt '1000' ] && procd_boot_trigger_delay='1000'
+
+       [ "$debug_dnsmasq" != '1' ]          && unset debug_dnsmasq
        [ "$nft_rule_counter" != '1' ]       && unset nft_rule_counter
        [ "$nft_set_auto_merge" != '1' ]     && unset nft_set_auto_merge
        [ "$nft_set_counter" != '1' ]        && unset nft_set_counter
@@ -513,7 +538,7 @@ load_environment() {
                _check_dhcp_force() {
                        is_lan "$1" || return 0
                        if [ "$(uci_get dhcp "$1" force 0)" = '0' ]; then
-                               state add 'warningSummary' 'warningDhcpLanForce' "$1"
+                               json add warning 'warningDhcpLanForce' "$1"
                        fi
                }
                local i _ret=0
@@ -522,22 +547,22 @@ load_environment() {
                        uci_commit firewall
                fi
                if [ "$(readlink /sbin/ip)" != "$ip_full" ]; then
-                       state add 'errorSummary' 'errorRequiredBinaryMissing' 'ip-full'
+                       json add error 'errorRequiredBinaryMissing' 'ip-full'
                        _ret='1'
                fi
                if ! nft_call list table inet fw4; then
-                       state add 'errorSummary' 'errorDefaultFw4TableMissing' 'fw4'
+                       json add error 'errorDefaultFw4TableMissing' 'fw4'
                        _ret='1'
                fi
                if is_config_enabled 'dns_policy' || is_tor_running; then
                        if ! nft_call list chain inet fw4 dstnat; then
-                               state add 'errorSummary' 'errorDefaultFw4ChainMissing' 'dstnat'
+                               json add error 'errorDefaultFw4ChainMissing' 'dstnat'
                                _ret='1'
                        fi
                fi
                for i in $chainsList; do
                        if ! nft_call list chain inet fw4 "mangle_${i}"; then
-                               state add 'errorSummary' 'errorDefaultFw4ChainMissing' "mangle_${i}"
+                               json add error 'errorDefaultFw4ChainMissing' "mangle_${i}"
                                _ret='1'
                        fi
                done
@@ -546,20 +571,25 @@ load_environment() {
                return "$_ret"
        }
        local param="$1" validation_result="$2"
+       [ -z "$load_environment_flag" ] || return 0
+       json init
        case "$param" in
                on_boot|on_start)
                        output 1 "Loading environment ($param) "
                        load_package_config "$param"
                        if [ "$enabled" -eq '0' ]; then
                                output 1 "$_FAIL_\n"
-                               state add 'errorSummary' 'errorServiceDisabled'
+                               json add error 'errorServiceDisabled'
+                               output_error "$(get_text 'errorServiceDisabled')"
+                               output "Run the following commands before starting service again:\n"
+                               output "uci set ${packageName}.config.enabled='1'; uci commit $packageName;\n"
                                return 1
                        fi
                        if [ -n "$validation_result" ] && [ "$validation_result" != '0' ]; then
                                output 1 "$_FAIL_\n"
-                               output "${_ERROR_}: The $packageName config validation failed!\n"
+                               json add error 'errorConfigValidation'
+                               output_error "$(get_text 'errorConfigValidation')"
                                output "Please check if the '$packageConfigFile' contains correct values for config options.\n"
-                               state add 'errorSummary' 'errorConfigValidation'
                                return 1
                        fi
                        _system_health_check || { output 1 "$_FAIL_\n"; return 1; }
@@ -578,6 +608,7 @@ load_environment() {
                        load_network "$param"
                ;;
        esac
+       load_environment_flag=1
 }
 
 # shellcheck disable=SC2317
@@ -620,8 +651,8 @@ load_network() {
 is_wan_up() {
        local sleepCount='1' param="$1"
        if [ -z "$(uci_get network "$procd_wan_interface")" ]; then
-               state add 'errorSummary' 'errorNoWanInterface' "$procd_wan_interface"
-               state add 'errorSummary' 'errorNoWanInterfaceHint'
+               json add error 'errorNoWanInterface' "$procd_wan_interface"
+               json add error 'errorNoWanInterfaceHint'
                return 1
        fi
        network_flush_cache
@@ -629,7 +660,7 @@ is_wan_up() {
        if [ -n "$wanGW" ]; then
                return 0
        else
-               state add 'errorSummary' 'errorNoWanGateway'
+               json add error 'errorNoWanGateway'
                return 1
        fi
 }
@@ -666,7 +697,7 @@ nft_file() {
                                cp -f "$nftTempFile" "$nftPermFile"; then
                                output_okn
                        else
-                               state add 'errorSummary' 'errorNftFileInstall' "$nftTempFile"
+                               json add error 'errorNftFileInstall' "$nftTempFile"
                                output_failn
                        fi
                ;;
@@ -685,7 +716,7 @@ nftset() {
        [ -x "$nft" ] || return 1
 
        if [ "${#nftset4}" -gt '255' ]; then 
-               state add 'errorSummary' 'errorNftsetNameTooLong' "$nftset4"
+               json add error 'errorNftsetNameTooLong' "$nftset4"
                return 1
        fi
 
@@ -706,7 +737,7 @@ nftset() {
                                [ -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"
+                                       json add error '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
@@ -831,73 +862,49 @@ cleanup_sets() {
        done
 }
 
-state() {
-       local action="$1" param="$2" value="${3//#/_}"
-       local array_name
-       shift 3
-# shellcheck disable=SC2124
-       local extras="$@"
-       local line error_id error_extra label
+json() {
+       local status message stats i
+       local action="$1" param="$2" value="$3"; shift 3; local info="$*";
+       [ "$param" = 'error' ] && param='errors'
+       [ "$param" = 'warning' ] && param='warnings'
+       { json_load_file "$runningStatusFile" || json_init; } >/dev/null 2>&1
        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)
-                                       array_name='errors';;
-                               warningSummary)
-                                       array_name='warnings';;
-                       esac
-                       json_add_array "$array_name"
-                       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"
+               'get')
+                       if json_select "$param" >/dev/null 2>&1; then
+                               if [ -n "$value" ]; then
+                                       {
+                                       if json_select "$value"; then
+                                               json_get_var 'i' "${info:-code}"
+                                               json_select ..
                                        fi
-                                       json_add_object "$array_name"
-                                       json_add_string 'id' "$error_id"
-                                       json_add_string 'extra' "$error_extra"
-                                       json_close_object
-                               done <<EOF
-$(eval echo "\$$param" | tr \# \\n)
-EOF
+                                       } >/dev/null 2>&1
+                               else
+                                       json_get_keys i
+                               fi
+                               printf "%b" "$i"
+                               json_select ..
                        fi
-                       json_close_array
-                       json_close_object
-                       json_dump
+                       return
                ;;
-               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
+               'add')
+                       { json_select "$param" || json_add_array "$param"; } >/dev/null 2>&1
+                       json_add_object ""
+                       json_add_string 'code' "$value"
+                       json_add_string 'info' "$info"
+                       json_close_object
+                       json_select ..
                ;;
-               set)
-                       eval "$param"='${value}${extras:+ $extras}'
+               'init')
+                       json_init
+                       json_add_array 'errors'
+                       json_close_array
+                       json_add_array 'warnings'
+                       json_close_array
                ;;
        esac
+       mkdir -p "${runningStatusFile%/*}"
+       json_dump > "$runningStatusFile"
+       sync
 }
 
 resolver() {
@@ -952,7 +959,6 @@ resolver() {
                                        for d in $value; do
                                                nftset 'add_dnsmasq_element' "$iface" "$target" "$type" "$uid" "$name" "$d"
                                        done
-#                                      nftset 'add_dnsmasq_element' "$iface" "$target" "$type" "$uid" "$name" "$(str_to_dnsmsaq_nftset "$value")"
                                ;;
                                create_resolver_set)
                                        [ -n "$resolver_set_supported" ] || return 1
@@ -960,14 +966,14 @@ resolver() {
                                ;;
                                check_support)
                                        if [ ! -x "$nft" ]; then
-                                               state add 'errorSummary' 'errorNoNft'
+                                               json add error 'errorNoNft'
                                                return 1
                                        fi
-                                       if ! dnsmasq -v 2>/dev/null | grep -q 'no-nftset' && dnsmasq -v 2>/dev/null | grep -q 'nftset'; then
+                                       if check_dnsmasq_nftset; then
                                                resolver_set_supported='true'
                                                return 0
                                        else
-                                               state add 'warningSummary' 'warningResolverNotSupported'
+                                               json add warning 'warningResolverNotSupported'
                                                return 1
                                        fi
                                ;;
@@ -1074,20 +1080,20 @@ dns_policy_routing() {
 
        if [ -z "${dest_dns_ipv4}${dest_dns_ipv6}" ]; then
                processPolicyError='true'
-               state add 'errorSummary' 'errorPolicyProcessNoInterfaceDns' "'$dest_dns'"
+               json add error 'errorPolicyProcessNoInterfaceDns' "'$dest_dns'"
                return 1
        fi
 
        if [ -z "$ipv6_enabled" ] && is_ipv6 "$(str_first_word "$src_addr")"; then
                processPolicyError='true'
-               state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name"
+               json add error 'errorPolicyProcessNoIpv6' "$name"
                return 1
        fi
 
        if { is_ipv4 "$(str_first_word "$src_addr")" && [ -z "$dest_dns_ipv4" ]; } || \
                { is_ipv6 "$(str_first_word "$src_addr")" && [ -z "$dest_dns_ipv6" ]; }; then 
                processPolicyError='true'
-               state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_dns'"
+               json add error 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_dns'"
                return 1
        fi
 
@@ -1119,7 +1125,7 @@ dns_policy_routing() {
                                        resolved_ipv4="$(resolveip_to_nftset4 "$d")"
                                        resolved_ipv6="$(resolveip_to_nftset6 "$d")"
                                        if [ -z "${resolved_ipv4}${resolved_ipv6}" ]; then
-                                               state add 'errorSummary' 'errorFailedToResolve' "$d"
+                                               json add error 'errorFailedToResolve' "$d"
                                        else
                                        [ -n "$resolved_ipv4" ] && inline_set_ipv4="${inline_set_ipv4:+$inline_set_ipv4, }$resolved_ipv4"
                                        [ -n "$resolved_ipv6" ] && inline_set_ipv6="${inline_set_ipv6:+$inline_set_ipv6, }$resolved_ipv6"
@@ -1154,15 +1160,15 @@ dns_policy_routing() {
 
                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"
+                       json add error 'errorPolicyProcessInsertionFailed' "$name"
+                       json add error 'errorPolicyProcessCMD' "nft $param4"
+                       json add error '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"
+                       json add error 'errorPolicyProcessInsertionFailedIpv4' "$name"
+                       json add error 'errorPolicyProcessCMD' "nft $param4"
                        logger -t "$packageName" "ERROR: nft $param4"
                fi
        done
@@ -1183,7 +1189,7 @@ policy_routing() {
        if [ -z "$ipv6_enabled" ] && \
                { is_ipv6 "$(str_first_word "$src_addr")" || is_ipv6 "$(str_first_word "$dest_addr")"; }; then
                processPolicyError='true'
-               state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name"
+               json add error 'errorPolicyProcessNoIpv6' "$name"
                return 1
        fi
 
@@ -1203,14 +1209,14 @@ policy_routing() {
                dest6="return"
        else
                processPolicyError='true'
-               state add 'errorSummary' 'errorPolicyProcessUnknownFwmark' "$iface"
+               json add error 'errorPolicyProcessUnknownFwmark' "$iface"
                return 1
        fi
 
        # TODO: implement actual family mismatch check on lists
 #      if is_family_mismatch "$src_addr" "$dest_addr"; then 
 #              processPolicyError='true'
-#              state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_addr'"
+#              json add error 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_addr'"
 #              return 1
 #      fi
 
@@ -1229,7 +1235,7 @@ policy_routing() {
                        unset proto_i
                elif ! is_supported_protocol "$proto_i"; then
                        processPolicyError='true'
-                       state add 'errorSummary' 'errorPolicyProcessUnknownProtocol' "${name}: '$proto_i'"
+                       json add error 'errorPolicyProcessUnknownProtocol' "${name}: '$proto_i'"
                        return 1
                fi
 
@@ -1255,7 +1261,7 @@ policy_routing() {
                                        resolved_ipv4="$(resolveip_to_nftset4 "$d")"
                                        resolved_ipv6="$(resolveip_to_nftset6 "$d")"
                                        if [ -z "${resolved_ipv4}${resolved_ipv6}" ]; then
-                                               state add 'errorSummary' 'errorFailedToResolve' "$d"
+                                               json add error 'errorFailedToResolve' "$d"
                                        else
                                        [ -n "$resolved_ipv4" ] && inline_set_ipv4="${inline_set_ipv4:+$inline_set_ipv4, }$resolved_ipv4"
                                        [ -n "$resolved_ipv6" ] && inline_set_ipv6="${inline_set_ipv6:+$inline_set_ipv6, }$resolved_ipv6"
@@ -1296,7 +1302,7 @@ policy_routing() {
                                                resolved_ipv4="$(resolveip_to_nftset4 "$d")"
                                                resolved_ipv6="$(resolveip_to_nftset6 "$d")"
                                                if [ -z "${resolved_ipv4}${resolved_ipv6}" ]; then
-                                                       state add 'errorSummary' 'errorFailedToResolve' "$d"
+                                                       json add error 'errorFailedToResolve' "$d"
                                                else
                                                [ -n "$resolved_ipv4" ] && inline_set_ipv4="${inline_set_ipv4:+$inline_set_ipv4, }$resolved_ipv4"
                                                [ -n "$resolved_ipv6" ] && inline_set_ipv6="${inline_set_ipv6:+$inline_set_ipv6, }$resolved_ipv6"
@@ -1352,15 +1358,15 @@ policy_routing() {
                                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"
+                                       json add error 'errorPolicyProcessInsertionFailed' "$name"
+                                       json add error 'errorPolicyProcessCMD' "nft $param4 $dest4"
+                                       json add error '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"
+                                       json add error 'errorPolicyProcessInsertionFailedIpv4' "$name"
+                                       json add error 'errorPolicyProcessCMD' "nft $param4 $dest4"
                                        logger -t "$packageName" "ERROR: nft $param4 $dest4"
                                fi
                        done
@@ -1384,15 +1390,15 @@ policy_routing() {
                        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"
+                               json add error 'errorPolicyProcessInsertionFailed' "$name"
+                               json add error 'errorPolicyProcessCMD' "nft $param4"
+                               json add error '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"
+                               json add error 'errorPolicyProcessInsertionFailedIpv4' "$name"
+                               json add error 'errorPolicyProcessCMD' "nft $param4"
                                logger -t "$packageName" "ERROR: nft $param4"
                        fi
                fi
@@ -1427,11 +1433,11 @@ dns_policy_process() {
        unset processDnsPolicyError
        output 2 "Routing '$name' DNS to $dest_dns "
        if [ -z "$src_addr" ]; then
-               state add 'errorSummary' 'errorPolicyNoSrcDest' "$name"
+               json add error 'errorPolicyNoSrcDest' "$name"
                output_fail; return 1;
        fi
        if [ -z "$dest_dns" ]; then
-               state add 'errorSummary' 'errorPolicyNoDns' "$name"
+               json add error 'errorPolicyNoDns' "$name"
                output_fail; return 1;
        fi
 
@@ -1474,15 +1480,15 @@ policy_process() {
        [ "$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"
+               json add error 'errorPolicyNoSrcDest' "$name"
                output_fail; return 1;
        fi
        if [ -z "$interface" ]; then
-               state add 'errorSummary' 'errorPolicyNoInterface' "$name"
+               json add error 'errorPolicyNoInterface' "$name"
                output_fail; return 1;
        fi
        if ! is_supported_interface "$interface"; then
-               state add 'errorSummary' 'errorPolicyUnknownInterface' "$name"
+               json add error 'errorPolicyUnknownInterface' "$name"
                output_fail; return 1;
        fi
 
@@ -1509,6 +1515,7 @@ policy_process() {
        local filter_list_src_addr='phys_dev phys_dev_negative mac_address mac_address_negative domain domain_negative ipv4 ipv4_negative ipv6 ipv6_negative'
        local filter_list_dest_addr='domain domain_negative ipv4 ipv4_negative ipv6 ipv6_negative'
        local filter_group_src_addr filtered_value_src_addr filter_group_dest_addr filtered_value_dest_addr
+       local processed_value_src_addr processed_value_dest_addr
        [ -z "$src_addr" ] && filter_list_src_addr='none'
        for filter_group_src_addr in $filter_list_src_addr; do
                filtered_value_src_addr="$(filter_options "$filter_group_src_addr" "$src_addr")"
@@ -1524,11 +1531,27 @@ policy_process() {
                                                        continue
                                        fi
                                        policy_routing "$name" "$interface" "$filtered_value_src_addr" "$src_port" "$filtered_value_dest_addr" "$dest_port" "$proto" "$chain" "$uid"
+                                       processed_value_src_addr="${processed_value_src_addr:+$processed_value_src_addr }$filtered_value_src_addr"
+                                       processed_value_dest_addr="${processed_value_dest_addr:+$processed_value_dest_addr }$filtered_value_dest_addr"
                                fi
                        done
                fi
        done
 
+       for i in $src_addr; do
+               if ! str_contains "$processed_value_src_addr" "$i"; then
+                       processPolicyError='true'
+                       json add error 'errorPolicyProcessUnknownEntry' "$name: $i"
+               fi
+       done
+
+       for i in $dest_addr; do
+               if ! str_contains "$processed_value_dest_addr" "$i"; then
+                       processPolicyError='true'
+                       json add error 'errorPolicyProcessUnknownEntry' "$name: $i"
+               fi
+       done
+
        if [ -n "$processPolicyError" ]; then
                output_fail
        else
@@ -1540,7 +1563,7 @@ 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'
+               json add error 'errorInterfaceRoutingEmptyValues'
                return 1
        fi
        case "$action" in
@@ -1602,7 +1625,7 @@ EOF
                                                        elif ip -6 address show dev "$dev6" | grep -q "POINTOPOINT"; then
                                                                try ip -6 route add default dev "$dev6" table "$tid" metric "$procd_wan6_metric" || ipv6_error=1
                                                        else
-                                                               state add 'errorSummary' 'errorInterfaceRoutingUnknownDevType' "$dev6"
+                                                               json add error 'errorInterfaceRoutingUnknownDevType' "$dev6"
                                                        fi
 #                                                      if ! ip -6 route add default via "$gw6" dev "$dev6" table "$tid" >/dev/null 2>&1; then  
 #                                                              try ip -6 route add default dev "$dev6" table "$tid" metric "$procd_wan6_metric" || ipv6_error=1
@@ -1693,7 +1716,7 @@ EOF
                                                elif ip -6 address show dev "$dev6" | grep -q "POINTOPOINT"; then
                                                        try ip -6 route add default dev "$dev6" table "$tid" metric "$procd_wan6_metric" || ipv6_error=1
                                                else
-                                                       state add 'errorSummary' 'errorInterfaceRoutingUnknownDevType' "$dev6"
+                                                       json add error 'errorInterfaceRoutingUnknownDevType' "$dev6"
                                                fi
                                                while read -r i; do
                                                        # shellcheck disable=SC2086
@@ -1752,7 +1775,7 @@ process_interface() {
 
        if [ "$iface" = 'tor' ]; then 
                case "$action" in
-                       create|reload)
+                       create|reload|reload_interface)
                                torDnsPort="$(get_tor_dns_port)"
                                torTrafficPort="$(get_tor_traffic_port)"
                                displayText="${iface}/53->${torDnsPort}/80,443->${torTrafficPort}"
@@ -1769,7 +1792,7 @@ process_interface() {
                disabled="$(uci_get 'network' "$iface" 'disabled')"
                listen_port="$(uci_get 'network' "$iface" 'listen_port')"
                case "$action" in
-                       create|reload)
+                       create|reload|reload_interface)
                                if [ "$disabled" != '1' ] && [ -n "$listen_port" ]; then
                                        if [ -n "$wanIface4" ]; then
                                                ip rule del sport "$listen_port" table "pbr_${wanIface4}" >/dev/null 2>&1
@@ -1796,7 +1819,7 @@ process_interface() {
        [ "$((ifaceMark))" -gt "$((fw_mask))" ] && return 1
 
        if is_ovpn "$iface" && ! is_ovpn_valid "$iface"; then
-               : || state add 'warningSummary' 'warningInvalidOVPNConfig' "$iface"
+               : || json add warning 'warningInvalidOVPNConfig' "$iface"
        fi
 
        network_get_device dev "$iface"
@@ -1841,7 +1864,7 @@ process_interface() {
                                gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\n"
                                if is_netifd_table_interface "$iface"; then output_okb; else output_ok; fi
                        else
-                               state add 'errorSummary' 'errorFailedSetup' "$displayText"
+                               json add error 'errorFailedSetup' "$displayText"
                                output_fail
                        fi
                ;;
@@ -1918,7 +1941,7 @@ process_interface() {
                                        gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\n"
                                        if is_netifd_table_interface "$iface"; then output_okb; else output_ok; fi
                                else
-                                       state add 'errorSummary' 'errorFailedReload' "$displayText"
+                                       json add error 'errorFailedReload' "$displayText"
                                        output_fail
                                fi
                        else
@@ -1936,26 +1959,26 @@ user_file_process() {
        local shellBin="${SHELL:-/bin/ash}"
        [ "$enabled" -gt '0' ] || return 0
        if [ ! -s "$path" ]; then
-               state add 'errorSummary' 'errorUserFileNotFound' "$path"
+               json add error 'errorUserFileNotFound' "$path"
                output_fail
                return 1
        fi
        if ! $shellBin -n "$path"; then
-               state add 'errorSummary' 'errorUserFileSyntax' "$path"
+               json add error 'errorUserFileSyntax' "$path"
                output_fail
                return 1
        fi
        if is_bad_user_file_nft_call "$path"; then
-               state add 'errorSummary' 'errorIncompatibleUserFile' "$path"
+               json add error 'errorIncompatibleUserFile' "$path"
                output_fail
                return 1
        fi
        output 2 "Running $path "
 # shellcheck disable=SC1090
        if ! . "$path"; then
-               state add 'errorSummary' 'errorUserFileRunning' "$path"
+               json add error 'errorUserFileRunning' "$path"
                if grep -q -w 'curl' "$path" && ! is_present 'curl'; then
-                       state add 'errorSummary' 'errorUserFileNoCurl' "$path"
+                       json add error 'errorUserFileNoCurl' "$path"
                fi
                output_fail
                return 1
@@ -1995,11 +2018,14 @@ on_interface_reload() {
 }
 
 start_service() {
-       local resolverStoredHash resolverNewHash i param="$1" reloadedIface
+       local resolverStoredHash resolverNewHash i param="$1" reloadedIface k
 
        [ -n "$pbr_boot_flag" ] && return 0
+       [ "$param" = 'on_boot' ] && return 0
        load_environment "${param:-on_start}" "$(load_validate_config)" || return 1
-       is_wan_up "$param" || return 1
+
+       output "Processing environment (${param:-on_start}) "
+       is_wan_up "$param" || { output_error "$(get_text 'errorUplinkDown')";  return 1; }
 
        process_interface 'all' 'prepare'
        config_foreach process_interface 'interface' 'pre_init'
@@ -2056,6 +2082,7 @@ start_service() {
 
        case $serviceStartTrigger in
                on_interface_reload)
+       output_okn
                        output 1 "Reloading Interface: $reloadedIface "
                        json_add_array 'gateways'
                        process_interface 'all' 'prepare'
@@ -2073,6 +2100,7 @@ start_service() {
                        cleanup_marking_chains
                        cleanup_rt_tables
                        nft_file 'create'
+       output_okn
                        output 1 'Processing interfaces '
                        json_add_array 'gateways'
                        process_interface 'all' 'prepare'
@@ -2116,21 +2144,38 @@ start_service() {
        esac
 
        if [ -z "$gatewaySummary" ]; then
-               state add 'errorSummary' 'errorNoGateways'
+               json add error 'errorNoGateways'
        fi
+       json_add_int 'packageCompat' "$packageCompat"
        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"
+       json_close_object
+       json_add_array 'errors'
+               for k in $(json get errors); do
+                       json_add_object "$k"
+                       json_add_string 'code' "$(json get error "$k" 'code')"
+                       json_add_string 'info' "$(json get error "$k" 'info')"
+                       json_close_object
+               done
+       json_close_array
+       json_add_array 'warnings'
+               for k in $(json get warnings); do
+                       json_add_object "$k"
+                       json_add_string 'code' "$(json get warning "$k" 'code')"
+                       json_add_string 'info' "$(json get warning "$k" 'info')"
+                       json_close_object
+               done
+       json_close_array
        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() {
+       [ -n "$pbr_boot_flag" ] && return 0
+       local error warning c
        if nft_file 'exists'; then
                procd_set_config_changed firewall
                if nft_file 'exists'; then
@@ -2142,12 +2187,26 @@ service_started() {
        else
                [ -n "$gatewaySummary" ] && output "$serviceName (nft mode) started with gateways:\n${gatewaySummary}"
        fi
-       state print 'errorSummary'
-       state print 'warningSummary'
+       error="$(json get error)"
+       warning="$(json get warning)"
+       if [ -n "$error" ]; then
+               for c in $error; do
+                       code="$(json get error "$c" 'code')"
+                       info="$(json get error "$c" 'info')"
+                       output_error "$(get_text "$code" "$info")"
+               done
+       fi
+       if [ -n "$warning" ]; then
+               for c in $warning; do
+                       code="$(json get warning "$c" 'code')"
+                       info="$(json get warning "$c" 'info')"
+                       output_warning "$(get_text "$code" "$info")"
+               done
+       fi
        touch "$packageLockFile"
-       if [ -n "$errorSummary" ]; then
+       if [ -n "$error" ]; then
                return 2
-       elif [ -n "$warningSummary" ]; then
+       elif [ -n "$warning" ]; then
                return 1
        else
                return 0
@@ -2165,9 +2224,9 @@ service_triggers() {
                load_validate_policy
                load_validate_include
        procd_close_validate
-       if [ -n "$pbr_boot_flag" ]; then
+       if [ -n "$pbr_boot_flag" ] && is_integer "$procd_boot_trigger_delay"; then
                output "Setting trigger (on_boot) "
-               procd_add_raw_trigger "interface.*.up" 5000 "/etc/init.d/${packageName}" start && output_okn || output_failn
+               procd_add_raw_trigger "interface.*.up" "$procd_boot_trigger_delay" "/etc/init.d/${packageName}" start && output_okn || output_failn
        else
                procd_open_trigger
                        procd_add_config_trigger "config.change" 'openvpn' "/etc/init.d/${packageName}" reload 'on_openvpn_change'
@@ -2353,6 +2412,7 @@ status_service() {
 # shellcheck disable=SC2120
 load_validate_config() {
        uci_load_validate "$packageName" "$packageName" "$1" "${2}${3:+ $3}" \
+               'debug_dnsmasq:bool:0' \
                'enabled:bool:0' \
                'strict_enforcement:bool:1' \
                'ipv6_enabled:bool:0' \
@@ -2364,8 +2424,9 @@ load_validate_config() {
                '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_reload_delay:integer:0' \
+               'procd_boot_trigger_delay:range(1000,10000):5000' \
                'procd_lan_device:list(or(network)):br-lan' \
+               'procd_reload_delay:uinteger:0' \
                'procd_wan_interface:network:wan' \
                'procd_wan6_interface:network:wan6' \
                'wan_ip_rules_priority:uinteger:30000' \