diff --git a/net/pbr/Makefile b/net/pbr/Makefile index 4dc30b0a19..f2af31b582 100644 --- a/net/pbr/Makefile +++ b/net/pbr/Makefile @@ -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 @@ -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 diff --git a/net/pbr/files/README.md b/net/pbr/README.md similarity index 100% rename from net/pbr/files/README.md rename to net/pbr/README.md diff --git a/net/pbr/files/etc/init.d/pbr b/net/pbr/files/etc/init.d/pbr index 69cc5b259c..c846e73144 100755 --- a/net/pbr/files/etc/init.d/pbr +++ b/net/pbr/files/etc/init.d/pbr @@ -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" - ;; - enabled) - return 0 - ;; - exists) - [ -s "$nftPermFile" ] && return 0 || return 1 - ;; - install) - [ -s "$nftTempFile" ] || return 1 - output "Installing fw4 nft file " - if nft_call -c -f "$nftTempFile" && \ - cp -f "$nftTempFile" "$nftPermFile"; then - output_okn - else - json add error 'errorNftFileInstall' "$nftTempFile" - output_failn - fi - ;; - netifd_exists) - [ -s "$nftNetifdPermFile" ] && return 0 || return 1 - ;; - netifd_create) - rm -f "$nftTempFile" "$nftNetifdPermFile" - for i in "$nftTempFile" "$nftNetifdPermFile"; do + create:netifd) + rm -f "$nftTempFile" "$nftNetifdFile" + for i in "$nftTempFile" "$nftNetifdFile"; do mkdir -p "${i%/*}" done { echo '#!/usr/sbin/nft -f'; echo ''; } > "$nftTempFile" ;; - netifd_delete|netifd_rm) - rm -f "$nftNetifdPermFile" + delete:main) + rm -f "$nftTempFile" "$nftMainFile" ;; - netifd_install) + delete:netifd) + output "Removing fw4 netifd nft file " + if rm -f "$nftNetifdFile"; then + output_okbn + else + json add error 'errorNftNetifdFileDelete' "$nftNetifdFile" + output_failn + fi + ;; + exists:main) + [ -s "$nftMainFile" ] && return 0 || return 1 + ;; + exists:netifd) + [ -s "$nftNetifdFile" ] && return 0 || return 1 + ;; + install:main) + [ -s "$nftTempFile" ] || return 1 + output "Installing fw4 nft file " + if nft_call -c -f "$nftTempFile" && \ + cp -f "$nftTempFile" "$nftMainFile"; then + output_okn + else + json add error 'errorNftMainFileInstall' "$nftTempFile" + output_failn + fi + ;; + 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" + # Normalize table name for split uplink scenarios + local table_iface="$iface" + if is_split_uplink && [ "$iface" = "$uplink_interface6" ]; then + table_iface="$uplink_interface4" + fi + if ! grep -q "$tid ${ipTablePrefix}_${table_iface}" "$rtTablesFile"; then + sed -i "/${ipTablePrefix}_${table_iface}/d" "$rtTablesFile" + echo "$tid ${ipTablePrefix}_${table_iface}" >> "$rtTablesFile" sync 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 + + if [ -n "$dev4" ]; then + 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 "$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 + + 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 - fi - if [ -n "$gw4" ] || [ -n "$strict_enforcement" ]; 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 - 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 - ipv6_error=0 + 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 + + 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 index d11ad84729..0000000000 --- a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_forward/30-pbr.nft +++ /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 index c98514bc5f..0000000000 --- a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_output/30-pbr.nft +++ /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 index a4471d376e..0000000000 --- a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_prerouting/30-pbr.nft +++ /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 index 987eece3ca..0000000000 --- a/net/pbr/files/usr/share/nftables.d/chain-pre/dstnat/30-pbr.nft +++ /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 index 0f1effcb18..0000000000 --- a/net/pbr/files/usr/share/nftables.d/table-post/30-pbr.nft +++ /dev/null @@ -1,4 +0,0 @@ -chain pbr_dstnat {} -chain pbr_forward {} -chain pbr_output {} -chain pbr_prerouting {}