pbr: update to 1.2.1-r87
authorStan Grishin <redacted>
Fri, 30 Jan 2026 20:22:03 +0000 (20:22 +0000)
committerStan Grishin <redacted>
Thu, 5 Feb 2026 21:06:55 +0000 (13:06 -0800)
Makefile:
* Remove installation of /usr/share/nftables.d/* files as they are no
  longer needed

Init script:
* bugfixes/more mature netifd extensions support
* refactor of the nft_file function and global variables it uses
* the "main" atomic nft file now includes creation of pbr chains and jumps
  from relevant fw4 chains to pbr chains
* more consistent use of "uplink" wording in the output and variable names
* implement resolver 'wait' call and use it before trying to resolve any
  policy entries
* major overhaul of the split uplink case (IPv4-only wan and IPv6-only
  wan6), should now create/use a single pbr_wan table for both legacy and
  IPv6 routing and the same marking chain
* updates to IDs and text of some error messages (needs luci app update)
* major speed improvements for service stop
* unify the cleanup_* functions into a single cleanup function
* reject creating/additions to nft sets for src_address entries as dnsmasq
  doesn't populate sets with local addresses
* minor bugfixes/code cleanups
* refactor processing of WG servers due to split uplink support (thanks
  @egc112!)
* clearer (hopefully) argument names for process_interface calls
* small improvements to status_service

Signed-off-by: Stan Grishin <redacted>
net/pbr/Makefile
net/pbr/README.md [moved from net/pbr/files/README.md with 100% similarity]
net/pbr/files/etc/init.d/pbr
net/pbr/files/usr/share/nftables.d/chain-post/mangle_forward/30-pbr.nft [deleted file]
net/pbr/files/usr/share/nftables.d/chain-post/mangle_output/30-pbr.nft [deleted file]
net/pbr/files/usr/share/nftables.d/chain-post/mangle_prerouting/30-pbr.nft [deleted file]
net/pbr/files/usr/share/nftables.d/chain-pre/dstnat/30-pbr.nft [deleted file]
net/pbr/files/usr/share/nftables.d/table-post/30-pbr.nft [deleted file]

index 4dc30b0a192a4152d5e31807949769eb153d1f94..f2af31b5820d25f1f552e2724e67044694b6118b 100644 (file)
@@ -1,11 +1,11 @@
 # SPDX-License-Identifier: AGPL-3.0-or-later
-# Copyright 2017-2025 MOSSDeF, Stan Grishin (stangri@melmac.ca).
+# Copyright 2017-2026 MOSSDeF, Stan Grishin (stangri@melmac.ca).
 
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=pbr
 PKG_VERSION:=1.2.1
-PKG_RELEASE:=45
+PKG_RELEASE:=87
 PKG_LICENSE:=AGPL-3.0-or-later
 PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
 
@@ -57,8 +57,6 @@ define Package/pbr/install
        $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.dnsprefetch $(1)/usr/share/pbr/pbr.user.dnsprefetch
        $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.aws $(1)/usr/share/pbr/pbr.user.aws
        $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.netflix $(1)/usr/share/pbr/pbr.user.netflix
-       $(INSTALL_DIR) $(1)/usr/share/nftables.d
-       $(CP) ./files/usr/share/nftables.d/* $(1)/usr/share/nftables.d/
        $(INSTALL_DIR) $(1)/etc/uci-defaults
        $(INSTALL_BIN) ./files/etc/uci-defaults/90-pbr $(1)/etc/uci-defaults/90-pbr
        $(INSTALL_BIN) ./files/etc/uci-defaults/91-pbr-nft $(1)/etc/uci-defaults/91-pbr-nft
similarity index 100%
rename from net/pbr/files/README.md
rename to net/pbr/README.md
index 69cc5b259cc053d7825b5b1cafbfb65f48ef73f9..c846e7314443249d5465c958de3b463673a4d278 100755 (executable)
@@ -32,7 +32,7 @@ fi
 
 readonly packageName='pbr'
 readonly PKG_VERSION='dev-test'
-readonly packageCompat='20'
+readonly packageCompat='24'
 readonly serviceName="$packageName $PKG_VERSION"
 readonly packageConfigFile="/etc/config/${packageName}"
 readonly packageDebugFile="/var/run/${packageName}.debug"
@@ -47,6 +47,8 @@ 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 _DOT_='.'
+readonly __DOT__='[w]'
 readonly ip_full='/usr/libexec/ip-full'
 # shellcheck disable=SC2155
 readonly ipTablePrefix="$packageName"
@@ -57,8 +59,8 @@ 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 nftNetifdPermFile="/usr/share/nftables.d/ruleset-post/20-${packageName}-netifd.nft"
+readonly nftMainFile="/usr/share/nftables.d/ruleset-post/30-${packageName}.nft"
+readonly nftNetifdFile="/usr/share/nftables.d/ruleset-post/20-${packageName}-netifd.nft"
 readonly nftPrefix="$packageName"
 readonly nftTable='fw4'
 readonly chainsList='forward output prerouting'
@@ -133,6 +135,7 @@ procd_boot_trigger_delay=
 procd_reload_delay=
 lan_device=
 uplink_interface=
+uplink_interface4=
 uplink_interface6=
 uplink_interface6_metric='128'
 resolver_set=
@@ -162,8 +165,6 @@ config_version=
 # run-time
 aghConfigFile='/etc/AdGuardHome/AdGuardHome.yaml'
 gatewaySummary=
-wanIface4=
-wanIface6=
 ifaceMark=
 ifaceTableID=
 ifacePriority=
@@ -171,8 +172,9 @@ ifacesAll=
 ifacesSupported=
 ifacesTriggers=
 firewallWanZone=
-wanGW4=
-wanGW6=
+uplinkGW=
+uplinkGW4=
+uplinkGW6=
 pbrBootFlag=
 serviceStartTrigger=
 processDnsPolicyError=
@@ -190,6 +192,7 @@ dnsmasq_ubus=
 nft_fw4_dump=
 loadEnvironmentFlag=
 loadPackageConfigFlag=
+resolverWorkingFlag=
 
 # shellcheck disable=SC1091
 . "${IPKG_INSTROOT}/lib/functions.sh"
@@ -231,6 +234,7 @@ 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_dot() { output 1 "$_DOT_"; output 2 "$__DOT__"; }
 output_error() { output "${_ERROR_} $*!\n"; }
 output_warning() { output "${_WARNING_} $*.\n"; }
 quiet_mode() {
@@ -243,7 +247,7 @@ pbr_find_iface() {
        local iface i param="$2"
        case "$param" in
                wan6)  iface="$uplink_interface6";;
-               wan|*) iface="$uplink_interface";;
+               wan|*) iface="$uplink_interface4";;
        esac
        eval "$1"='${iface}'
 }
@@ -258,7 +262,7 @@ pbr_get_gateway4() {
 }
 pbr_get_gateway6() {
        local iface="$2" dev="$3" gw
-       [ "$iface" = "$uplink_interface" ] && iface="$uplink_interface6"
+       is_wan "$iface" && iface="$uplink_interface6"
        network_get_gateway6 gw "$iface" true
        if [ -z "$gw" ] || [ "$gw" = '::/0' ] || [ "$gw" = '::0/0' ] || [ "$gw" = '::' ]; then
                gw="$(ip -6 a list dev "$dev" 2>/dev/null | grep inet6 | grep 'scope global' | awk '{print $2}')"
@@ -347,6 +351,7 @@ is_supported_iface_dev() { local n dev; for n in $ifacesSupported; do network_ge
 is_supported_protocol(){ grep -qi "^${1:--}" /etc/protocols;}
 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_split_uplink() { [ -n "$ipv6_enabled" ] && [ "$uplink_interface4" != "$uplink_interface6" ]; }
 is_supported_interface() { { is_lan "$1" || is_disabled_interface "$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_netbird() { local d; network_get_device d "$1"; [ "${d:0:2}" = "wt" ]; }
 is_tailscale() { local d; network_get_device d "$1"; [ "${d:0:9}" = "tailscale" ]; }
@@ -359,8 +364,8 @@ 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_wan() { [ "$1" = "$uplink_interface4" ]; }
+is_wan6() { [ -n "$ipv6_enabled" ] && [ "$1" = "$uplink_interface6" ]; }
 is_wg() { local p lp; network_get_protocol p "$1"; uci_get_listen_port lp "$1"; [ -z "$lp" ] && [ "${p:0:9}" = "wireguard" ]; }
 is_wg_server() { local p lp; network_get_protocol p "$1"; uci_get_listen_port lp "$1"; [ -n "$lp" ] && [ "${p:0:9}" = "wireguard" ]; }
 is_xray() { [ -n "$(get_xray_traffic_port "$1")" ]; }
@@ -378,7 +383,7 @@ get_rt_tables_id() { local iface="$1"; grep "${ipTablePrefix}_${iface}\$" "$rtTa
 get_rt_tables_next_id() { echo "$(($(sort -r -n "$rtTablesFile" | grep -o -E -m 1 "^[0-9]+")+1))"; }
 get_rt_tables_non_pbr_next_id() { echo "$(($(grep -v "${ipTablePrefix}_" "$rtTablesFile" | sort -r -n  | grep -o -E -m 1 "^[0-9]+")+1))"; }
 # shellcheck disable=SC2016
-resolveip_to_nftset() { resolveip "$@" | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d'; }
+resolveip_to_nftset() { resolver 'wait' && resolveip "$@" | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d'; }
 resolveip_to_nftset4() { resolveip_to_nftset -4 "$@"; }
 resolveip_to_nftset6() { [ -n "$ipv6_enabled" ] && resolveip_to_nftset -6 "$@"; }
 # shellcheck disable=SC2016
@@ -422,8 +427,8 @@ sanitize_list() { sed 's/#.*//;s/^[ \t]*//;s/[ \t]*$//;s/[ \t][ \t]*/ /g;/^[ \t]
 
 # luci app specific
 is_enabled() { uci_get "$1" 'config' 'enabled'; }
-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_nft_file() { [ -s "$nftMainFile" ]; }
+is_running_nft() { "$nft" list table inet "$nftTable" | grep chain | grep -q "${nftPrefix}_mark_" >/dev/null 2>&1; }
 check_nft() { [ -x "$nft" ]; }
 check_agh() { [ -x "$agh" ] && { [ -s "$aghConfigFile" ] || [ -s "${agh%/*}/AdGuardHome.yaml" ]; }; }
 check_dnsmasq() { command -v dnsmasq >/dev/null 2>&1; }
@@ -452,7 +457,7 @@ get_text() {
                errorNoNft) printf "Resolver set support (%s) requires nftables, but nft binary cannot be found" "$resolver_set";;
                errorResolverNotSupported) printf "Resolver set (%s) is not supported on this system" "$resolver_set";;
                errorServiceDisabled) printf "The %s service is currently disabled" "$packageName";;
-               errorNoWanGateway) printf "The %s service failed to discover WAN gateway" "$serviceName";;
+               errorNoUplinkGateway) printf "The %s service failed to discover uplink gateway" "$serviceName";;
                errorNoUplinkInterface) printf "The %s interface not found, you need to set the 'pbr.config.uplink_interface' option" "$1";;
                errorNoUplinkInterfaceHint) printf "Refer to %s" "$1";;
                errorNftsetNameTooLong) printf "The nft set name '%s' is longer than allowed 255 characters" "$1";;
@@ -479,9 +484,10 @@ get_text() {
                errorPolicyProcessInsertionFailedIpv4) printf "Insertion failed for IPv4 for policy '%s'" "$1";;
                errorPolicyProcessUnknownEntry) printf "Unknown entry in policy '%s'" "$1";;
                errorInterfaceRoutingEmptyValues) printf "Received empty tid/mark or interface name when setting up routing";;
+               errorInterfaceMarkOverflow) printf "Interface mark for '%s' exceeds the fwmask value" "$1";;
                errorFailedToResolve) printf "Failed to resolve '%s'" "$1";;
                errorInvalidOVPNConfig) printf "Invalid OpenVPN config for '%s' interface" "$1";;
-               errorNftFileInstall) printf "Failed to install fw4 nft file '%s'" "$1";;
+               errorNftMainFileInstall) printf "Failed to install fw4 nft file '%s'" "$1";;
                errorTryFailed) printf "Command failed: %s" "$1";;
                errorDownloadUrlNoHttps) printf "Failed to download '%s', HTTPS is not supported" "$1";;
                errorDownloadUrl) printf "Failed to download '%s'" "$1";;
@@ -495,8 +501,8 @@ get_text() {
                errorUplinkDown) printf "Uplink/WAN interface is still down, increase value of 'procd_boot_trigger_delay' option";;
                errorMktempFileCreate) printf "Failed to create temporary file with mktemp mask: '%s'" "$1";;
                errorSummary) printf "Errors encountered, please check %s" "$1";;
-               errorNetifdNftFileInstall) printf "Netifd setup: failed to install fw4 netifd nft file '%s'" "$1";;
-               errorNetifdNftFileRemove) printf "Netifd setup: failed to remove fw4 netifd nft file '%s'" "$1";;
+               errorNftNetifdFileInstall) printf "Netifd setup: failed to install fw4 netifd nft file '%s'" "$1";;
+               errorNftNetifdFileDelete) printf "Netifd setup: failed to remove fw4 netifd nft file '%s'" "$1";;
                errorNetifdMissingOption) printf "Netifd setup: required option '%s' is missing" "$1";;
                errorNetifdInvalidGateway4) printf "Netifd setup: invalid value of netifd_interface_default option '%s'" "$1";;
                errorNetifdInvalidGateway6) printf "Netifd setup: invalid value of netifd_interface_default6 option '%s'" "$1";;
@@ -506,12 +512,13 @@ get_text() {
                warningTorUnsetParams) printf "Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'" "$1";;
                warningTorUnsetProto) printf "Please unset 'proto' or set 'proto' to 'all' for policy '%s'" "$1";;
                warningTorUnsetChainNft) printf "Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'" "$1";;
-               warningOutdatedWebUIApp) printf "The WebUI application is outdated (version %s), please update it" "$1";;
+               warningOutdatedLuciPackage) printf "The WebUI application is outdated (version %s), please update it" "$1";;
                warningDnsmasqInstanceNoConfdir) printf "Dnsmasq instance '%s' targeted in settings, but it doesn't have its own confdir" "$1";;
                warningDhcpLanForce) printf "Please set 'dhcp.%s.force=1' to speed up service start-up" "$1";;
                warningSummary) printf "Warnings encountered, please check %s" "$(get_url '#WarningMessagesDetails')";;
                warningIncompatibleDHCPOption6) printf "Incompatible DHCP Option 6 for interface '%s'" "$1";;
                warningNetifdMissingInterfaceLocal) printf "Netifd setup: option netifd_interface_local is missing, assuming '%s'" "$1";;
+               warningUplinkDown) printf "Uplink/WAN interface is still down, going back to boot mode";;
                *) printf "Unknown error/warning '%s'" "$1";;
        esac
 }
@@ -588,17 +595,20 @@ load_package_config() {
        config_get      uplink_ip_rules_priority  'config' 'uplink_ip_rules_priority' '30000'
        config_get      uplink_mark               'config' 'uplink_mark'              '00010000'
        config_get      verbosity                 'config' 'verbosity'                '2'
+       config_get_bool webui_show_ignore_target  'config' 'webui_show_ignore_target' '0'
        config_get_bool netifd_enabled            'config' 'netifd_enabled'
        config_get_bool netifd_strict_enforcement 'config' 'netifd_strict_enforcement'
        config_get      netifd_interface_default  'config' 'netifd_interface_default'
        config_get      netifd_interface_default6 'config' 'netifd_interface_default6'
        config_get      netifd_interface_local    'config' 'netifd_interface_local'
 
+       uplink_interface4="$uplink_interface"
        fw_mask="0x${fw_mask}"
        uplink_mark="0x${uplink_mark}"
 
        [ "$resolver_set" = 'none' ]      && unset resolver_set
        [ "$enabled" = '1' ]              || unset enabled
+       [ "$ipv6_enabled" = '1' ]         || unset uplink_interface6
        [ "$ipv6_enabled" = '1' ]         || unset ipv6_enabled
        [ "$strict_enforcement" = '1' ]   || unset strict_enforcement
 
@@ -723,7 +733,6 @@ load_environment() {
                ;;
                on_triggers)
                        [ -n "$loadPackageConfigFlag" ] || load_package_config "$param"
-#                      load_network "$param"
                ;;
                on_interface_reload|on_reload|on_stop|*)
                        output 1 "Loading environment ($param) "
@@ -741,58 +750,56 @@ load_network() {
 # shellcheck disable=SC2329
        _build_ifaces_supported() { is_supported_interface "$1" && ! str_contains "$ifacesSupported" "$1" && ifacesSupported="${ifacesSupported}${1} "; }
 # shellcheck disable=SC2329
-       _find_firewall_wan_zone() { [ "$(uci_get 'firewall' "$1" 'name')" = "wan" ] && firewallWanZone="$1"; }
+       _find_firewall_wan_zone() { [ "$(uci_get 'firewall' "$1" 'name')" = 'wan' ] && firewallWanZone="$1"; }
        local i param="$1"
        local dev4 dev6
        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} "
+                       is_supported_interface "$i" && ! str_contains "$ifacesSupported" "$i" && ifacesSupported="${ifacesSupported}${i} "
                done
                config_load 'network'
                config_foreach _build_ifaces_supported 'interface'
        fi
-       wanIface4="$uplink_interface"
-       network_get_device dev4 "$wanIface4"
-       [ -z "$dev4" ] && network_get_physdev dev4 "$wanIface4"
-       [ -z "$wanGW4" ] && pbr_get_gateway4 wanGW4 "$wanIface4" "$dev4"
+       network_get_device dev4 "$uplink_interface4"
+       [ -z "$dev4" ] && network_get_physdev dev4 "$uplink_interface4"
+       [ -z "$uplinkGW4" ] && pbr_get_gateway4 uplinkGW4 "$uplink_interface4" "$dev4"
        if [ -n "$ipv6_enabled" ]; then
-               wanIface6="$uplink_interface6"
-               network_get_device dev6 "$wanIface6"
-               [ -z "$dev6" ] && network_get_physdev dev6 "$wanIface6"
-               [ -z "$wanGW6" ] && pbr_get_gateway6 wanGW6 "$wanIface6" "$dev6"
+               network_get_device dev6 "$uplink_interface6"
+               [ -z "$dev6" ] && network_get_physdev dev6 "$uplink_interface6"
+               [ -z "$uplinkGW6" ] && pbr_get_gateway6 uplinkGW6 "$uplink_interface6" "$dev6"
        fi
 
        case "$param" in
                on_boot|on_start)
-                       [ -n "$wanIface4" ] && output 2 "Using uplink${wanIface6:+ IPv4} interface (${param}): $wanIface4 $__OK__\n"
-                       [ -n "$wanGW4" ] && output 2 "Found uplink${wanIface6:+ IPv4} gateway (${param}): $wanGW4 $__OK__\n"
-                       [ -n "$wanIface6" ] && output 2 "Using uplink IPv6 interface (${param}): $wanIface6 $__OK__\n"
-                       [ -n "$wanGW6" ] && output 2 "Found uplink IPv6 gateway (${param}): $wanGW6 $__OK__\n"
+                       [ -n "$uplink_interface4" ] && output 2 "Using uplink${uplink_interface6:+ IPv4} interface (${param}): $uplink_interface4 $__OK__\n"
+                       [ -n "$uplinkGW4" ] && output 2 "Found uplink${uplink_interface6:+ IPv4} gateway (${param}): $uplinkGW4 $__OK__\n"
+                       [ -n "$uplink_interface6" ] && output 2 "Using uplink IPv6 interface (${param}): $uplink_interface6 $__OK__\n"
+                       [ -n "$uplinkGW6" ] && output 2 "Found uplink IPv6 gateway (${param}): $uplinkGW6 $__OK__\n"
                ;;
        esac
-       wanGW="${wanGW4:-$wanGW6}"
+       uplinkGW="${uplinkGW4:-$uplinkGW6}"
 }
 
 is_wan_up() {
        local param="$1"
-       if [ -z "$(uci_get network "$uplink_interface")" ]; then
-               json add error 'errorNoUplinkInterface' "$uplink_interface"
+       if [ -z "$(uci_get network "$uplink_interface4")" ]; then
+               json add error 'errorNoUplinkInterface' "$uplink_interface4"
                json add error 'errorNoUplinkInterfaceHint' "$(get_url '#uplink_interface')"
                return 1
        fi
        network_flush_cache
        load_network "$param"
-       if [ -n "$wanGW" ]; then
+       if [ -n "$uplinkGW" ]; then
                return 0
        else
-               json add error 'errorNoWanGateway'
+               json add error 'errorNoUplinkGateway'
                return 1
        fi
 }
 
-nft() { [ -n "$*" ] && nft_file 'add_command' "$@"; }
+nft() { [ -n "$*" ] && nft_file 'add' 'main' "$@"; }
 nft4() { nft "$@"; }
 nft6() { [ -n "$ipv6_enabled" ] || return 0; nft "$@"; }
 nft_call() { "$nft" "$@" >/dev/null 2>&1; }
@@ -808,75 +815,99 @@ nft_check_element() {
        esac
 }
 nft_file() {
-       local i chain
-       case "$1" in
-               add|add_command)
-                       shift
+       local i chain command="$1" target="$2"
+       case "$command:$target" in
+               add:*)
+                       shift 2
                        echo "$*" >> "$nftTempFile"
                ;;
-               create)
-                       rm -f "$nftTempFile" "$nftPermFile"
-                       for i in "$nftTempFile" "$nftPermFile"; do
+               create:main)
+                       rm -f "$nftTempFile" "$nftMainFile"
+                       for i in "$nftTempFile" "$nftMainFile"; do
                                mkdir -p "${i%/*}"
                        done
                        { echo '#!/usr/sbin/nft -f'; echo ''; } > "$nftTempFile"
-                       # Insert PBR guards at the top of main caller chains so first PBR match wins, while preserving foreign marks.
+                       # Create pbr chains in fw4 table
+                       for chain in dstnat $chainsList; do
+                               echo "add chain inet $nftTable ${nftPrefix}_${chain} {}" >> "$nftTempFile"
+                       done
+                       echo "" >> "$nftTempFile"
+                       # Add jump rules from fw4 chains to pbr chains
+                       echo "add rule inet $nftTable dstnat jump ${nftPrefix}_dstnat" >> "$nftTempFile"
+                       echo "add rule inet $nftTable mangle_prerouting jump ${nftPrefix}_prerouting" >> "$nftTempFile"
+                       echo "add rule inet $nftTable mangle_output jump ${nftPrefix}_output" >> "$nftTempFile"
+                       echo "add rule inet $nftTable mangle_forward jump ${nftPrefix}_forward" >> "$nftTempFile"
+                       echo "" >> "$nftTempFile"
+                       # Insert PBR guards at the top of pbr chains so first PBR match wins, while preserving foreign marks.
                        for chain in $chainsList; do
                                echo "add rule inet $nftTable ${nftPrefix}_${chain} ${nftRuleParams:+$nftRuleParams }meta mark & $fw_mask != 0 return" >> "$nftTempFile"
                        done
                ;;
-               delete|rm|remove)
-                       rm -f "$nftTempFile" "$nftPermFile"
+               create:netifd)
+                       rm -f "$nftTempFile" "$nftNetifdFile"
+                       for i in "$nftTempFile" "$nftNetifdFile"; do
+                               mkdir -p "${i%/*}"
+                       done
+                       { echo '#!/usr/sbin/nft -f'; echo ''; } > "$nftTempFile"
+               ;;
+               delete:main)
+                       rm -f "$nftTempFile" "$nftMainFile"
+               ;;
+               delete:netifd)
+                       output "Removing fw4 netifd nft file "
+                       if rm -f "$nftNetifdFile"; then
+                               output_okbn
+                       else
+                               json add error 'errorNftNetifdFileDelete' "$nftNetifdFile"
+                               output_failn
+                       fi
                ;;
-               enabled)
-                       return 0
+               exists:main)
+                       [ -s "$nftMainFile" ] && return 0 || return 1
                ;;
-               exists)
-                       [ -s "$nftPermFile" ] && return 0 || return 1
+               exists:netifd)
+                       [ -s "$nftNetifdFile" ] && return 0 || return 1
                ;;
-               install)
+               install:main)
                        [ -s "$nftTempFile" ] || return 1
                        output "Installing fw4 nft file "
                        if nft_call -c -f "$nftTempFile" && \
-                               cp -f "$nftTempFile" "$nftPermFile"; then
+                               cp -f "$nftTempFile" "$nftMainFile"; then
                                output_okn
                        else
-                               json add error 'errorNftFileInstall' "$nftTempFile"
+                               json add error 'errorNftMainFileInstall' "$nftTempFile"
                                output_failn
                        fi
                ;;
-               netifd_exists)
-                       [ -s "$nftNetifdPermFile" ] && return 0 || return 1
-               ;;
-               netifd_create)
-                       rm -f "$nftTempFile" "$nftNetifdPermFile"
-                       for i in "$nftTempFile" "$nftNetifdPermFile"; do
-                               mkdir -p "${i%/*}"
-                       done
-                       { echo '#!/usr/sbin/nft -f'; echo ''; } > "$nftTempFile"
-               ;;
-               netifd_delete|netifd_rm)
-                       rm -f "$nftNetifdPermFile"
-               ;;
-               netifd_install)
+               install:netifd)
                        [ -s "$nftTempFile" ] || return 1
                        output "Installing fw4 netifd nft file "
                        if nft_call -c -f "$nftTempFile" && \
-                               cp -f "$nftTempFile" "$nftNetifdPermFile"; then
+                               cp -f "$nftTempFile" "$nftNetifdFile"; then
                                output_okbn
                        else
-                               json add error 'errorNetifdNftFileInstall' "$nftTempFile"
+                               json add error 'errorNftNetifdFileInstall' "$nftTempFile"
                                output_failn
                        fi
                ;;
-               netifd_remove)
-                       output "Removing fw4 netifd nft file "
-                       if rm -f "$nftNetifdPermFile"; then
-                               output_okbn
-                       else
-                               json add error 'errorNetifdNftFileRemove' "$nftTempFile"
-                               output_failn
-                       fi
+               match:temp)
+                       grep -q "$3" "$nftTempFile"
+               ;;
+               sed:temp)
+                       shift 2
+                       sed -i "$*" "$nftTempFile" >/dev/null 2>&1
+               ;;
+               sed:netifd)
+                       shift 2
+                       sed -i "$*" "$nftNetifdFile" >/dev/null 2>&1
+               ;;
+               show:main)
+                       echo "$packageName fw4 nft file: $nftMainFile"
+                       sed '1d;2d;' "$nftMainFile"
+               ;;
+               show:netifd)
+                       echo "$packageName fw4 netifd nft file: $nftNetifdFile"
+                       sed '1d;2d;' "$nftNetifdFile"
                ;;
        esac
 }
@@ -1001,44 +1032,91 @@ nftset() {
        fi
 }
 
-cleanup_rt_tables() {
-       local i
-# shellcheck disable=SC2013
-       for i in $(grep -oh "${ipTablePrefix}_.*" "$rtTablesFile"); do
-               ! is_netifd_table "$i" && sed -i "/${i}/d" "$rtTablesFile"
-       done
-       sync
-}
-
-cleanup_main_table() {
-       for prio in $(ip -4 rule show \
-    | awk -v mask="$fw_mask" '/fwmark/ && $0 ~ mask && /lookup main/ && /suppress_prefixlength 0/ {sub(":", "", $1); print $1}'
-); do
-    ip -4 rule del priority "$prio"
-done
-}
-
-cleanup_main_chains() {
-       local i j
-       for i in $chainsList dstnat; do
-               i="$(str_to_lower "$i")"
-               nft_call flush chain inet "$nftTable" "${nftPrefix}_${i}"
-       done
-}
-
-cleanup_marking_chains() {
-       local i j
-       for i in $(get_mark_nft_chains); do
-               nft_call flush chain inet "$nftTable" "$i"
-               nft_call delete chain inet "$nftTable" "$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"
+cleanup() {
+       local action i prio
+       for action in "$@"; do
+               case "$action" in
+                       rt_tables)
+                               # shellcheck disable=SC2013
+                               for i in $(grep -oh "${ipTablePrefix}_.*" "$rtTablesFile"); do
+                                       ! is_netifd_table "$i" && sed -i "/${i}/d" "$rtTablesFile"
+                               done
+                               sync
+                       ;;
+                       main_table)
+                               # Get all rules to delete in one pass (format: "priority suppress_prefixlength_value table_name")
+                               ip -4 rule show | awk '
+                                       /lookup[[:space:]]+main[[:space:]]+suppress_prefixlength[[:space:]]+[0-9]+/ { 
+                                               sub(":", "", $1)
+                                               match($0, /suppress_prefixlength[[:space:]]+([0-9]+)/, arr)
+                                               print $1 " " arr[1] " main"
+                                       }
+                                       /lookup '"$ipTablePrefix"'_/ { 
+                                               sub(":", "", $1)
+                                               match($0, /lookup[[:space:]]+([^[:space:]]+)/, arr)
+                                               print $1 " 0 " arr[1]
+                                       }
+                               ' | while read -r prio len table; do
+                                       if [ "$table" != "main" ] && is_netifd_table "$table"; then
+                                               continue  # Skip netifd-managed tables
+                                       fi
+                                       if [ "$len" = "0" ]; then
+                                               # pbr table rule - try priority deletion first
+                                               ip -4 rule del priority "$prio" 2>/dev/null
+                                       else
+                                               # suppress_prefixlength rule - try priority first, then full spec
+                                               if ! ip -4 rule del priority "$prio" 2>/dev/null; then
+                                                       ip -4 rule del lookup main suppress_prefixlength "$len" priority "$prio" 2>/dev/null || \
+                                                       ip -4 rule del table main suppress_prefixlength "$len" priority "$prio" 2>/dev/null
+                                               fi
+                                       fi
+                               done
+                               # Always attempt IPv6 cleanup regardless of current ipv6_enabled setting
+                               # since rules might exist from when IPv6 was previously enabled
+                               ip -6 rule show 2>/dev/null | awk '
+                                       /lookup[[:space:]]+main[[:space:]]+suppress_prefixlength[[:space:]]+[0-9]+/ { 
+                                               sub(":", "", $1)
+                                               match($0, /suppress_prefixlength[[:space:]]+([0-9]+)/, arr)
+                                               print $1 " " arr[1] " main"
+                                       }
+                                       /lookup '"$ipTablePrefix"'_/ { 
+                                               sub(":", "", $1)
+                                               match($0, /lookup[[:space:]]+([^[:space:]]+)/, arr)
+                                               print $1 " 0 " arr[1]
+                                       }
+                               ' | while read -r prio len table; do
+                                       if [ "$table" != "main" ] && is_netifd_table "$table"; then
+                                               continue  # Skip netifd-managed tables
+                                       fi
+                                       if [ "$len" = "0" ]; then
+                                               ip -6 rule del priority "$prio" 2>/dev/null
+                                       else
+                                               if ! ip -6 rule del priority "$prio" 2>/dev/null; then
+                                                       ip -6 rule del lookup main suppress_prefixlength "$len" priority "$prio" 2>/dev/null || \
+                                                       ip -6 rule del table main suppress_prefixlength "$len" priority "$prio" 2>/dev/null
+                                               fi
+                                       fi
+                               done
+                       ;;
+                       main_chains)
+                               for i in $chainsList dstnat; do
+                                       i="$(str_to_lower "$i")"
+                                       nft_call flush chain inet "$nftTable" "${i}"
+                               done
+                       ;;
+                       marking_chains)
+                               for i in $(get_mark_nft_chains); do
+                               #       nft_call flush chain inet "$nftTable" "$i"
+                                       nft_call delete chain inet "$nftTable" "$i"
+                               done
+                       ;;
+                       sets)
+                               for i in $(get_nft_sets); do
+                               #       nft_call flush set inet "$nftTable" "$i"
+                                       nft_call delete set inet "$nftTable" "$i"
+                               done
+                       ;;
+               esac
        done
 }
 
@@ -1136,12 +1214,27 @@ resolver() {
                                restart) return 0;;
                                compare_hash) return 0;;
                                store_hash) return 0;;
+                               wait)
+                                       [ -n "$resolverWorkingFlag" ] && return 0
+                                       local timeout="${iface:-30}" count=0
+                                       local hostname="$(uci_get 'system' '@system[0]' 'hostname' 'OpenWrt')"
+                                       while [ "$count" -lt "$timeout" ]; do
+                                               if resolveip "$hostname" >/dev/null 2>&1; then
+                                                       resolverWorkingFlag='true'
+                                                       return 0
+                                               fi
+                                               sleep 1
+                                               count=$((count + 1))
+                                       done
+                                       return 1
+                               ;;
                        esac
                ;;
                dnsmasq.nftset)
                        case "$param" in
                                add_resolver_element)
                                        [ -n "$resolverSetSupported" ] || return 1
+                                       [ "$target" = 'src' ] && return 1 # dnsmasq doesn't populate nft sets for local addresses
                                        local d
                                        for d in $value; do
                                                nftset 'add_dnsmasq_element' "$iface" "$target" "$type" "$uid" "$name" "$d"
@@ -1149,6 +1242,7 @@ resolver() {
                                ;;
                                create_resolver_set)
                                        [ -n "$resolverSetSupported" ] || return 1
+                                       [ "$target" = 'src' ] && return 1 # dnsmasq doesn't populate nft sets for local addresses
                                        nftset 'create_dnsmasq_set' "$iface" "$target" "$type" "$uid" "$name" "$value"
                                ;;
                                check_support)
@@ -1165,6 +1259,7 @@ resolver() {
                                        rm -f "$packageDnsmasqFile"
                                        config_load 'dhcp'
                                        config_foreach _dnsmasq_instance_config 'dnsmasq' 'cleanup'
+                                       return 0
                                ;;
                                configure)
                                        [ -n "$resolverSetSupported" ] || return 1
@@ -1215,7 +1310,23 @@ resolver() {
                                        [ "$resolverNewHash" != "$resolverStoredHash" ]
                                ;;
                                store_hash)
-                                       [ -s "$packageDnsmasqFile" ] && resolverStoredHash="$(md5sum "$packageDnsmasqFile" | awk '{ print $1; }')";;
+                                       [ -s "$packageDnsmasqFile" ] && resolverStoredHash="$(md5sum "$packageDnsmasqFile" | awk '{ print $1; }')"
+                                       return 0
+                               ;;
+                               wait)
+                                       [ -n "$resolverWorkingFlag" ] && return 0
+                                       local timeout="${iface:-30}" count=0
+                                       local hostname="$(uci_get 'system' '@system[0]' 'hostname' 'OpenWrt')"
+                                       while [ "$count" -lt "$timeout" ]; do
+                                               if resolveip "$hostname" >/dev/null 2>&1; then
+                                                       resolverWorkingFlag='true'
+                                                       return 0
+                                               fi
+                                               sleep 1
+                                               count=$((count + 1))
+                                       done
+                                       return 1
+                               ;;
                        esac
                ;;
                unbound.nftset)
@@ -1239,71 +1350,131 @@ netifd() {
        # Usage: netifd install [iface] | netifd remove [iface] | netifd uninstall
        _netifd_process_interface() {
                local iface="$1" action="${2:-install}"
-               local rt_name="${ipTablePrefix}_${iface%6}"
+               # Normalize table name for split uplink scenarios
+               local rt_name="${ipTablePrefix}_${iface}"
+               if is_split_uplink && [ "$iface" = "$uplink_interface6" ]; then
+                       rt_name="${ipTablePrefix}_${uplink_interface4}"
+               fi
 
+               # clean-up first for repeated netifd install calls in different netifd_strict_enforcement modes
+               uci_remove 'network' "${iface}" 'ip4table' 2>/dev/null
+               uci_remove 'network' "${iface}" 'ip6table' 2>/dev/null
                uci_remove 'network' 'rule' "${rt_name}_ipv4"  2>/dev/null
                uci_remove 'network' 'rule6' "${rt_name}_ipv6" 2>/dev/null
 
+               # process local interfaces and set up rules for LANs if netifd_strict_enforcement is enabled
                if [ -n "$netifd_strict_enforcement" ] && str_contains "$netifd_interface_local" "$iface"; then
-                       if [ -n "$netifd_interface_default" ]; then
-                               uci_add 'network' 'rule' "${rt_name}_ipv4"
-                               uci_set 'network' "${rt_name}_ipv4" 'in' "${iface}"
-                               uci_set 'network' "${rt_name}_ipv4" 'lookup' "${ipTablePrefix}_${netifd_interface_default}"
-                               uci_set 'network' "${rt_name}_ipv4" 'priority' "${lan_priority}"
-                       fi
-                       if [ -n "$ipv6_enabled" ] && [ -n "$netifd_interface_default6" ]; then
-                               uci_add 'network' 'rule6' "${rt_name}_ipv6"
-                               uci_set 'network' "${rt_name}_ipv6" 'in' "${iface}"
-                               uci_set 'network' "${rt_name}_ipv6" 'lookup' "${ipTablePrefix}_${netifd_interface_default6}"
-                               uci_set 'network' "${rt_name}_ipv6" 'priority' "${lan_priority}"
-                       fi
-               lan_priority="$((lan_priority + 1))"
+                       case "$action" in
+                               install)
+                                       if [ -n "$netifd_interface_default" ]; then
+                                               uci_add 'network' 'rule' "${rt_name}_ipv4"
+                                               uci_set 'network' "${rt_name}_ipv4" 'in' "${iface}"
+                                               uci_set 'network' "${rt_name}_ipv4" 'lookup' "${ipTablePrefix}_${netifd_interface_default}"
+                                               uci_set 'network' "${rt_name}_ipv4" 'priority' "${lan_priority}"
+                                       fi
+                                       if [ -n "$ipv6_enabled" ] && [ -n "$netifd_interface_default6" ]; then
+                                               uci_add 'network' 'rule6' "${rt_name}_ipv6"
+                                               uci_set 'network' "${rt_name}_ipv6" 'in' "${iface}"
+                                               uci_set 'network' "${rt_name}_ipv6" 'lookup' "${ipTablePrefix}_${netifd_interface_default6}"
+                                               uci_set 'network' "${rt_name}_ipv6" 'priority' "${lan_priority}"
+                                       fi
+                                       lan_priority="$((lan_priority + 1))"
+                               ;;
+                               remove|uninstall)
+                                       : # rules already removed above
+                               ;;
+                       esac
                fi
+
+               # process only WAN and supported tunnels below
                is_supported_interface "$iface" || return 0
 
-               if [ -z "$target_iface" ] || [ "$target_ifance" = "$iface" ]; then
-                       is_wan6 "$iface" && return # TODO: properly process wan/wan6 at some point
-                       if [ -z "$netifd_strict_enforcement" ] && [ "$netifd_interface_default" = "$iface" ]; then
-                               rt_name='main'
+               local _mark="$mark" _priority="$priority" _tid="$tid"
+               local splitUplinkSecondIface
+
+               if is_split_uplink; then
+                       if is_wan "$iface" || is_wan6 "$iface"; then
+                               if [ -n "$_uplinkMark" ] && [ -n "$_uplinkPriority" ] && [ -n "$_uplinkTableID" ]; then
+                                       _mark="$_uplinkMark"
+                                       _priority="$_uplinkPriority"
+                                       _tid="$_uplinkTableID"
+                                       splitUplinkSecondIface='true'
+                               else
+                                       _uplinkMark="$_mark"
+                                       _uplinkPriority="$_priority"
+                                       _uplinkTableID="$_tid"
+                               fi
                        fi
+               fi
+
+               # use main table for default uplink if netifd_strict_enforcement is not enabled
+               [ -z "$netifd_strict_enforcement" ] && [ "$netifd_interface_default" = "$iface" ] && \
+                       rt_name='main'
+
+               if [ -z "$target_iface" ] || [ "$target_iface" = "$iface" ]; then
                        case "$action" in
                                install)
                                        output 2 "Setting up netifd extensions for $iface... "
-                                       [ "$rt_name" = 'main' ] || sed -i "\#${rt_name}\$#d" "$rtTablesFile" >/dev/null 2>&1
-                                       [ "$rt_name" = 'main' ] || echo "${tid} ${rt_name}" >> "$rtTablesFile"
-                                       uci_set 'network' "${iface}" 'ip4table' "${rt_name}"
-                                       uci_set 'network' "${iface}" 'ip6table' "${rt_name}"
-                                       uci_add 'network' 'rule' "${rt_name}_ipv4"
-                                       uci_set 'network' "${rt_name}_ipv4" 'priority' "${priority}"
-                                       uci_set 'network' "${rt_name}_ipv4" 'lookup' "${rt_name}"
-                                       uci_set 'network' "${rt_name}_ipv4" 'mark' "${mark}"
-                                       uci_set 'network' "${rt_name}_ipv4" 'mask' "${fw_mask}"
-                                       if [ -n "$ipv6_enabled" ]; then
+                                       if ! is_split_uplink || ! is_wan6 "$iface"; then
+                                               uci_set 'network' "${iface}" 'ip4table' "${rt_name}"
+                                               uci_add 'network' 'rule' "${rt_name}_ipv4"
+                                               uci_set 'network' "${rt_name}_ipv4" 'priority' "${_priority}"
+                                               uci_set 'network' "${rt_name}_ipv4" 'lookup' "${rt_name}"
+                                               uci_set 'network' "${rt_name}_ipv4" 'mark' "${_mark}"
+                                               uci_set 'network' "${rt_name}_ipv4" 'mask' "${fw_mask}"
+                                       fi
+                                       if [ -n "$ipv6_enabled" ] && { ! is_split_uplink || ! is_wan "$iface"; }; then
+                                               uci_set 'network' "${iface}" 'ip6table' "${rt_name}"
                                                uci_add 'network' 'rule6' "${rt_name}_ipv6"
-                                               uci_set 'network' "${rt_name}_ipv6" 'priority' "${priority}"
+                                               uci_set 'network' "${rt_name}_ipv6" 'priority' "${_priority}"
                                                uci_set 'network' "${rt_name}_ipv6" 'lookup' "${rt_name}"
-                                               uci_set 'network' "${rt_name}_ipv6" 'mark' "${mark}"
+                                               uci_set 'network' "${rt_name}_ipv6" 'mark' "${_mark}"
                                                uci_set 'network' "${rt_name}_ipv6" 'mask' "${fw_mask}"
                                        fi
-                                       sed -i "\#${mark}#d" "$nftTempFile" >/dev/null 2>&1
-                                       nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}"
-                                       nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} ${nftRuleParams} meta mark set (meta mark & ${fw_maskXor}) | ${mark}"
-                                       nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return"
+                                       if ! is_split_uplink || ! is_wan6 "$iface"; then
+                                       [ "$rt_name" = 'main' ] || sed -i "\#${rt_name}\$#d" "$rtTablesFile" >/dev/null 2>&1
+                                       [ "$rt_name" = 'main' ] || echo "${_tid} ${rt_name}" >> "$rtTablesFile"
+                                               nft_file 'sed' 'temp' "\#${_mark}#d"
+                                       fi
+                                       if ! nft_file 'match' 'temp' "${nftPrefix}_mark_${_mark}"; then
+                                               nft add chain inet "$nftTable" "${nftPrefix}_mark_${_mark}"
+                                               nft add rule inet "$nftTable" "${nftPrefix}_mark_${_mark} ${nftRuleParams} meta mark set (meta mark & ${fw_maskXor}) | ${_mark}"
+                                               nft add rule inet "$nftTable" "${nftPrefix}_mark_${_mark} return"
+                                       fi
+                                       local dscp="$(uci_get "$packageName" 'config' "${iface}_dscp")"
+                                       if [ "${dscp:-0}" -ge '1' ] && [ "${dscp:-0}" -le '63' ]; then
+                                               if ! is_split_uplink || ! is_wan6 "$iface"; then
+                                                       nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv4Flag} dscp ${dscp} ${nftRuleParams} goto ${nftPrefix}_mark_${_mark}"
+                                               fi
+                                               if [ -n "$ipv6_enabled" ] && { ! is_split_uplink || ! is_wan "$iface"; }; then
+                                                       nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv6Flag} dscp ${dscp} ${nftRuleParams} goto ${nftPrefix}_mark_${_mark}"
+                                               fi
+                                       fi
+                                       if [ "$iface" = "$icmp_interface" ]; then
+                                               if ! is_split_uplink || ! is_wan6 "$iface"; then
+                                                       nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv4Flag} protocol icmp ${nftRuleParams} goto ${nftPrefix}_mark_${_mark}"
+                                               fi
+                                               if [ -n "$ipv6_enabled" ] && { ! is_split_uplink || ! is_wan "$iface"; }; then
+                                                       nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv6Flag} protocol icmp ${nftRuleParams} goto ${nftPrefix}_mark_${_mark}"
+                                               fi
+                                       fi
+
                                        output_okb
                                ;;
                                remove|uninstall)
                                        output 2 "Removing netifd extensions for $iface... "
                                        [ "$rt_name" = 'main' ] || sed -i "\#${rt_name}\$#d" "$rtTablesFile" >/dev/null 2>&1
-                                       sed -i "\#${mark}#d" "$nftNetifdPermFile" >/dev/null 2>&1
-                                       uci_remove 'network' "${iface}" 'ip4table' "${rt_name}" 2>/dev/null
-                                       uci_remove 'network' "${iface}" 'ip6table' "${rt_name}" 2>/dev/null
+                                       nft_file 'sed' 'netifd' "\#${_mark}#d"
                                        output_okb
                                ;;
                        esac
                fi
-               mark="$(printf '0x%06x' $((mark + uplink_mark)))"
-               priority="$((priority - 1))"
-               tid="$((tid + 1))"
+
+               if [ -z "$splitUplinkSecondIface" ]; then
+                       mark="$(printf '0x%06x' $((_mark + uplink_mark)))"
+                       priority="$((_priority - 1))"
+                       tid="$((_tid + 1))"
+               fi
        }
 
        load_package_config
@@ -1311,10 +1482,11 @@ netifd() {
 
        local action="${1:-install}"
        local target_iface="$2"
+       local lan_priority="$((uplink_ip_rules_priority + 1000))"
        local mark="$(printf '0x%06x' "$uplink_mark")"
        local priority="$uplink_ip_rules_priority"
-       local lan_priority="$((uplink_ip_rules_priority + 1000))"
        local tid="$(get_rt_tables_non_pbr_next_id)"
+       local _uplinkMark _uplinkPriority _uplinkTableID
 
        case "$action" in
                check)
@@ -1344,7 +1516,7 @@ netifd() {
                        fi
                        if [ -z "$netifd_interface_local" ]; then
                                json add warning 'warningNetifdMissingInterfaceLocal' 'lan'
-                               output_error 'warningNetifdMissingInterfaceLocal' 'lan'
+                               output_warning 'warningNetifdMissingInterfaceLocal' 'lan'
                                netifd_interface_local='lan'
                        fi
                        [ "$netifd_strict_enforcement" = '1' ] || unset netifd_strict_enforcement
@@ -1355,34 +1527,34 @@ netifd() {
                ;;
        esac
 
-       nft_file 'netifd_create'
+       nft_file 'create' 'netifd'
        output 1 "Netifd extensions $action ${target_iface:+on $target_iface }"
+       uci_remove 'network' 'rule' "main_ipv4"  2>/dev/null
+       uci_remove 'network' 'rule6' "main_ipv6" 2>/dev/null
        config_load 'network'
        config_foreach _netifd_process_interface 'interface' "$action"
        output_1_newline
 
        case "$action" in
                install)
-                       nft_file 'netifd_install'
+                       nft_file 'install' 'netifd'
                        if [ -z "$target_iface" ]; then
                                uci_set "$packageName" 'config' 'netifd_enabled' '1'
                        fi
                ;;
                remove)
                        if [ -z "$target_iface" ]; then
-                               nft_file 'netifd_remove'
+                               nft_file 'delete' 'netifd'
                                uci_remove "$packageName" 'config' 'netifd_enabled' 2>/dev/null
                        fi
                ;;
                uninstall)
-                       nft_file 'netifd_remove'
+                       if [ -z "$target_iface" ]; then
+                               nft_file 'delete' 'netifd'
+                       fi
                ;;
        esac
        uci_commit "$packageName"
-#      cat "$nftNetifdPermFile"
-#      cat "$rtTablesFile"
-#      uci changes
-#      uci revert network
        uci_commit 'network'
        sync
        output "Restarting network ${action:+(on_$action) }"
@@ -1396,23 +1568,24 @@ dns_policy_routing() {
        local negation value dest4 dest6 first_value
        local inline_set_ipv4_empty_flag inline_set_ipv6_empty_flag
        local name="$1" src_addr="$2" dest_dns="$3" uid="$4" dest_dns_port="$5"
+       local dest_dns_ipv4="$6" dest_dns_ipv6="$7"
        local chain='dstnat' iface='dns'
 
        if [ -z "${dest_dns_ipv4}${dest_dns_ipv6}" ]; then
-               processPolicyError='true'
+               processDnsPolicyError='true'
                json add error 'errorPolicyProcessNoInterfaceDns' "'$dest_dns'"
                return 1
        fi
 
        if [ -z "$ipv6_enabled" ] && is_ipv6 "$(str_first_word "$src_addr")"; then
-               processPolicyError='true'
+               processDnsPolicyError='true'
                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'
+               processDnsPolicyError='true'
                json add error 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_dns':'$dest_dns_port'"
                return 1
        fi
@@ -1479,14 +1652,14 @@ dns_policy_routing() {
                fi
 
                if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then
-                       processPolicyError='true'
+                       processDnsPolicyError='true'
                        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'
+                       processDnsPolicyError='true'
                        json add error 'errorPolicyProcessInsertionFailedIpv4' "$name"
                        json add error 'errorPolicyProcessCMD' "nft $param4"
                        logger -t "$packageName" "ERROR: nft $param4"
@@ -1573,24 +1746,31 @@ policy_routing() {
                                param4="${param4:+$param4 }ether saddr ${negation:+$negation }{ $(inline_set "$value") }"
                                param6="${param6:+$param6 }ether saddr ${negation:+$negation }{ $(inline_set "$value") }"
                        elif is_domain "$first_value_src"; then
-                               local inline_set_ipv4='' inline_set_ipv6='' d=''
-                               unset src_inline_set_ipv4_empty_flag
-                               unset src_inline_set_ipv6_empty_flag
-                               for d in $value; do
-                                       local resolved_ipv4 resolved_ipv6
-                                       resolved_ipv4="$(resolveip_to_nftset4 "$d")"
-                                       resolved_ipv6="$(resolveip_to_nftset6 "$d")"
-                                       if [ -z "${resolved_ipv4}${resolved_ipv6}" ]; then
-                                               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"
-                                       fi
-                               done
-                               [ -n "$inline_set_ipv4" ] || src_inline_set_ipv4_empty_flag='true'
-                               [ -n "$inline_set_ipv6" ] || src_inline_set_ipv6_empty_flag='true'
-                               param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }{ $inline_set_ipv4 }"
-                               param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }{ $inline_set_ipv6 }"
+                               local target='src' 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} saddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}${nftset_suffix}"
+                                       param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}${nftset_suffix}"
+                               else
+                                       local inline_set_ipv4='' inline_set_ipv6='' d=''
+                                       unset src_inline_set_ipv4_empty_flag
+                                       unset src_inline_set_ipv6_empty_flag
+                                       for d in $value; do
+                                               local resolved_ipv4 resolved_ipv6
+                                               resolved_ipv4="$(resolveip_to_nftset4 "$d")"
+                                               resolved_ipv6="$(resolveip_to_nftset6 "$d")"
+                                               if [ -z "${resolved_ipv4}${resolved_ipv6}" ]; then
+                                                       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"
+                                               fi
+                                       done
+                                       [ -n "$inline_set_ipv4" ] || src_inline_set_ipv4_empty_flag='true'
+                                       [ -n "$inline_set_ipv6" ] || src_inline_set_ipv6_empty_flag='true'
+                                       param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }{ $inline_set_ipv4 }"
+                                       param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }{ $inline_set_ipv6 }"
+                               fi
                        else
                                param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }{ $(inline_set "$value") }"
                                param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }{ $(inline_set "$value") }"
@@ -1733,6 +1913,15 @@ dns_policy_process() {
        src_addr="$(str_extras_to_space "$src_addr")"
        dest_dns="$(str_extras_to_space "$dest_dns")"
 
+       unset j
+       for i in $src_addr; do
+               if is_url "$i"; then
+                       i="$(process_url "$i")"
+               fi
+               j="${j:+$j }$i"
+       done
+       src_addr="$j"
+
        local dest_dns_interface dest_dns_ipv4 dest_dns_ipv6
        dest_dns_interface="$(str_first_value_interface "$dest_dns")"
        dest_dns_ipv4="$(str_first_value_ipv4 "$dest_dns")"
@@ -1740,14 +1929,14 @@ dns_policy_process() {
        if is_supported_interface "$dest_dns_interface"; then
                local d
                for d in $(uci -q get network."$dest_dns_interface".dns); do
-                       if ! is_family_mismatch "$src_addr" "$d"; then
-                               if is_ipv4 "$d"; then
-                                       dest_dns_ipv4="${dest_dns_ipv4:-$d}"
-                               elif is_ipv6 "$d"; then
-                                       dest_dns_ipv6="${dest_dns_ipv6:-$d}"
+                               if ! is_family_mismatch "$src_addr" "$d"; then
+                                       if is_ipv4 "$d"; then
+                                               dest_dns_ipv4="${dest_dns_ipv4:-$d}"
+                                       elif is_ipv6 "$d"; then
+                                               dest_dns_ipv6="${dest_dns_ipv6:-$d}"
+                                       fi
                                fi
-                       fi
-               done
+                       done
        fi
 
        unset processDnsPolicyError
@@ -1773,7 +1962,7 @@ dns_policy_process() {
                        if str_contains "$filter_group_src_addr" 'ipv6' && [ -z "$dest_dns_ipv6" ] ; then
                                        continue
                        fi
-                       dns_policy_routing "$name" "$filtered_value_src_addr" "$dest_dns" "$uid" "$dest_dns_port"
+                       dns_policy_routing "$name" "$filtered_value_src_addr" "$dest_dns" "$uid" "$dest_dns_port" "$dest_dns_ipv4" "$dest_dns_ipv6"
                fi
        done
 
@@ -1880,7 +2069,7 @@ policy_process() {
 }
 
 interface_routing() {
-       local action="$1" tid="$2" mark="$3" iface="$4" gw4="$5" dev="$6" gw6="$7" dev6="$8" priority="$9"
+       local action="$1" tid="$2" mark="$3" iface="$4" gw4="$5" dev4="$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
                json add error 'errorInterfaceRoutingEmptyValues'
@@ -1889,44 +2078,51 @@ interface_routing() {
        case "$action" in
                create)
                        is_netifd_interface "$iface" && return 0
-                       ifacesTriggers="${ifacesTriggers:+$ifacesTriggers }$iface"
-                       if ! grep -q "$tid ${ipTablePrefix}_${iface}" "$rtTablesFile"; then
-                               sed -i "/${ipTablePrefix}_${iface}/d" "$rtTablesFile"
-                               echo "$tid ${ipTablePrefix}_${iface}" >> "$rtTablesFile"
-                               sync
+                       # Normalize table name for split uplink scenarios
+                       local table_iface="$iface"
+                       if is_split_uplink && [ "$iface" = "$uplink_interface6" ]; then
+                               table_iface="$uplink_interface4"
                        fi
-                       # Ensure a clean slate for this table before adding routes/rules
-                       ip -4 rule flush table "$tid" >/dev/null 2>&1
-                       ip -4 route flush table "$tid" >/dev/null 2>&1
-                       if [ -n "$ipv6_enabled" ]; then
-                               ip -6 rule flush table "$tid" >/dev/null 2>&1
-                               ip -6 route flush table "$tid" >/dev/null 2>&1
+                       if ! grep -q "$tid ${ipTablePrefix}_${table_iface}" "$rtTablesFile"; then
+                               sed -i "/${ipTablePrefix}_${table_iface}/d" "$rtTablesFile"
+                               echo "$tid ${ipTablePrefix}_${table_iface}" >> "$rtTablesFile"
+                               sync
                        fi
-                       if [ -n "$gw4" ] || [ -n "$strict_enforcement" ]; then
+
+                       if [ -n "$dev4" ]; then
                                ipv4_error=0
-                               if [ -z "$gw4" ]; then
-                                       try ip -4 route replace unreachable default table "$tid" || ipv4_error=1
-                               else
-                                       try ip -4 route replace default via "$gw4" dev "$dev" table "$tid" || ipv4_error=1
+                               ip -4 rule flush table "$tid" >/dev/null 2>&1
+                               ip -4 route flush table "$tid" >/dev/null 2>&1
+
+                               if [ -n "$gw4" ] || [ -n "$strict_enforcement" ]; then
+                                       if [ -z "$gw4" ]; then
+                                               try ip -4 route replace unreachable default table "$tid" || ipv4_error=1
+                                       else
+                                               try ip -4 route replace default via "$gw4" dev "$dev4" table "$tid" || ipv4_error=1
+                                       fi
+                                       try ip -4 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1
                                fi
-                               # try ip -4 rule replace fwmark "${mark}/${fw_mask}" lookup 'main' suppress_prefixlength 0 priority "$((priority - 1000))" || ipv4_error=1
-                               ip -4 rule del lookup 'main' suppress_prefixlength "$prefixlength" priority "$priority" >/dev/null 2>&1
-                               try ip -4 rule add lookup 'main' suppress_prefixlength "$prefixlength" priority "$((priority - 1))" || ipv4_error=1
-                               # {
-                                       # for prio in $(ip -4 rule show | awk '/lookup main/ && /suppress_prefixlength 0/ {gsub(":", "", $1); print $1}'); do
-                                               # rule="$(ip -4 rule show | awk -v p="$prio" '($1==p":"){ $1=""; sub(/^ /,""); print }')"
-                                               # [ -n "$rule" ] || continue
-                                               # rule="${rule/lookup main/lookup $tid}"
-                                               # ip -4 rule replace priority "$prio" $rule >/dev/null 2>&1 || ipv4_error=1
-                                       # done
-                               # }
-                               try ip -4 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1
-                       fi
-                       try nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1
-                       try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} ${nftRuleParams} meta mark set (meta mark & ${fw_maskXor}) | ${mark}" || ipv4_error=1
-                       try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" || ipv4_error=1
-                       if [ -n "$ipv6_enabled" ]; then
+
+                               if ! nft_file 'match' 'temp' "${nftPrefix}_mark_${mark}"; then
+                                       try nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1
+                                       try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} ${nftRuleParams} meta mark set (meta mark & ${fw_maskXor}) | ${mark}" || ipv4_error=1
+                                       try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" || ipv4_error=1
+                               fi
+
+                               dscp="$(uci_get "$packageName" 'config' "${iface}_dscp" '0')"
+                               if [ "$dscp" -ge '1' ] && [ "$dscp" -le '63' ]; then
+                                       try nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv4Flag} dscp ${dscp} ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" || s=1
+                               fi
+                               if [ "$iface" = "$icmp_interface" ]; then
+                                       try nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv4Flag} protocol icmp ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" || s=1
+                               fi
+                       fi
+
+                       if [ -n "$ipv6_enabled" ] && [ -n "$dev6" ]; then
                                ipv6_error=0
+                               ip -6 rule flush table "$tid" >/dev/null 2>&1
+                               ip -6 route flush table "$tid" >/dev/null 2>&1
+
                                if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ -n "$strict_enforcement" ]; then
                                        if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then
                                                try ip -6 route replace unreachable default table "$tid" || ipv6_error=1
@@ -1942,34 +2138,26 @@ interface_routing() {
                                                try ip -6 route replace "$(ip -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" || ipv6_error=1
                                                try ip -6 route replace default dev "$dev6" table "$tid" || ipv6_error=1
                                        fi
-                                       # try ip -6 rule replace fwmark "${mark}/${fw_mask}" lookup 'main' suppress_prefixlength 0 priority "$((priority - 1000))" || ipv6_error=1
-                                       ip -6 rule del lookup 'main' suppress_prefixlength "$prefixlength" priority "$priority" >/dev/null 2>&1
-                                       try ip -6 rule add lookup 'main' suppress_prefixlength "$prefixlength" priority "$((priority - 1))" || ipv6_error=1
-                                       # {
-                                               # for prio in $(ip -6 rule show | awk '/lookup main/ && /suppress_prefixlength 0/ {gsub(":", "", $1); print $1}'); do
-                                                       # rule="$(ip -6 rule show | awk -v p="$prio" '($1==p":"){ $1=""; sub(/^ /,""); print }')"
-                                                       # [ -n "$rule" ] || continue
-                                                       # rule="${rule/lookup main/lookup $tid}"
-                                                       # ip -6 rule replace priority "$prio" $rule >/dev/null 2>&1 || ipv6_error=1
-                                               # done
-                                       # }
                                        try ip -6 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1
                                fi
-                       fi
-                       if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then
-                               dscp="$(uci_get "$packageName" 'config' "${iface}_dscp")"
-                               if [ "${dscp:-0}" -ge '1' ] && [ "${dscp:-0}" -le '63' ]; then
-                                       try nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv4Flag} dscp ${dscp} ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" || s=1
-                                       if [ -n "$ipv6_enabled" ]; then
-                                               try nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv6Flag} dscp ${dscp} ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" || s=1
-                                       fi
+
+                               if ! nft_file 'match' 'temp' "${nftPrefix}_mark_${mark}"; then
+                                       try nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv6_error=1
+                                       try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} ${nftRuleParams} meta mark set (meta mark & ${fw_maskXor}) | ${mark}" || ipv6_error=1
+                                       try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" || ipv6_error=1
+                               fi
+
+                               dscp="$(uci_get "$packageName" 'config' "${iface}_dscp" '0')"
+                               if [ "$dscp" -ge '1' ] && [ "$dscp" -le '63' ]; then
+                                       try nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv6Flag} dscp ${dscp} ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" || s=1
                                fi
                                if [ "$iface" = "$icmp_interface" ]; then
-                                       try nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv4Flag} protocol icmp ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" || s=1
-                                       if [ -n "$ipv6_enabled" ]; then
-                                               try nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv6Flag} protocol icmp ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" || s=1
-                                       fi
+                                       try nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv6Flag} protocol icmp ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" || s=1
                                fi
+                       fi
+
+                       if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then
+                               s=0
                        else
                                s=1
                        fi
@@ -1993,40 +2181,36 @@ interface_routing() {
                        ip -4 route flush table "$tid" >/dev/null 2>&1
                        ip -6 rule flush table "$tid" >/dev/null 2>&1
                        ip -6 route flush table "$tid" >/dev/null 2>&1
-                       sed -i "/${ipTablePrefix}_${iface}\$/d" "$rtTablesFile"
+                       # Normalize table name for split uplink scenarios
+                       local table_iface="$iface"
+                       if is_split_uplink && [ "$iface" = "$uplink_interface6" ]; then
+                               table_iface="$uplink_interface4"
+                       fi
+                       sed -i "/${ipTablePrefix}_${table_iface}\$/d" "$rtTablesFile"
                        sync
                        return "$s"
                ;;
                reload_interface)
                        is_netifd_interface "$iface" && return 0
-                       ipv4_error=0
-                       ip -4 rule flush table "$tid" >/dev/null 2>&1
-                       ip -4 route flush table "$tid" >/dev/null 2>&1
-                       if [ -n "$ipv6_enabled" ]; then
-                               ip -6 rule flush table "$tid" >/dev/null 2>&1
-                               ip -6 route flush table "$tid" >/dev/null 2>&1
-                       fi
-                       if [ -n "$gw4" ] || [ -n "$strict_enforcement" ]; then
-                               if [ -z "$gw4" ]; then
-                                       try ip -4 route replace unreachable default table "$tid" || ipv4_error=1
-                               else
-                                       try ip -4 route replace default via "$gw4" dev "$dev" table "$tid" || ipv4_error=1
+
+                       if [ -n "$dev4" ]; then
+                               ipv4_error=0
+                               ip -4 rule flush fwmark "${mark}/${fw_mask}" table "$tid" >/dev/null 2>&1
+                               ip -4 route flush table "$tid" >/dev/null 2>&1
+                               if [ -n "$gw4" ] || [ -n "$strict_enforcement" ]; then
+                                       if [ -z "$gw4" ]; then
+                                               try ip -4 route replace unreachable default table "$tid" || ipv4_error=1
+                                       else
+                                               try ip -4 route replace default via "$gw4" dev "$dev4" table "$tid" || ipv4_error=1
+                                       fi
+                                       try ip -4 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1
                                fi
-                               # try ip -4 rule replace fwmark "${mark}/${fw_mask}" lookup 'main' suppress_prefixlength 0 priority "$((priority - 1000))" || ipv4_error=1
-                               ip -4 rule del lookup 'main' suppress_prefixlength "$prefixlength" priority "$priority" >/dev/null 2>&1
-                               try ip -4 rule add lookup 'main' suppress_prefixlength "$prefixlength" priority "$((priority - 1))" || ipv4_error=1
-                               # {
-                                       # for prio in $(ip -4 rule show | awk '/lookup main/ && /suppress_prefixlength 0/ {gsub(":", "", $1); print $1}'); do
-                                               # rule="$(ip -4 rule show | awk -v p="$prio" '($1==p":"){ $1=""; sub(/^ /,""); print }')"
-                                               # [ -n "$rule" ] || continue
-                                               # rule="${rule/lookup main/lookup $tid}"
-                                               # ip -4 rule replace priority "$prio" $rule >/dev/null 2>&1 || ipv4_error=1
-                                       # done
-                               # }
-                               try ip -4 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1
-                       fi
-                       if [ -n "$ipv6_enabled" ]; then
+                       fi
+
+                       if [ -n "$ipv6_enabled" ] && [ -n "$dev6" ]; then
                                ipv6_error=0
+                               ip -6 rule flush fwmark "${mark}/${fw_mask}" table "$tid" >/dev/null 2>&1
+                               ip -6 route flush table "$tid" >/dev/null 2>&1
                                if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ -n "$strict_enforcement" ]; then
                                        if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then
                                                try ip -6 route replace unreachable default table "$tid" || ipv6_error=1
@@ -2042,20 +2226,10 @@ interface_routing() {
                                                try ip -6 route replace "$(ip -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" || ipv6_error=1
                                                try ip -6 route replace default dev "$dev6" table "$tid" || ipv6_error=1
                                        fi
-                                       # try ip -6 rule replace fwmark "${mark}/${fw_mask}" lookup 'main' suppress_prefixlength 0 priority "$((priority - 1000))" || ipv6_error=1
-                                       ip -6 rule del lookup 'main' suppress_prefixlength "$prefixlength" priority "$priority" >/dev/null 2>&1
-                                       try ip -6 rule add lookup 'main' suppress_prefixlength "$prefixlength" priority "$((priority - 1))" || ipv6_error=1
-                                       # {
-                                               # for prio in $(ip -6 rule show | awk '/lookup main/ && /suppress_prefixlength 0/ {gsub(":", "", $1); print $1}'); do
-                                                       # rule="$(ip -6 rule show | awk -v p="$prio" '($1==p":"){ $1=""; sub(/^ /,""); print }')"
-                                                       # [ -n "$rule" ] || continue
-                                                       # rule="${rule/lookup main/lookup $tid}"
-                                                       # ip -6 rule replace priority "$prio" $rule >/dev/null 2>&1 || ipv6_error=1
-                                               # done
-                                       # }
                                        try ip -6 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1
                                fi
                        fi
+
                        if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then
                                s=0
                        else
@@ -2087,15 +2261,48 @@ json_add_gateway() {
 }
 
 process_interface() {
-       local gw4 gw6 dev dev6 s=0 dscp iface="$1" action="$2" reloadedIface="$3"
+       local gw4 gw6 dev4 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' "$uplink_mark")"
-               ifacePriority="$uplink_ip_rules_priority"
-               unset ifaceTableID
-               return 0
+       if [ "$iface" = 'all' ]; then
+               case "$action" in
+                       reset_globals)
+                               config_load 'network'
+                               ifaceMark="$(printf '0x%06x' "$uplink_mark")"
+                               ifacePriority="$uplink_ip_rules_priority"
+                               unset ifaceTableID
+                               unset _uplinkMark _uplinkPriority _uplinkTableID
+                               return 0
+                       ;;
+                       create_global_rules)
+                               ip -4 rule del lookup 'main' suppress_prefixlength "$prefixlength" priority "$ifacePriority" >/dev/null 2>&1
+                               try ip -4 rule add lookup 'main' suppress_prefixlength "$prefixlength" priority "$ifacePriority" || ipv4_error=1
+                               if [ -n "$ipv6_enabled" ]; then
+                                       ip -6 rule del lookup 'main' suppress_prefixlength "$prefixlength" priority "$ifacePriority" >/dev/null 2>&1
+                                       try ip -6 rule add lookup 'main' suppress_prefixlength "$prefixlength" priority "$ifacePriority" || ipv6_error=1
+                               fi
+                               _wg_server() {
+                                       local iface="$1"
+                                       if is_wg_server "$iface" && ! is_ignored_interface "$iface"; then
+                                               local disabled listen_port
+                                               config_get disabled "$iface" 'disabled'
+                                               config_get listen_port "$iface" 'listen_port'
+                                               if [ "$disabled" != '1' ] && [ -n "$listen_port" ]; then
+                                                       if [ -n "$uplink_interface4" ]; then
+                                                               ip rule del sport "$listen_port" table "${ipTablePrefix}_${uplink_interface4}" >/dev/null 2>&1
+                                                               ip rule add sport "$listen_port" table "${ipTablePrefix}_${uplink_interface4}" >/dev/null 2>&1
+                                                               if [ -n "$ipv6_enabled" ]; then
+                                                                       ip -6 rule del sport "$listen_port" table "${ipTablePrefix}_${uplink_interface4}" >/dev/null 2>&1
+                                                                       ip -6 rule add sport "$listen_port" table "${ipTablePrefix}_${uplink_interface4}" >/dev/null 2>&1
+                                                               fi
+                                                       fi
+                                               fi
+                                       fi
+                               }
+                               config_foreach _wg_server 'interface'
+                               return 0
+                       ;;
+               esac
        fi
 
        if [ "$iface" = 'tor' ]; then
@@ -2113,26 +2320,12 @@ process_interface() {
        fi
 
        if is_wg_server "$iface" && ! is_ignored_interface "$iface"; then
-               local disabled listen_port
-               disabled="$(uci_get 'network' "$iface" 'disabled')"
-               listen_port="$(uci_get 'network' "$iface" 'listen_port')"
                case "$action" in
-                       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
-                                               ip rule add sport "$listen_port" table "pbr_${wanIface4}" >/dev/null 2>&1
-                                       fi
-                                       if [ -n "$ipv6_enabled" ] && [ -n "$wanIface6" ]; then
-                                               ip -6 rule del sport "$listen_port" table "pbr_${wanIface4}" >/dev/null 2>&1
-                                               ip -6 rule add sport "$listen_port" table "pbr_${wanIface4}" >/dev/null 2>&1
-                                       fi
-                               fi
-                       ;;
                        destroy)
+                               local listen_port="$(uci_get 'network' "$iface" 'listen_port')"
                                if [ -n "$listen_port" ]; then
-                                       ip rule del sport "$listen_port" table "pbr_${wanIface4}" >/dev/null 2>&1
-                                       ip -6 rule del sport "$listen_port" table "pbr_${wanIface4}" >/dev/null 2>&1
+                                       ip rule del sport "$listen_port" table "pbr_${uplink_interface4}" >/dev/null 2>&1
+                                       ip -6 rule del sport "$listen_port" table "pbr_${uplink_interface4}" >/dev/null 2>&1
                                fi
                        ;;
                esac
@@ -2140,46 +2333,76 @@ process_interface() {
        fi
 
        is_supported_interface "$iface" || return 0
-       is_wan6 "$iface" && return 0
-       [ "$((ifaceMark))" -gt "$((fw_mask))" ] && return 1
+       if [ "$((ifaceMark))" -gt "$((fw_mask))" ]; then
+               json add error 'errorInterfaceMarkOverflow' "$iface"
+               return 1
+       fi
 
        if is_ovpn "$iface" && ! is_ovpn_valid "$iface"; then
-               : || json add warning 'warningInvalidOVPNConfig' "$iface"
+               : # output_warning '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"
+       network_get_device dev4 "$iface"
+       [ -z "$dev4" ] && network_get_physdev dev4 "$iface"
+       if is_wan "$iface" && [ -n "$uplink_interface6" ]; then
+               network_get_device dev6 "$uplink_interface6"
+               [ -z "$dev6" ] && network_get_physdev dev6 "$uplink_interface6"
        fi
 
-       [ -z "$dev6" ] && dev6="$dev"
+       [ -z "$dev6" ] && dev6="$dev4"
        [ -z "$ifaceMark" ] && ifaceMark="$(printf '0x%06x' "$uplink_mark")"
        [ -z "$ifacePriority" ] && ifacePriority="$uplink_ip_rules_priority"
 
+       local _mark="$ifaceMark" _priority="$ifacePriority" _tid="$ifaceTableID"
+       local splitUplinkSecondIface
+
+       if is_split_uplink; then
+               if is_wan "$iface" || is_wan6 "$iface"; then
+                       if [ -n "$_uplinkMark" ] && [ -n "$_uplinkPriority" ] && [ -n "$_uplinkTableID" ]; then
+                               _mark="$_uplinkMark"
+                               _priority="$_uplinkPriority"
+                               _tid="$_uplinkTableID"
+                               splitUplinkSecondIface='true'
+                       else
+                               _uplinkMark="$ifaceMark"
+                               _uplinkPriority="$ifacePriority"
+                       fi
+               fi
+       fi
+
        case "$action" in
-               pre_init)
-                       [ -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 + uplink_mark)))"
-                       ifacePriority="$((ifacePriority - 1))"
-                       ifaceTableID="$((ifaceTableID + 1))"
-                       return 0
+               enumerate_interface)
+                       if [ -z "$splitUplinkSecondIface" ] && [ -z "$_tid" ]; then
+                               _tid="$(get_rt_tables_non_pbr_next_id)"
+                               ifaceTableID="$_tid"
+                       fi
+
+                       eval "enum_mark_${iface//-/_}"='$_mark'
+                       eval "enum_priority_${iface//-/_}"='$_priority'
+                       eval "enum_tid_${iface//-/_}"='$_tid'
+                       ifacesTriggers="${ifacesTriggers:+$ifacesTriggers }$iface"
                ;;
                create)
-                       ifaceTableID="$(get_rt_tables_id "$iface")"
-                       [ -z "$ifaceTableID" ] && ifaceTableID="$(get_rt_tables_next_id)"
-                       eval "mark_${iface//-/_}"='$ifaceMark'
-                       eval "tid_${iface//-/_}"='$ifaceTableID'
-                       pbr_get_gateway4 gw4 "$iface" "$dev"
+                       if [ -z "$splitUplinkSecondIface" ]; then
+                               _tid="$(get_rt_tables_id "$iface")"
+                               [ -z "$_tid" ] && _tid="$(get_rt_tables_next_id)"
+                               ifaceTableID="$_tid"
+                       fi
+                       eval "mark_${iface//-/_}"='$_mark'
+                       eval "tid_${iface//-/_}"='$_tid'
+                       pbr_get_gateway4 gw4 "$iface" "$dev4"
                        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
+                       if is_split_uplink; then
+                               if is_wan "$iface"; then
+                                       gw6=""; dev6=""
+                               elif is_wan6 "$iface"; then
+                                       gw4=""; dev4=""
+                               fi
+                       fi
+                       [ "$iface" != "$dev4" ] && dispDev="$dev4"
+                       if is_default_dev "$dev4"; then
                                [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__"
                        fi
                        if is_netifd_interface_default "$iface"; then
@@ -2187,8 +2410,8 @@ process_interface() {
                        fi
                        displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}"
                        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"
+                       if interface_routing 'create' "$_tid" "$_mark" "$iface" "$gw4" "$dev4" "$gw6" "$dev6" "$_priority"; then
+                               json_add_gateway 'create' "$_tid" "$_mark" "$iface" "$gw4" "$dev4" "$gw6" "$dev6" "$_priority" "$dispStatus"
                                gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\n"
                                if is_netifd_interface "$iface"; then output_okb; else output_ok; fi
                        else
@@ -2197,58 +2420,78 @@ process_interface() {
                        fi
                ;;
                create_user_set)
-                       ifaceTableID="$(get_rt_tables_id "$iface")"
-                       [ -z "$ifaceTableID" ] && ifaceTableID="$(get_rt_tables_next_id)"
-                       eval "mark_${iface//-/_}"='$ifaceMark'
-                       eval "tid_${iface//-/_}"='$ifaceTableID'
-                       pbr_get_gateway4 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
+                       if [ -z "$splitUplinkSecondIface" ]; then
+                               _tid="$(get_rt_tables_id "$iface")"
+                               [ -z "$_tid" ] && _tid="$(get_rt_tables_next_id)"
+                               ifaceTableID="$_tid"
+                       fi
+                       eval "mark_${iface//-/_}"='$_mark'
+                       eval "tid_${iface//-/_}"='$_tid'
+                       if is_split_uplink; then
+                               if is_wan "$iface"; then
+                                       dev6=""
+                               elif is_wan6 "$iface"; then
+                                       dev4=""
+                               fi
+                       fi
+                       [ "$iface" != "$dev4" ] && dispDev="$dev4"
+                       if is_default_dev "$dev4"; then
                                [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__"
                        fi
                        if is_netifd_interface_default "$iface"; then
                                [ "$verbosity" = '1' ] && dispStatus="$_OKB_" || dispStatus="$__OKB__"
                        fi
-                       displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}"
-                       interface_routing 'create_user_set' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority"
+                       displayText="${iface}/${dispDev:+$dispDev/}"
+                       interface_routing 'create_user_set' "$_tid" "$_mark" "$iface" "" "$dev4" "" "$dev6" "$_priority"
                ;;
                destroy)
-                       ifaceTableID="$(get_rt_tables_id "$iface")"
-                       [ -z "$ifaceTableID" ] && ifaceTableID="$(get_rt_tables_next_id)"
-                       eval "mark_${iface//-/_}"='$ifaceMark'
-                       eval "tid_${iface//-/_}"='$ifaceTableID'
-                       pbr_get_gateway4 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
+                       if [ -z "$splitUplinkSecondIface" ]; then
+                               _tid="$(get_rt_tables_id "$iface")"
+                               [ -z "$_tid" ] && _tid="$(get_rt_tables_next_id)"
+                               ifaceTableID="$_tid"
+                       fi
+                       eval "mark_${iface//-/_}"='$_mark'
+                       eval "tid_${iface//-/_}"='$_tid'
+                       if is_split_uplink; then
+                               if is_wan "$iface"; then
+                                       dev6=""
+                               elif is_wan6 "$iface"; then
+                                       dev4=""
+                               fi
+                       fi
+                       [ "$iface" != "$dev4" ] && dispDev="$dev4"
+                       if is_default_dev "$dev4"; then
                                [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__"
                        fi
                        if is_netifd_interface_default "$iface"; then
                                [ "$verbosity" = '1' ] && dispStatus="$_OKB_" || dispStatus="$__OKB__"
                        fi
-                       displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}"
-                       displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}"
+                       displayText="${iface}/${dispDev:+$dispDev}"
                        output 2 "Removing routing for '$displayText' "
-                       #interface_routing 'destroy' "${ifaceTableID}" "${ifaceMark}" "${iface}"
-                       interface_routing 'destroy' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority"
+                       interface_routing 'destroy' "$_tid" "$_mark" "$iface" "" "$dev4" "" "$dev6" "$_priority"
                        if is_netifd_interface "$iface"; then output_okb; else output_ok; fi
                ;;
                reload)
-                       ifaceTableID="$(get_rt_tables_id "$iface")"
-                       [ -z "$ifaceTableID" ] && ifaceTableID="$(get_rt_tables_next_id)"
-                       eval "mark_${iface//-/_}"='$ifaceMark'
-                       eval "tid_${iface//-/_}"='$ifaceTableID'
-                       pbr_get_gateway4 gw4 "$iface" "$dev"
+                       if [ -z "$splitUplinkSecondIface" ]; then
+                               _tid="$(get_rt_tables_id "$iface")"
+                               [ -z "$_tid" ] && _tid="$(get_rt_tables_next_id)"
+                               ifaceTableID="$_tid"
+                       fi
+                       eval "mark_${iface//-/_}"='$_mark'
+                       eval "tid_${iface//-/_}"='$_tid'
+                       pbr_get_gateway4 gw4 "$iface" "$dev4"
                        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
+                       if is_split_uplink; then
+                               if is_wan "$iface"; then
+                                       gw6=""; dev6=""
+                               elif is_wan6 "$iface"; then
+                                       gw4=""; dev4=""
+                               fi
+                       fi
+                       [ "$iface" != "$dev4" ] && dispDev="$dev4"
+                       if is_default_dev "$dev4"; then
                                [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__"
                        fi
                        if is_netifd_interface_default "$iface"; then
@@ -2258,16 +2501,26 @@ process_interface() {
                        gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\n"
                ;;
                reload_interface)
-                       ifaceTableID="$(get_rt_tables_id "$iface")"
-                       [ -z "$ifaceTableID" ] && ifaceTableID="$(get_rt_tables_next_id)"
-                       eval "mark_${iface//-/_}"='$ifaceMark'
-                       eval "tid_${iface//-/_}"='$ifaceTableID'
-                       pbr_get_gateway4 gw4 "$iface" "$dev"
+                       if [ -z "$splitUplinkSecondIface" ]; then
+                               _tid="$(get_rt_tables_id "$iface")"
+                               [ -z "$_tid" ] && _tid="$(get_rt_tables_next_id)"
+                               ifaceTableID="$_tid"
+                       fi
+                       eval "mark_${iface//-/_}"='$_mark'
+                       eval "tid_${iface//-/_}"='$_tid'
+                       pbr_get_gateway4 gw4 "$iface" "$dev4"
                        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
+                       if is_split_uplink; then
+                               if is_wan "$iface"; then
+                                       gw6=""; dev6=""
+                               elif is_wan6 "$iface"; then
+                                       gw4=""; dev4=""
+                               fi
+                       fi
+                       [ "$iface" != "$dev4" ] && dispDev="$dev4"
+                       if is_default_dev "$dev4"; then
                                [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__"
                        fi
                        if is_netifd_interface_default "$iface"; then
@@ -2276,8 +2529,8 @@ process_interface() {
                        displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}"
                        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"
+                               if interface_routing 'reload_interface' "$_tid" "$_mark" "$iface" "$gw4" "$dev4" "$gw6" "$dev6" "$_priority"; then
+                                       json_add_gateway 'reload_interface' "$_tid" "$_mark" "$iface" "$gw4" "$dev4" "$gw6" "$dev6" "$_priority" "$dispStatus"
                                        gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\n"
                                        if is_netifd_interface "$iface"; then output_okb; else output_ok; fi
                                else
@@ -2285,13 +2538,23 @@ process_interface() {
                                        output_fail
                                fi
                        else
-                               json_add_gateway 'skip_interface' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority" "$dispStatus"
+                               json_add_gateway 'skip_interface' "$_tid" "$_mark" "$iface" "$gw4" "$dev4" "$gw6" "$dev6" "$_priority" "$dispStatus"
                                gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\n"
                        fi
                ;;
        esac
-       ifaceMark="$(printf '0x%06x' $((ifaceMark + uplink_mark)))"
-       ifacePriority="$((ifacePriority - 1))"
+
+       if is_split_uplink && [ -z "$splitUplinkSecondIface" ]; then
+               if is_wan "$iface" || is_wan6 "$iface"; then
+                       _uplinkTableID="$_tid"
+               fi
+       fi
+
+       if [ -z "$splitUplinkSecondIface" ]; then
+               ifaceMark="$(printf '0x%06x' $((ifaceMark + uplink_mark)))"
+               ifacePriority="$((ifacePriority - 1))"
+               ifaceTableID="$((ifaceTableID + 1))"
+       fi
        return $s
 }
 
@@ -2329,7 +2592,7 @@ user_file_process() {
 }
 
 boot() {
-       nft_file 'delete'
+       nft_file 'delete' 'main'
        rc_procd start_service 'on_boot' && service_started 'on_boot'
 }
 
@@ -2353,10 +2616,15 @@ start_service() {
        load_environment "${param:-on_start}" "$(load_validate_config)" || return 1
 
        output "Processing environment (${param:-on_start}) "
-       is_wan_up "$param" || { output_error "$(get_text 'errorUplinkDown')";  return 1; }
+       if ! is_wan_up "$param"; then
+               output_failn
+               output_warning "$(get_text 'warningUplinkDown')"
+               pbrBootFlag=1
+               return 0
+       fi
 
-       process_interface 'all' 'prepare'
-       config_foreach process_interface 'interface' 'pre_init'
+       process_interface 'all' 'reset_globals'
+       config_foreach process_interface 'interface' 'enumerate_interface'
 
        case "$param" in
                on_boot)
@@ -2367,10 +2635,10 @@ start_service() {
                ;;
                on_interface_reload)
                        reloadedIface="$2"
-                       local tid pre_init_tid
+                       local tid enum_tid
                        tid="$(get_rt_tables_id "$reloadedIface")"
-                       pre_init_tid="$(eval echo "\$pre_init_tid_${reloadedIface//-/_}")"
-                       if [ "$tid" = "$pre_init_tid" ]; then
+                       enum_tid="$(eval echo "\$enum_tid_${reloadedIface//-/_}")"
+                       if [ "$tid" = "$enum_tid" ]; then
                                serviceStartTrigger='on_interface_reload'
                        else
                                serviceStartTrigger='on_start'
@@ -2413,7 +2681,7 @@ start_service() {
                        output_okn
                        output 1 "Reloading Interface: $reloadedIface "
                        json_add_array 'gateways'
-                       process_interface 'all' 'prepare'
+                       process_interface 'all' 'reset_globals'
                        config_foreach process_interface 'interface' 'reload_interface' "$reloadedIface"
                        json_close_array
                        output_1_newline
@@ -2421,19 +2689,16 @@ start_service() {
                on_reload|on_start|*)
                        resolver 'store_hash'
                        resolver 'configure'
-#                      cleanup_main_table
-#                      cleanup_main_chains
-#                      cleanup_sets
-#                      cleanup_marking_chains
-                       cleanup_rt_tables
-                       nft_file 'create'
+                       cleanup 'main_table' 'rt_tables' 'main_chains' 'sets'
+                       nft_file 'create' 'main'
                        output_okn
                        output 1 'Processing interfaces '
                        json_add_array 'gateways'
-                       process_interface 'all' 'prepare'
+                       process_interface 'all' 'reset_globals'
                        config_foreach process_interface 'interface' 'create'
                        process_interface 'tor' 'destroy'
                        is_tor_running && process_interface 'tor' 'create'
+                       process_interface 'all' 'create_global_rules'
                        json_close_array
                        ip route flush cache
                        output_1_newline
@@ -2450,7 +2715,7 @@ start_service() {
                                output_1_newline
                        fi
                        if is_config_enabled 'include' || [ -d "/etc/${packageName}.d/" ]; then
-                               process_interface 'all' 'prepare'
+                               process_interface 'all' 'reset_globals'
                                config_foreach process_interface 'interface' 'create_user_set'
                                output 1 'Processing user file(s) '
                                config_load "$packageName"
@@ -2464,7 +2729,7 @@ start_service() {
                                fi
                                output_1_newline
                        fi
-                       nft_file 'install'
+                       nft_file 'install' 'main'
                        resolver 'compare_hash' && resolver 'restart'
                ;;
        esac
@@ -2500,7 +2765,7 @@ service_running() { procd_set_config_changed firewall; }
 service_started() {
        [ -n "$pbrBootFlag" ] && return 0
        local error warning c
-       if nft_file 'exists'; then
+       if nft_file 'exists' 'main'; then
                procd_set_config_changed firewall
                [ -n "$gatewaySummary" ] && output "$serviceName (fw4 nft file mode) started with gateways:\n${gatewaySummary}"
        else
@@ -2575,28 +2840,27 @@ stop_service() {
        rm -f "$packageLockFile"
        [ "$1" = 'quiet' ] && quiet_mode 'on'
        load_environment 'on_stop'
-       if nft_file 'exists'; then
+       if nft_file 'exists' 'main'; then
                nft_file_mode=1
        fi
-       output 'Resetting chains and sets '
-#      if nft_file 'delete' && cleanup_main_table && cleanup_main_chains && cleanup_sets && cleanup_marking_chains; then
-       if nft_file 'delete'; then
+       output 'Resetting routing '
+       if nft_file 'delete' 'main' && \
+               cleanup 'main_table' 'rt_tables' 'main_chains' && \
+               ip route flush cache; then
                output_okn
        else
                output_failn
        fi
-       output 1 'Resetting interfaces '
-       config_load 'network'
-       config_foreach process_interface 'interface' 'destroy'
-       process_interface 'tor' 'destroy'
-       cleanup_rt_tables
-       output 1 "\n"
-       ip route flush cache
        unset ifaceMark
        unset ifaceTableID
-       resolver 'store_hash'
-       resolver 'cleanup'
+       output 'Resetting resolver '
+       if resolver 'store_hash' && resolver 'cleanup'; then
+               output_okn
+       else
+               output_failn
+       fi
        resolver 'compare_hash' && resolver 'restart'
+
        if [ -n "$enabled" ]; then
                if [ -n "$nft_file_mode" ]; then
                        output "$serviceName (fw4 nft file mode) stopped "; output_okn;
@@ -2609,41 +2873,40 @@ stop_service() {
 version() { echo "$PKG_VERSION"; }
 
 status_service() {
-       local i dev dev6 wanTID ipv6_enabled
+       local i dev4 dev6 wanTID ipv6_enabled
 
-       [ "$(uci_get "$packageName" config ipv6_enabled)" = "1" ] && ipv6_enabled='true'
+       load_package_config 'status'
+       load_network 'status'
 
-       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 -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}')"
-               [ "$wanGW6" = "default" ] && wanGW6="$(ip -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}')"
-       fi
+       [ -f "/etc/os-release" ] && . /etc/os-release
        while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done
        [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support"
 # shellcheck disable=SC2154
-       status="$serviceName installed 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}."
+       status="$serviceName on $OPENWRT_RELEASE.\n"
+       if [ -n "$uplink_interface4" ]; then
+               network_get_device dev4 "$uplink_interface4"
+               [ -z "$dev4" ] && network_get_physdev dev4 "$uplink_interface4"
+               status="${status}Uplink (IPv4): ${uplink_interface4}${dev4:+/$dev4}/${uplinkGW4:-0.0.0.0}.\n"
+       fi
+       if [ -n "$uplink_interface6" ]; then
+               network_get_device dev6 "$uplink_interface6"
+               [ -z "$dev6" ] && network_get_physdev dev6 "$uplink_interface6"
+               [ -z "$dev6" ] && dev6="$dev4"
+               status="${status}Uplink (IPv6): ${uplink_interface6}${dev6:+/$dev6}/${uplinkGW6:-::/0}.\n"
+       fi
 
        echo "$_SEPARATOR_"
        echo "$packageName - environment"
-       echo "$status"
+       echo -en "$status"
        echo "$_SEPARATOR_"
        dnsmasq --version 2>/dev/null | sed '/^$/,$d'
-       if nft_file 'netifd_exists'; then
+       if nft_file 'exists' 'netifd'; then
                echo "$_SEPARATOR_"
-               echo "$packageName fw4 netifd nft file: $nftNetifdPermFile"
-               sed '1d;2d;' "$nftNetifdPermFile"
+               nft_file 'show' 'netifd'
        fi
-       if nft_file 'exists'; then
+       if nft_file 'exists' 'main'; then
                echo "$_SEPARATOR_"
-               echo "$packageName fw4 nft file: $nftPermFile"
-               sed '1d;2d;' "$nftPermFile"
+               nft_file 'show' 'main'
        fi
        echo "$_SEPARATOR_"
        echo "$packageName chains - policies"
@@ -2728,7 +2991,7 @@ load_validate_dns_policy() {
        local src_addr
        local dest_dns
        local dest_dns_port
-       uci_load_validate "$packageName" 'policy' "$1" "${2}${3:+ $3}" \
+       uci_load_validate "$packageName" 'dns_policy' "$1" "${2}${3:+ $3}" \
                'name:string:Untitled' \
                'enabled:bool:1' \
                'src_addr:list(neg(or(host,network,macaddr,string)))' \
diff --git a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_forward/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_forward/30-pbr.nft
deleted file mode 100644 (file)
index d11ad84..0000000
+++ /dev/null
@@ -1 +0,0 @@
-jump pbr_forward comment "Jump into pbr forward chain";
diff --git a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_output/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_output/30-pbr.nft
deleted file mode 100644 (file)
index c98514b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-jump pbr_output comment "Jump into pbr output chain";
diff --git a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_prerouting/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_prerouting/30-pbr.nft
deleted file mode 100644 (file)
index a4471d3..0000000
+++ /dev/null
@@ -1 +0,0 @@
-jump pbr_prerouting comment "Jump into pbr prerouting chain";
diff --git a/net/pbr/files/usr/share/nftables.d/chain-pre/dstnat/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-pre/dstnat/30-pbr.nft
deleted file mode 100644 (file)
index 987eece..0000000
+++ /dev/null
@@ -1 +0,0 @@
-jump pbr_dstnat comment "Jump into pbr dstnat chain";
diff --git a/net/pbr/files/usr/share/nftables.d/table-post/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/table-post/30-pbr.nft
deleted file mode 100644 (file)
index 0f1effc..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-chain pbr_dstnat {}
-chain pbr_forward {}
-chain pbr_output {}
-chain pbr_prerouting {}
git clone https://git.99rst.org/PROJECT