#!/bin/sh /etc/rc.common # Copyright 2020-2024 MOSSDeF, Stan Grishin (stangri@melmac.ca) # shellcheck disable=SC2018,SC2019,SC2034,SC3043,SC3057,SC3060 # sysctl net.ipv4.conf.default.rp_filter=1 # sysctl net.ipv4.conf.all.rp_filter=1 # shellcheck disable=SC2034 START=94 # shellcheck disable=SC2034 USE_PROCD=1 [ -n "${IPKG_INSTROOT}" ] && return 0 readonly packageName='pbr' readonly PKG_VERSION='dev-test' readonly packageCompat='5' readonly serviceName="$packageName $PKG_VERSION" readonly serviceTrapSignals='exit SIGHUP SIGQUIT SIGKILL' readonly packageConfigFile="/etc/config/${packageName}" readonly packageLockFile="/var/run/${packageName}.lock" readonly dnsmasqFileDefault="/var/dnsmasq.d/${packageName}" readonly _OK_='\033[0;32m\xe2\x9c\x93\033[0m' readonly __OK__='\033[0;32m[\xe2\x9c\x93]\033[0m' readonly _OKB_='\033[1;34m\xe2\x9c\x93\033[0m' readonly __OKB__='\033[1;34m[\xe2\x9c\x93]\033[0m' readonly _FAIL_='\033[0;31m\xe2\x9c\x97\033[0m' readonly __FAIL__='\033[0;31m[\xe2\x9c\x97]\033[0m' readonly _ERROR_='\033[0;31mERROR\033[0m' readonly _WARNING_='\033[0;33mWARNING\033[0m' readonly ip_full='/usr/libexec/ip-full' # shellcheck disable=SC2155 readonly ip_bin="$(command -v ip)" readonly ipTablePrefix='pbr' # shellcheck disable=SC2155 readonly iptables="$(command -v iptables)" # shellcheck disable=SC2155 readonly ip6tables="$(command -v ip6tables)" # shellcheck disable=SC2155 readonly ipset="$(command -v ipset)" readonly ipsPrefix='pbr' readonly iptPrefix='PBR' # shellcheck disable=SC2155 readonly agh="$(command -v AdGuardHome)" readonly aghIpsetFile="/var/run/${packageName}.adguardhome.ipsets" # shellcheck disable=SC2155 readonly nft="$(command -v nft)" readonly nftIPv4Flag='ip' readonly nftIPv6Flag='ip6' readonly nftTempFile="/var/run/${packageName}.nft" readonly nftPermFile="/usr/share/nftables.d/ruleset-post/30-${packageName}.nft" readonly nftPrefix='pbr' readonly nftTable='fw4' readonly chainsList='forward input output postrouting prerouting' readonly ssConfigFile='/etc/shadowsocks' readonly torConfigFile='/etc/tor/torrc' readonly xrayIfacePrefix='xray_' readonly rtTablesFile='/etc/iproute2/rt_tables' # package config options procd_boot_timeout= enabled= fw_mask= icmp_interface= ignored_interface= ipv6_enabled= nft_file_support= nft_user_set_policy= nft_user_set_counter= procd_boot_delay= procd_reload_delay= procd_lan_interface= procd_wan_ignore_status= procd_wan_interface= procd_wan6_interface= resolver_set= resolver_instance= rule_create_option= secure_reload= strict_enforcement= supported_interface= verbosity= wan_ip_rules_priority= wan_mark= nft_set_auto_merge= nft_set_counter= nft_set_flags_interval= nft_set_flags_timeout= nft_set_flags_gc_interval= nft_set_policy= nft_set_timeout= # run-time aghConfigFile='/etc/AdGuardHome/AdGuardHome.yaml' gatewaySummary= errorSummary= warningSummary= wanIface4= wanIface6= dnsmasqFile= dnsmasqFileList= ifaceMark= ifaceTableID= ifacePriority= ifacesAll= ifacesSupported= firewallWanZone= wanGW4= wanGW6= serviceStartTrigger= processDnsPolicyError= processPolicyError= processPolicyWarning= resolver_set_supported= policy_routing_nft_prev_param4= policy_routing_nft_prev_param6= nft_set_params= torDnsPort= torTrafficPort= # shellcheck disable=SC1091 . /lib/functions.sh # shellcheck disable=SC1091 . /lib/functions/network.sh # shellcheck disable=SC1091 . /usr/share/libubox/jshn.sh output_ok() { output 1 "$_OK_"; output 2 "$__OK__\\n"; } output_okn() { output 1 "$_OK_\\n"; output 2 "$__OK__\\n"; } output_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"; } # shellcheck disable=SC2317 str_replace() { printf "%b" "$1" | sed -e "s/$(printf "%b" "$2")/$(printf "%b" "$3")/g"; } str_replace() { echo "${1//$2/$3}"; } str_contains() { [ -n "$1" ] && [ -n "$2" ] && [ "${1//$2}" != "$1" ]; } str_contains_word() { echo "$1" | grep -q -w "$2"; } str_to_lower() { echo "$1" | tr 'A-Z' 'a-z'; } str_to_upper() { echo "$1" | tr 'a-z' 'A-Z'; } str_extras_to_underscore() { echo "$1" | tr '[\. ~`!@#$%^&*()\+/,<>?//;:]' '_'; } str_extras_to_space() { echo "$1" | tr ';{}' ' '; } debug() { local i j; for i in "$@"; do eval "j=\$$i"; echo "${i}: ${j} "; done; } quiet_mode() { case "$1" in on) verbosity=0;; off) verbosity="$(uci_get "$packageName" 'config' 'verbosity' '2')";; esac } output() { # Target verbosity level with the first parameter being an integer is_integer() { case "$1" in (*[!0123456789]*) return 1;; ('') return 1;; (*) return 0;; esac } local msg memmsg logmsg text local sharedMemoryOutput="/dev/shm/$packageName-output" if [ -z "$verbosity" ] && [ -n "$packageName" ]; then verbosity="$(uci_get "$packageName" 'config' 'verbosity' '2')" fi if [ "$#" -ne '1' ] && is_integer "$1"; then if [ "$((verbosity & $1))" -gt '0' ] || [ "$verbosity" = "$1" ]; then shift; text="$*"; else return 0; fi fi text="${text:-$*}"; [ -t 1 ] && printf "%b" "$text" msg="${text//$serviceName /service }"; if [ "$(printf "%b" "$msg" | wc -l)" -gt '0' ]; then [ -s "$sharedMemoryOutput" ] && memmsg="$(cat "$sharedMemoryOutput")" logmsg="$(printf "%b" "${memmsg}${msg}" | sed 's/\x1b\[[0-9;]*m//g')" logger -t "${packageName:-service} [$$]" "$(printf "%b" "$logmsg")" rm -f "$sharedMemoryOutput" else printf "%b" "$msg" >> "$sharedMemoryOutput" fi } pbr_find_iface() { local iface i param="$2" case "$param" in wan6) iface="$procd_wan6_interface";; wan|*) iface="$procd_wan_interface";; esac eval "$1"='${iface}' } pbr_get_gateway4() { local iface="$2" dev="$3" gw network_get_gateway gw "$iface" true if [ -z "$gw" ] || [ "$gw" = '0.0.0.0' ]; then # gw="$(ubus call "network.interface.${iface}" status | jsonfilter -e "@.route[0].nexthop")" gw="$($ip_bin -4 a list dev "$dev" 2>/dev/null | grep inet | awk '{print $2}' | awk -F "/" '{print $1}')" fi eval "$1"='$gw' } pbr_get_gateway6() { local iface="$2" dev="$3" gw network_get_gateway6 gw "$iface" true if [ -z "$gw" ] || [ "$gw" = '::/0' ] || [ "$gw" = '::0/0' ] || [ "$gw" = '::' ]; then gw="$($ip_bin -6 a list dev "$dev" 2>/dev/null | grep inet6 | grep 'scope global' | awk '{print $2}')" fi eval "$1"='$gw' } # shellcheck disable=SC2016 is_bad_user_file_nft_call() { grep -q '"\$nft" list' "$1" || grep '"\$nft" -f' "$1";} is_config_enabled() { _check_config() { local en; config_get_bool en "$1" 'enabled' '1'; [ "$en" -gt '0' ] && _cfg_enabled=0; } local cfg="$1" _cfg_enabled=1 [ -n "$1" ] || return 1 config_load "$packageName" config_foreach _check_config "$cfg" return "$_cfg_enabled" } uci_get_device() { uci_get 'network' "$1" 'device' || uci_get 'network' "$1" 'dev'; } uci_get_protocol() { uci_get 'network' "$1" 'proto'; } is_default_dev() { [ "$1" = "$($ip_bin -4 r | grep -m1 'dev' | grep -Eso 'dev [^ ]*' | awk '{print $2}')" ]; } is_domain() { ! is_ipv6 "$1" && str_contains "$1" '[a-zA-Z]'; } is_dslite() { local p; network_get_protocol p "$1"; [ "${p:0:6}" = "dslite" ]; } is_family_mismatch() { ( is_ipv4_netmask "${1//!}" && is_ipv6 "${2//!}" ) || ( is_ipv6 "${1//!}" && is_ipv4_netmask "${2//!}" ); } is_greater() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; } is_greater_or_equal() { test "$(printf '%s\n' "$@" | sort -V | head -n '1')" = "$2"; } is_ignored_interface() { str_contains_word "$ignored_interface" "$1"; } is_ignore_target() { [ "$(str_to_lower "$1")" = 'ignore' ]; } is_integer() { case "$1" in (*[!0123456789]*) return 1;; ('') return 1;; (*) return 0;; esac } is_ipset_type_supported() { ipset help hash:"$1" >/dev/null 2>&1; } is_nft_mode() { return 1; } is_ipv4() { expr "$1" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; } is_ipv6() { ! is_mac_address "$1" && str_contains "$1" ':'; } is_ipv6_global() { [ "${1:0:4}" = '2001' ]; } is_ipv6_link_local() { [ "${1:0:4}" = 'fe80' ]; } is_ipv6_unique_local() { [ "${1:0:2}" = 'fc' ] || [ "${1:0:2}" = 'fd' ]; } is_list() { str_contains "$1" ',' || str_contains "$1" ' '; } is_ipv4_netmask() { local ip="${1%/*}"; [ "$ip" != "$1" ] && is_ipv4 "$ip"; } is_lan() { local d; network_get_device d "$1"; str_contains "$d" 'br-lan'; } is_l2tp() { local p; network_get_protocol p "$1"; [ "${p:0:4}" = "l2tp" ]; } is_mac_address() { expr "$1" : '[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]$' >/dev/null; } is_netifd_table() { grep -q "ip.table.*$1" /etc/config/network; } is_netifd_table_interface() { local iface="$1"; [ "$(uci_get 'network' "$iface" 'ip4table')" = "${packageName}_${iface%6}" ]; } is_oc() { local p; network_get_protocol p "$1"; [ "${p:0:11}" = "openconnect" ]; } is_ovpn() { local d; uci_get_device d "$1"; [ "${d:0:3}" = "tun" ] || [ "${d:0:3}" = "tap" ] || [ -f "/sys/devices/virtual/net/${d}/tun_flags" ]; } is_ovpn_valid() { local dev_net dev_ovpn; uci_get_device dev_net "$1"; dev_ovpn="$(uci_get 'openvpn' "$1" 'dev')"; [ -n "$dev_net" ] && [ -n "$dev_ovpn" ] && [ "$dev_net" = "$dev_ovpn" ]; } is_phys_dev() { [ "${1:0:1}" = "@" ] && ip l show | grep -E -q "^\\d+\\W+${1:1}"; } is_present() { command -v "$1" >/dev/null 2>&1; } is_service_running() { if is_nft_mode; then is_service_running_nft; else is_service_running_iptables; fi; } is_service_running_iptables() { [ -x "$iptables" ] && "$iptables" -t mangle -L | grep -q "${iptPrefix}_PREROUTING" >/dev/null 2>&1; } is_service_running_nft() { [ -x "$nft" ] && [ -n "$(get_mark_nft_chains)" ]; } is_supported_iface_dev() { local n dev; for n in $ifacesSupported; do network_get_device dev "$n"; [ "$1" = "$dev" ] && return 0; done; return 1; } is_supported_protocol() { grep -o '^[^#]*' /etc/protocols | grep -w -v '0' | grep . | awk '{print $1}' | grep -q "$1"; } is_pptp() { local p; network_get_protocol p "$1"; [ "${p:0:4}" = "pptp" ]; } is_softether() { local d; network_get_device d "$1"; [ "${d:0:4}" = "vpn_" ]; } is_supported_interface() { is_lan "$1" && return 1; str_contains_word "$supported_interface" "$1" || { ! is_ignored_interface "$1" && { is_wan "$1" || is_wan6 "$1" || is_tunnel "$1"; }; } || is_ignore_target "$1" || is_xray "$1"; } is_tailscale() { local d; network_get_device d "$1"; [ "${d:0:9}" = "tailscale" ]; } is_tor() { [ "$(str_to_lower "$1")" = "tor" ]; } is_tor_running() { local ret=0 is_ignored_interface 'tor' && return 1 [ -s "$torConfigFile" ] || return 1 json_load "$(ubus call service list "{ 'name': 'tor' }")" >/dev/null || return 1 json_select 'tor' >/dev/null || return 1 json_select 'instances' >/dev/null || return 1 json_select 'instance1' >/dev/null || return 1 json_get_var ret 'running' >/dev/null || return 1 json_cleanup if [ "$ret" = "0" ]; then return 1; else return 0; fi } is_tunnel() { is_dslite "$1" || is_l2tp "$1" || is_oc "$1" || is_ovpn "$1" || is_pptp "$1" || is_softether "$1" || is_tailscale "$1" || is_tor "$1" || is_wg "$1"; } is_url() { is_url_file "$1" || is_url_dl "$1"; } is_url_dl() { is_url_ftp "$1" || is_url_http "$1" || is_url_https "$1"; } is_url_file() { [ "$1" != "${1#file://}" ];} is_url_ftp() { [ "$1" != "${1#ftp://}" ];} is_url_http() { [ "$1" != "${1#http://}" ];} is_url_https() { [ "$1" != "${1#https://}" ];} is_wan() { [ "$1" = "$wanIface4" ] || { [ "${1##wan}" != "$1" ] && [ "${1##wan6}" = "$1" ]; } || [ "${1%%wan}" != "$1" ]; } is_wan6() { [ -n "$wanIface6" ] && [ "$1" = "$wanIface6" ] || [ "${1/#wan6}" != "$1" ] || [ "${1/%wan6}" != "$1" ]; } is_wg() { local p lp; network_get_protocol p "$1"; uci_get_listen_port lp "$1"; [ -z "$lp" ] && [ "${p:0:9}" = "wireguard" ]; } is_xray() { [ -n "$(get_xray_traffic_port "$1")" ]; } dnsmasq_kill() { killall -q -s HUP dnsmasq; } dnsmasq_restart() { output 3 'Restarting dnsmasq '; if /etc/init.d/dnsmasq restart >/dev/null 2>&1; then output_okn; else output_failn; fi; } # shellcheck disable=SC2155 get_ss_traffic_ports() { local i="$(jsonfilter -i "$ssConfigFile" -q -e "@.inbounds[*].port")"; echo "${i:-443}"; } # shellcheck disable=SC2155 get_tor_dns_port() { local i="$(grep -m1 DNSPort "$torConfigFile" | awk -F: '{print $2}')"; echo "${i:-9053}"; } # shellcheck disable=SC2155 get_tor_traffic_port() { local i="$(grep -m1 TransPort "$torConfigFile" | awk -F: '{print $2}')"; echo "${i:-9040}"; } get_xray_traffic_port() { local i="${1//$xrayIfacePrefix}"; [ "$i" = "$1" ] && unset i; echo "$i"; } get_rt_tables_id() { local iface="$1"; grep "${ipTablePrefix}_${iface}\$" "$rtTablesFile" | awk '{print $1;}'; } 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_ipt() { resolveip "$@" | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d'; } resolveip_to_ipt4() { resolveip_to_ipt -4 "$@"; } resolveip_to_ipt6() { [ -n "$ipv6_enabled" ] && resolveip_to_ipt -6 "$@"; } # shellcheck disable=SC2016 resolveip_to_nftset() { resolveip "$@" | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; } resolveip_to_nftset4() { resolveip_to_nftset -4 "$@"; } resolveip_to_nftset6() { [ -n "$ipv6_enabled" ] && resolveip_to_nftset -6 "$@"; } # shellcheck disable=SC2016 ipv4_leases_to_nftset() { [ -s '/tmp/dhcp.leases' ] || return 1; grep "$1" '/tmp/dhcp.leases' | awk '{print $3}' | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; } # shellcheck disable=SC2016 ipv6_leases_to_nftset() { [ -s '/tmp/hosts/odhcpd' ] || return 1; grep -v '^#' '/tmp/hosts/odhcpd' | grep "$1" | awk '{print $1}' | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; } # shellcheck disable=SC3037 ports_to_nftset() { echo -en "$1"; } get_mark_ipt_chains() { [ -n "$(command -v iptables-save)" ] && iptables-save | grep ":${iptPrefix}_MARK_" | awk '{ print $1 }' | sed 's/://'; } get_mark_nft_chains() { [ -x "$nft" ] && "$nft" list table inet "$nftTable" 2>/dev/null | grep chain | grep "${nftPrefix}_mark_" | awk '{ print $2 }'; } get_ipsets() { [ -x "$(command -v ipset)" ] && ipset list | grep "${ipsPrefix}_" | awk '{ print $2 }'; } get_nft_sets() { [ -x "$nft" ] && "$nft" list table inet "$nftTable" 2>/dev/null | grep 'set' | grep "${nftPrefix}_" | awk '{ print $2 }'; } __ubus_get() { ubus call service list "{ 'name': '$packageName' }" | jsonfilter -e "$1"; } ubus_get_status() { __ubus_get "@.${packageName}.instances.main.data.status.${1}"; } ubus_get_interface() { __ubus_get "@.${packageName}.instances.main.data.gateways[@.name='${1}']${2:+.$2}"; } ubus_get_gateways() { __ubus_get "@.${packageName}.instances.main.data.gateways"; } uci_get_device() { local __tmp __tmp="$(uci_get 'network' "$2" 'device')" [ -z "$__tmp" ] && unset "$1" && return 1 eval "$1=$__tmp" } uci_get_listen_port() { local __tmp __tmp="$(uci_get 'network' "$2" 'listen_port')" [ -z "$__tmp" ] && unset "$1" && return 1 eval "$1=$__tmp" } # luci app specific is_enabled() { uci_get "$1" 'config' 'enabled'; } is_running_iptables() { iptables -t mangle -L | grep -q PBR_PREROUTING >/dev/null 2>&1; } is_running_nft_file() { [ -s "$nftPermFile" ]; } is_running_nft() { "$nft" list table inet fw4 | grep chain | grep -q pbr_mark_ >/dev/null 2>&1; } is_running() { is_running_iptables || is_running_nft; } check_ipset() { { [ -n "$ipset" ] && "$ipset" help hash:net; } >/dev/null 2>&1; } check_nft() { [ -x "$nft" ]; } check_agh() { [ -x "$agh" ] && { [ -s "$aghConfigFile" ] || [ -s "${agh%/*}/AdGuardHome.yaml" ]; }; } check_dnsmasq() { command -v dnsmasq >/dev/null 2>&1; } check_unbound() { command -v unbound >/dev/null 2>&1; } check_agh_ipset() { check_ipset || return 1 check_agh || return 1 is_greater_or_equal "$($agh --version | sed 's|AdGuard Home, version v\(.*\)|\1|' | sed 's|-.*||')" '0.107.13' } check_dnsmasq_ipset() { local o; check_ipset || return 1 check_dnsmasq || return 1 o="$(dnsmasq -v 2>/dev/null)" ! echo "$o" | grep -q 'no-ipset' && echo "$o" | grep -q 'ipset' } check_dnsmasq_nftset() { local o; check_nft || return 1 check_dnsmasq || return 1 o="$(dnsmasq -v 2>/dev/null)" ! echo "$o" | grep -q 'no-nftset' && echo "$o" | grep -q 'nftset' } print_json_bool() { json_init; json_add_boolean "$1" "$2"; json_dump; json_cleanup; } print_json_string() { json_init; json_add_string "$1" "$2"; json_dump; json_cleanup; } if type extra_command >/dev/null 2>&1; then extra_command 'status' "Generates output required to troubleshoot routing issues Use '-d' option for more detailed output Use '-p' option to automatically upload data under VPR paste.ee account WARNING: while paste.ee uploads are unlisted, they are still publicly available List domain names after options to include their lookup in report" extra_command 'version' 'Show version information' extra_command 'on_firewall_reload' ' Run service on firewall reload' extra_command 'on_interface_reload' ' Run service on indicated interface reload' else # shellcheck disable=SC2034 EXTRA_COMMANDS='on_firewall_reload on_interface_reload status version' # shellcheck disable=SC2034 EXTRA_HELP=" status Generates output required to troubleshoot routing issues Use '-d' option for more detailed output Use '-p' option to automatically upload data under VPR paste.ee account WARNING: while paste.ee uploads are unlisted, they are still publicly available List domain names after options to include their lookup in report" fi get_text() { local r case "$1" in errorConfigValidation) r="Config ($packageConfigFile) validation failure!";; errorNoIpFull) r="ip-full binary cannot be found!";; errorNoIptables) r="iptables binary cannot be found!";; errorNoIpset) r="Resolver set support (${resolver_set}) requires ipset, but ipset binary cannot be found!";; errorNoNft) r="Resolver set support (${resolver_set}) requires nftables, but nft binary cannot be found!";; errorResolverNotSupported) r="Resolver set (${resolver_set}) is not supported on this system!";; errorServiceDisabled) r="The ${packageName} service is currently disabled!";; errorNoWanGateway) r="The ${serviceName} service failed to discover WAN gateway!";; errorNoWanInterface) r="The %s inteface not found, you need to set the 'pbr.config.procd_wan_interface' option!";; errorNoWanInterfaceHint) r="Refer to https://docs.openwrt.melmac.net/pbr/#procd_wan_interface.";; errorIpsetNameTooLong) r="The ipset name '%s' is longer than allowed 31 characters!";; errorNftsetNameTooLong) r="The nft set name '%s' is longer than allowed 255 characters!";; errorUnexpectedExit) r="Unexpected exit or service termination: '%s'!";; errorPolicyNoSrcDest) r="Policy '%s' has no source/destination parameters!";; errorPolicyNoInterface) r="Policy '%s' has no assigned interface!";; errorPolicyNoDns) r="Policy '%s' has no assigned DNS!";; errorPolicyProcessNoInterfaceDns) r="Interface '%s' has no assigned DNS!";; errorPolicyUnknownInterface) r="Policy '%s' has an unknown interface!";; errorPolicyProcessCMD) r="'%s'!";; errorFailedSetup) r="Failed to set up '%s'!";; errorFailedReload) r="Failed to reload '%s'!";; errorUserFileNotFound) r="Custom user file '%s' not found or empty!";; errorUserFileSyntax) r="Syntax error in custom user file '%s'!";; errorUserFileRunning) r="Error running custom user file '%s'!";; errorUserFileNoCurl) r="Use of 'curl' is detected in custom user file '%s', but 'curl' isn't installed!";; errorNoGateways) r="Failed to set up any gateway!";; errorResolver) r="Resolver '%s'!";; errorPolicyProcessNoIpv6) r="Skipping IPv6 policy '%s' as IPv6 support is disabled!";; errorPolicyProcessUnknownFwmark) r="Unknown packet mark for interface '%s'!";; errorPolicyProcessMismatchFamily) r="Mismatched IP family between in policy '%s'!";; errorPolicyProcessUnknownProtocol) r="Unknown protocol in policy '%s'!";; errorPolicyProcessInsertionFailed) r="Insertion failed for both IPv4 and IPv6 for policy '%s'!";; errorPolicyProcessInsertionFailedIpv4) r="Insertion failed for IPv4 for policy '%s'!";; errorInterfaceRoutingEmptyValues) r="Received empty tid/mark or interface name when setting up routing!";; errorFailedToResolve) r="Failed to resolve '%s'!";; errorTryFailed) r="Command failed: %s";; errorNftFileInstall) r="Failed to install fw4 nft file '%s'!";; errorDownloadUrlNoHttps) r="Failed to download '%s', HTTPS is not supported!";; errorDownloadUrl) r="Failed to download '%s'!";; errorNoDownloadWithSecureReload) r="Policy '%s' refers to URL which can't be downloaded in 'secure_reload' mode!";; errorFileSchemaRequiresCurl) r="The file:// schema requires curl, but it's not detected on this system!";; warningInvalidOVPNConfig) r="Invalid OpenVPN config for '%s' interface.";; warningResolverNotSupported) r="Resolver set (${resolver_set}) is not supported on this system.";; warningAGHVersionTooLow) r="Installed AdGuardHome ('%s') doesn't support 'ipset_file' option.";; warningPolicyProcessCMD) r="'%s'";; warningTorUnsetParams) r="Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'.";; warningTorUnsetProto) r="Please unset 'proto' or set 'proto' to 'all' for policy '%s'.";; warningTorUnsetChainIpt) r="Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'.";; warningTorUnsetChainNft) r="Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'.";; warningOutdatedWebUIApp) r="The WebUI application is outdated (version %s), please update it.";; warningBadNftCallsInUserFile) r="Incompatible nft calls detected in user include file, disabling fw4 nft file support.";; warningDnsmasqInstanceNoConfdir) r="Dnsmasq instance (%s) targeted in settings, but it doesn't have its own confdir.";; esac echo "$r" } process_url() { local url="$1" local dl_command dl_https_supported dl_temp_file # TODO: check for FILE schema and missing curl if is_present 'curl'; then dl_command="curl --silent --insecure" dl_flag="-o" elif is_present '/usr/libexec/wget-ssl'; then dl_command="/usr/libexec/wget-ssl --no-check-certificate -q" dl_flag="-O" elif is_present wget && wget --version 2>/dev/null | grep -q "+https"; then dl_command="wget --no-check-certificate -q" dl_flag="-O" else dl_command="uclient-fetch --no-check-certificate -q" dl_flag="-O" fi if curl --version 2>/dev/null | grep -q "Protocols: .*https.*" \ || wget --version 2>/dev/null | grep -q "+ssl"; then dl_https_supported=1 else unset dl_https_supported fi while [ -z "$dl_temp_file" ] || [ -e "$dl_temp_file" ]; do dl_temp_file="$(mktemp -u -q -t ${packageName}_tmp.XXXXXXXX)" done if is_url_file "$url" && ! is_present 'curl'; then state add 'errorSummary' 'errorFileSchemaRequiresCurl' "$url" elif is_url_https "$url" && [ -z "$dl_https_supported" ]; then state add 'errorSummary' 'errorDownloadUrlNoHttps' "$url" elif $dl_command "$url" "$dl_flag" "$dl_temp_file" 2>/dev/null; then sed 'N;s/\n/ /;s/\s\+/ /g;' "$dl_temp_file" else state add 'errorSummary' 'errorDownloadUrl' "$url" fi rm -f "$dl_temp_file" } load_package_config() { _check_user_files_for_bad_nft_calls() { local cfg="$1" local en path config_get_bool en "$cfg" 'enabled' '1' config_get path "$cfg" 'path' [ "$en" -eq '0' ] && return 0 [ -z "$path" ] && return 0 [ -s "$path" ] || return 0 is_bad_user_file_nft_call "$path" && user_file_check_result='bad' } local param="$1" local user_file_check_result i config_load "$packageName" config_get_bool enabled 'config' 'enabled' '0' config_get fw_mask 'config' 'fw_mask' 'ff0000' config_get icmp_interface 'config' 'icmp_interface' config_get ignored_interface 'config' 'ignored_interface' config_get_bool ipv6_enabled 'config' 'ipv6_enabled' '0' config_get_bool nft_file_support 'config' 'nft_file_support' '1' config_get_bool nft_set_auto_merge 'config' 'nft_set_auto_merge' '1' config_get_bool nft_set_counter 'config' 'nft_set_counter' '1' config_get_bool nft_set_flags_interval 'config' 'nft_set_flags_interval' '1' config_get_bool nft_set_flags_timeout 'config' 'nft_set_flags_timeout' '0' config_get nft_set_gc_interval 'config' 'nft_set_gc_interval' config_get nft_set_policy 'config' 'nft_set_policy' 'performance' config_get nft_set_timeout 'config' 'nft_set_timeout' config_get resolver_set 'config' 'resolver_set' config_get resolver_instance 'config' 'resolver_instance' '*' config_get rule_create_option 'config' 'rule_create_option' 'add' config_get_bool secure_reload 'config' 'secure_reload' '0' config_get_bool strict_enforcement 'config' 'strict_enforcement' '1' config_get supported_interface 'config' 'supported_interface' config_get verbosity 'config' 'verbosity' '2' config_get procd_boot_delay 'config' 'procd_boot_delay' '0' config_get procd_boot_timeout 'config' 'procd_boot_timeout' '30' config_get procd_lan_interface 'config' 'procd_lan_interface' config_get procd_wan_ignore_status 'config' 'procd_wan_ignore_status' '0' config_get procd_wan_interface 'config' 'procd_wan_interface' 'wan' config_get procd_wan6_interface 'config' 'procd_wan6_interface' 'wan6' config_get wan_ip_rules_priority 'config' 'wan_ip_rules_priority' '30000' config_get wan_mark 'config' 'wan_mark' '010000' fw_mask="0x${fw_mask}" wan_mark="0x${wan_mark}" if [ -x "$agh" ] && [ ! -s "$aghConfigFile" ]; then [ -s "${agh%/*}/AdGuardHome.yaml" ] && aghConfigFile="${agh%/*}/AdGuardHome.yaml" fi [ -n "$ipv6_enabled" ] && [ "$ipv6_enabled" -eq '0' ] && unset ipv6_enabled [ -n "$nft_file_support" ] && [ "$nft_file_support" -eq '0' ] && unset nft_file_support [ -n "$nft_user_set_counter" ] && [ "$nft_user_set_counter" -eq '0' ] && unset nft_user_set_counter [ -n "$secure_reload" ] && [ "$secure_reload" -eq '0' ] && unset secure_reload config_foreach _check_user_files_for_bad_nft_calls 'include' [ -n "$user_file_check_result" ] && unset nft_file_support [ -n "$nft_file_support" ] && unset secure_reload is_config_enabled 'include' && unset secure_reload if is_nft_mode; then fw_maskXor="$(printf '%#x' "$((fw_mask ^ 0xffffffff))")" fw_maskXor="${fw_maskXor:-0xff00ffff}" else case $rule_create_option in insert|-i|-I) rule_create_option='-I';; add|-a|-A|*) rule_create_option='-A';; esac fi [ "$nft_set_auto_merge" != '1' ] && unset nft_set_auto_merge [ "$nft_set_counter" != '1' ] && unset nft_set_counter [ "$nft_set_flags_interval" != '1' ] && unset nft_set_flags_interval [ "$nft_set_flags_timeout" != '1' ] && unset nft_set_flags_timeout [ -z "${nft_set_flags_timeout}${nft_set_timeout}" ] && unset nft_set_gc_interval local nft_set_flags if [ -n "${nft_set_flags_interval}${nft_set_flags_timeout}" ]; then [ -n "$nft_set_flags_interval" ] && nft_set_flags='flags interval' if [ -n "$nft_set_flags_timeout" ]; then if [ -n "$nft_set_flags" ]; then nft_set_flags="${nft_set_flags}, timeout" else nft_set_flags='flags timeout' fi fi fi nft_set_params=" \ ${nft_set_auto_merge:+ auto-merge;} \ ${nft_set_counter:+ counter;} \ ${nft_set_flags:+ $nft_set_flags;} \ ${nft_set_gc_interval:+ gc_interval "$nft_set_gc_interval";} \ ${nft_set_policy:+ policy "$nft_set_policy";} \ ${nft_set_timeout:+ timeout "$nft_set_timeout";} \ " resolver 'check_support' && resolver 'configure_instances' } load_environment() { local param="$1" validation_result="$2" load_package_config "$param" case "$param" in on_start) if [ "$enabled" -eq '0' ]; then state add 'errorSummary' 'errorServiceDisabled' return 1 fi if [ -n "$validation_result" ] && [ "$validation_result" != '0' ]; then output "${_ERROR_}: The $packageName config validation failed!\\n" output "Please check if the '$packageConfigFile' contains correct values for config options.\\n" state add 'errorSummary' 'errorConfigValidation' return 1 fi if [ ! -x "$ip_bin" ]; then state add 'errorSummary' 'errorNoIpFull' return 1 fi if is_nft_mode; then if [ "$(uci_get 'firewall' 'defaults' 'auto_includes')" = '0' ]; then uci_remove 'firewall' 'defaults' 'auto_includes' uci_commit firewall fi else if [ -z "$iptables" ] || [ ! -x "$iptables" ]; then state add 'errorSummary' 'errorNoIptables' return 1 fi fi ;; on_stop) : ;; esac load_network "$param" } load_network() { _build_ifaces_supported() { is_supported_interface "$1" && ! str_contains "$ifacesSupported" "$1" && ifacesSupported="${ifacesSupported}${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} " done config_load 'network' config_foreach _build_ifaces_supported 'interface' fi wanIface4="$procd_wan_interface" network_get_device dev4 "$wanIface4" [ -z "$dev4" ] && network_get_physdev dev4 "$wanIface4" [ -z "$wanGW4" ] && pbr_get_gateway4 wanGW4 "$wanIface4" "$dev4" if [ -n "$ipv6_enabled" ]; then wanIface6="$procd_wan6_interface" network_get_device dev6 "$wanIface6" [ -z "$dev6" ] && network_get_physdev dev6 "$wanIface6" [ -z "$wanGW6" ] && pbr_get_gateway6 wanGW6 "$wanIface6" "$dev6" fi case "$param" in on_boot|on_start) [ -n "$wanIface4" ] && output 2 "Using wan interface (${param}): $wanIface4 \\n" [ -n "$wanGW4" ] && output 2 "Found wan gateway (${param}): $wanGW4 \\n" [ -n "$wanIface6" ] && output 2 "Using wan6 interface (${param}): $wanIface6 \\n" [ -n "$wanGW6" ] && output 2 "Found wan6 gateway (${param}): $wanGW6 \\n" ;; esac wanGW="${wanGW4:-$wanGW6}" } is_wan_up() { local sleepCount='1' param="$1" load_network "$param" [ "$procd_wan_ignore_status" -eq '0' ] || return 0 [ "$param" = 'on_boot' ] || procd_boot_timeout='1' if [ -z "$(uci_get network "$procd_wan_interface")" ]; then state add 'errorSummary' 'errorNoWanInterface' "$procd_wan_interface" state add 'errorSummary' 'errorNoWanInterfaceHint' return 1 fi while [ -z "$wanGW" ] ; do load_network "$param" if [ "$((sleepCount))" -gt "$((procd_boot_timeout))" ] || [ -n "$wanGW" ]; then break; fi output "$serviceName waiting for $procd_wan_interface gateway...\\n" sleep 1 network_flush_cache sleepCount=$((sleepCount+1)) done if [ -n "$wanGW" ]; then return 0 else state add 'errorSummary' 'errorNoWanGateway' return 1 fi } # shellcheck disable=SC2086 ipt4() { local d [ -x "$iptables" ] || return 1 for d in "${*//-A/-D}" "${*//-I/-D}" "${*//-N/-F}" "${*//-N/-X}"; do [ "$d" != "$*" ] && "$iptables" $d >/dev/null 2>&1 done d="$*"; "$iptables" $d >/dev/null 2>&1 } # shellcheck disable=SC2086 ipt6() { local d [ -n "$ipv6_enabled" ] || return 0 [ -x "$ip6tables" ] || return 1 for d in "${*//-A/-D}" "${*//-I/-D}" "${*//-N/-F}" "${*//-N/-X}"; do [ "$d" != "$*" ] && "$ip6tables" $d >/dev/null 2>&1 done d="$*" "$ip6tables" $d >/dev/null 2>&1 } # shellcheck disable=SC2086 ipt() { local d failFlagIpv4=1 failFlagIpv6=1 [ -x "$iptables" ] || return 1 for d in "${*//-A/-D}" "${*//-I/-D}" "${*//-N/-F}" "${*//-N/-X}"; do if [ "$d" != "$*" ]; then "$iptables" $d >/dev/null 2>&1 if [ -x "$ip6tables" ]; then "$ip6tables" $d >/dev/null 2>&1 fi fi done d="$*"; "$iptables" $d >/dev/null 2>&1 && failFlagIpv4=0; if [ -n "$ipv6_enabled" ] && [ -x "$ip6tables" ]; then "$ip6tables" $d >/dev/null 2>&1 && failFlagIpv6=0 fi [ "$failFlagIpv4" -eq '0' ] || [ "$failFlagIpv6" -eq '0' ] } # shellcheck disable=SC2086 ips4() { [ -x "$ipset" ] && "$ipset" "$@" >/dev/null 2>&1; } ips6() { [ -x "$ipset" ] && { if [ -n "$ipv6_enabled" ] && [ -n "$*" ]; then "$ipset" "$@" >/dev/null 2>&1; else return 1; fi; }; } ips() { local command="$1" iface="$2" target="${3:-dst}" type="${4:-ip}" uid="$5" comment="$6" param="$7" mark="$7" local ipset4 ipset6 i local ipv4_error=1 ipv6_error=1 ipset4="${ipsPrefix}${iface:+_$iface}_4${target:+_$target}${type:+_$type}${uid:+_$uid}" ipset6="${ipsPrefix}${iface:+_$iface}_6${target:+_$target}${type:+_$type}${uid:+_$uid}" [ -x "$ipset" ] || return 1 if [ "${#ipset4}" -gt '31' ]; then state add 'errorSummary' 'errorIpsetNameTooLong' "$ipset4" return 1 fi case "$command" in add) ips4 -q -! add "$ipset4" ["$param"] comment "$comment" && ipv4_error=0 ips6 -q -! add "$ipset6" ["$param"] comment "$comment" && ipv6_error=0 ;; add_agh_element) [ -n "$ipv6_enabled" ] || unset ipset6 echo "${param}/${ipset4}${ipset6:+,$ipset6}" >> "$aghIpsetFile" && ipv4_error=0 ;; add_dnsmasq_element) [ -n "$ipv6_enabled" ] || unset ipset6 # shellcheck disable=SC2086 echo "ipset=/${param}/${ipset4}${ipset6:+,$ipset6} # $comment" | tee -a $dnsmasqFileList >/dev/null 2>&1 && ipv4_error=0 ;; create) ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0 ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv6_error=0 ;; create_agh_set) ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0 ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv6_error=0 ;; create_dnsmasq_set) ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0 ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv6_error=0 ;; create_user_set) case "$type" in ip|net) ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0 ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv6_error=0 case "$target" in dst) ipt4 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" dst -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 ipt6 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" dst -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 ;; src) ipt4 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 ipt6 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 ;; esac ;; mac) ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0 ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv4_error=0 ipt4 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 ipt6 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 ;; esac ;; delete|destroy) ips4 -q -! destroy "$ipset4" && ipv4_error=0 ips6 -q -! destroy "$ipset6" && ipv6_error=0 ;; delete_user_set) ips4 -q -! destroy "$ipset4" && ipv4_error=0 ips6 -q -! destroy "$ipset6" family inet6 && ipv6_error=0 case "$type" in ip|net) case "$target" in dst) ipt4 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" dst -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 ipt6 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" dst -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 ;; src) ipt4 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 ipt6 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 ;; esac ;; mac) ipt4 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 ipt6 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 ;; esac ;; flush|flush_user_set) ips4 -q -! flush "$ipset4" && ipv4_error=0 ips6 -q -! flush "$ipset6" && ipv6_error=0 ;; esac if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then return 0 else return 1 fi } nft_call() { [ -x "$nft" ] && "$nft" "$@" >/dev/null 2>&1; } nft_file() { local i [ -x "$nft" ] || return 1 case "$1" in add|add_command) [ -n "$nft_file_support" ] || return 1 shift grep -q "$*" "$nftTempFile" || echo "$*" >> "$nftTempFile" ;; create) rm -f "$nftTempFile" "$nftPermFile" for i in "$nftTempFile" "$nftPermFile"; do mkdir -p "${i%/*}" done [ -n "$nft_file_support" ] || return 1 { echo '#!/usr/sbin/nft -f'; echo ''; } > "$nftTempFile" ;; delete|rm|remove) rm -f "$nftTempFile" "$nftPermFile" ;; enabled) [ -n "$nft_file_support" ] && return 0 || return 1 ;; exists) [ -s "$nftPermFile" ] && return 0 || return 1 ;; install) [ -n "$nft_file_support" ] || return 1 [ -s "$nftTempFile" ] || return 1 output "Installing fw4 nft file " if nft_call -c -f "$nftTempFile" && \ cp -f "$nftTempFile" "$nftPermFile"; then output_okn else state add 'errorSummary' 'errorNftFileInstall' "$nftTempFile" output_failn fi ;; esac } nft() { [ -x "$nft" ] && [ -n "$*" ] && { nft_file 'add_command' "$@" || "$nft" "$@" >/dev/null 2>&1;} } nft4() { nft "$@"; } nft6() { [ -n "$ipv6_enabled" ] || return 0; nft "$@"; } nftset() { local command="$1" iface="$2" target="${3:-dst}" type="${4:-ip}" uid="$5" comment="$6" param="$7" mark="$7" local nftset4 nftset6 i param4 param6 local ipv4_error=1 ipv6_error=1 nftset4="${nftPrefix}${iface:+_$iface}_4${target:+_$target}${type:+_$type}${uid:+_$uid}" nftset6="${nftPrefix}${iface:+_$iface}_6${target:+_$target}${type:+_$type}${uid:+_$uid}" [ -x "$nft" ] || return 1 if [ "${#nftset4}" -gt '255' ]; then state add 'errorSummary' 'errorNftsetNameTooLong' "$nftset4" return 1 fi case "$command" in add) if is_mac_address "$param" || is_list "$param"; then nft4 add element inet "$nftTable" "$nftset4" "{ $param }" && ipv4_error=0 nft6 add element inet "$nftTable" "$nftset6" "{ $param }" && ipv6_error=0 elif is_ipv4_netmask "$param" || is_ipv4 "$param"; then nft4 add element inet "$nftTable" "$nftset4" "{ $param }" && ipv4_error=0 elif is_ipv6 "$param"; then nft6 add element inet "$nftTable" "$nftset6" "{ $param }" && ipv6_error=0 else if [ "$target" = 'src' ]; then param4="$(ipv4_leases_to_nftset "$param")" param6="$(ipv6_leases_to_nftset "$param")" fi [ -z "$param4" ] && param4="$(resolveip_to_nftset4 "$param")" [ -z "$param6" ] && param6="$(resolveip_to_nftset6 "$param")" if [ -z "$param4" ] && [ -z "$param6" ]; then state add 'errorSummary' 'errorFailedToResolve' "$param" else [ -n "$param4" ] && nft4 add element inet "$nftTable" "$nftset4" "{ $param4 }" && ipv4_error=0 [ -n "$param6" ] && nft6 add element inet "$nftTable" "$nftset6" "{ $param6 }" && ipv6_error=0 fi fi ;; add_dnsmasq_element) [ -n "$ipv6_enabled" ] || unset nftset6 # shellcheck disable=SC2086 echo "nftset=/${param}/4#inet#${nftTable}#${nftset4}${nftset6:+,6#inet#${nftTable}#$nftset6} # $comment" | tee -a $dnsmasqFileList >/dev/null 2>&1 && ipv4_error=0 ;; create) case "$type" in ip|net) nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; $nft_set_params comment \"$comment\";}" && ipv4_error=0 nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0 ;; mac) nft4 add set inet "$nftTable" "$nftset4" "{ type ether_addr; $nft_set_params comment \"$comment\"; }" && ipv4_error=0 nft6 add set inet "$nftTable" "$nftset6" "{ type ether_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0 ;; esac ;; create_dnsmasq_set) nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; $nft_set_params comment \"$comment\"; }" && ipv4_error=0 nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0 ;; create_user_set) case "$type" in ip|net) nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; $nft_set_params comment \"$comment\"; }" && ipv4_error=0 nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0 case "$target" in dst) nft4 add rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv4Flag" daddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 nft6 add rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv6Flag" daddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 ;; src) nft4 add rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv4Flag" saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 nft6 add rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv6Flag" saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 ;; esac ;; mac) nft4 add set inet "$nftTable" "$nftset4" "{ type ether_addr; $nft_set_params comment \"$comment\"; }" && ipv4_error=0 nft6 add set inet "$nftTable" "$nftset6" "{ type ether_addr; $nft_set_params comment \"$comment\"; }" && ipv6_error=0 nft4 add rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 nft6 add rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 ;; esac ;; delete|destroy) nft_call delete set inet "$nftTable" "$nftset4" && ipv4_error=0 nft_call delete set inet "$nftTable" "$nftset6" && ipv6_error=0 ;; delete_user_set) nft_call delete set inet "$nftTable" "$nftset4" && ipv4_error=0 nft_call delete set inet "$nftTable" "$nftset6" && ipv6_error=0 case "$type" in ip|net) case "$target" in dst) nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv4Flag" daddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv6Flag" daddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 ;; src) nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv4Flag" saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" "$nftIPv6Flag" saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 ;; esac ;; mac) nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 nft_call delete rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 ;; esac ;; flush|flush_user_set) nft_call flush set inet "$nftTable" "$nftset4" && ipv4_error=0 nft_call flush set inet "$nftTable" "$nftset6" && ipv6_error=0 ;; esac # nft6 returns true if IPv6 support is not enabled [ -z "$ipv6_enabled" ] && ipv6_error='1' if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then return 0 else return 1 fi } cleanup_rt_tables() { 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_chains() { local i j for i in $chainsList dstnat_lan; do i="$(str_to_lower "$i")" nft_call flush chain inet "$nftTable" "${nftPrefix}_${i}" done for i in $chainsList; do i="$(str_to_upper "$i")" ipt -t mangle -D "${i}" -m mark --mark "0x0/${fw_mask}" -j "${iptPrefix}_${i}" ipt -t mangle -F "${iptPrefix}_${i}" ipt -t mangle -X "${iptPrefix}_${i}" done ipt -t nat -F "${iptPrefix}_PREROUTING" ipt -t nat -X "${iptPrefix}_PREROUTING" } 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 for i in $(get_mark_ipt_chains); do ipt -t mangle -F "$i" ipt -t mangle -X "$i" done } cleanup_sets() { local i for i in $(get_nft_sets); do nft_call flush set inet "$nftTable" "$i" nft_call delete set inet "$nftTable" "$i" done for i in $(get_ipsets); do ipset -q -! flush "$i" >/dev/null 2>&1 ipset -q -! destroy "$i" >/dev/null 2>&1 done } state() { local action="$1" param="$2" value="${3//#/_}" shift 3 # shellcheck disable=SC2124 local extras="$@" local line error_id error_extra label case "$action" in add) line="$(eval echo "\$$param")" eval "$param"='${line:+$line#}${value}${extras:+ $extras}' ;; json) json_init json_add_object "$packageName" case "$param" in errorSummary) json_add_array 'errors';; warningSummary) json_add_array 'warnings';; esac if [ -n "$(eval echo "\$$param")" ]; then while read -r line; do if str_contains "$line" ' '; then error_id="${line% *}" error_extra="${line#* }" else error_id="$line" fi json_add_object json_add_string 'id' "$error_id" json_add_string 'extra' "$error_extra" json_close_object done </dev/null 2>&1 rm -f "$aghIpsetFile" local dfl for dfl in $dnsmasqFileList; do rm -f "$dfl" done return 0 fi case "$resolver_set" in ''|none) case "$param" in add_resolver_element) return 1;; create_resolver_set) return 1;; check_support) return 0;; cleanup) return 0;; configure) return 0;; init) return 0;; init_end) return 0;; kill) return 0;; reload) return 0;; restart) return 0;; compare_hash) return 0;; store_hash) return 0;; esac ;; adguardhome.ipset) case "$param" in add_resolver_element) [ -n "$resolver_set_supported" ] && ips 'add_agh_element' "$@";; create_resolver_set) [ -n "$resolver_set_supported" ] && ips 'create_agh_set' "$@";; check_support) if [ ! -x "$ipset" ]; then state add 'errorSummary' 'errorNoIpset' return 1 fi if [ -n "$agh" ] && [ -s "$aghConfigFile" ]; then agh_version="$($agh --version | sed 's|AdGuard Home, version v\(.*\)|\1|' | sed 's|-.*||')" if is_greater_or_equal "$agh_version" '0.107.13'; then resolver_set_supported='true' return 0 else state add 'warningSummary' 'warningAGHVersionTooLow' "$agh_version" return 1 fi else state add 'warningSummary' 'warningResolverNotSupported' return 1 fi ;; cleanup) [ -z "$resolver_set_supported" ] && return 0 rm -f "$aghIpsetFile" sed -i "/ipset_file: ${aghIpsetFile}/d" "$aghConfigFile" >/dev/null 2>&1 ;; configure) [ -z "$resolver_set_supported" ] && return 1 mkdir -p "${aghIpsetFile%/*}" touch "$aghIpsetFile" sed -i '/ipset_file/d' "$aghConfigFile" >/dev/null 2>&1 sed -i "/ ipset:/a \ \ ipset_file: $aghIpsetFile" "$aghConfigFile" ;; init) :;; init_end) :;; kill) [ -n "$resolver_set_supported" ] && [ -n "$agh" ] && killall -q -s HUP "$agh";; reload) [ -z "$resolver_set_supported" ] && return 1 output 3 'Reloading adguardhome ' if /etc/init.d/adguardhome reload >/dev/null 2>&1; then output_okn return 0 else output_failn return 1 fi ;; restart) [ -z "$resolver_set_supported" ] && return 1 output 3 'Restarting adguardhome ' if /etc/init.d/adguardhome restart >/dev/null 2>&1; then output_okn return 0 else output_failn return 1 fi ;; compare_hash) [ -z "$resolver_set_supported" ] && return 1 local resolverNewHash if [ -s "$aghIpsetFile" ]; then resolverNewHash="$(md5sum "$aghIpsetFile" | awk '{ print $1; }')" fi [ "$resolverNewHash" != "$resolverStoredHash" ] ;; store_hash) [ -s "$aghIpsetFile" ] && resolverStoredHash="$(md5sum "$aghIpsetFile" | awk '{ print $1; }')";; esac ;; dnsmasq.ipset) case "$param" in add_resolver_element) [ -n "$resolver_set_supported" ] && ips 'add_dnsmasq_element' "$@";; create_resolver_set) [ -n "$resolver_set_supported" ] && ips 'create_dnsmasq_set' "$@";; check_support) if [ ! -x "$ipset" ]; then state add 'errorSummary' 'errorNoIpset' return 1 fi if ! dnsmasq -v 2>/dev/null | grep -q 'no-ipset' && dnsmasq -v 2>/dev/null | grep -q 'ipset'; then resolver_set_supported='true' return 0 else state add 'warningSummary' 'warningResolverNotSupported' return 1 fi ;; cleanup) if [ -n "$resolver_set_supported" ]; then local dfl for dfl in $dnsmasqFileList; do rm -f "$dfl" done fi ;; configure) if [ -n "$resolver_set_supported" ]; then local dfl for dfl in $dnsmasqFileList; do mkdir -p "${dfl%/*}" chmod -R 660 "${dfl%/*}" chown -R root:dnsmasq "${dfl%/*}" touch "$dfl" chmod 660 "$dfl" chown root:dnsmasq "$dfl" done fi ;; configure_instances) config_load 'dhcp' if [ "$resolver_instance" = "*" ]; then config_foreach _resolver_dnsmasq_confdir 'dnsmasq' dnsmasqFile="${dnsmasqFile:-$dnsmasqFileDefault}" str_contains "$dnsmasqFileList" "$dnsmasqFileDefault" || \ dnsmasqFileList="${dnsmasqFileList:+$dnsmasqFileList }${dnsmasqFileDefault}" else for i in $resolver_instance; do _resolver_dnsmasq_confdir "@dnsmasq[$i]" \ || _resolver_dnsmasq_confdir "$i" done dnsmasqFile="${dnsmasqFile:-$dnsmasqFileDefault}" str_contains "$dnsmasqFileList" "$dnsmasqFileDefault" || \ dnsmasqFileList="${dnsmasqFileList:-$dnsmasqFileDefault}" fi ;; init) :;; init_end) :;; kill) [ -n "$resolver_set_supported" ] && killall -q -s HUP dnsmasq;; reload) [ -z "$resolver_set_supported" ] && return 1 output 3 'Reloading dnsmasq ' if /etc/init.d/dnsmasq reload >/dev/null 2>&1; then output_okn return 0 else output_failn return 1 fi ;; restart) [ -z "$resolver_set_supported" ] && return 1 output 3 'Restarting dnsmasq ' if /etc/init.d/dnsmasq restart >/dev/null 2>&1; then output_okn return 0 else output_failn return 1 fi ;; compare_hash) [ -z "$resolver_set_supported" ] && return 1 local resolverNewHash if [ -s "$dnsmasqFile" ]; then resolverNewHash="$(md5sum "$dnsmasqFile" | awk '{ print $1; }')" fi [ "$resolverNewHash" != "$resolverStoredHash" ] ;; store_hash) [ -s "$dnsmasqFile" ] && resolverStoredHash="$(md5sum "$dnsmasqFile" | awk '{ print $1; }')";; esac ;; dnsmasq.nftset) case "$param" in add_resolver_element) [ -n "$resolver_set_supported" ] && nftset 'add_dnsmasq_element' "$@";; create_resolver_set) [ -n "$resolver_set_supported" ] && nftset 'create_dnsmasq_set' "$@";; check_support) if [ ! -x "$nft" ]; then state add 'errorSummary' 'errorNoNft' return 1 fi if ! dnsmasq -v 2>/dev/null | grep -q 'no-nftset' && dnsmasq -v 2>/dev/null | grep -q 'nftset'; then resolver_set_supported='true' return 0 else state add 'warningSummary' 'warningResolverNotSupported' return 1 fi ;; cleanup) if [ -n "$resolver_set_supported" ]; then local dfl for dfl in $dnsmasqFileList; do rm -f "$dfl" done fi ;; configure) if [ -n "$resolver_set_supported" ]; then local dfl for dfl in $dnsmasqFileList; do mkdir -p "${dfl%/*}" chmod -R 660 "${dfl%/*}" chown -R root:dnsmasq "${dfl%/*}" touch "$dfl" chmod 660 "$dfl" chown root:dnsmasq "$dfl" done fi ;; configure_instances) config_load 'dhcp' if [ "$resolver_instance" = "*" ]; then config_foreach _resolver_dnsmasq_confdir 'dnsmasq' dnsmasqFile="${dnsmasqFile:-$dnsmasqFileDefault}" str_contains "$dnsmasqFileList" "$dnsmasqFileDefault" || \ dnsmasqFileList="${dnsmasqFileList:+$dnsmasqFileList }${dnsmasqFileDefault}" else for i in $resolver_instance; do _resolver_dnsmasq_confdir "@dnsmasq[$i]" \ || _resolver_dnsmasq_confdir "$i" done dnsmasqFile="${dnsmasqFile:-$dnsmasqFileDefault}" str_contains "$dnsmasqFileList" "$dnsmasqFileDefault" || \ dnsmasqFileList="${dnsmasqFileList:-$dnsmasqFileDefault}" fi ;; init) :;; init_end) :;; kill) [ -n "$resolver_set_supported" ] && killall -q -s HUP dnsmasq;; reload) [ -z "$resolver_set_supported" ] && return 1 output 3 'Reloading dnsmasq ' if /etc/init.d/dnsmasq reload >/dev/null 2>&1; then output_okn return 0 else output_failn return 1 fi ;; restart) [ -z "$resolver_set_supported" ] && return 1 output 3 'Restarting dnsmasq ' if /etc/init.d/dnsmasq restart >/dev/null 2>&1; then output_okn return 0 else output_failn return 1 fi ;; compare_hash) [ -z "$resolver_set_supported" ] && return 1 local resolverNewHash if [ -s "$dnsmasqFile" ]; then resolverNewHash="$(md5sum "$dnsmasqFile" | awk '{ print $1; }')" fi [ "$resolverNewHash" != "$resolverStoredHash" ] ;; store_hash) [ -s "$dnsmasqFile" ] && resolverStoredHash="$(md5sum "$dnsmasqFile" | awk '{ print $1; }')";; esac ;; unbound.ipset) case "$param" in add_resolver_element) :;; create_resolver_set) :;; check_support) :;; cleanup) :;; configure) :;; init) :;; init_end) :;; kill) :;; reload) :;; restart) :;; compare_hash) :;; store_hash) :;; esac ;; unbound.nftset) case "$param" in add_resolver_element) :;; create_resolver_set) :;; check_support) :;; cleanup) :;; configure) :;; init) :;; init_end) :;; kill) :;; reload) :;; restart) :;; compare_hash) :;; store_hash) :;; esac ;; esac } trap_process() { output "\\n" output "Unexpected exit or service termination: '${1}'!\\n" state add 'errorSummary' 'errorUnexpectedExit' "$1" traffic_killswitch 'remove' } traffic_killswitch() { local s=0 case "$1" in insert) local lan_subnet wan_device wan6_device [ -n "$secure_reload" ] || return 0 nft_file 'enabled' && return 0 for i in $serviceTrapSignals; do # shellcheck disable=SC2064 trap "trap_process $i" "$i" done output 3 'Activating traffic killswitch ' network_get_subnet lan_subnet "${procd_lan_interface:-lan}" network_get_physdev wan_device "${wanIface4:-wan}" network_get_physdev wan6_device "${wanIface6:-wan6}" if is_nft_mode; then nft_call add chain inet "$nftTable" "${nftPrefix}_killswitch" '{ type filter hook forward priority 0; policy accept; }' || s=1 nft_call add rule inet "$nftTable" "${nftPrefix}_killswitch" oifname "$wan_device" "$nftIPv4Flag" saddr "$lan_subnet" counter reject || s=1 nft_call add rule inet "$nftTable" "${nftPrefix}_killswitch" oifname "$wan6_device" "$nftIPv6Flag" saddr "$lan_subnet" counter reject else ipt -N "${iptPrefix}_KILLSWITCH" || s=1 ipt -A "${iptPrefix}_KILLSWITCH" -s "$lan_subnet" -o "$wan_device" -j REJECT || s=1 ipt -A "${iptPrefix}_KILLSWITCH" -s "$lan_subnet" -o "$wan6_device" -j REJECT ipt -I FORWARD -j "${iptPrefix}_KILLSWITCH" || s=1 fi if [ "$s" -eq '0' ]; then output_okn else output_failn fi ;; remove) if [ -n "$secure_reload" ] && ! nft_file 'enabled'; then output 3 'Deactivating traffic killswitch ' fi if is_nft_mode; then nft_call flush chain inet "$nftTable" "${nftPrefix}_killswitch" || s=1 nft_call delete chain inet "$nftTable" "${nftPrefix}_killswitch" || s=1 else ipt -D FORWARD -j "${iptPrefix}_KILLSWITCH" || s=1 ipt -F "${iptPrefix}_KILLSWITCH" || s=1 ipt -X "${iptPrefix}_KILLSWITCH" || s=1 fi if [ -n "$secure_reload" ] && ! nft_file 'enabled'; then if [ "$s" -eq '0' ]; then output_okn else output_failn fi fi # shellcheck disable=SC2086 trap - $serviceTrapSignals ;; esac } # original idea by @egc112: https://github.com/egc112/OpenWRT-egc-add-on/tree/main/stop-dns-leak dns_policy_routing() { if is_nft_mode; then dns_policy_routing_nft "$@"; else dns_policy_routing_iptables "$@"; fi; } dns_policy_routing_iptables() { local mark param4 param6 i negation value dest4 dest6 ipInsertOption="-A" local ip4error='1' ip6error='1' iface='dns' local name="$1" src_addr="$2" dest_dns="$3" uid="$4" local proto='tcp udp' chain='PREROUTING' if [ -n "$ipv6_enabled" ] && { is_ipv6 "$src_addr" || is_ipv6 "$dest_dns"; }; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name" return 1 fi if is_family_mismatch "$src_addr" "$dest_dns"; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '${src_addr}' '${dest_dns}'" return 1 fi if is_supported_interface "$dest_dns"; then local d for d in $(uci -q get network."$dest_dns".dns); do if ! is_family_mismatch "$src_addr" "$d"; then if is_ipv4 "$d"; then dest_dns4="${dest_dns4:-$d}" elif is_ipv6 "$d"; then dest_dns6="${dest_dns6:-$d}" fi fi done else dest_dns4="$dest_dns" dest_dns6="$dest_dns" fi if [ -z "${dest_dns4}${dest_dns6}" ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessNoInterfaceDns' "'$dest_dns'" return 1 fi dest4="--dport 53 -j DNAT --to $dest_dns4" dest6="--dport 53 -j DNAT --to $dest_dns6" for i in $proto; do param4="-t nat ${ipInsertOption} ${iptPrefix}_${chain} ${dest4} -p $i" param6="-t nat ${ipInsertOption} ${iptPrefix}_${chain} ${dest6} -p $i" if [ -n "$src_addr" ]; then if [ "${src_addr:0:1}" = "!" ]; then negation='!'; value="${src_addr:1}" else unset negation; value="$src_addr"; fi if is_phys_dev "$value"; then param4="$param4 ${negation:+$negation }-m physdev --physdev-in ${value:1}" param6="$param6 ${negation:+$negation }-m physdev --physdev-in ${value:1}" elif is_ipv4_netmask "$value"; then local target='src' type='net' if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $src_addr" && \ ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $src_addr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" else param4="$param4 ${negation:+$negation }-s $value" param6="$param6 ${negation:+$negation }-s $value" fi elif is_mac_address "$value"; then local target='src' type='mac' if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $src_addr" && \ ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $src_addr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" else param4="$param4 -m mac ${negation:+$negation }--mac-source $value" param6="$param6 -m mac ${negation:+$negation }--mac-source $value" fi else local target='src' type='ip' if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $src_addr" && \ ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $src_addr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" else local resolvedIP4 resolvedIP6 resolvedIP4="$(resolveip_to_ipt4 "$value")" resolvedIP6="$(resolveip_to_ipt6 "$value")" if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then state add 'errorSummary' 'errorFailedToResolve' "$value" fi param4="$param4 ${negation:+$negation }-s $resolvedIP4" param6="$param6 ${negation:+$negation }-s $resolvedIP6" fi fi fi if [ -n "$name" ]; then param4="$param4 -m comment --comment $(str_extras_to_underscore "$name")" param6="$param6 -m comment --comment $(str_extras_to_underscore "$name")" fi local ipv4_error='0' ipv6_error='0' if [ "$param4" = "$param6" ]; then ipt4 "$param4" || ipv4_error='1' else ipt4 "$param4" || ipv4_error='1' ipt6 "$param6" || ipv6_error='1' fi if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param4" state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param6" logger -t "$packageName" "ERROR: iptables $param4" logger -t "$packageName" "ERROR: iptables $param6" elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param4" logger -t "$packageName" "ERROR: iptables $param4" fi done } dns_policy_routing_nft() { local mark i nftInsertOption='add' local param4 param6 proto_i negation value dest4 dest6 dest_dns4 dest_dns6 local name="$1" src_addr="$2" dest_dns="$3" uid="$4" local proto='tcp udp' chain='dstnat_lan' iface='dns' if [ -z "$ipv6_enabled" ] && { is_ipv6 "$src_addr" || is_ipv6 "$dest_dns"; }; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name" return 1 fi if is_family_mismatch "$src_addr" "$dest_dns"; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_dns'" return 1 fi if is_supported_interface "$dest_dns"; then local d for d in $(uci -q get network."$dest_dns".dns); do if ! is_family_mismatch "$src_addr" "$d"; then if is_ipv4 "$d"; then dest_dns4="${dest_dns4:-$d}" elif is_ipv6 "$d"; then dest_dns6="${dest_dns6:-$d}" fi fi done else dest_dns4="$dest_dns" dest_dns6="$dest_dns" fi if [ -z "${dest_dns4}${dest_dns6}" ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessNoInterfaceDns' "'$dest_dns'" return 1 fi dest4="dport 53 counter dnat ip to $dest_dns4" dest6="dport 53 counter dnat ip to $dest_dns6" for proto_i in $proto; do unset param4 unset param6 if [ -n "$src_addr" ]; then if [ "${src_addr:0:1}" = "!" ]; then negation='!='; value="${src_addr:1}" else unset negation; value="$src_addr"; fi if is_phys_dev "$value"; then param4="${param4:+$param4 }iifname ${negation:+$negation }${value:1}" param6="${param6:+$param6 }iifname ${negation:+$negation }${value:1}" elif is_mac_address "$value"; then local target='src' type='mac' if nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \ nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then param4="${param4:+$param4 }ether saddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}" param6="${param6:+$param6 }ether saddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}" else param4="${param4:+$param4 }ether saddr ${negation:+$negation }${value}" param6="${param6:+$param6 }ether saddr ${negation:+$negation }${value}" fi else local target='src' type='ip' if nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \ nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}" param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}" else param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }${value}" param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }${value}" fi fi fi param4="$nftInsertOption rule inet ${nftTable} ${nftPrefix}_${chain} ${param4} ${proto_i:+$proto_i }${dest4} comment \"$name\"" param6="$nftInsertOption rule inet ${nftTable} ${nftPrefix}_${chain} ${param6} ${proto_i:+$proto_i }${dest6} comment \"$name\"" local ipv4_error='0' ipv6_error='0' if [ "$policy_routing_nft_prev_param4" != "$param4" ]; then nft4 "$param4" || ipv4_error='1' policy_routing_nft_prev_param4="$param4" fi if [ "$policy_routing_nft_prev_param6" != "$param6" ] && \ [ "$param4" != "$param6" ]; then nft6 "$param6" || ipv6_error='1' policy_routing_nft_prev_param6="$param6" fi if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4" state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param6" logger -t "$packageName" "ERROR: nft $param4" logger -t "$packageName" "ERROR: nft $param6" elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4" logger -t "$packageName" "ERROR: nft $param4" fi done } dns_policy_process() { local i j uid="$4" if [ -z "$uid" ]; then # first call [ "$enabled" -gt '0' ] || return 0 unset processDnsPolicyError uid="$1" output 2 "Routing '$name' DNS to $dest_dns " if [ -z "${src_addr}" ]; then state add 'errorSummary' 'errorPolicyNoSrcDest' "$name" output_fail; return 1; fi if [ -z "$dest_dns" ]; then state add 'errorSummary' 'errorPolicyNoDns' "$name" output_fail; return 1; fi dns_policy_process "$name" "$src_addr" "$dest_dns" "$uid" if [ -n "$processPolicyError" ]; then output_fail else output_ok fi else # recursive call, get options from passed variables local name="$1" src_addr="$2" dest_dns="$3" if str_contains "$src_addr" '[ ;\{\}]'; then for i in $(str_extras_to_space "$src_addr"); do [ -n "$i" ] && dns_policy_process "$name" "$i" "$dest_dns" "$uid"; done elif str_contains "$dest_dns" '[ ;\{\}]'; then for i in $(str_extras_to_space "$dest_dns"); do [ -n "$i" ] && dns_policy_process "$name" "$src_addr" "$i" "$uid"; done else if is_url "$src_addr"; then src_addr="$(process_url "$src_addr")" [ -n "$src_addr" ] && dns_policy_process "$name" "$src_addr" "$dest_dns" "$uid" else # if only src_addr is set add option 121 to dhcp leases? dns_policy_routing "$name" "$src_addr" "$dest_dns" "$uid" fi fi fi } policy_routing() { if is_nft_mode; then policy_routing_nft "$@"; else policy_routing_iptables "$@"; fi; } policy_routing_iptables() { local mark param4 param6 i negation value dest4 dest6 ipInsertOption="-A" local ip4error='1' ip6error='1' local name="$1" iface="$2" laddr="$3" lport="$4" raddr="$5" rport="$6" proto chain uid="$9" proto="$(str_to_lower "$7")" chain="$(str_to_upper "$8")" chain="${chain:-PREROUTING}" mark=$(eval echo "\$mark_${iface//-/_}") if [ -n "$ipv6_enabled" ] && { is_ipv6 "$laddr" || is_ipv6 "$raddr"; }; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name" return 1 fi if is_tor "$iface"; then return 1 elif is_xray "$iface"; then unset rport [ -z "$lport" ] && lport='0-52,54-65535' proto='tcp udp' dest4="-j TPROXY --on-ip 0.0.0.0 --on-port $(get_xray_traffic_port "$iface")" dest6="-j TPROXY --on-ip :: --on-port $(get_xray_traffic_port "$iface")" elif [ -n "$mark" ]; then dest4="-g ${iptPrefix}_MARK_${mark}" dest6="-g ${iptPrefix}_MARK_${mark}" elif [ "$iface" = "ignore" ]; then dest4="-j RETURN" dest6="-j RETURN" else processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessUnknownFwmark' "$iface" return 1 fi if is_family_mismatch "$laddr" "$raddr"; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$laddr' '$raddr'" return 1 fi if [ -z "$proto" ]; then if [ -n "${lport}${rport}" ]; then proto='tcp udp' else proto='all' fi fi for i in $proto; do if [ "$i" = 'all' ]; then param4="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest4" param6="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest6" elif ! is_supported_protocol "$i"; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessUnknownProtocol' "${name}: '$i'" return 1 else param4="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest4 -p $i" param6="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest6 -p $i" fi if [ -n "$laddr" ]; then if [ "${laddr:0:1}" = "!" ]; then negation='!'; value="${laddr:1}" else unset negation; value="$laddr"; fi if is_phys_dev "$value"; then param4="$param4 ${negation:+$negation }-m physdev --physdev-in ${value:1}" param6="$param6 ${negation:+$negation }-m physdev --physdev-in ${value:1}" elif is_ipv4_netmask "$value"; then local target='src' type='net' if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $laddr" && \ ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $laddr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" else param4="$param4 ${negation:+$negation }-s $value" param6="$param6 ${negation:+$negation }-s $value" fi elif is_mac_address "$value"; then local target='src' type='mac' if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $laddr" && \ ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $laddr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" else param4="$param4 -m mac ${negation:+$negation }--mac-source $value" param6="$param6 -m mac ${negation:+$negation }--mac-source $value" fi else local target='src' type='ip' if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $laddr" && \ ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $laddr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" else local resolvedIP4 resolvedIP6 resolvedIP4="$(resolveip_to_ipt4 "$value")" resolvedIP6="$(resolveip_to_ipt6 "$value")" if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then state add 'errorSummary' 'errorFailedToResolve' "$value" fi param4="$param4 ${negation:+$negation }-s $resolvedIP4" param6="$param6 ${negation:+$negation }-s $resolvedIP6" fi fi fi if [ -n "$lport" ]; then if [ "${lport:0:1}" = "!" ]; then negation='!'; value="${lport:1}" else unset negation; value="$lport"; fi param4="$param4 -m multiport ${negation:+$negation }--sport ${value//-/:}" param6="$param6 -m multiport ${negation:+$negation }--sport ${value//-/:}" fi if [ -n "$raddr" ]; then if [ "${raddr:0:1}" = "!" ]; then negation='!'; value="${raddr:1}" else unset negation; value="$raddr"; fi if is_ipv4_netmask "$value"; then local target='dst' type='net' if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $raddr" && \ ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $raddr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" else param4="$param4 ${negation:+$negation }-d ${value}" param6="$param6 ${negation:+$negation }-d ${value}" fi elif is_domain "$value"; then local target='dst' type='ip' if resolver 'create_resolver_set' "$iface" "$target" "$type" "$uid" "${name}: $raddr" && \ resolver 'add_resolver_element' "$iface" "$target" "$type" "$uid" "${name}: $raddr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" elif ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $raddr" && \ ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $raddr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" else local resolvedIP4 resolvedIP6 resolvedIP4="$(resolveip_to_ipt4 "$value")" resolvedIP6="$(resolveip_to_ipt6 "$value")" if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then state add 'errorSummary' 'errorFailedToResolve' "$value" fi param4="$param4 ${negation:+$negation }-d $resolvedIP4" param6="$param6 ${negation:+$negation }-d $resolvedIP6" fi else local target='dst' type='ip' if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $raddr" && \ ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $raddr" "$value"; then param4="$param4 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" param6="$param6 -m set ${negation:+$negation }--match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" else param4="$param4 ${negation:+$negation }-d ${value}" param6="$param6 ${negation:+$negation }-d ${value}" fi fi fi if [ -n "$rport" ]; then if [ "${rport:0:1}" = "!" ]; then negation='!'; value="${rport:1}" else unset negation; value="$rport"; fi param4="$param4 -m multiport ${negation:+$negation }--dport ${value//-/:}" param6="$param6 -m multiport ${negation:+$negation }--dport ${value//-/:}" fi if [ -n "$name" ]; then param4="$param4 -m comment --comment $(str_extras_to_underscore "$name")" param6="$param6 -m comment --comment $(str_extras_to_underscore "$name")" fi local ipv4_error='0' ipv6_error='0' if [ "$param4" = "$param6" ]; then ipt4 "$param4" || ipv4_error='1' else ipt4 "$param4" || ipv4_error='1' ipt6 "$param6" || ipv6_error='1' fi if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param4" state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param6" logger -t "$packageName" "ERROR: iptables $param4" logger -t "$packageName" "ERROR: iptables $param6" elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param4" logger -t "$packageName" "ERROR: iptables $param4" fi done } policy_routing_nft() { local mark i nftInsertOption='add' local param4 param6 proto_i negation value dest4 dest6 local name="$1" iface="$2" src_addr="$3" src_port="$4" dest_addr="$5" dest_port="$6" proto chain uid="$9" proto="$(str_to_lower "$7")" chain="$(str_to_lower "$8")" chain="${chain:-prerouting}" mark=$(eval echo "\$mark_${iface//-/_}") if [ -z "$ipv6_enabled" ] && { is_ipv6 "$src_addr" || is_ipv6 "$dest_addr"; }; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name" return 1 fi if is_tor "$iface"; then unset dest_port unset proto elif is_xray "$iface"; then unset dest_port [ -z "$src_port" ] && src_port='0-65535' dest4="tproxy $nftIPv4Flag to: $(get_xray_traffic_port "$iface") accept" dest6="tproxy $nftIPv6Flag to: $(get_xray_traffic_port "$iface") accept" elif [ -n "$mark" ]; then dest4="goto ${nftPrefix}_mark_${mark}" dest6="goto ${nftPrefix}_mark_${mark}" elif [ "$iface" = "ignore" ]; then dest4="return" dest6="return" else processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessUnknownFwmark' "$iface" return 1 fi if is_family_mismatch "$src_addr" "$dest_addr"; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_addr'" return 1 fi if [ -z "$proto" ]; then if [ -n "${src_port}${dest_port}" ]; then proto='tcp udp' else proto='all' fi fi for proto_i in $proto; do unset param4 unset param6 if [ "$proto_i" = 'all' ]; then unset proto_i elif ! is_supported_protocol "$proto_i"; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessUnknownProtocol' "${name}: '$proto_i'" return 1 fi if [ -n "$src_addr" ]; then if [ "${src_addr:0:1}" = "!" ]; then negation='!='; value="${src_addr:1}" else unset negation; value="$src_addr"; fi if is_phys_dev "$value"; then param4="${param4:+$param4 }iifname ${negation:+$negation }${value:1}" param6="${param6:+$param6 }iifname ${negation:+$negation }${value:1}" elif is_mac_address "$value"; then local target='src' type='mac' if nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \ nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then param4="${param4:+$param4 }ether saddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}" param6="${param6:+$param6 }ether saddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}" else param4="${param4:+$param4 }ether saddr ${negation:+$negation }${value}" param6="${param6:+$param6 }ether saddr ${negation:+$negation }${value}" fi else local target='src' type='ip' if nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \ nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}" param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}" else param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }${value}" param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }${value}" fi fi fi if [ -n "$dest_addr" ]; then if [ "${dest_addr:0:1}" = "!" ]; then negation='!='; value="${dest_addr:1}" else unset negation; value="$dest_addr"; fi if is_phys_dev "$value"; then param4="${param4:+$param4 }oifname ${negation:+$negation }${value:1}" param6="${param6:+$param6 }oifname ${negation:+$negation }${value:1}" elif is_domain "$value"; then local target='dst' type='ip' if resolver 'create_resolver_set' "$iface" "$target" "$type" "$uid" "$name" && \ resolver 'add_resolver_element' "$iface" "$target" "$type" "$uid" "$name" "$value"; then param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}" param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}" elif nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \ nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}" param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}" else local resolvedIP4 resolvedIP6 resolvedIP4="$(resolveip_to_nftset4 "$value")" resolvedIP6="$(resolveip_to_nftset6 "$value")" if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then state add 'errorSummary' 'errorFailedToResolve' "$value" fi param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }{ $resolvedIP4 }" param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }{ $resolvedIP6 }" fi else local target='dst' type='ip' if nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \ nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}" param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}" else param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }${value}" param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }${value}" fi fi fi if [ -n "$src_port" ]; then if [ "${src_port:0:1}" = "!" ]; then negation='!='; value="${src_port:1}" else unset negation; value="$src_port"; fi param4="${param4:+$param4 }${proto_i:+$proto_i }sport ${negation:+$negation }{$(ports_to_nftset "$value")}" param6="${param6:+$param6 }${proto_i:+$proto_i }sport ${negation:+$negation }{$(ports_to_nftset "$value")}" fi if [ -n "$dest_port" ]; then if [ "${dest_port:0:1}" = "!" ]; then negation='!='; value="${dest_port:1}" else unset negation; value="$dest_port"; fi param4="${param4:+$param4 }${proto_i:+$proto_i }dport ${negation:+$negation }{$(ports_to_nftset "$value")}" param6="${param6:+$param6 }${proto_i:+$proto_i }dport ${negation:+$negation }{$(ports_to_nftset "$value")}" fi if is_tor "$iface"; then local dest_udp_53 dest_tcp_80 dest_udp_80 dest_tcp_443 dest_udp_443 local ipv4_error='0' ipv6_error='0' local dest_i dest4 dest6 param4="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} dstnat meta nfproto ipv4 $param4" param6="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} dstnat meta nfproto ipv6 $param6" dest_udp_53="udp dport 53 counter redirect to :${torDnsPort} comment 'Tor-DNS-UDP'" dest_tcp_80="tcp dport 80 counter redirect to :${torTrafficPort} comment 'Tor-HTTP-TCP'" dest_udp_80="udp dport 80 counter redirect to :${torTrafficPort} comment 'Tor-HTTP-UDP'" dest_tcp_443="tcp dport 443 counter redirect to :${torTrafficPort} comment 'Tor-HTTPS-TCP'" dest_udp_443="udp dport 443 counter redirect to :${torTrafficPort} comment 'Tor-HTTPS-UDP'" for dest_i in dest_udp_53 dest_tcp_80 dest_udp_80 dest_tcp_443 dest_udp_443; do eval "dest4=\$$dest_i" eval "dest6=\$$dest_i" nft4 "$param4" "$dest4" || ipv4_error='1' nft6 "$param6" "$dest6" || ipv6_error='1' if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4 $dest4" state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param6 $dest6" logger -t "$packageName" "ERROR: nft $param4 $dest4" logger -t "$packageName" "ERROR: nft $param6 $dest6" elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4 $dest4" logger -t "$packageName" "ERROR: nft $param4 $dest4" fi done else param4="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} $param4 $dest4 comment \"$name\"" param6="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} $param6 $dest6 comment \"$name\"" local ipv4_error='0' ipv6_error='0' if [ "$policy_routing_nft_prev_param4" != "$param4" ]; then nft4 "$param4" || ipv4_error='1' policy_routing_nft_prev_param4="$param4" fi if [ "$policy_routing_nft_prev_param6" != "$param6" ] && \ [ "$param4" != "$param6" ]; then nft6 "$param6" || ipv6_error='1' policy_routing_nft_prev_param6="$param6" fi if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4" state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param6" logger -t "$packageName" "ERROR: nft $param4" logger -t "$packageName" "ERROR: nft $param6" elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name" state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4" logger -t "$packageName" "ERROR: nft $param4" fi fi done } policy_process() { local i j uid="$9" if [ -z "$uid" ]; then # first call [ "$enabled" -gt '0' ] || return 0 unset processPolicyError uid="$1" if is_nft_mode; then chain="$(str_to_lower "$chain")" else chain="$(str_to_upper "$chain")" fi proto="$(str_to_lower "$proto")" [ "$proto" = 'auto' ] && unset proto [ "$proto" = 'all' ] && unset proto output 2 "Routing '$name' via $interface " if [ -z "${src_addr}${src_port}${dest_addr}${dest_port}" ]; then state add 'errorSummary' 'errorPolicyNoSrcDest' "$name" output_fail; return 1; fi if [ -z "$interface" ]; then state add 'errorSummary' 'errorPolicyNoInterface' "$name" output_fail; return 1; fi if ! is_supported_interface "$interface"; then state add 'errorSummary' 'errorPolicyUnknownInterface' "$name" output_fail; return 1; fi src_port="${src_port// / }"; src_port="${src_port// /,}"; src_port="${src_port//,\!/ !}"; dest_port="${dest_port// / }"; dest_port="${dest_port// /,}"; dest_port="${dest_port//,\!/ !}"; policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid" if [ -n "$processPolicyError" ]; then output_fail else output_ok fi else # recursive call, get options from passed variables local name="$1" interface="$2" src_addr="$3" src_port="$4" dest_addr="$5" dest_port="$6" proto="$7" chain="$8" if str_contains "$src_addr" '[ ;\{\}]'; then for i in $(str_extras_to_space "$src_addr"); do [ -n "$i" ] && policy_process "$name" "$interface" "$i" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"; done elif str_contains "$src_port" '[ ;\{\}]'; then for i in $(str_extras_to_space "$src_port"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$i" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"; done elif str_contains "$dest_addr" '[ ;\{\}]'; then for i in $(str_extras_to_space "$dest_addr"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$i" "$dest_port" "$proto" "$chain" "$uid"; done elif str_contains "$dest_port" '[ ;\{\}]'; then for i in $(str_extras_to_space "$dest_port"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$i" "$proto" "$chain" "$uid"; done elif str_contains "$proto" '[ ;\{\}]'; then for i in $(str_extras_to_space "$proto"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$i" "$chain" "$uid"; done else if [ -n "$secure_reload" ] && { is_url_dl "$src_addr" || is_url_dl "$dest_addr"; }; then state add 'errorSummary' 'errorNoDownloadWithSecureReload' "$name" elif is_url "$src_addr"; then src_addr="$(process_url "$src_addr")" [ -n "$src_addr" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid" elif is_url "$dest_addr"; then dest_addr="$(process_url "$dest_addr")" [ -n "$dest_addr" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid" else # if only src_addr is set add option 121 to dhcp leases? policy_routing "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid" fi fi fi } try() { if ! "$@"; then state add 'errorSummary' 'errorTryFailed' "$*" return 1 fi } interface_routing() { local action="$1" tid="$2" mark="$3" iface="$4" gw4="$5" dev="$6" gw6="$7" dev6="$8" priority="$9" local dscp s=0 i ipv4_error=1 ipv6_error=1 if [ -z "$tid" ] || [ -z "$mark" ] || [ -z "$iface" ]; then state add 'errorSummary' 'errorInterfaceRoutingEmptyValues' return 1 fi case "$action" in create) if is_netifd_table_interface "$iface"; then ipv4_error=0 $ip_bin -4 rule del table "$tid" >/dev/null 2>&1 try "$ip_bin" -4 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 if is_nft_mode; then try nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1 try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} counter mark set mark and ${fw_maskXor} xor ${mark}" || ipv4_error=1 try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" || ipv4_error=1 else ipt -t mangle -N "${iptPrefix}_MARK_${mark}" || ipv4_error=1 ipt -t mangle -A "${iptPrefix}_MARK_${mark}" -j MARK --set-xmark "${mark}/${fw_mask}" || ipv4_error=1 ipt -t mangle -A "${iptPrefix}_MARK_${mark}" -j RETURN || ipv4_error=1 fi if [ -n "$ipv6_enabled" ]; then ipv6_error=0 $ip_bin -6 rule del table "$tid" >/dev/null 2>&1 try "$ip_bin" -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$((priority-1))" || ipv6_error=1 fi else if ! grep -q "$tid ${ipTablePrefix}_${iface}" "$rtTablesFile"; then sed -i "/${ipTablePrefix}_${iface}/d" "$rtTablesFile" sync echo "$tid ${ipTablePrefix}_${iface}" >> "$rtTablesFile" sync fi $ip_bin -4 rule del table "$tid" >/dev/null 2>&1 $ip_bin -4 route flush table "$tid" >/dev/null 2>&1 if [ -n "$gw4" ] || [ "$strict_enforcement" -ne '0' ]; then ipv4_error=0 if [ -z "$gw4" ]; then try "$ip_bin" -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1 else try "$ip_bin" -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1 fi # shellcheck disable=SC2086 while read -r i; do i="$(echo "$i" | sed 's/ linkdown$//')" i="$(echo "$i" | sed 's/ onlink$//')" idev="$(echo "$i" | grep -Eso 'dev [^ ]*' | awk '{print $2}')" if ! is_supported_iface_dev "$idev"; then try "$ip_bin" -4 route add $i table "$tid" >/dev/null 2>&1 || ipv4_error=1 fi done << EOF $($ip_bin -4 route list table main) EOF try "$ip_bin" -4 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 if is_nft_mode; then try nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1 try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} counter mark set mark and ${fw_maskXor} xor ${mark}" || ipv4_error=1 try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" || ipv4_error=1 else ipt -t mangle -N "${iptPrefix}_MARK_${mark}" || ipv4_error=1 ipt -t mangle -A "${iptPrefix}_MARK_${mark}" -j MARK --set-xmark "${mark}/${fw_mask}" || ipv4_error=1 ipt -t mangle -A "${iptPrefix}_MARK_${mark}" -j RETURN || ipv4_error=1 fi fi if [ -n "$ipv6_enabled" ]; then ipv6_error=0 $ip_bin -6 rule del table "$tid" >/dev/null 2>&1 $ip_bin -6 route flush table "$tid" >/dev/null 2>&1 if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ "$strict_enforcement" -ne '0' ]; then if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then try "$ip_bin" -6 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv6_error=1 elif "$ip_bin" -6 route list table main | grep -q " dev $dev6 "; then "$ip_bin" -6 route add default via "$gw6" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 while read -r i; do i="$(echo "$i" | sed 's/ linkdown$//')" i="$(echo "$i" | sed 's/ onlink$//')" # shellcheck disable=SC2086 try "$ip_bin" -6 route add $i table "$tid" >/dev/null 2>&1 || ipv6_error=1 done << EOF $($ip_bin -6 route list table main | grep " dev $dev6 ") EOF else try "$ip_bin" -6 route add "$($ip_bin -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 try "$ip_bin" -6 route add default dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 fi fi try "$ip_bin" -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$((priority-1))" >/dev/null 2>&1 || ipv6_error=1 fi fi if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then dscp="$(uci_get "$packageName" 'config' "${iface}_dscp")" if is_nft_mode; then if [ "${dscp:-0}" -ge '1' ] && [ "${dscp:-0}" -le '63' ]; then try nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv4Flag} dscp ${dscp} goto ${nftPrefix}_mark_${mark}" || s=1 if [ -n "$ipv6_enabled" ]; then try nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv6Flag} dscp ${dscp} goto ${nftPrefix}_mark_${mark}" || s=1 fi fi if [ "$iface" = "$icmp_interface" ]; then try nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv4Flag} protocol icmp goto ${nftPrefix}_mark_${mark}" || s=1 if [ -n "$ipv6_enabled" ]; then try nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv6Flag} protocol icmp goto ${nftPrefix}_mark_${mark}" || s=1 fi fi else if [ "${dscp:-0}" -ge '1' ] && [ "${dscp:-0}" -le '63' ]; then ipt -t mangle -I "${iptPrefix}_PREROUTING" -m dscp --dscp "${dscp}" -g "${iptPrefix}_MARK_${mark}" || s=1 fi if [ "$iface" = "$icmp_interface" ]; then ipt -t mangle -I "${iptPrefix}_OUTPUT" -p icmp -g "${iptPrefix}_MARK_${mark}" || s=1 fi fi else s=1 fi return "$s" ;; create_user_set) if is_nft_mode; then nftset 'create_user_set' "$iface" 'dst' 'ip' 'user' '' "$mark" || s=1 nftset 'create_user_set' "$iface" 'src' 'ip' 'user' '' "$mark" || s=1 nftset 'create_user_set' "$iface" 'src' 'mac' 'user' '' "$mark" || s=1 else ips 'create_user_set' "$iface" 'dst' 'ip' 'user' '' "$mark" || s=1 ips 'create_user_set' "$iface" 'dst' 'net' 'user' '' "$mark" || s=1 ips 'create_user_set' "$iface" 'src' 'ip' 'user' '' "$mark" || s=1 ips 'create_user_set' "$iface" 'src' 'net' 'user' '' "$mark" || s=1 ips 'create_user_set' "$iface" 'src' 'mac' 'user' '' "$mark" || s=1 fi return "$s" ;; delete|destroy) $ip_bin rule del table "$tid" >/dev/null 2>&1 if ! is_netifd_table_interface "$iface"; then $ip_bin route flush table "$tid" >/dev/null 2>&1 sed -i "/${ipTablePrefix}_${iface}\$/d" "$rtTablesFile" sync fi return "$s" ;; reload_interface) is_netifd_table_interface "$iface" && return 0; ipv4_error=0 $ip_bin rule del table "$tid" >/dev/null 2>&1 if ! is_netifd_table_interface "$iface"; then $ip_bin route flush table "$tid" >/dev/null 2>&1 fi if [ -n "$gw4" ] || [ "$strict_enforcement" -ne '0' ]; then if [ -z "$gw4" ]; then try "$ip_bin" -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1 else try "$ip_bin" -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1 fi try "$ip_bin" rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 fi if [ -n "$ipv6_enabled" ]; then ipv6_error=0 if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ "$strict_enforcement" -ne '0' ]; then if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then try "$ip_bin" -6 route add unreachable default table "$tid" || ipv6_error=1 elif $ip_bin -6 route list table main | grep -q " dev $dev6 "; then while read -r i; do # shellcheck disable=SC2086 try "$ip_bin" -6 route add $i table "$tid" >/dev/null 2>&1 || ipv6_error=1 done << EOF $($ip_bin -6 route list table main | grep " dev $dev6 ") EOF else try "$ip_bin" -6 route add "$($ip_bin -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 try "$ip_bin" -6 route add default dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 fi fi try "$ip_bin" -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1 fi if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then s=0 else s=1 fi return "$s" ;; esac } json_add_gateway() { local action="$1" tid="$2" mark="$3" iface="$4" gw4="$5" dev4="$6" gw6="$7" dev6="$8" priority="$9" default="${10}" json_add_object '' json_add_string 'name' "$iface" json_add_string 'device_ipv4' "$dev4" json_add_string 'gateway_ipv4' "$gw4" json_add_string 'device_ipv6' "$dev6" json_add_string 'gateway_ipv6' "$gw6" if [ -n "$default" ]; then json_add_boolean 'default' '1' else json_add_boolean 'default' '0' fi json_add_string 'action' "$action" json_add_string 'table_id' "$tid" json_add_string 'mark' "$mark" json_add_string 'priority' "$priority" json_close_object } interface_process() { local gw4 gw6 dev dev6 s=0 dscp iface="$1" action="$2" reloadedIface="$3" local displayText dispDev dispGw4 dispGw6 dispStatus if [ "$iface" = 'all' ] && [ "$action" = 'prepare' ]; then config_load 'network' ifaceMark="$(printf '0x%06x' "$wan_mark")" ifacePriority="$wan_ip_rules_priority" unset ifaceTableID return 0 fi if [ "$iface" = 'tor' ]; then case "$action" in create|reload) torDnsPort="$(get_tor_dns_port)" torTrafficPort="$(get_tor_traffic_port)" displayText="${iface}/53->${torDnsPort}/80,443->${torTrafficPort}" gatewaySummary="${gatewaySummary}${displayText}\\n" ;; destroy) ;; esac return 0 fi is_supported_interface "$iface" || return 0 is_wan6 "$iface" && return 0 [ "$((ifaceMark))" -gt "$((fw_mask))" ] && return 1 if is_ovpn "$iface" && ! is_ovpn_valid "$iface"; then : || state add 'warningSummary' 'warningInvalidOVPNConfig' "$iface" fi network_get_device dev "$iface" [ -z "$dev" ] && network_get_physdev dev "$iface" if is_wan "$iface" && [ -n "$wanIface6" ] && str_contains "$wanIface6" "$iface"; then network_get_device dev6 "$wanIface6" [ -z "$dev6" ] && network_get_physdev dev6 "$wanIface6" fi [ -z "$dev6" ] && dev6="$dev" [ -z "$ifaceMark" ] && ifaceMark="$(printf '0x%06x' "$wan_mark")" [ -z "$ifacePriority" ] && ifacePriority="$wan_ip_rules_priority" 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 + wan_mark)))" ifacePriority="$((ifacePriority - 1))" ifaceTableID="$((ifaceTableID + 1))" return 0 ;; 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" pbr_get_gateway6 gw6 "$iface" "$dev6" dispGw4="${gw4:-0.0.0.0}" dispGw6="${gw6:-::/0}" [ "$iface" != "$dev" ] && dispDev="$dev" if is_default_dev "$dev"; then [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__" fi displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}" output 2 "Setting up routing for '$displayText' " if interface_routing 'create' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority"; then json_add_gateway 'create' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority" "$dispStatus" gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\\n" if is_netifd_table_interface "$iface"; then output_okb; else output_ok; fi else state add 'errorSummary' 'errorFailedSetup' "$displayText" output_fail 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 [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__" fi displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}" interface_routing 'create_user_set' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority" ;; 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 [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__" fi displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}" displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}" output 2 "Removing routing for '$displayText' " interface_routing 'destroy' "${ifaceTableID}" "${ifaceMark}" "${iface}" if is_netifd_table_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" pbr_get_gateway6 gw6 "$iface" "$dev6" dispGw4="${gw4:-0.0.0.0}" dispGw6="${gw6:-::/0}" [ "$iface" != "$dev" ] && dispDev="$dev" if is_default_dev "$dev"; then [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__" fi displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}" 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" pbr_get_gateway6 gw6 "$iface" "$dev6" dispGw4="${gw4:-0.0.0.0}" dispGw6="${gw6:-::/0}" [ "$iface" != "$dev" ] && dispDev="$dev" if is_default_dev "$dev"; then [ "$verbosity" = '1' ] && dispStatus="$_OK_" || dispStatus="$__OK__" fi displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}" if [ "$iface" = "$reloadedIface" ]; then output 2 "Reloading routing for '$displayText' " if interface_routing 'reload_interface' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority"; then json_add_gateway 'reload_interface' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority" "$dispStatus" gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\\n" if is_netifd_table_interface "$iface"; then output_okb; else output_ok; fi else state add 'errorSummary' 'errorFailedReload' "$displayText" output_fail fi else gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\\n" fi ;; esac # ifaceTableID="$((ifaceTableID + 1))" ifaceMark="$(printf '0x%06x' $((ifaceMark + wan_mark)))" ifacePriority="$((ifacePriority - 2))" return $s } user_file_process() { local shellBin="${SHELL:-/bin/ash}" [ "$enabled" -gt '0' ] || return 0 if [ ! -s "$path" ]; then state add 'errorSummary' 'errorUserFileNotFound' "$path" output_fail return 1 fi if ! $shellBin -n "$path"; then state add 'errorSummary' 'errorUserFileSyntax' "$path" output_fail return 1 fi output 2 "Running $path " # shellcheck disable=SC1090 if ! . "$path"; then state add 'errorSummary' 'errorUserFileRunning' "$path" if grep -q -w 'curl' "$path" && ! is_present 'curl'; then state add 'errorSummary' 'errorUserFileNoCurl' "$path" fi output_fail return 1 else output_ok return 0 fi } boot() { local procd_boot_delay config_load "$packageName" config_get procd_boot_delay 'config' 'procd_boot_delay' '0' nft_file 'delete' ubus -t 30 wait_for network.interface 2>/dev/null { is_integer "$procd_boot_delay" && sleep "$procd_boot_delay"; \ rc_procd start_service 'on_boot' && service_started 'on_boot'; } & } on_firewall_reload() { if [ ! -e "$packageLockFile" ]; then logger -t "$packageName" "Reload on firewall action aborted: service is stopped." return 0 else if nft_file 'exists'; then logger -t "$packageName" "Reusing the fw4 nft file." else rc_procd start_service 'on_firewall_reload' "$1" fi fi } on_interface_reload() { if [ ! -e "$packageLockFile" ]; then logger -t "$packageName" "Reload on interface change aborted: service is stopped." return 0 else rc_procd start_service 'on_interface_reload' "$1" fi } start_service() { local resolverStoredHash resolverNewHash i param="$1" reloadedIface load_environment "${param:-on_start}" "$(load_validate_config)" || return 1 is_wan_up "$param" || return 1 interface_process 'all' 'prepare' config_foreach interface_process 'interface' 'pre_init' case "$param" in on_boot) serviceStartTrigger='on_start' ;; on_firewall_reload) serviceStartTrigger='on_start' ;; on_interface_reload) reloadedIface="$2" local tid pre_init_tid tid="$(get_rt_tables_id "$reloadedIface")" pre_init_tid="$(eval echo "\$pre_init_tid_${reloadedIface//-/_}")" if [ "$tid" = "$pre_init_tid" ]; then # logger -t "$packageName" "Updated interface $reloadedIface TID: ${tid}; Pre-Init TID: ${pre_init_tid}. Reloading..." serviceStartTrigger='on_interface_reload' else # logger -t "$packageName" "Updated interface $reloadedIface TID: ${tid}; Pre-Init TID: ${pre_init_tid}. Restarting..." serviceStartTrigger='on_start' unset reloadedIface fi # if is_ovpn "$reloadedIface"; then # logger -t "$packageName" "Updated interface is an OpenVPN tunnel, restarting." # serviceStartTrigger='on_start' # unset reloadedIface # else # serviceStartTrigger='on_interface_reload' # fi ;; on_reload) serviceStartTrigger='on_reload' ;; on_restart) serviceStartTrigger='on_start' ;; esac if [ -n "$reloadedIface" ] && ! is_supported_interface "$reloadedIface"; then return 0 fi if [ -n "$(ubus_get_status error)" ] || [ -n "$(ubus_get_status warning)" ]; then serviceStartTrigger='on_start' unset reloadedIface elif ! is_service_running; then serviceStartTrigger='on_start' unset reloadedIface elif [ -z "$(ubus_get_status gateways)" ]; then serviceStartTrigger='on_start' unset reloadedIface # elif [ "$serviceStartTrigger" = 'on_interface_reload' ] && \ # [ -z "$(ubus_get_interface "$reloadedIface" 'gateway_ipv4')" ] && \ # [ -z "$(ubus_get_interface "$reloadedIface" 'gateway_ipv6')" ]; then # serviceStartTrigger='on_start' # unset reloadedIface else serviceStartTrigger="${serviceStartTrigger:-on_start}" fi procd_open_instance 'main' procd_set_param command /bin/true procd_set_param stdout 1 procd_set_param stderr 1 procd_open_data case $serviceStartTrigger in on_interface_reload) output 1 "Reloading Interface: $reloadedIface " json_add_array 'gateways' interface_process 'all' 'prepare' config_foreach interface_process 'interface' 'reload_interface' "$reloadedIface" json_close_array output 1 '\n' ;; on_reload) traffic_killswitch 'insert' resolver 'store_hash' resolver 'cleanup_all' resolver 'configure' resolver 'init' cleanup_main_chains cleanup_sets nft_file 'create' if ! is_nft_mode; then for i in $chainsList; do i="$(str_to_upper "$i")" ipt -t mangle -N "${iptPrefix}_${i}" ipt -t mangle "$rule_create_option" "$i" -m mark --mark "0x0/${fw_mask}" -j "${iptPrefix}_${i}" done ipt -t nat -N "${iptPrefix}_PREROUTING" fi json_add_array 'gateways' interface_process 'all' 'prepare' config_foreach interface_process 'interface' 'reload' interface_process 'tor' 'destroy' is_tor_running && interface_process 'tor' 'reload' json_close_array if is_config_enabled 'policy'; then output 1 'Processing policies ' config_load "$packageName" config_foreach load_validate_policy 'policy' policy_process output 1 '\n' fi if is_config_enabled 'dns_policy'; then output 1 'Processing dns policies ' config_load "$packageName" config_foreach load_validate_dns_policy 'dns_policy' dns_policy_process output 1 '\n' fi if is_config_enabled 'include'; then interface_process 'all' 'prepare' config_foreach interface_process 'interface' 'create_user_set' output 1 'Processing user file(s) ' config_load "$packageName" config_foreach load_validate_include 'include' user_file_process output 1 '\n' fi nft_file 'install' resolver 'init_end' ! nft_file 'exists' && resolver 'compare_hash' && resolver 'restart' traffic_killswitch 'remove' ;; on_start|*) traffic_killswitch 'insert' resolver 'store_hash' resolver 'cleanup_all' resolver 'configure' resolver 'init' cleanup_main_chains cleanup_sets cleanup_marking_chains cleanup_rt_tables nft_file 'create' if ! is_nft_mode; then for i in $chainsList; do i="$(str_to_upper "$i")" ipt -t mangle -N "${iptPrefix}_${i}" ipt -t mangle "$rule_create_option" "$i" -m mark --mark "0x0/${fw_mask}" -j "${iptPrefix}_${i}" done ipt -t nat -N "${iptPrefix}_PREROUTING" fi output 1 'Processing interfaces ' json_add_array 'gateways' interface_process 'all' 'prepare' config_foreach interface_process 'interface' 'create' interface_process 'tor' 'destroy' is_tor_running && interface_process 'tor' 'create' json_close_array ip route flush cache output 1 '\n' if is_config_enabled 'policy'; then output 1 'Processing policies ' config_load "$packageName" config_foreach load_validate_policy 'policy' policy_process output 1 '\n' fi if is_config_enabled 'dns_policy'; then output 1 'Processing dns policies ' config_load "$packageName" config_foreach load_validate_dns_policy 'dns_policy' dns_policy_process output 1 '\n' fi if is_config_enabled 'include'; then interface_process 'all' 'prepare' config_foreach interface_process 'interface' 'create_user_set' output 1 'Processing user file(s) ' config_load "$packageName" config_foreach load_validate_include 'include' user_file_process output 1 '\n' fi nft_file 'install' resolver 'init_end' ! nft_file 'exists' && resolver 'compare_hash' && resolver 'restart' traffic_killswitch 'remove' ;; esac if [ -z "$gatewaySummary" ]; then state add 'errorSummary' 'errorNoGateways' fi json_add_object 'status' [ -n "$gatewaySummary" ] && json_add_string 'gateways' "$gatewaySummary" [ -n "$errorSummary" ] && json_add_string 'errors' "$errorSummary" [ -n "$warningSummary" ] && json_add_string 'warnings' "$warningSummary" if [ "$strict_enforcement" -ne '0' ] && str_contains "$gatewaySummary" '0.0.0.0'; then json_add_string 'mode' 'strict' fi json_close_object procd_close_data procd_close_instance } service_started() { if nft_file 'exists'; then procd_set_config_changed firewall if nft_file 'exists'; then resolver 'compare_hash' && resolver 'restart' [ -n "$gatewaySummary" ] && output "$serviceName (fw4 nft file mode) started with gateways:\\n${gatewaySummary}" else output "$serviceName FAILED TO START in fw4 nft file mode!!!" output "Check the output of nft -c -f $nftTempFile" fi elif is_nft_mode; then [ -n "$gatewaySummary" ] && output "$serviceName (nft mode) started with gateways:\\n${gatewaySummary}" else [ -n "$gatewaySummary" ] && output "$serviceName (iptables mode) started with gateways:\\n${gatewaySummary}" fi state print 'errorSummary' state print 'warningSummary' touch "$packageLockFile" if [ -n "$errorSummary" ]; then return 2 elif [ -n "$warningSummary" ]; then return 1 else return 0 fi } service_triggers() { local n load_environment 'on_triggers' # shellcheck disable=SC2034 PROCD_RELOAD_DELAY=$(( procd_reload_delay * 1000 )) procd_open_validate load_validate_config load_validate_policy load_validate_include procd_close_validate procd_open_trigger procd_add_config_trigger "config.change" 'openvpn' "/etc/init.d/${packageName}" reload 'on_openvpn_change' procd_add_config_trigger "config.change" "${packageName}" /etc/init.d/${packageName} reload for n in $ifacesSupported; do procd_add_interface_trigger "interface.*" "$n" /etc/init.d/${packageName} on_interface_reload "$n" done procd_close_trigger # procd_add_raw_trigger "interface.*.up" 4000 "/etc/init.d/${packageName}" restart 'on_interface_up' if [ "$serviceStartTrigger" = 'on_start' ]; then output 3 "$serviceName monitoring interfaces: ${ifacesSupported}\\n" fi } stop_service() { local i nft_file_mode load_environment 'on_stop' ! is_service_running && [ "$(get_rt_tables_next_id)" = "$(get_rt_tables_non_pbr_next_id)" ] && return 0 [ "$1" = 'quiet' ] && quiet_mode 'on' traffic_killswitch 'insert' if nft_file 'exists'; then nft_file_mode=1 fi nft_file 'delete' cleanup_main_chains cleanup_sets cleanup_marking_chains output 1 'Resetting interfaces ' config_load 'network' config_foreach interface_process 'interface' 'destroy' interface_process 'tor' 'destroy' cleanup_rt_tables output 1 "\\n" ip route flush cache unset ifaceMark unset ifaceTableID resolver 'store_hash' resolver 'cleanup_all' resolver 'compare_hash' && resolver 'restart' traffic_killswitch 'remove' if [ "$enabled" -ne '0' ]; then if [ -n "$nft_file_mode" ]; then output "$serviceName (fw4 nft file mode) stopped "; output_okn; elif is_nft_mode; then output "$serviceName (nft mode) stopped "; output_okn; else output "$serviceName (iptables mode) stopped "; output_okn; fi fi rm -f "$packageLockFile" } version() { echo "$PKG_VERSION"; } status_service() { local _SEPARATOR_='============================================================' load_environment 'on_status' if is_nft_mode; then status_service_nft "$@" else status_service_iptables "$@" fi } status_service_iptables() { local dist vers out id s param status set_d set_p tableCount i=0 dev dev6 j wan_tid json_load "$(ubus call system board)"; json_select release; json_get_var dist distribution; json_get_var vers version if [ -n "$wanIface4" ]; then network_get_gateway wanGW4 "$wanIface4" network_get_device dev "$wanIface4" fi if [ -n "$wanIface6" ]; then network_get_device dev6 "$wanIface6" wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}') [ "$wanGW6" = "default" ] && wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}') fi while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support" status="$serviceName running on $dist $vers." [ -n "$wanIface4" ] && status="$status WAN (IPv4): ${wanIface4}/${dev}/${wanGW4:-0.0.0.0}." [ -n "$wanIface6" ] && status="$status WAN (IPv6): ${wanIface6}/${dev6}/${wanGW6:-::/0}." { echo "$status" echo "$_SEPARATOR_" dnsmasq --version 2>/dev/null | sed '/^$/,$d' if [ -n "$1" ]; then echo "$_SEPARATOR_" echo "Resolving domains" for i in $1; do echo "$i: $(resolveip "$i" | tr '\n' ' ')" done fi echo "$_SEPARATOR_" echo "Routes/IP Rules" tableCount="$(grep -c "${packageName}_" $rtTablesFile)" || tableCount=0 if [ -n "$set_d" ]; then route; else route | grep '^default'; fi if [ -n "$set_d" ]; then ip rule list; fi wan_tid=$(($(get_rt_tables_next_id)-tableCount)) i=0; while [ "$i" -lt "$tableCount" ]; do echo "IPv4 table $((wan_tid + i)) route: $($ip_bin -4 route show table $((wan_tid + i)) | grep default)" echo "IPv4 table $((wan_tid + i)) rule(s):" $ip_bin -4 rule list table "$((wan_tid + i))" i=$((i + 1)) done if [ -n "$ipv6_enabled" ]; then i=0; while [ "$i" -lt "$tableCount" ]; do $ip_bin -6 route show table $((wan_tid + i)) | while read -r param; do echo "IPv6 Table $((wan_tid + i)): $param" done i=$((i + 1)) done fi for j in Mangle NAT; do if [ -z "$set_d" ]; then for i in $chainsList; do i="$(str_to_upper "$i")" if iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_${i}" >/dev/null 2>&1; then echo "$_SEPARATOR_" echo "$j IP Table: $i" iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_${i}" if [ -n "$ipv6_enabled" ]; then echo "$_SEPARATOR_" echo "$j IPv6 Table: $i" iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_${i}" fi fi done else echo "$_SEPARATOR_" echo "$j IP Table" iptables -L -t "$(str_to_lower $j)" if [ -n "$ipv6_enabled" ]; then echo "$_SEPARATOR_" echo "$j IPv6 Table" iptables -L -t "$(str_to_lower $j)" fi fi i=0; ifaceMark="$wan_mark"; while [ "$i" -lt "$tableCount" ]; do if iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_MARK_${ifaceMark}" >/dev/null 2>&1; then echo "$_SEPARATOR_" echo "$j IP Table MARK Chain: ${iptPrefix}_MARK_${ifaceMark}" iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_MARK_${ifaceMark}" ifaceMark="$(printf '0x%06x' $((ifaceMark + wan_mark)))"; fi i=$((i + 1)) done done echo "$_SEPARATOR_" echo "Current ipsets" ipset save if [ -s "$dnsmasqFileDefault" ]; then echo "$_SEPARATOR_" echo "DNSMASQ sets" cat "$dnsmasqFileDefault" fi if [ -s "$aghIpsetFile" ]; then echo "$_SEPARATOR_" echo "AdGuardHome sets" cat "$aghIpsetFile" fi echo "$_SEPARATOR_" } | tee -a /var/${packageName}-support if [ -n "$set_p" ]; then printf "%b" "Pasting to paste.ee... " if curl --version 2>/dev/null | grep -q "Protocols: .*https.*"; then json_init; json_add_string 'description' "${packageName}-support" json_add_array 'sections'; json_add_object '0' json_add_string 'name' "$(uci_get 'system' '@system[0]' 'hostname')" json_add_string 'contents' "$(cat /var/${packageName}-support)" json_close_object; json_close_array; payload=$(json_dump) out=$(curl -s -k "https://api.paste.ee/v1/pastes" -X "POST" -H "Content-Type: application/json" -H "X-Auth-Token:uVOJt6pNqjcEWu7qiuUuuxWQafpHhwMvNEBviRV2B" -d "$payload") json_load "$out"; json_get_var id id; json_get_var s success [ "$s" = "1" ] && printf "%b" "https://paste.ee/p/$id $__OK__\\n" || printf "%b" "$__FAIL__\\n" [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support" else printf "%b" "${__FAIL__}\\n" printf "%b" "${_ERROR_}: The curl, libopenssl or ca-bundle packages were not found!\\nRun 'opkg update; opkg install curl libopenssl ca-bundle' to install them.\\n" fi else printf "%b" "Your support details have been logged to '/var/${packageName}-support'. $__OK__\\n" fi } status_service_nft() { local i dev dev6 wan_tid json_load "$(ubus call system board)"; json_select release; json_get_var dist distribution; json_get_var vers version if [ -n "$wanIface4" ]; then network_get_gateway wanGW4 "$wanIface4" network_get_device dev "$wanIface4" fi if [ -n "$wanIface6" ]; then network_get_device dev6 "$wanIface6" wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}') [ "$wanGW6" = "default" ] && wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}') fi while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support" status="$serviceName running on $dist $vers." [ -n "$wanIface4" ] && status="$status WAN (IPv4): ${wanIface4}/${dev}/${wanGW4:-0.0.0.0}." [ -n "$wanIface6" ] && status="$status WAN (IPv6): ${wanIface6}/${dev6}/${wanGW6:-::/0}." echo "$_SEPARATOR_" echo "$packageName - environment" echo "$status" echo "$_SEPARATOR_" dnsmasq --version 2>/dev/null | sed '/^$/,$d' if nft_file 'exists'; then echo "$_SEPARATOR_" echo "$packageName fw4 nft file: $nftPermFile" sed '1d;2d;' "$nftPermFile" fi echo "$_SEPARATOR_" echo "$packageName chains - policies" for i in $chainsList dstnat_lan; do "$nft" -a list table inet "$nftTable" | sed -n "/chain ${nftPrefix}_${i} {/,/\t}/p" done echo "$_SEPARATOR_" echo "$packageName chains - marking" for i in $(get_mark_nft_chains); do "$nft" -a list table inet "$nftTable" | sed -n "/chain ${i} {/,/\t}/p" done echo "$_SEPARATOR_" echo "$packageName nft sets" for i in $(get_nft_sets); do "$nft" -a list table inet "$nftTable" | sed -n "/set ${i} {/,/\t}/p" done if [ -s "$dnsmasqFileDefault" ]; then echo "$_SEPARATOR_" echo "dnsmasq sets" cat "$dnsmasqFileDefault" fi # echo "$_SEPARATOR_" # ip rule list | grep "${packageName}_" echo "$_SEPARATOR_" tableCount="$(grep -c "${packageName}_" $rtTablesFile)" || tableCount=0 wan_tid=$(($(get_rt_tables_next_id)-tableCount)) i=0; while [ "$i" -lt "$tableCount" ]; do echo "IPv4 table $((wan_tid + i)) route: $($ip_bin -4 route show table $((wan_tid + i)) | grep default)" echo "IPv4 table $((wan_tid + i)) rule(s):" $ip_bin -4 rule list table "$((wan_tid + i))" if [ -n "$ipv6_enabled" ]; then echo "IPv6 table $((wan_tid + i)) route: $($ip_bin -6 route show table $((wan_tid + i)) | grep default)" echo "IPv6 table $((wan_tid + i)) rule(s):" $ip_bin -6 route show table $((wan_tid + i)) fi i=$((i + 1)) done } # shellcheck disable=SC2120 load_validate_config() { uci_load_validate "$packageName" "$packageName" "$1" "${2}${3:+ $3}" \ 'enabled:bool:0' \ 'strict_enforcement:bool:1' \ 'secure_reload:bool:0' \ 'ipv6_enabled:bool:0' \ 'resolver_set:or("", "none", "adguardhome.ipset", "dnsmasq.ipset", "dnsmasq.nftset")' \ 'resolver_instance:list(or(integer, string)):*' \ 'verbosity:range(0,2):2' \ 'wan_mark:regex("[A-Fa-f0-9]{8}"):010000' \ 'fw_mask:regex("[A-Fa-f0-9]{8}"):ff0000' \ 'icmp_interface:or("", tor, uci("network", "@interface"))' \ 'ignored_interface:list(or(tor, uci("network", "@interface")))' \ 'supported_interface:list(or(ignore, tor, regex("xray_.*"), uci("network", "@interface")))' \ 'procd_boot_delay:integer:0' \ 'procd_boot_timeout:integer:30' \ 'procd_lan_interface:string' \ 'procd_reload_delay:integer:0' \ 'procd_wan_ignore_status:bool:0' \ 'procd_wan_interface:network:wan' \ 'procd_wan6_interface:network:wan6' \ 'wan_ip_rules_priority:uinteger:30000' \ 'rule_create_option:or("", add, insert):add' \ 'webui_supported_protocol:list(string)' \ 'nft_file_support:bool:1'\ 'nft_set_auto_merge:bool:1'\ 'nft_set_counter:bool:1'\ 'nft_set_flags_interval:bool:1'\ 'nft_set_flags_timeout:bool:0'\ 'nft_set_gc_interval:or("", string)'\ 'nft_set_policy:or("", memory, performance):performance'\ 'nft_set_timeout:or("", string)' } # shellcheck disable=SC2120 load_validate_dns_policy() { local name local enabled local src_addr local dest_dns uci_load_validate "$packageName" 'policy' "$1" "${2}${3:+ $3}" \ 'name:string:Untitled' \ 'enabled:bool:1' \ 'src_addr:list(neg(or(host,network,macaddr,string)))' \ 'dest_dns:list(or(host,network,string))' } # shellcheck disable=SC2120 load_validate_policy() { local name local enabled local interface local proto local chain local src_addr local src_port local dest_addr local dest_port uci_load_validate "$packageName" 'policy' "$1" "${2}${3:+ $3}" \ 'name:string:Untitled' \ 'enabled:bool:1' \ 'interface:or("ignore", "tor", regex("xray_.*"), uci("network", "@interface")):wan' \ 'proto:or(string)' \ 'chain:or("", "forward", "input", "output", "prerouting", "postrouting", "FORWARD", "INPUT", "OUTPUT", "PREROUTING", "POSTROUTING"):prerouting' \ 'src_addr:list(neg(or(host,network,macaddr,string)))' \ 'src_port:list(neg(or(portrange,string)))' \ 'dest_addr:list(neg(or(host,network,string)))' \ 'dest_port:list(neg(or(portrange,string)))' } # shellcheck disable=SC2120 load_validate_include() { local path= local enabled= uci_load_validate "$packageName" 'include' "$1" "${2}${3:+ $3}" \ 'path:file' \ 'enabled:bool:0' }