From: Stan Grishin Date: Sat, 3 Aug 2024 23:17:13 +0000 (+0000) Subject: pbr: update to 1.1.6-20 X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=refs%2Fpull%2F24738%2Fhead;p=feed%2Fpackages.git pbr: update to 1.1.6-20 This version is the final version supporting iptables and: * it separates the old iptables/nft-capable init script from the new nft-only init script * the new nft-script is a significant rewrite of the old recursive calls/policy parsing and tries to create inline nft sets which offers performance improvements Signed-off-by: Stan Grishin --- diff --git a/net/pbr/Makefile b/net/pbr/Makefile index 5014894d06..0dbe47e3ce 100644 --- a/net/pbr/Makefile +++ b/net/pbr/Makefile @@ -1,11 +1,11 @@ -# Copyright 2017-2023 MOSSDeF, Stan Grishin (stangri@melmac.ca) -# This is free software, licensed under the GNU General Public License v3. +# Copyright 2017-2024 MOSSDeF, Stan Grishin (stangri@melmac.ca). +# This is free software, licensed under AGPL-3.0-or-later. include $(TOPDIR)/rules.mk PKG_NAME:=pbr -PKG_VERSION:=1.1.4 -PKG_RELEASE:=16 +PKG_VERSION:=1.1.6 +PKG_RELEASE:=20 PKG_LICENSE:=AGPL-3.0-or-later PKG_MAINTAINER:=Stan Grishin @@ -16,12 +16,11 @@ define Package/pbr/default CATEGORY:=Network SUBMENU:=Routing and Redirection TITLE:=Policy Based Routing Service - URL:=https://docs.openwrt.melmac.net/pbr/ + URL:=https://github.com/stangri/pbr/ DEPENDS:=+ip-full +jshn +jsonfilter +resolveip DEPENDS+=+!BUSYBOX_DEFAULT_AWK:gawk DEPENDS+=+!BUSYBOX_DEFAULT_GREP:grep DEPENDS+=+!BUSYBOX_DEFAULT_SED:sed - CONFLICTS:=vpnbypass vpn-policy-routing PROVIDES:=pbr PKGARCH:=all endef @@ -32,7 +31,6 @@ $(call Package/pbr/default) DEPENDS+=+kmod-nft-core +kmod-nft-nat +nftables-json VARIANT:=nftables DEFAULT_VARIANT:=1 - PROVIDES+=vpnbypass vpn-policy-routing endef define Package/pbr-iptables @@ -44,7 +42,7 @@ endef define Package/pbr-netifd $(call Package/pbr/default) - TITLE+= with netifd support + TITLE+= with nft/nft set and netifd support VARIANT:=netifd endef @@ -54,18 +52,18 @@ endef define Package/pbr/description $(call Package/pbr/default/description) - This version supports OpenWrt with both firewall3/ipset/iptables and firewall4/nft. + This version supports OpenWrt (23.05 and newer) with firewall4/nft. endef define Package/pbr-iptables/description $(call Package/pbr/default/description) - This version supports OpenWrt with firewall3/ipset/iptables. + This version supports OpenWrt (22.03 and older) with firewall3/ipset/iptables. endef define Package/pbr-netifd/description $(call Package/pbr/default/description) - This version supports OpenWrt with both firewall3/ipset/iptables and firewall4/nft. - This version uses OpenWrt native netifd/tables to set up interfaces. This is WIP. + This version supports OpenWrt with (23.05 and newer) firewall4/nft. + This version uses OpenWrt native netifd/tables to set up interfaces. This is a WIP. endef define Package/pbr/default/conffiles @@ -83,26 +81,24 @@ define Build/Compile endef define Package/pbr/default/install - $(INSTALL_DIR) $(1)/etc/init.d - $(INSTALL_BIN) ./files/etc/init.d/pbr $(1)/etc/init.d/pbr - $(SED) "s|^\(readonly PKG_VERSION\).*|\1='$(PKG_VERSION)-$(PKG_RELEASE)'|" $(1)/etc/init.d/pbr $(INSTALL_DIR) $(1)/etc/uci-defaults $(INSTALL_BIN) ./files/etc/uci-defaults/90-pbr $(1)/etc/uci-defaults/90-pbr $(INSTALL_DIR) $(1)/usr/share/pbr $(INSTALL_DATA) ./files/usr/share/pbr/.keep $(1)/usr/share/pbr/.keep - $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.aws $(1)/usr/share/pbr/pbr.user.aws - $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.netflix $(1)/usr/share/pbr/pbr.user.netflix $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.wg_server_and_client $(1)/usr/share/pbr/pbr.user.wg_server_and_client endef -# $(INSTALL_DIR) $(1)/etc/hotplug.d/iface -# $(INSTALL_DATA) ./files/etc/hotplug.d/iface/70-pbr $(1)/etc/hotplug.d/iface/70-pbr define Package/pbr/install $(call Package/pbr/default/install,$(1)) + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/pbr $(1)/etc/init.d/pbr + $(SED) "s|^\(readonly PKG_VERSION\).*|\1='$(PKG_VERSION)-$(PKG_RELEASE)'|" $(1)/etc/init.d/pbr $(INSTALL_DIR) $(1)/etc/config $(INSTALL_CONF) ./files/etc/config/pbr $(1)/etc/config/pbr $(INSTALL_DIR) $(1)/usr/share/pbr $(INSTALL_DATA) ./files/usr/share/pbr/firewall.include $(1)/usr/share/pbr/firewall.include + $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.aws $(1)/usr/share/pbr/pbr.user.aws + $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.netflix $(1)/usr/share/pbr/pbr.user.netflix $(INSTALL_DIR) $(1)/usr/share/nftables.d $(CP) ./files/usr/share/nftables.d/* $(1)/usr/share/nftables.d/ $(INSTALL_DIR) $(1)/etc/uci-defaults @@ -111,16 +107,24 @@ endef define Package/pbr-iptables/install $(call Package/pbr/default/install,$(1)) + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/pbr-iptables $(1)/etc/init.d/pbr + $(SED) "s|^\(readonly PKG_VERSION\).*|\1='$(PKG_VERSION)-$(PKG_RELEASE)'|" $(1)/etc/init.d/pbr $(INSTALL_DIR) $(1)/etc/hotplug.d/firewall $(INSTALL_DATA) ./files/etc/hotplug.d/firewall/70-pbr $(1)/etc/hotplug.d/firewall/70-pbr $(INSTALL_DIR) $(1)/etc/config - $(INSTALL_CONF) ./files/etc/config/pbr.iptables $(1)/etc/config/pbr + $(INSTALL_CONF) ./files/etc/config/pbr-iptables $(1)/etc/config/pbr + $(INSTALL_DIR) $(1)/usr/share/pbr + $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.aws-iptables $(1)/usr/share/pbr/pbr.user.aws + $(INSTALL_DATA) ./files/usr/share/pbr/pbr.user.netflix-iptables $(1)/usr/share/pbr/pbr.user.netflix $(INSTALL_DIR) $(1)/etc/uci-defaults $(INSTALL_BIN) ./files/etc/uci-defaults/91-pbr-iptables $(1)/etc/uci-defaults/91-pbr-iptables endef define Package/pbr-netifd/install $(call Package/pbr/default/install,$(1)) + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/pbr $(1)/etc/init.d/pbr $(INSTALL_DIR) $(1)/etc/config $(INSTALL_CONF) ./files/etc/config/pbr $(1)/etc/config/pbr $(INSTALL_DIR) $(1)/etc/uci-defaults diff --git a/net/pbr/README.md b/net/pbr/README.md new file mode 100644 index 0000000000..98feae65df --- /dev/null +++ b/net/pbr/README.md @@ -0,0 +1,4 @@ +# README + +Documentation for this project is available at [https://docs.openwrt.melmac.net/pbr/](https://docs.openwrt.melmac.net/pbr/). + diff --git a/net/pbr/files/etc/config/pbr b/net/pbr/files/etc/config/pbr index 5b0d57036d..640ecf0caa 100644 --- a/net/pbr/files/etc/config/pbr +++ b/net/pbr/files/etc/config/pbr @@ -6,14 +6,14 @@ config pbr 'config' list resolver_instance '*' option ipv6_enabled '0' list ignored_interface 'vpnserver' - option nft_file_support '0' option boot_timeout '30' option rule_create_option 'add' option procd_boot_delay '0' option procd_reload_delay '1' option webui_show_ignore_target '0' + option nft_rule_counter '0' option nft_set_auto_merge '1' - option nft_set_counter '1' + option nft_set_counter '0' option nft_set_flags_interval '1' option nft_set_flags_timeout '0' option nft_set_gc_interval '' @@ -37,6 +37,12 @@ config include option path '/usr/share/pbr/pbr.user.wg_server_and_client' option enabled '0' +config dns_policy + option name 'Redirect Local IP DNS' + option src_addr '192.168.1.5' + option dest_dns '1.1.1.1' + option enabled '0' + config policy option name 'Ignore Local Requests' option interface 'ignore' diff --git a/net/pbr/files/etc/config/pbr-iptables b/net/pbr/files/etc/config/pbr-iptables new file mode 100644 index 0000000000..aa63e5edd3 --- /dev/null +++ b/net/pbr/files/etc/config/pbr-iptables @@ -0,0 +1,50 @@ +config pbr 'config' + option enabled '0' + option verbosity '2' + option strict_enforcement '1' + option resolver_set 'dnsmasq.ipset' + list resolver_instance '*' + option ipv6_enabled '0' + list ignored_interface 'vpnserver' + option boot_timeout '30' + option rule_create_option 'add' + option procd_boot_delay '0' + option procd_reload_delay '1' + option webui_show_ignore_target '0' + list webui_supported_protocol 'all' + list webui_supported_protocol 'tcp' + list webui_supported_protocol 'udp' + list webui_supported_protocol 'tcp udp' + list webui_supported_protocol 'icmp' + +config include + option path '/usr/share/pbr/pbr.user.aws' + option enabled 0 + +config include + option path '/usr/share/pbr/pbr.user.netflix' + option enabled 0 + +config include + option path '/usr/share/pbr/pbr.user.wg_server_and_client' + option enabled 0 + +config policy + option name 'Plex/Emby Local Server' + option interface 'wan' + option src_port '8096 8920 32400' + option enabled '0' + +config policy + option name 'Plex/Emby Remote Servers' + option interface 'wan' + option dest_addr 'plex.tv my.plexapp.com emby.media app.emby.media tv.emby.media' + option enabled '0' + +config policy + option name 'WireGuard Server' + option interface 'wan' + option src_port '51820' + option chain 'OUTPUT' + option proto 'udp' + option enabled '0' diff --git a/net/pbr/files/etc/init.d/pbr b/net/pbr/files/etc/init.d/pbr index ac7ad079b0..620edd61d7 100755 --- a/net/pbr/files/etc/init.d/pbr +++ b/net/pbr/files/etc/init.d/pbr @@ -1,6 +1,6 @@ #!/bin/sh /etc/rc.common -# Copyright 2020-2023 MOSSDeF, Stan Grishin (stangri@melmac.ca) -# shellcheck disable=SC2018,SC2019,SC3043,SC3057,SC3060 +# 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 @@ -10,12 +10,11 @@ START=94 # shellcheck disable=SC2034 USE_PROCD=1 -#!/bin/sh -# Copyright 2023 MOSSDeF, Stan Grishin (stangri@melmac.ca) -# shellcheck disable=SC2018,SC2019,SC2034,SC3043,SC3057,SC3060 +[ -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}" @@ -31,27 +30,16 @@ 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' +readonly ipTablePrefix="$packageName" # shellcheck disable=SC2155 readonly agh="$(command -v AdGuardHome)" -readonly aghConfigFile='/etc/adguardhome.yaml' -readonly aghIpsetFile="/var/run/${packageName}.adguardhome.ipsets" # shellcheck disable=SC2155 readonly nft="$(command -v nft)" readonly nftIPv4Flag='ip' readonly nftIPv6Flag='ip6' readonly nftTempFile="/var/run/${packageName}.nft" readonly nftPermFile="/usr/share/nftables.d/ruleset-post/30-${packageName}.nft" -readonly nftPrefix='pbr' +readonly nftPrefix="$packageName" readonly nftTable='fw4' readonly chainsList='forward input output postrouting prerouting' readonly ssConfigFile='/etc/shadowsocks' @@ -66,7 +54,7 @@ fw_mask= icmp_interface= ignored_interface= ipv6_enabled= -nft_file_support= +nft_file_support='1' nft_user_set_policy= nft_user_set_counter= procd_boot_delay= @@ -77,13 +65,13 @@ 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_rule_counter= nft_set_auto_merge= nft_set_counter= nft_set_flags_interval= @@ -93,6 +81,7 @@ nft_set_policy= nft_set_timeout= # run-time +aghConfigFile='/etc/AdGuardHome/AdGuardHome.yaml' gatewaySummary= errorSummary= warningSummary= @@ -109,11 +98,13 @@ firewallWanZone= wanGW4= wanGW6= serviceStartTrigger= +processDnsPolicyError= processPolicyError= processPolicyWarning= resolver_set_supported= policy_routing_nft_prev_param4= policy_routing_nft_prev_param6= +nft_rule_params= nft_set_params= torDnsPort= torTrafficPort= @@ -131,16 +122,21 @@ 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"; } +str_contains() { [ -n "$1" ] && [ -n "$2" ] && [ "${1//$2}" != "$1" ]; } +str_contains_word() { echo "$1" | grep -q -w "$2"; } +str_extras_to_underscore() { echo "$1" | tr '[\. ~`!@#$%^&*()\+/,<>?//;:]' '_'; } +str_extras_to_space() { echo "$1" | tr ',;{}' ' '; } +str_first_value_interface() { local i; for i in $1; do is_supported_interface "$i" && { echo "$i"; break; }; done; } +str_first_value_ipv4() { local i; for i in $1; do is_ipv4 "$i" && { echo "$i"; break; }; done; } +str_first_value_ipv6() { local i; for i in $1; do is_ipv6 "$i" && { echo "$i"; break; }; done; } +str_first_word() { echo "${1%% *}"; } # 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_dnsmsaq_nftset() { echo "$1" | tr ' ' '/'; } 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; } +debug() { local i j; for i in "$@"; do eval "j=\$$i"; logger "${i}: ${j} "; done; } quiet_mode() { case "$1" in on) verbosity=0;; @@ -161,13 +157,13 @@ 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 + 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 + 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")" @@ -189,7 +185,7 @@ pbr_get_gateway4() { 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}')" + gw="$(ip -4 a list dev "$dev" 2>/dev/null | grep inet | awk '{print $2}' | awk -F "/" '{print $1}')" fi eval "$1"='$gw' } @@ -197,15 +193,110 @@ 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}')" + gw="$(ip -6 a list dev "$dev" 2>/dev/null | grep inet6 | grep 'scope global' | awk '{print $2}')" fi eval "$1"='$gw' } +filter_options() { + local opt="$1" value="$2" + local i _ret= + + case "$opt" in + phys_dev) + for i in $value; do + if is_phys_dev "$i"; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + phys_dev_negative) + for i in $value; do + if is_negation "$i" && is_phys_dev "${i:1}"; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + mac_address) + for i in $value; do + if is_mac_address "$i"; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + mac_address_negative) + for i in $value; do + if is_negation "$i" && is_mac_address "${i:1}"; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + domain) + for i in $value; do + if is_domain "$i"; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + domain_negative) + for i in $value; do + if is_negation "$i" && is_domain "${i:1}"; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + ipv4) + for i in $value; do + if is_ipv4 "$i" || is_ipv4_netmask "$i"; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + ipv4_negative) + for i in $value; do + if is_negation "$i" && { is_ipv4 "${i:1}" || is_ipv4_netmask "${i:1}"; }; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + ipv6) + for i in $value; do + if is_ipv6 "$i"; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + ipv6_negative) + for i in $value; do + if is_negation "$i" && is_ipv6 "${i:1}"; then + _ret="${_ret:+$_ret }$i" + fi + done + ;; + none) + : + ;; + *) + echo '' + return 1 + ;; + esac + echo "$_ret" + return 0 +} +inline_set() { + local value="$1" inline_set i + for i in $value; do + [ "${i:0:1}" = "!" ] && i=${i:1} + [ "${i:0:1}" = "@" ] && i=${i:1} + inline_set="${inline_set:+$inline_set, }$i" + done + echo "$inline_set" +} # 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; } + _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" @@ -214,12 +305,12 @@ is_config_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_default_dev() { [ "$1" = "$(ip -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_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() { @@ -229,8 +320,6 @@ is_integer() { (*) return 0;; esac } -is_ipset_type_supported() { ipset help hash:"$1" >/dev/null 2>&1; } -is_nft_mode() { [ -x "$nft" ] && ! str_contains "$resolver_set" 'ipset' && "$nft" list chains inet | grep -q "${nftPrefix}_prerouting"; } is_ipv4() { expr "$1" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; } is_ipv6() { ! is_mac_address "$1" && str_contains "$1" ':'; } is_ipv6_global() { [ "${1:0:4}" = '2001' ]; } @@ -241,15 +330,16 @@ 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_negation() { [ "${1:0:1}" = '!' ]; } is_netifd_table() { grep -q "ip.table.*$1" /etc/config/network; } is_netifd_table_interface() { local iface="$1"; [ "$(uci_get 'network' "$iface" 'ip4table')" = "${packageName}_${iface%6}" ]; } 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_phys_dev_quick() { [ "${1:0: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() { is_service_running_nft; } 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"; } @@ -294,11 +384,7 @@ get_rt_tables_id() { local iface="$1"; grep "${ipTablePrefix}_${iface}\$" "$rtTa get_rt_tables_next_id() { echo "$(($(sort -r -n "$rtTablesFile" | grep -o -E -m 1 "^[0-9]+")+1))"; } get_rt_tables_non_pbr_next_id() { echo "$(($(grep -v "${ipTablePrefix}_" "$rtTablesFile" | sort -r -n | grep -o -E -m 1 "^[0-9]+")+1))"; } # shellcheck disable=SC2016 -resolveip_to_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_nftset() { resolveip "$@" | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d'; } resolveip_to_nftset4() { resolveip_to_nftset -4 "$@"; } resolveip_to_nftset6() { [ -n "$ipv6_enabled" ] && resolveip_to_nftset -6 "$@"; } # shellcheck disable=SC2016 @@ -306,10 +392,8 @@ ipv4_leases_to_nftset() { [ -s '/tmp/dhcp.leases' ] || return 1; grep "$1" '/tmp # shellcheck disable=SC2016 ipv6_leases_to_nftset() { [ -s '/tmp/hosts/odhcpd' ] || return 1; grep -v '^#' '/tmp/hosts/odhcpd' | grep "$1" | awk '{print $1}' | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; } # shellcheck disable=SC3037 -ports_to_nftset() { echo -ne "$1"; } -get_mark_ipt_chains() { [ -n "$(command -v iptables-save)" ] && iptables-save | grep ":${iptPrefix}_MARK_" | awk '{ print $1 }' | sed 's/://'; } +ports_to_nftset() { echo -en "$1"; } 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}"; } @@ -330,27 +414,12 @@ uci_get_listen_port() { # luci app specific is_enabled() { uci_get "$1" 'config' 'enabled'; } -is_running_iptables() { iptables -t mangle -L | grep -q PBR_PREROUTING >/dev/null 2>&1; } is_running_nft_file() { [ -s "$nftPermFile" ]; } is_running_nft() { "$nft" list table inet fw4 | grep chain | grep -q pbr_mark_ >/dev/null 2>&1; } -is_running() { is_running_iptables || is_running_nft; } -check_ipset() { { [ -n "$ipset" ] && "$ipset" help hash:net; } >/dev/null 2>&1; } -check_nft() { [ -n "$nft" ]; } -check_agh() { [ -n "$agh" ] && [ -s "$aghConfigFile" ]; } +check_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 @@ -360,6 +429,12 @@ check_dnsmasq_nftset() { } print_json_bool() { json_init; json_add_boolean "$1" "$2"; json_dump; json_cleanup; } print_json_string() { json_init; json_add_string "$1" "$2"; json_dump; json_cleanup; } +try() { + if ! "$@"; then + state add 'errorSummary' 'errorTryFailed' "$*" + return 1 + fi +} if type extra_command >/dev/null 2>&1; then extra_command 'status' "Generates output required to troubleshoot routing issues @@ -386,19 +461,18 @@ get_text() { 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'!";; @@ -425,11 +499,9 @@ get_text() { 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.";; @@ -480,9 +552,9 @@ 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_bool en "$cfg" 'enabled' '1' config_get path "$cfg" 'path' - [ "$en" -eq 0 ] && return 0 + [ "$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' @@ -495,9 +567,9 @@ load_package_config() { 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_rule_counter 'config' 'nft_rule_counter' '0' 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_counter 'config' 'nft_set_counter' '0' 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' @@ -505,7 +577,6 @@ load_package_config() { 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' @@ -520,24 +591,21 @@ load_package_config() { config_get wan_mark 'config' 'wan_mark' '010000' fw_mask="0x${fw_mask}" wan_mark="0x${wan_mark}" - [ -n "$ipv6_enabled" ] && [ "$ipv6_enabled" -eq 0 ] && unset ipv6_enabled - [ -n "$nft_file_support" ] && [ "$nft_file_support" -eq 0 ] && unset nft_file_support - [ -n "$nft_user_set_counter" ] && [ "$nft_user_set_counter" -eq 0 ] && unset nft_user_set_counter - [ -n "$secure_reload" ] && [ "$secure_reload" -eq 0 ] && unset secure_reload + 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 + fw_maskXor="$(printf '%#x' "$((fw_mask ^ 0xffffffff))")" + fw_maskXor="${fw_maskXor:-0xff00ffff}" + [ "$nft_rule_counter" != '1' ] && unset nft_rule_counter [ "$nft_set_auto_merge" != '1' ] && unset nft_set_auto_merge [ "$nft_set_counter" != '1' ] && unset nft_set_counter [ "$nft_set_flags_interval" != '1' ] && unset nft_set_flags_interval @@ -554,6 +622,9 @@ load_package_config() { fi fi fi + + nft_rule_params="${nft_rule_counter:+counter}" + nft_set_params=" \ ${nft_set_auto_merge:+ auto-merge;} \ ${nft_set_counter:+ counter;} \ @@ -571,7 +642,7 @@ load_environment() { load_package_config "$param" case "$param" in on_start) - if [ "$enabled" -eq 0 ]; then + if [ "$enabled" -eq '0' ]; then state add 'errorSummary' 'errorServiceDisabled' return 1 fi @@ -581,20 +652,14 @@ load_environment() { 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 + # TODO: implement ip-full check +# if [ ! -x ip ]; then +# state add 'errorSummary' 'errorNoIpFull' +# return 1 +# fi + if [ "$(uci_get 'firewall' 'defaults' 'auto_includes')" = '0' ]; then + uci_remove 'firewall' 'defaults' 'auto_includes' + uci_commit firewall fi ;; on_stop) @@ -652,7 +717,7 @@ is_wan_up() { fi while [ -z "$wanGW" ] ; do load_network "$param" - if [ $((sleepCount)) -gt $((procd_boot_timeout)) ] || [ -n "$wanGW" ]; then break; fi + 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 @@ -666,152 +731,6 @@ is_wan_up() { 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 @@ -865,7 +784,7 @@ nftset() { [ -x "$nft" ] || return 1 - if [ "${#nftset4}" -gt 255 ]; then + if [ "${#nftset4}" -gt '255' ]; then state add 'errorSummary' 'errorNftsetNameTooLong' "$nftset4" return 1 fi @@ -922,20 +841,20 @@ nftset() { 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 + nft4 add rule inet "$nftTable" "${nftPrefix}_prerouting" "${nftIPv4Flag}" daddr "@${nftset4}" "${nft_rule_params}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 + nft6 add rule inet "$nftTable" "${nftPrefix}_prerouting" "${nftIPv6Flag}" daddr "@${nftset6}" "${nft_rule_params}" 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 + nft4 add rule inet "$nftTable" "${nftPrefix}_prerouting" "${nftIPv4Flag}" saddr "@${nftset4}" "${nft_rule_params}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 + nft6 add rule inet "$nftTable" "${nftPrefix}_prerouting" "${nftIPv6Flag}" saddr "@${nftset6}" "${nft_rule_params}" 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 + nft4 add rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset4}" "${nft_rule_params}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 + nft6 add rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset6}" "${nft_rule_params}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 ;; esac ;; @@ -960,8 +879,8 @@ nftset() { 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 + 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 ;; @@ -989,29 +908,19 @@ cleanup_rt_tables() { } cleanup_main_chains() { - local i - for i in $chainsList; do + 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 } cleanup_marking_chains() { - local i + 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() { @@ -1020,10 +929,6 @@ cleanup_sets() { 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() { @@ -1109,12 +1014,10 @@ _resolver_dnsmasq_confdir() { resolver() { local agh_version - local param="$1" + local param="$1" iface="$2" target="$3" type="$4" uid="$5" name="$6" value="$7" shift if [ "$param" = 'cleanup_all' ]; then - sed -i "/ipset_file: ${aghIpsetFile}/d" "$aghConfigFile" >/dev/null 2>&1 - rm -f "$aghIpsetFile" local dfl for dfl in $dnsmasqFileList; do rm -f "$dfl" @@ -1131,190 +1034,28 @@ resolver() { 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; }')";; + init_end) return 0;; + kill) return 0;; + reload) return 0;; + restart) return 0;; + compare_hash) return 0;; + store_hash) return 0;; esac ;; dnsmasq.nftset) case "$param" in add_resolver_element) - [ -n "$resolver_set_supported" ] && nftset 'add_dnsmasq_element' "$@";; + [ -n "$resolver_set_supported" ] || return 1 + local d + for d in $value; do + nftset 'add_dnsmasq_element' "$iface" "$target" "$type" "$uid" "$name" "$d" + done +# nftset 'add_dnsmasq_element' "$iface" "$target" "$type" "$uid" "$name" "$(str_to_dnsmsaq_nftset "$value")" + ;; create_resolver_set) - [ -n "$resolver_set_supported" ] && nftset 'create_dnsmasq_set' "$@";; + [ -n "$resolver_set_supported" ] || return 1 + nftset 'create_dnsmasq_set' "$iface" "$target" "$type" "$uid" "$name" "$value" + ;; check_support) if [ ! -x "$nft" ]; then state add 'errorSummary' 'errorNoNft' @@ -1404,22 +1145,6 @@ resolver() { [ -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) :;; @@ -1461,17 +1186,10 @@ 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 + 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 + if [ "$s" -eq '0' ]; then output_okn else output_failn @@ -1481,16 +1199,10 @@ traffic_killswitch() { 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 + nft_call flush chain inet "$nftTable" "${nftPrefix}_killswitch" || s=1 + nft_call delete chain inet "$nftTable" "${nftPrefix}_killswitch" || s=1 if [ -n "$secure_reload" ] && ! nft_file 'enabled'; then - if [ "$s" -eq 0 ]; then + if [ "$s" -eq '0' ]; then output_okn else output_failn @@ -1502,225 +1214,125 @@ traffic_killswitch() { esac } -policy_routing() { if is_nft_mode; then policy_routing_nft "$@"; else policy_routing_iptables "$@"; fi; } -policy_routing_iptables() { - local mark param4 param6 i negation value dest4 dest6 ipInsertOption="-A" - local ip4error='1' ip6error='1' - local name="$1" iface="$2" laddr="$3" lport="$4" raddr="$5" rport="$6" proto chain uid="$9" - proto="$(str_to_lower "$7")" - chain="$(str_to_upper "$8")" - chain="${chain:-PREROUTING}" - mark=$(eval echo "\$mark_${iface//-/_}") +# original idea by @egc112: https://github.com/egc112/OpenWRT-egc-add-on/tree/main/stop-dns-leak +dns_policy_routing() { + local mark i nftInsertOption='add' proto='tcp udp' proto_i + local param4 param6 + local negation value dest4 dest6 first_value + local inline_set_ipv4_empty_flag inline_set_ipv6_empty_flag + local name="$1" src_addr="$2" dest_dns="$3" uid="$4" + local chain='dstnat_lan' iface='dns' - if [ -n "$ipv6_enabled" ] && { is_ipv6 "$laddr" || is_ipv6 "$raddr"; }; then + if [ -z "${dest_dns_ipv4}${dest_dns_ipv6}" ]; then processPolicyError='true' - state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name" + state add 'errorSummary' 'errorPolicyProcessNoInterfaceDns' "'$dest_dns'" 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 + if [ -z "$ipv6_enabled" ] && is_ipv6 "$(str_first_word "$src_addr")"; then processPolicyError='true' - state add 'errorSummary' 'errorPolicyProcessUnknownFwmark' "$iface" + state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name" return 1 fi - if is_family_mismatch "$laddr" "$raddr"; then + if { is_ipv4 "$(str_first_word "$src_addr")" && [ -z "$dest_dns_ipv4" ]; } || \ + { is_ipv6 "$(str_first_word "$src_addr")" && [ -z "$dest_dns_ipv6" ]; }; then processPolicyError='true' - state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$laddr' '$raddr'" + state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_dns'" return 1 fi - if [ -z "$proto" ]; then - if [ -n "${lport}${rport}" ]; then - proto='tcp udp' - else - proto='all' - fi - fi + for proto_i in $proto; do + unset param4 + unset param6 - 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 + dest4="dport 53 counter dnat ip to ${dest_dns_ipv4}:53" + dest6="dport 53 counter dnat ip6 to ${dest_dns_ipv6}:53" - if [ -n "$laddr" ]; then - if [ "${laddr:0:1}" = "!" ]; then - negation='!'; value="${laddr:1}" + if [ -n "$src_addr" ]; then + if [ "${src_addr:0:1}" = "!" ]; then + negation='!='; src_addr="${src_addr//\!}"; nftset_suffix='_neg'; else - unset negation; value="$laddr"; + unset negation; unset nftset_suffix; 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" + value="$src_addr" + first_value="$(str_first_word "$value")" + if is_phys_dev_quick "$first_value"; then + param4="${param4:+$param4 }iifname ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }iifname ${negation:+$negation }{ $(inline_set "$value") }" + elif is_mac_address "$first_value"; then + param4="${param4:+$param4 }ether saddr ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }ether saddr ${negation:+$negation }{ $(inline_set "$value") }" + elif is_domain "$first_value"; then + local inline_set_ipv4='' inline_set_ipv6='' d='' + for d in $value; do + local resolved_ipv4 resolved_ipv6 + resolved_ipv4="$(resolveip_to_nftset4 "$d")" + resolved_ipv6="$(resolveip_to_nftset6 "$d")" + if [ -z "${resolved_ipv4}${resolved_ipv6}" ]; then + state add 'errorSummary' 'errorFailedToResolve' "$d" + else + [ -n "$resolved_ipv4" ] && inline_set_ipv4="${inline_set_ipv4:+$inline_set_ipv4, }$resolved_ipv4" + [ -n "$resolved_ipv6" ] && inline_set_ipv6="${inline_set_ipv6:+$inline_set_ipv6, }$resolved_ipv6" fi - 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}" + done + [ -n "$inline_set_ipv4" ] || inline_set_ipv4_empty_flag='true' + [ -n "$inline_set_ipv6" ] || inline_set_ipv6_empty_flag='true' + param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }{ $inline_set_ipv4 }" + param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }{ $inline_set_ipv6 }" else - unset negation; value="$lport"; + param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }{ $(inline_set "$value") }" 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 + param4="$nftInsertOption rule inet ${nftTable} ${nftPrefix}_${chain} ${param4} ${proto_i} ${nft_rule_params} ${dest4} comment \"$name\"" + param6="$nftInsertOption rule inet ${nftTable} ${nftPrefix}_${chain} ${param6} ${proto_i} ${nft_rule_params} ${dest6} comment \"$name\"" - 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//-/:}" + local ipv4_error='0' ipv6_error='0' + if [ "$policy_routing_nft_prev_param4" != "$param4" ] && \ + [ -n "$first_value" ] && ! is_ipv6 "$first_value" && \ + [ -z "$inline_set_ipv4_empty_flag" ] && [ -n "$dest_dns_ipv4" ]; then + nft4 "$param4" || ipv4_error='1' + policy_routing_nft_prev_param4="$param4" 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")" + if [ "$policy_routing_nft_prev_param6" != "$param6" ] && [ "$param4" != "$param6" ] && \ + [ -n "$first_value" ] && ! is_ipv4 "$first_value" && \ + [ -z "$inline_set_ipv6_empty_flag" ] && [ -n "$dest_dns_ipv6" ]; then + nft6 "$param6" || ipv6_error='1' + policy_routing_nft_prev_param6="$param6" 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' + 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 - - 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() { + +policy_routing() { local mark i nftInsertOption='add' - local param4 param6 proto_i negation value dest4 dest6 + local param4 param6 proto_i negation value dest4 dest6 + local nftset_suffix first_value_src first_value_dest + local src_inline_set_ipv4_empty_flag src_inline_set_ipv6_empty_flag + local dest_inline_set_ipv4_empty_flag dest_inline_set_ipv6_empty_flag 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 + if [ -z "$ipv6_enabled" ] && \ + { is_ipv6 "$(str_first_word "$src_addr")" || is_ipv6 "$(str_first_word "$dest_addr")"; }; then processPolicyError='true' state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name" return 1 @@ -1746,11 +1358,12 @@ policy_routing_nft() { 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 + # TODO: implement actual family mismatch check on lists +# if is_family_mismatch "$src_addr" "$dest_addr"; then +# processPolicyError='true' +# state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$src_addr' '$dest_addr'" +# return 1 +# fi if [ -z "$proto" ]; then if [ -n "${src_port}${dest_port}" ]; then @@ -1773,75 +1386,81 @@ policy_routing_nft() { if [ -n "$src_addr" ]; then if [ "${src_addr:0:1}" = "!" ]; then - negation='!='; value="${src_addr:1}" + negation='!='; value="${src_addr//\!}"; nftset_suffix='_neg'; else - unset negation; value="$src_addr"; + unset negation; value="$src_addr"; unset nftset_suffix; 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 + first_value_src="$(str_first_word "$value")" + if is_phys_dev_quick "$first_value_src"; then + param4="${param4:+$param4 }iifname ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }iifname ${negation:+$negation }{ $(inline_set "$value") }" + elif is_mac_address "$first_value_src"; then + param4="${param4:+$param4 }ether saddr ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }ether saddr ${negation:+$negation }{ $(inline_set "$value") }" + elif is_domain "$first_value_src"; then + local inline_set_ipv4='' inline_set_ipv6='' d='' + unset src_inline_set_ipv4_empty_flag + unset src_inline_set_ipv6_empty_flag + for d in $value; do + local resolved_ipv4 resolved_ipv6 + resolved_ipv4="$(resolveip_to_nftset4 "$d")" + resolved_ipv6="$(resolveip_to_nftset6 "$d")" + if [ -z "${resolved_ipv4}${resolved_ipv6}" ]; then + state add 'errorSummary' 'errorFailedToResolve' "$d" + else + [ -n "$resolved_ipv4" ] && inline_set_ipv4="${inline_set_ipv4:+$inline_set_ipv4, }$resolved_ipv4" + [ -n "$resolved_ipv6" ] && inline_set_ipv6="${inline_set_ipv6:+$inline_set_ipv6, }$resolved_ipv6" + fi + done + [ -n "$inline_set_ipv4" ] || src_inline_set_ipv4_empty_flag='true' + [ -n "$inline_set_ipv6" ] || src_inline_set_ipv6_empty_flag='true' + param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }{ $inline_set_ipv4 }" + param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }{ $inline_set_ipv6 }" 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 + param4="${param4:+$param4 }${nftIPv4Flag} saddr ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }${nftIPv6Flag} saddr ${negation:+$negation }{ $(inline_set "$value") }" fi fi if [ -n "$dest_addr" ]; then if [ "${dest_addr:0:1}" = "!" ]; then - negation='!='; value="${dest_addr:1}" + negation='!='; value="${src_addr//\!}"; nftset_suffix='_neg'; else - unset negation; value="$dest_addr"; + unset negation; value="$dest_addr"; unset nftset_suffix; 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 + first_value_dest="$(str_first_word "$value")" + if is_phys_dev_quick "$first_value_dest"; then + param4="${param4:+$param4 }oifname ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }oifname ${negation:+$negation }{ $(inline_set "$value") }" + elif is_domain "$first_value_dest"; 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}" + param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_4_${target}_${type}_${uid}${nftset_suffix}" + param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }@${nftPrefix}_${iface}_6_${target}_${type}_${uid}${nftset_suffix}" 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 }" + local inline_set_ipv4='' inline_set_ipv6='' d='' + unset dest_inline_set_ipv4_empty_flag + unset dest_inline_set_ipv6_empty_flag + for d in $value; do + local resolved_ipv4 resolved_ipv6 + resolved_ipv4="$(resolveip_to_nftset4 "$d")" + resolved_ipv6="$(resolveip_to_nftset6 "$d")" + if [ -z "${resolved_ipv4}${resolved_ipv6}" ]; then + state add 'errorSummary' 'errorFailedToResolve' "$d" + else + [ -n "$resolved_ipv4" ] && inline_set_ipv4="${inline_set_ipv4:+$inline_set_ipv4, }$resolved_ipv4" + [ -n "$resolved_ipv6" ] && inline_set_ipv6="${inline_set_ipv6:+$inline_set_ipv6, }$resolved_ipv6" + fi + done + [ -n "$inline_set_ipv4" ] || dest_inline_set_ipv4_empty_flag='true' + [ -n "$inline_set_ipv6" ] || dest_inline_set_ipv6_empty_flag='true' + param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }{ $inline_set_ipv4 }" + param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }{ $inline_set_ipv6 }" 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 + param4="${param4:+$param4 }${nftIPv4Flag} daddr ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }${nftIPv6Flag} daddr ${negation:+$negation }{ $(inline_set "$value") }" fi fi @@ -1851,8 +1470,8 @@ policy_routing_nft() { 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")}" + param4="${param4:+$param4 }${proto_i:+$proto_i }sport ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }${proto_i:+$proto_i }sport ${negation:+$negation }{ $(inline_set "$value") }" fi if [ -n "$dest_port" ]; then @@ -1861,8 +1480,8 @@ policy_routing_nft() { 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")}" + param4="${param4:+$param4 }${proto_i:+$proto_i }dport ${negation:+$negation }{ $(inline_set "$value") }" + param6="${param6:+$param6 }${proto_i:+$proto_i }dport ${negation:+$negation }{ $(inline_set "$value") }" fi if is_tor "$iface"; then @@ -1896,17 +1515,22 @@ policy_routing_nft() { 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\"" + param4="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} ${param4} ${nft_rule_params} ${dest4} comment \"$name\"" + param6="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} ${param6} ${nft_rule_params} ${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" + if [ "$policy_routing_nft_prev_param4" != "$param4" ] && \ + [ -z "$src_inline_set_ipv4_empty_flag" ] && [ -z "$dest_inline_set_ipv4_empty_flag" ] && \ + [ "$filter_group_src_addr" != 'ipv6' ] && [ "$filter_group_src_addr" != 'ipv6_negative' ] && \ + [ "$filter_group_dest_addr" != 'ipv6' ] && [ "$filter_group_dest_addr" != 'ipv6_negative' ]; 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" + if [ "$policy_routing_nft_prev_param6" != "$param6" ] && [ "$param4" != "$param6" ] && \ + [ -z "$src_inline_set_ipv6_empty_flag" ] && [ -z "$dest_inline_set_ipv6_empty_flag" ] && \ + [ "$filter_group_src_addr" != 'ipv4' ] && [ "$filter_group_src_addr" != 'ipv4_negative' ] && \ + [ "$filter_group_dest_addr" != 'ipv4' ] && [ "$filter_group_dest_addr" != 'ipv4_negative' ]; 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 @@ -1926,73 +1550,144 @@ policy_routing_nft() { done } -policy_process() { - local i j uid="$9" - if [ -z "$uid" ]; then # first non-recursive call - [ "$enabled" -gt 0 ] || return 0 - unset processPolicyError - uid="$1" - if is_nft_mode; then - chain="$(str_to_lower "$chain")" - else - chain="$(str_to_upper "$chain")" - fi - proto="$(str_to_lower "$proto")" - [ "$proto" = 'auto' ] && unset proto - [ "$proto" = 'all' ] && unset proto - output 2 "Routing '$name' via $interface " - if [ -z "${src_addr}${src_port}${dest_addr}${dest_port}" ]; then - state add 'errorSummary' 'errorPolicyNoSrcDest' "$name" - output_fail; return 1; - fi - if [ -z "$interface" ]; then - state add 'errorSummary' 'errorPolicyNoInterface' "$name" - output_fail; return 1; - fi - if ! is_supported_interface "$interface"; then - state add 'errorSummary' 'errorPolicyUnknownInterface' "$name" - output_fail; return 1; - fi - src_port="${src_port// / }"; src_port="${src_port// /,}"; src_port="${src_port//,\!/ !}"; - dest_port="${dest_port// / }"; dest_port="${dest_port// /,}"; dest_port="${dest_port//,\!/ !}"; - policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid" - if [ -n "$processPolicyError" ]; then - output_fail - else - output_ok - fi - else # recursive call, get options from passed variables - local name="$1" interface="$2" src_addr="$3" src_port="$4" dest_addr="$5" dest_port="$6" proto="$7" chain="$8" - if str_contains "$src_addr" '[ ;\{\}]'; then - for i in $(str_extras_to_space "$src_addr"); do [ -n "$i" ] && policy_process "$name" "$interface" "$i" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"; done - elif str_contains "$src_port" '[ ;\{\}]'; then - for i in $(str_extras_to_space "$src_port"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$i" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"; done - elif str_contains "$dest_addr" '[ ;\{\}]'; then - for i in $(str_extras_to_space "$dest_addr"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$i" "$dest_port" "$proto" "$chain" "$uid"; done - elif str_contains "$dest_port" '[ ;\{\}]'; then - for i in $(str_extras_to_space "$dest_port"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$i" "$proto" "$chain" "$uid"; done - elif str_contains "$proto" '[ ;\{\}]'; then - for i in $(str_extras_to_space "$proto"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$i" "$chain" "$uid"; done - else - if [ -n "$secure_reload" ] && { is_url_dl "$src_addr" || is_url_dl "$dest_addr"; }; then - state add 'errorSummary' 'errorNoDownloadWithSecureReload' "$name" - elif is_url "$src_addr"; then - src_addr="$(process_url "$src_addr")" - [ -n "$src_addr" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid" - elif is_url "$dest_addr"; then - dest_addr="$(process_url "$dest_addr")" - [ -n "$dest_addr" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid" - else - policy_routing "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid" +dns_policy_process() { + local i j uid="$1" + + [ "$enabled" -gt '0' ] || return 0 + + src_addr="$(str_extras_to_space "$src_addr")" + dest_dns="$(str_extras_to_space "$dest_dns")" + + local dest_dns_interface dest_dns_ipv4 dest_dns_ipv6 + dest_dns_interface="$(str_first_value_interface "$dest_dns")" + dest_dns_ipv4="$(str_first_value_ipv4 "$dest_dns")" + dest_dns_ipv6="$(str_first_value_ipv6 "$dest_dns")" + if is_supported_interface "$dest_dns_interface"; then + local d + for d in $(uci -q get network."$dest_dns_interface".dns); do + if ! is_family_mismatch "$src_addr" "$d"; then + if is_ipv4 "$d"; then + dest_dns_ipv4="${dest_dns_ipv4:-$d}" + elif is_ipv6 "$d"; then + dest_dns_ipv6="${dest_dns_ipv6:-$d}" + fi fi + done + fi + + unset processDnsPolicyError + 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 + + # group by type of src_addr values so that one nft set can be created per type within policy + local filter_list_src_addr='phys_dev phys_dev_negative mac_address mac_address_negative domain domain_negative ipv4 ipv4_negative ipv6 ipv6_negative' + local filter_group_src_addr filtered_value_src_addr + for filter_group_src_addr in $filter_list_src_addr; do + filtered_value_src_addr=$(filter_options "$filter_group_src_addr" "$src_addr") + if [ -n "$src_addr" ] && [ -n "$filtered_value_src_addr" ]; then + if str_contains "$filter_group_src_addr" 'ipv4' && [ -z "$dest_dns_ipv4" ] ; then + continue + fi + if str_contains "$filter_group_src_addr" 'ipv6' && [ -z "$dest_dns_ipv6" ] ; then + continue + fi + dns_policy_routing "$name" "$filtered_value_src_addr" "$dest_dns" "$uid" fi + done + + if [ -n "$processDnsPolicyError" ]; then + output_fail + else + output_ok fi } -try() { - if ! "$@"; then - state add 'errorSummary' 'errorTryFailed' "$*" - return 1 +policy_process() { + local i j uid="$1" + + [ "$enabled" -gt '0' ] || return 0 + + src_addr="$(str_extras_to_space "$src_addr")" + src_port="$(str_extras_to_space "$src_port")" + dest_addr="$(str_extras_to_space "$dest_addr")" + dest_port="$(str_extras_to_space "$dest_port")" + + unset processPolicyError + 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 + + unset j + for i in $src_addr; do + if [ -n "$secure_reload" ] && is_url_dl "$i"; then + state add 'errorSummary' 'errorNoDownloadWithSecureReload' "$name" + elif is_url "$i"; then + i="$(process_url "$i")" + fi + j="${j:+$j }$i" + done + src_addr="$j" + + unset j + for i in $dest_addr; do + if [ -n "$secure_reload" ] && is_url_dl "$i"; then + state add 'errorSummary' 'errorNoDownloadWithSecureReload' "$name" + elif is_url "$i"; then + i="$(process_url "$i")" + fi + j="${j:+$j }$i" + done + dest_addr="$j" + + # TODO: if only src_addr is set add option 121 to dhcp leases? + + local filter_list_src_addr='phys_dev phys_dev_negative mac_address mac_address_negative domain domain_negative ipv4 ipv4_negative ipv6 ipv6_negative' + local filter_list_dest_addr='domain domain_negative ipv4 ipv4_negative ipv6 ipv6_negative' + local filter_group_src_addr filtered_value_src_addr filter_group_dest_addr filtered_value_dest_addr + [ -z "$src_addr" ] && filter_list_src_addr='none' + for filter_group_src_addr in $filter_list_src_addr; do + filtered_value_src_addr=$(filter_options "$filter_group_src_addr" "$src_addr") + if [ -z "$src_addr" ] || { [ -n "$src_addr" ] && [ -n "$filtered_value_src_addr" ]; }; then + [ -z "$dest_addr" ] && filter_list_dest_addr='none' + for filter_group_dest_addr in $filter_list_dest_addr; do + filtered_value_dest_addr=$(filter_options "$filter_group_dest_addr" "$dest_addr") + if [ -z "$dest_addr" ] || { [ -n "$dest_addr" ] && [ -n "$filtered_value_dest_addr" ]; }; then + if str_contains "$filter_group_src_addr" 'ipv4' && str_contains "$filter_group_dest_addr" 'ipv6'; then + continue + fi + if str_contains "$filter_group_src_addr" 'ipv6' && str_contains "$filter_group_dest_addr" 'ipv4'; then + continue + fi + policy_routing "$name" "$interface" "$filtered_value_src_addr" "$src_port" "$filtered_value_dest_addr" "$dest_port" "$proto" "$chain" "$uid" + fi + done + fi + done + + if [ -n "$processPolicyError" ]; then + output_fail + else + output_ok fi } @@ -2007,21 +1702,15 @@ interface_routing() { 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 + ip -4 rule del table "$tid" >/dev/null 2>&1 + try ip -4 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 + try nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1 + try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} ${nft_rule_params} mark set mark and ${fw_maskXor} xor ${mark}" || ipv4_error=1 + try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" || ipv4_error=1 if [ -n "$ipv6_enabled" ]; then ipv6_error=0 - $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 + ip -6 rule del table "$tid" >/dev/null 2>&1 + try ip -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 @@ -2030,14 +1719,14 @@ interface_routing() { 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 + ip -4 rule del table "$tid" >/dev/null 2>&1 + ip -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 + try ip -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 + try ip -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 @@ -2045,68 +1734,53 @@ interface_routing() { 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 + try ip -4 route add $i table "$tid" >/dev/null 2>&1 || ipv4_error=1 fi done << EOF - $($ip_bin -4 route list table main) + $(ip -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 + try ip -4 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 + try nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1 + try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} ${nft_rule_params} mark set mark and ${fw_maskXor} xor ${mark}" || ipv4_error=1 + try nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" || ipv4_error=1 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 + ip -6 rule del table "$tid" >/dev/null 2>&1 + ip -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 + try ip -6 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv6_error=1 + elif ip -6 route list table main | grep -q " dev $dev6 "; then + ip -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 + try ip -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 ") + $(ip -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 + try ip -6 route add "$(ip -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + try ip -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 + try ip -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 + 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 + if [ "${dscp:-0}" -ge '1' ] && [ "${dscp:-0}" -le '63' ]; then + try nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv4Flag} dscp ${dscp} ${nft_rule_params} goto ${nftPrefix}_mark_${mark}" || s=1 + if [ -n "$ipv6_enabled" ]; then + try nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv6Flag} dscp ${dscp} ${nft_rule_params} goto ${nftPrefix}_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 + if [ "$iface" = "$icmp_interface" ]; then + try nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv4Flag} protocol icmp ${nft_rule_params} goto ${nftPrefix}_mark_${mark}" || s=1 + if [ -n "$ipv6_enabled" ]; then + try nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv6Flag} protocol icmp ${nft_rule_params} goto ${nftPrefix}_mark_${mark}" || s=1 fi fi else @@ -2115,23 +1789,15 @@ EOF 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 + 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 return "$s" ;; delete|destroy) - $ip_bin rule del table "$tid" >/dev/null 2>&1 + ip 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 + ip route flush table "$tid" >/dev/null 2>&1 sed -i "/${ipTablePrefix}_${iface}\$/d" "$rtTablesFile" sync fi @@ -2140,38 +1806,38 @@ EOF reload_interface) is_netifd_table_interface "$iface" && return 0; ipv4_error=0 - $ip_bin rule del table "$tid" >/dev/null 2>&1 + ip 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 + ip route flush table "$tid" >/dev/null 2>&1 fi - if [ -n "$gw4" ] || [ "$strict_enforcement" -ne 0 ]; then + 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 + try ip -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 + try ip -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 + try ip 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 { [ -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 + try ip -6 route add unreachable default table "$tid" || ipv6_error=1 + elif ip -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 + try ip -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 ") + $(ip -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 + try ip -6 route add "$(ip -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + try ip -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 + try ip -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 + if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then s=0 else s=1 @@ -2229,7 +1895,7 @@ interface_process() { is_supported_interface "$iface" || return 0 is_wan6 "$iface" && return 0 - [ $((ifaceMark)) -gt $((fw_mask)) ] && return 1 + [ "$((ifaceMark))" -gt "$((fw_mask))" ] && return 1 if is_ovpn "$iface" && ! is_ovpn_valid "$iface"; then : || state add 'warningSummary' 'warningInvalidOVPNConfig' "$iface" @@ -2369,7 +2035,7 @@ interface_process() { user_file_process() { local shellBin="${SHELL:-/bin/ash}" - [ "$enabled" -gt 0 ] || return 0 + [ "$enabled" -gt '0' ] || return 0 if [ ! -s "$path" ]; then state add 'errorSummary' 'errorUserFileNotFound' "$path" output_fail @@ -2449,20 +2115,11 @@ start_service() { 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' @@ -2485,11 +2142,6 @@ start_service() { 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 @@ -2518,13 +2170,6 @@ start_service() { cleanup_main_chains cleanup_sets nft_file 'create' - if ! is_nft_mode; then - for i in $chainsList; do - i="$(str_to_upper "$i")" - ipt -t mangle -N "${iptPrefix}_${i}" - ipt -t mangle "$rule_create_option" "$i" -m mark --mark "0x0/${fw_mask}" -j "${iptPrefix}_${i}" - done - fi json_add_array 'gateways' interface_process 'all' 'prepare' config_foreach interface_process 'interface' 'reload' @@ -2537,6 +2182,12 @@ start_service() { 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' @@ -2561,13 +2212,6 @@ start_service() { cleanup_marking_chains cleanup_rt_tables nft_file 'create' - if ! is_nft_mode; then - for i in $chainsList; do - i="$(str_to_upper "$i")" - ipt -t mangle -N "${iptPrefix}_${i}" - ipt -t mangle "$rule_create_option" "$i" -m mark --mark "0x0/${fw_mask}" -j "${iptPrefix}_${i}" - done - fi output 1 'Processing interfaces ' json_add_array 'gateways' interface_process 'all' 'prepare' @@ -2583,6 +2227,12 @@ start_service() { 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' @@ -2605,7 +2255,7 @@ start_service() { [ -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 + if [ "$strict_enforcement" -ne '0' ] && str_contains "$gatewaySummary" '0.0.0.0'; then json_add_string 'mode' 'strict' fi json_close_object @@ -2623,10 +2273,8 @@ service_started() { 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}" + [ -n "$gatewaySummary" ] && output "$serviceName (nft mode) started with gateways:\\n${gatewaySummary}" fi state print 'errorSummary' state print 'warningSummary' @@ -2689,13 +2337,11 @@ stop_service() { resolver 'cleanup_all' resolver 'compare_hash' && resolver 'restart' traffic_killswitch 'remove' - if [ "$enabled" -ne 0 ]; then + 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; + output "$serviceName (nft mode) stopped "; output_okn; fi fi rm -f "$packageLockFile" @@ -2704,16 +2350,6 @@ stop_service() { version() { echo "$PKG_VERSION"; } status_service() { - local _SEPARATOR_='============================================================' - load_environment 'on_status' - if is_nft_mode; then - status_service_nft "$@" - else - status_service_iptables "$@" - fi -} - -status_service_nft() { local i dev dev6 wan_tid json_load "$(ubus call system board)"; json_select release; json_get_var dist distribution; json_get_var vers version @@ -2723,11 +2359,12 @@ status_service_nft() { 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}') + wanGW6=$(ip -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}') + [ "$wanGW6" = "default" ] && wanGW6=$(ip -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}') fi while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support" +# shellcheck disable=SC2154 status="$serviceName 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}." @@ -2744,7 +2381,7 @@ status_service_nft() { fi echo "$_SEPARATOR_" echo "$packageName chains - policies" - for i in forward input output prerouting postrouting; do + for i in $chainsList dstnat_lan; do "$nft" -a list table inet "$nftTable" | sed -n "/chain ${nftPrefix}_${i} {/,/\t}/p" done echo "$_SEPARATOR_" @@ -2757,154 +2394,29 @@ status_service_nft() { for i in $(get_nft_sets); do "$nft" -a list table inet "$nftTable" | sed -n "/set ${i} {/,/\t}/p" done - if [ -s "$dnsmasqFile" ]; then + if [ -s "$dnsmasqFileDefault" ]; then echo "$_SEPARATOR_" echo "dnsmasq sets" - cat "$dnsmasqFile" + 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)" + i=0; while [ "$i" -lt "$tableCount" ]; do + echo "IPv4 table $((wan_tid + i)) route: $(ip -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))" + ip -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)) route: $(ip -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)) + ip -6 route show table $((wan_tid + i)) fi i=$((i + 1)) done } -status_service_iptables() { - local dist vers out id s param status set_d set_p tableCount i=0 dev dev6 j wan_tid - - json_load "$(ubus call system board)"; json_select release; json_get_var dist distribution; json_get_var vers version - if [ -n "$wanIface4" ]; then - network_get_gateway wanGW4 "$wanIface4" - network_get_device dev "$wanIface4" - fi - if [ -n "$wanIface6" ]; then - network_get_device dev6 "$wanIface6" - wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}') - [ "$wanGW6" = "default" ] && wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}') - fi - while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done - [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support" - status="$serviceName running on $dist $vers." - [ -n "$wanIface4" ] && status="$status WAN (IPv4): ${wanIface4}/${dev}/${wanGW4:-0.0.0.0}." - [ -n "$wanIface6" ] && status="$status WAN (IPv6): ${wanIface6}/${dev6}/${wanGW6:-::/0}." - { - echo "$status" - echo "$_SEPARATOR_" - dnsmasq --version 2>/dev/null | sed '/^$/,$d' - if [ -n "$1" ]; then - echo "$_SEPARATOR_" - echo "Resolving domains" - for i in $1; do - echo "$i: $(resolveip "$i" | tr '\n' ' ')" - done - fi - - echo "$_SEPARATOR_" - echo "Routes/IP Rules" - tableCount="$(grep -c "${packageName}_" $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 "$dnsmasqFile" ]; then - echo "$_SEPARATOR_" - echo "DNSMASQ sets" - cat "$dnsmasqFile" - fi - if [ -s "$aghIpsetFile" ]; then - echo "$_SEPARATOR_" - echo "AdGuardHome sets" - cat "$aghIpsetFile" - fi - echo "$_SEPARATOR_" - } | tee -a /var/${packageName}-support - if [ -n "$set_p" ]; then - printf "%b" "Pasting to paste.ee... " - if 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 -} - # shellcheck disable=SC2120 load_validate_config() { uci_load_validate "$packageName" "$packageName" "$1" "${2}${3:+ $3}" \ @@ -2912,7 +2424,7 @@ load_validate_config() { 'strict_enforcement:bool:1' \ 'secure_reload:bool:0' \ 'ipv6_enabled:bool:0' \ - 'resolver_set:or("", "none", "dnsmasq.ipset", "dnsmasq.nftset")' \ + 'resolver_set:or("", "none", "dnsmasq.nftset")' \ 'resolver_instance:list(or(integer, string)):*' \ 'verbosity:range(0,2):2' \ 'wan_mark:regex("[A-Fa-f0-9]{8}"):010000' \ @@ -2928,11 +2440,10 @@ load_validate_config() { '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_rule_counter:bool:0'\ 'nft_set_auto_merge:bool:1'\ - 'nft_set_counter:bool:1'\ + 'nft_set_counter:bool:0'\ 'nft_set_flags_interval:bool:1'\ 'nft_set_flags_timeout:bool:0'\ 'nft_set_gc_interval:or("", string)'\ @@ -2940,6 +2451,19 @@ load_validate_config() { '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 diff --git a/net/pbr/files/etc/init.d/pbr-iptables b/net/pbr/files/etc/init.d/pbr-iptables new file mode 100755 index 0000000000..18275ba645 --- /dev/null +++ b/net/pbr/files/etc/init.d/pbr-iptables @@ -0,0 +1,3279 @@ +#!/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' +} diff --git a/net/pbr/files/etc/uci-defaults/91-pbr-nft b/net/pbr/files/etc/uci-defaults/91-pbr-nft index 0406e2a435..14262f0b15 100644 --- a/net/pbr/files/etc/uci-defaults/91-pbr-nft +++ b/net/pbr/files/etc/uci-defaults/91-pbr-nft @@ -14,12 +14,6 @@ if [ "$(uci_get pbr config resolver_set)" != 'dnsmasq.nftset' ]; then if check_dnsmasq_nftset; then output "Setting resolver_set to 'dnsmasq.nftset'... " uci_set pbr config resolver_set 'dnsmasq.nftset' && output_okn || output_failn - elif check_agh_ipset; then - output "Setting resolver_set to 'adguardhome.ipset'... " - uci_set pbr config resolver_set 'adguardhome.ipset' && output_okn || output_failn - elif check_dnsmasq_ipset; then - output "Setting resolver_set to 'dnsmasq.ipset'... " - uci_set pbr config resolver_set 'dnsmasq.ipset' && output_okn || output_failn else output "Setting resolver_set to 'none'... " uci_set pbr config resolver_set 'none' && output_okn || output_failn diff --git a/net/pbr/files/usr/share/nftables.d/chain-post/dstnat_lan/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-post/dstnat_lan/30-pbr.nft new file mode 100644 index 0000000000..2434649f3f --- /dev/null +++ b/net/pbr/files/usr/share/nftables.d/chain-post/dstnat_lan/30-pbr.nft @@ -0,0 +1 @@ +jump pbr_dstnat_lan comment "Jump into pbr dstnat_lan chain"; diff --git a/net/pbr/files/usr/share/nftables.d/table-post/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/table-post/30-pbr.nft index 4dd9b28131..0a9118d834 100644 --- a/net/pbr/files/usr/share/nftables.d/table-post/30-pbr.nft +++ b/net/pbr/files/usr/share/nftables.d/table-post/30-pbr.nft @@ -1,3 +1,4 @@ +chain pbr_dstnat_lan {} chain pbr_forward {} chain pbr_input {} chain pbr_output {} diff --git a/net/pbr/files/usr/share/pbr/pbr.user.aws b/net/pbr/files/usr/share/pbr/pbr.user.aws index 6e1fb26934..46cd4b3f1a 100644 --- a/net/pbr/files/usr/share/pbr/pbr.user.aws +++ b/net/pbr/files/usr/share/pbr/pbr.user.aws @@ -4,8 +4,6 @@ TARGET_INTERFACE='wan' TARGET_NFTSET_4="pbr_${TARGET_INTERFACE}_4_dst_ip_user" TARGET_NFTSET_6="pbr_${TARGET_INTERFACE}_6_dst_ip_user" -TARGET_IPSET_4="pbr_${TARGET_INTERFACE}_4_dst_net_user" -TARGET_IPSET_6="pbr_${TARGET_INTERFACE}_6_dst_net_user" TARGET_TABLE='inet fw4' TARGET_URL="https://ip-ranges.amazonaws.com/ip-ranges.json" TARGET_DL_FILE_4="/var/pbr_tmp_aws_ip_ranges.ipv4" @@ -16,31 +14,21 @@ _ret=0 if [ ! -s "$TARGET_DL_FILE_4" ]; then uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | grep "ip_prefix" | sed 's/^.*\"ip_prefix\": \"//; s/\",//' > "$TARGET_DL_FILE_4" fi + if [ -s "$TARGET_DL_FILE_4" ]; then - if [ -n "$nft" ] && [ -x "$nft" ]; then - while read -r p; do "$nft" "add element $TARGET_TABLE $TARGET_NFTSET_4 { $p }" || _ret=1; done < "$TARGET_DL_FILE_4" - elif ipset -q list "$TARGET_IPSET_4" >/dev/null 2>&1; then - if awk -v ipset="$TARGET_IPSET_4" '{print "add " ipset " " $1}' "$TARGET_DL_FILE_4" | ipset restore -!; then - _ret=0 - else - _ret=1 - fi - fi + params= + while read -r p; do params="${params:+$params, }${p}"; done < "$TARGET_DL_FILE_4" + [ -n "$params" ] && nft "add element $TARGET_TABLE $TARGET_NFTSET_4 { $params }" || _ret=1 fi if [ -n "$TARGET_DL_FILE_6" ] && [ ! -s "$TARGET_DL_FILE_6" ]; then uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | grep "ipv6_prefix" | sed 's/^.*\"ipv6_prefix\": \"//; s/\",//' > "$TARGET_DL_FILE_6" fi + if [ -s "$TARGET_DL_FILE_6" ]; then - if [ -n "$nft" ] && [ -x "$nft" ]; then - while read -r p; do "$nft" "add element $TARGET_TABLE $TARGET_NFTSET_6 { $p }" || _ret=1; done < "$TARGET_DL_FILE_6" - elif ipset -q list "$TARGET_IPSET_6" >/dev/null 2>&1; then - if awk -v ipset="$TARGET_IPSET_6" '{print "add " ipset " " $1}' "$TARGET_DL_FILE_6" | ipset restore -!; then - _ret=0 - else - _ret=1 - fi - fi + params= + while read -r p; do params="${params:+$params, }${p}"; done < "$TARGET_DL_FILE_6" + [ -n "$params" ] && nft "add element $TARGET_TABLE $TARGET_NFTSET_6 { $params }" || _ret=1 fi return $_ret diff --git a/net/pbr/files/usr/share/pbr/pbr.user.aws-iptables b/net/pbr/files/usr/share/pbr/pbr.user.aws-iptables new file mode 100644 index 0000000000..835376bb12 --- /dev/null +++ b/net/pbr/files/usr/share/pbr/pbr.user.aws-iptables @@ -0,0 +1,46 @@ +#!/bin/sh +# This file is heavily based on code from https://github.com/Xentrk/netflix-vpn-bypass/blob/master/IPSET_Netflix.sh + +TARGET_INTERFACE='wan' +TARGET_NFTSET_4="pbr_${TARGET_INTERFACE}_4_dst_ip_user" +TARGET_NFTSET_6="pbr_${TARGET_INTERFACE}_6_dst_ip_user" +TARGET_IPSET_4="pbr_${TARGET_INTERFACE}_4_dst_net_user" +TARGET_IPSET_6="pbr_${TARGET_INTERFACE}_6_dst_net_user" +TARGET_TABLE='inet fw4' +TARGET_URL="https://ip-ranges.amazonaws.com/ip-ranges.json" +TARGET_DL_FILE_4="/var/pbr_tmp_aws_ip_ranges.ipv4" +# Uncomment the following line if you enabled ipv6 for pbr and want IPv6 entries added to the IPv6 set +# TARGET_DL_FILE_6="/var/pbr_tmp_aws_ip_ranges.ipv6" +_ret=0 + +if [ ! -s "$TARGET_DL_FILE_4" ]; then + uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | grep "ip_prefix" | sed 's/^.*\"ip_prefix\": \"//; s/\",//' > "$TARGET_DL_FILE_4" +fi +if [ -s "$TARGET_DL_FILE_4" ]; then + if [ -n "$nft" ] && [ -x "$nft" ]; then + while read -r p; do nft "add element $TARGET_TABLE $TARGET_NFTSET_4 { $p }" || _ret=1; done < "$TARGET_DL_FILE_4" + elif ipset -q list "$TARGET_IPSET_4" >/dev/null 2>&1; then + if awk -v ipset="$TARGET_IPSET_4" '{print "add " ipset " " $1}' "$TARGET_DL_FILE_4" | ipset restore -!; then + _ret=0 + else + _ret=1 + fi + fi +fi + +if [ -n "$TARGET_DL_FILE_6" ] && [ ! -s "$TARGET_DL_FILE_6" ]; then + uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | grep "ipv6_prefix" | sed 's/^.*\"ipv6_prefix\": \"//; s/\",//' > "$TARGET_DL_FILE_6" +fi +if [ -s "$TARGET_DL_FILE_6" ]; then + if [ -n "$nft" ] && [ -x "$nft" ]; then + while read -r p; do nft "add element $TARGET_TABLE $TARGET_NFTSET_6 { $p }" || _ret=1; done < "$TARGET_DL_FILE_6" + elif ipset -q list "$TARGET_IPSET_6" >/dev/null 2>&1; then + if awk -v ipset="$TARGET_IPSET_6" '{print "add " ipset " " $1}' "$TARGET_DL_FILE_6" | ipset restore -!; then + _ret=0 + else + _ret=1 + fi + fi +fi + +return $_ret diff --git a/net/pbr/files/usr/share/pbr/pbr.user.netflix b/net/pbr/files/usr/share/pbr/pbr.user.netflix index 465d0bcd8f..3bbe09b830 100644 --- a/net/pbr/files/usr/share/pbr/pbr.user.netflix +++ b/net/pbr/files/usr/share/pbr/pbr.user.netflix @@ -6,8 +6,6 @@ TARGET_INTERFACE='wan' TARGET_NFTSET_4="pbr_${TARGET_INTERFACE}_4_dst_ip_user" TARGET_NFTSET_6="pbr_${TARGET_INTERFACE}_6_dst_ip_user" -TARGET_IPSET_4="pbr_${TARGET_INTERFACE}_4_dst_net_user" -TARGET_IPSET_6="pbr_${TARGET_INTERFACE}_6_dst_net_user" TARGET_TABLE='inet fw4' TARGET_ASN='2906' TARGET_DL_FILE_4="/var/pbr_tmp_AS${TARGET_ASN}.ipv4" @@ -34,16 +32,11 @@ if [ ! -s "$TARGET_DL_FILE_4" ]; then uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | jsonfilter -e '@.data.ipv4_prefixes[*].prefix' > "$TARGET_DL_FILE_4" fi fi + if [ -s "$TARGET_DL_FILE_4" ]; then - if [ -n "$nft" ] && [ -x "$nft" ]; then - while read -r p; do "$nft" "add element $TARGET_TABLE $TARGET_NFTSET_4 { $p }" || _ret=1; done < "$TARGET_DL_FILE_4" - elif ipset -q list "$TARGET_IPSET_4" >/dev/null 2>&1; then - if awk -v ipset="$TARGET_IPSET_4" '{print "add " ipset " " $1}' "$TARGET_DL_FILE_4" | ipset restore -!; then - _ret=0 - else - _ret=1 - fi - fi + params= + while read -r p; do params="${params:+$params, }${p}"; done < "$TARGET_DL_FILE_4" + [ -n "$params" ] && nft "add element $TARGET_TABLE $TARGET_NFTSET_4 { $params }" || _ret=1 fi if [ -n "$TARGET_DL_FILE_6" ] && [ ! -s "$TARGET_DL_FILE_6" ]; then @@ -53,15 +46,9 @@ if [ -n "$TARGET_DL_FILE_6" ] && [ ! -s "$TARGET_DL_FILE_6" ]; then fi fi if [ -s "$TARGET_DL_FILE_6" ]; then - if [ -n "$nft" ] && [ -x "$nft" ]; then - while read -r p; do "$nft" "add element $TARGET_TABLE $TARGET_NFTSET_6 { $p }" || _ret=1; done < "$TARGET_DL_FILE_6" - elif ipset -q list "$TARGET_IPSET_6" >/dev/null 2>&1; then - if awk -v ipset="$TARGET_IPSET_6" '{print "add " ipset " " $1}' "$TARGET_DL_FILE_6" | ipset restore -!; then - _ret=0 - else - _ret=1 - fi - fi + params= + while read -r p; do params="${params:+$params, }${p}"; done < "$TARGET_DL_FILE_6" + [ -n "$params" ] && nft "add element $TARGET_TABLE $TARGET_NFTSET_6 { $params }" || _ret=1 fi return $_ret diff --git a/net/pbr/files/usr/share/pbr/pbr.user.netflix-iptables b/net/pbr/files/usr/share/pbr/pbr.user.netflix-iptables new file mode 100644 index 0000000000..bb1481d7a2 --- /dev/null +++ b/net/pbr/files/usr/share/pbr/pbr.user.netflix-iptables @@ -0,0 +1,67 @@ +#!/bin/sh +# This file is heavily based on code from https://github.com/Xentrk/netflix-vpn-bypass/blob/master/IPSET_Netflix.sh +# Credits to https://forum.openwrt.org/u/dscpl for api.hackertarget.com code. +# Credits to https://github.com/kkeker and https://github.com/tophirsch for api.bgpview.io code. + +TARGET_INTERFACE='wan' +TARGET_NFTSET_4="pbr_${TARGET_INTERFACE}_4_dst_ip_user" +TARGET_NFTSET_6="pbr_${TARGET_INTERFACE}_6_dst_ip_user" +TARGET_IPSET_4="pbr_${TARGET_INTERFACE}_4_dst_net_user" +TARGET_IPSET_6="pbr_${TARGET_INTERFACE}_6_dst_net_user" +TARGET_TABLE='inet fw4' +TARGET_ASN='2906' +TARGET_DL_FILE_4="/var/pbr_tmp_AS${TARGET_ASN}.ipv4" +# Uncomment the following line if you enabled ipv6 for pbr and want IPv6 entries added to the IPv6 set +# TARGET_DL_FILE_6="/var/pbr_tmp_AS${TARGET_ASN}.ipv6" +DB_SOURCE='ipinfo.io' +#DB_SOURCE='api.hackertarget.com' +#DB_SOURCE='api.bgpview.io' +REGEX_IPV4='[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\/[0-9]\{1,\}' +REGEX_IPV6='.*::.*' +_ret=0 + +if [ ! -s "$TARGET_DL_FILE_4" ]; then + if [ "$DB_SOURCE" = "ipinfo.io" ]; then + TARGET_URL="https://ipinfo.io/AS${TARGET_ASN}" + uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | sed -n "s|\(.*\)/AS${TARGET_ASN}/\($REGEX_IPV4\)\"|\2|p" > "$TARGET_DL_FILE_4" + fi + if [ "$DB_SOURCE" = "api.hackertarget.com" ]; then + TARGET_URL="https://api.hackertarget.com/aslookup/?q=AS${TARGET_ASN}" + uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | sed '1d' > "$TARGET_DL_FILE_4" + fi + if [ "$DB_SOURCE" = "api.bgpview.io" ]; then + TARGET_URL="https://api.bgpview.io/asn/${TARGET_ASN}/prefixes" + uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | jsonfilter -e '@.data.ipv4_prefixes[*].prefix' > "$TARGET_DL_FILE_4" + fi +fi +if [ -s "$TARGET_DL_FILE_4" ]; then + if [ -n "$nft" ] && [ -x "$nft" ]; then + while read -r p; do nft "add element $TARGET_TABLE $TARGET_NFTSET_4 { $p }" || _ret=1; done < "$TARGET_DL_FILE_4" + elif ipset -q list "$TARGET_IPSET_4" >/dev/null 2>&1; then + if awk -v ipset="$TARGET_IPSET_4" '{print "add " ipset " " $1}' "$TARGET_DL_FILE_4" | ipset restore -!; then + _ret=0 + else + _ret=1 + fi + fi +fi + +if [ -n "$TARGET_DL_FILE_6" ] && [ ! -s "$TARGET_DL_FILE_6" ]; then + if [ "$DB_SOURCE" = "ipinfo.io" ]; then + TARGET_URL="https://ipinfo.io/AS${TARGET_ASN}" + uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | sed -n "s|\(.*\)/AS${TARGET_ASN}/\($REGEX_IPV6\)\"|\2|p" > "$TARGET_DL_FILE_6" + fi +fi +if [ -s "$TARGET_DL_FILE_6" ]; then + if [ -n "$nft" ] && [ -x "$nft" ]; then + while read -r p; do nft "add element $TARGET_TABLE $TARGET_NFTSET_6 { $p }" || _ret=1; done < "$TARGET_DL_FILE_6" + elif ipset -q list "$TARGET_IPSET_6" >/dev/null 2>&1; then + if awk -v ipset="$TARGET_IPSET_6" '{print "add " ipset " " $1}' "$TARGET_DL_FILE_6" | ipset restore -!; then + _ret=0 + else + _ret=1 + fi + fi +fi + +return $_ret diff --git a/net/pbr/files/usr/share/pbr/pbr.user.wg_server_and_client b/net/pbr/files/usr/share/pbr/pbr.user.wg_server_and_client index 2d717e6639..0af651c56d 100644 --- a/net/pbr/files/usr/share/pbr/pbr.user.wg_server_and_client +++ b/net/pbr/files/usr/share/pbr/pbr.user.wg_server_and_client @@ -6,10 +6,11 @@ WAN_INTERFACE='wan' _ret='1' insert_ip_rule() { - local proto listen_port + local disabled proto listen_port + config_get disabled "$1" disabled "0" config_get proto "$1" proto config_get listen_port "$1" listen_port - if [ "$proto" = 'wireguard' ] && [ -n "$listen_port" ]; then + if [ "$disabled" -ne '1' ] && [ "$proto" = 'wireguard' ] && [ -n "$listen_port" ]; then ip rule del sport "$listen_port" table "pbr_${WAN_INTERFACE}" >/dev/null 2>&1 ip rule add sport "$listen_port" table "pbr_${WAN_INTERFACE}" >/dev/null 2>&1 && _ret=0 fi