diff options
author | Stan Grishin <stangri@melmac.ca> | 2022-10-31 23:08:01 +0000 |
---|---|---|
committer | Stan Grishin <stangri@melmac.ca> | 2022-12-03 02:30:29 +0000 |
commit | 47eca64cb82a5459668fc14df9422e365c8343b6 (patch) | |
tree | 29ff6c255b296f6ff3fde60352ea91bd0fc55fb8 /net/pbr | |
parent | b40372da41303711bcc6c5b7640f7a3c00c47d54 (diff) |
pbr: initial commit
* The makefile produces the nft and iptables capable `pbr` package
and the `pbr-iptables` package for legacy setups
* This replaces `vpnbypass` and `vpn-policy-routing` packages
* I'm soliciting feedback on this package and my intention is to
update the version to 1.0.0 before this is merged, but I need the
feedback on this and luci-app-pbr before then.
Signed-off-by: Stan Grishin <stangri@melmac.ca>
Diffstat (limited to 'net/pbr')
19 files changed, 2894 insertions, 0 deletions
diff --git a/net/pbr/Makefile b/net/pbr/Makefile new file mode 100644 index 000000000..0f2842ed8 --- /dev/null +++ b/net/pbr/Makefile @@ -0,0 +1,201 @@ +# Copyright 2017-2022 Stan Grishin (stangri@melmac.ca) +# This is free software, licensed under the GNU General Public License v3. + +include $(TOPDIR)/rules.mk + +PKG_NAME:=pbr +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_LICENSE:=GPL-3.0-or-later +PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca> + +include $(INCLUDE_DIR)/package.mk + +define Package/pbr/default + SECTION:=net + CATEGORY:=Network + SUBMENU:=VPN + PROVIDES:=pbr + TITLE:=Policy Based Routing Service + URL:=https://docs.openwrt.melmac.net/pbr/ + DEPENDS:=+ip-full +jshn +jsonfilter +resolveip + CONFLICTS:=vpnbypass vpn-policy-routing + PROVIDES:=vpnbypass vpn-policy-routing + PKGARCH:=all +endef + +define Package/pbr +$(call Package/pbr/default) + TITLE+= with nft/nft set support + DEPENDS+=+firewall4 +kmod-nft-core +kmod-nft-nat +nftables-json +endef + +define Package/pbr-iptables +$(call Package/pbr/default) + TITLE+= with iptables/ipset support + DEPENDS+=+ipset +iptables +kmod-ipt-ipset +iptables-mod-ipopt +endef + +define Package/pbr-netifd +$(call Package/pbr/default) + TITLE+= with netifd support +endef + +define Package/pbr/description +This service enables policy-based routing for WAN interfaces and various VPN tunnels. +This version supports OpenWrt with both fw3/ipset/iptables and fw4/nft. +endef + +define Package/pbr-iptables/description +This service enables policy-based routing for WAN interfaces and various VPN tunnels. +This version supports OpenWrt with fw3/ipset/iptables. +endef + +define Package/pbr-netifd/description +This service enables policy-based routing for WAN interfaces and various VPN tunnels. +This version supports OpenWrt with both fw3/ipset/iptables and fw4/nft. +This version uses OpenWrt native netifd/tables to set up interfaces. This is WIP. +endef + +define Package/pbr/conffiles +/etc/config/pbr +endef + +Package/pbr-iptables/conffiles = $(Package/pbr/conffiles) +Package/pbr-netifd/conffiles = $(Package/pbr/conffiles) + +define Build/Configure +endef + +define Build/Compile +endef + +define Package/pbr/default/install + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/pbr.init $(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_DIR) $(1)/etc/hotplug.d/iface + $(INSTALL_DATA) ./files/etc/hotplug.d/iface/70-pbr $(1)/etc/hotplug.d/iface/70-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/pbr.firewall.include $(1)/usr/share/pbr/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 +endef + +define Package/pbr/install +$(call Package/pbr/default/install,$(1)) + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/pbr $(1)/etc/config/pbr + $(INSTALL_DIR) $(1)/usr/share/nftables.d + $(CP) ./files/usr/share/nftables.d/* $(1)/usr/share/nftables.d/ +endef + +define Package/pbr-iptables/install +$(call Package/pbr/default/install,$(1)) + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/pbr.iptables $(1)/etc/config/pbr +endef + +define Package/pbr-netifd/install +$(call Package/pbr/default/install,$(1)) + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/pbr $(1)/etc/config/pbr + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_BIN) ./files/etc/uci-defaults/91-pbr $(1)/etc/uci-defaults/91-pbr +endef + +define Package/pbr/postinst + #!/bin/sh + # check if we are on real system + if [ -z "$${IPKG_INSTROOT}" ]; then + chmod -x /etc/init.d/pbr || true + fw4 -q reload || true + chmod +x /etc/init.d/pbr || true + echo -n "Installing rc.d symlink for pbr... " + /etc/init.d/pbr enable && echo "OK" || echo "FAIL" + fi + exit 0 +endef + +define Package/pbr/prerm + #!/bin/sh + # check if we are on real system + if [ -z "$${IPKG_INSTROOT}" ]; then + uci -q delete firewall.pbr || true + echo "Stopping pbr service... " + /etc/init.d/pbr stop || true + echo -n "Removing rc.d symlink for pbr... " + /etc/init.d/pbr disable && echo "OK" || echo "FAIL" + fi + exit 0 +endef + +define Package/pbr/postrm + #!/bin/sh + # check if we are on real system + if [ -z "$${IPKG_INSTROOT}" ]; then + fw4 -q reload || true + fi + exit 0 +endef + +define Package/pbr-iptables/postinst + #!/bin/sh + # check if we are on real system + if [ -z "$${IPKG_INSTROOT}" ]; then + echo -n "Installing rc.d symlink for pbr... " + /etc/init.d/pbr enable && echo "OK" || echo "FAIL" + fi + exit 0 +endef + +define Package/pbr-iptables/prerm + #!/bin/sh + # check if we are on real system + if [ -z "$${IPKG_INSTROOT}" ]; then + uci -q delete firewall.pbr || true + echo "Stopping pbr service... " + /etc/init.d/pbr stop || true + echo -n "Removing rc.d symlink for pbr... " + /etc/init.d/pbr disable && echo "OK" || echo "FAIL" + fi + exit 0 +endef + +define Package/pbr-netifd/postinst + #!/bin/sh + # check if we are on real system + if [ -z "$${IPKG_INSTROOT}" ]; then + echo -n "Installing rc.d symlink for pbr... " + /etc/init.d/pbr enable && echo "OK" || echo "FAIL" + # echo -n "Installing netifd support for pbr... " + # /etc/init.d/pbr netifd install && echo "OK" || echo "FAIL" + # echo -n "Restarting network... " + # /etc/init.d/network restart && echo "OK" || echo "FAIL" + fi + exit 0 +endef + +define Package/pbr-netifd/prerm + #!/bin/sh + # check if we are on real system + if [ -z "$${IPKG_INSTROOT}" ]; then + uci -q delete firewall.pbr || true + echo "Stopping pbr service... " + /etc/init.d/pbr stop || true + # echo -n "Removing netifd support for pbr... " + # /etc/init.d/pbr netifd remove && echo "OK" || echo "FAIL" + echo -n "Removing rc.d symlink for pbr... " + /etc/init.d/pbr disable && echo "OK" || echo "FAIL" + # echo -n "Restarting network... " + # /etc/init.d/network restart && echo "OK" || echo "FAIL" + fi + exit 0 +endef + +$(eval $(call BuildPackage,pbr)) +$(eval $(call BuildPackage,pbr-iptables)) +#$(eval $(call BuildPackage,pbr-netifd)) diff --git a/net/pbr/files/README.md b/net/pbr/files/README.md new file mode 100644 index 000000000..494a97c13 --- /dev/null +++ b/net/pbr/files/README.md @@ -0,0 +1,3 @@ +# README + +README 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 new file mode 100644 index 000000000..355fac205 --- /dev/null +++ b/net/pbr/files/etc/config/pbr @@ -0,0 +1,45 @@ +config pbr 'config' + option enabled '0' + option verbosity '2' + option strict_enforcement '1' + option resolver_set 'none' + option ipv6_enabled '0' + list ignored_interface 'vpnserver' + list ignored_interface 'wgserver' + option boot_timeout '30' + option rule_create_option 'add' + 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 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/config/pbr.iptables b/net/pbr/files/etc/config/pbr.iptables new file mode 100644 index 000000000..c62712644 --- /dev/null +++ b/net/pbr/files/etc/config/pbr.iptables @@ -0,0 +1,45 @@ +config pbr 'config' + option enabled '0' + option verbosity '2' + option strict_enforcement '1' + option resolver_set 'dnsmasq.ipset' + option ipv6_enabled '0' + list ignored_interface 'vpnserver' + list ignored_interface 'wgserver' + option boot_timeout '30' + option rule_create_option 'add' + 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 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/hotplug.d/firewall/70-pbr b/net/pbr/files/etc/hotplug.d/firewall/70-pbr new file mode 100755 index 000000000..c129006c5 --- /dev/null +++ b/net/pbr/files/etc/hotplug.d/firewall/70-pbr @@ -0,0 +1,6 @@ +#!/bin/sh +[ "$ACTION" = "reload" ] ||[ "$ACTION" = "restart" ] || exit 0 +if [ -x /etc/init.d/pbr ] && /etc/init.d/pbr enabled; then + logger -t "pbr" "Reloading pbr due to $ACTION of firewall" + /etc/init.d/pbr reload +fi diff --git a/net/pbr/files/etc/hotplug.d/iface/70-pbr b/net/pbr/files/etc/hotplug.d/iface/70-pbr new file mode 100644 index 000000000..172385a11 --- /dev/null +++ b/net/pbr/files/etc/hotplug.d/iface/70-pbr @@ -0,0 +1,8 @@ +#!/bin/sh +# shellcheck disable=SC1091,SC3060 +[ -s /etc/openwrt_release ] && . /etc/openwrt_release +[ "${DISTRIB_RELEASE//19.07}" = "$DISTRIB_RELEASE" ] && exit 0 +if [ -x /etc/init.d/pbr ] && /etc/init.d/pbr enabled; then + logger -t pbr "Reloading pbr $INTERFACE due to $ACTION of $INTERFACE ($DEVICE)" + /etc/init.d/pbr reload_interface "$INTERFACE" +fi diff --git a/net/pbr/files/etc/init.d/pbr.init b/net/pbr/files/etc/init.d/pbr.init new file mode 100755 index 000000000..cf807f433 --- /dev/null +++ b/net/pbr/files/etc/init.d/pbr.init @@ -0,0 +1,2394 @@ +#!/bin/sh /etc/rc.common +# Copyright 2020-2022 Stan Grishin (stangri@melmac.ca) +# shellcheck disable=SC1091,SC2018,SC2019,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 + +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 + +readonly PKG_VERSION='dev-test' +readonly packageName='pbr' +readonly serviceName="$packageName $PKG_VERSION" +readonly serviceTrapSignals='exit SIGHUP SIGQUIT SIGKILL' +readonly packageConfigFile="/etc/config/${packageName}" +readonly nftTempFile="/var/run/${packageName}.nft" +#readonly nftPermFile="/etc/nftables.d/table-post/30-pbr.nft" +readonly dnsmasqFile="/var/dnsmasq.d/${packageName}" +readonly sharedMemoryOutput="/dev/shm/$packageName-output" +readonly _OK_='\033[0;32m\xe2\x9c\x93\033[0m' +readonly _FAIL_='\033[0;31m\xe2\x9c\x97\033[0m' +readonly __OK__='\033[0;32m[\xe2\x9c\x93]\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' +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 aghConfigFile='/etc/adguardhome.yaml' +readonly aghIpsetFile="/var/run/${packageName}.adguardhome.ipsets" +# shellcheck disable=SC2155 +readonly nft="$(command -v nft)" +readonly nftTable="fw4" +readonly nftPrefix='pbr' +readonly chainsList='forward input output postrouting prerouting' + +# package config options +boot_timeout= +enabled= +fw_mask= +icmp_interface= +ignored_interface= +ipv6_enabled= +procd_boot_delay= +procd_reload_delay= +resolver_set= +rule_create_option= +secure_reload= +strict_enforcement= +supported_interface= +verbosity= +wan_ip_rules_priority= +wan_mark= + +# run-time +gatewaySummary= +errorSummary= +warningSummary= +wanIface4= +wanIface6= +ifaceMark= +ifaceTableID= +ifacePriority= +ifacesAll= +ifacesSupported= +wanGW4= +wanGW6= +serviceStartTrigger= +processPolicyError= +processPolicyWarning= +resolver_set_supported= +nftPrevParam4= +nftPrevParam6= + + +get_text() { + local r + case "$1" in + errorConfigValidation) r="Config ($packageConfigFile) validation failure!";; + errorNoIpFull) r="ip-full 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!";; + errorIpsetNameTooLong) r="The ipset name '%s' is longer than allowed 31 characters!";; + errorNftsetNameTooLong) r="The nft set name '%s' is longer than allowed 31 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!";; + errorPolicyUnknownInterface) r="Policy '%s' has an unknown interface!";; + errorPolicyProcess) 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!";; + ererrorUserFileSyntax) 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!";; + warningResolverNotSupported) r="Resolver set (${resolver_set}) is not supported on this system.";; + warningAGHVersionTooLow) r="Installed AdGuardHome (%s) doesn't support 'ipset_file' option.";; + warningPolicyProcess) r="%s";; + esac + echo "$r" +} + +version() { echo "$PKG_VERSION"; } +output_ok() { output 1 "$_OK_"; output 2 "$__OK__\\n"; } +output_okn() { output 1 "$_OK_\\n"; output 2 "$__OK__\\n"; } +output_fail() { s=1; output 1 "$_FAIL_"; output 2 "$__FAIL__\\n"; } +output_failn() { output 1 "$_FAIL_\\n"; output 2 "$__FAIL__\\n"; } +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" ]; } +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"; } +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; } +output() { +# Can take a single parameter (text) to be output at any verbosity +# Or target verbosity level and text to be output at specifc verbosity + local msg memmsg logmsg + verbosity="${verbosity:-2}" + if [ "$#" -ne 1 ]; then + if [ $((verbosity & $1)) -gt 0 ] || [ "$verbosity" = "$1" ]; then shift; else return 0; fi + fi + [ -t 1 ] && printf "%b" "$1" + msg="${1//$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 +} +is_present() { command -v "$1" >/dev/null 2>&1; } +is_installed() { [ -s "/usr/lib/opkg/info/${1}.control" ]; } +is_variant_installed() { [ "$(echo /usr/lib/opkg/info/"${1}"*.control)" != "/usr/lib/opkg/info/${1}*.control" ]; } +is_nft() { [ -x "$nft" ] && ! str_contains "$resolver_set" 'ipset' && "$nft" list chains inet | grep -q "${nftPrefix}_prerouting"; } +_build_ifaces_all() { ifacesAll="${ifacesAll}${1} "; } +_build_ifaces_supported() { is_supported_interface "$1" && ifacesSupported="${ifacesSupported}${1} "; } +pbr_find_iface() { + local iface i param="$2" + [ "$param" = 'wan6' ] || param='wan' + "network_find_${param}" iface + is_tunnel "$iface" && unset iface + if [ -z "$iface" ]; then + for i in $ifacesAll; do + if "is_${param}" "$i"; then break; else unset i; fi + done + fi + eval "$1"='${iface:-$i}' +} +pbr_get_gateway() { + 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")" +# fi + if [ -z "$gw" ] || [ "$gw" = '0.0.0.0' ]; then + gw="$($ip_full -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_full -6 a list dev "$dev" 2>/dev/null | grep inet6 | awk '{print $2}')" + fi + eval "$1"='$gw' +} +is_dslite() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:6}" = "dslite" ]; } +is_l2tp() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:4}" = "l2tp" ]; } +is_oc() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:11}" = "openconnect" ]; } +is_ovpn() { local dev; network_get_device dev "$1"; [ "${dev:0:3}" = "tun" ] || [ "${dev:0:3}" = "tap" ] || [ -f "/sys/devices/virtual/net/${dev}/tun_flags" ]; } +is_pptp() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:4}" = "pptp" ]; } +is_softether() { local dev; network_get_device dev "$1"; [ "${dev:0:4}" = "vpn_" ]; } +is_tor() { [ "$(str_to_lower "$1")" = "tor" ]; } +is_tor_running() { + local ret=0 + if [ -s "/etc/tor/torrc" ]; then + json_load "$(ubus call service list "{ 'name': 'tor' }")" + json_select 'tor'; json_select 'instances'; json_select 'instance1'; + json_get_var ret 'running'; json_cleanup + fi + if [ "$ret" = "0" ]; then return 1; else return 0; fi +} +is_wg() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:9}" = "wireguard" ]; } +is_tunnel() { is_dslite "$1" || is_l2tp "$1" || is_oc "$1" || is_ovpn "$1" || is_pptp "$1" || is_softether "$1" || is_tor "$1" || is_wg "$1"; } +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_ignored_interface() { str_contains_word "$ignored_interface" "$1"; } +is_supported_interface() { str_contains_word "$supported_interface" "$1" || { ! is_ignored_interface "$1" && { is_wan "$1" || is_wan6 "$1" || is_tunnel "$1"; }; } || is_ignore_target "$1"; } +is_ignore_target() { [ "$(str_to_lower "$1")" = 'ignore' ]; } +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_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_family_mismatch() { ( is_netmask "${1//!}" && is_ipv6 "${2//!}" ) || ( is_ipv6 "${1//!}" && is_netmask "${2//!}" ); } +is_ipv6_link_local() { [ "${1:0:4}" = "fe80" ]; } +is_ipv6_unique_local() { [ "${1:0:2}" = "fc" ] || [ "${1:0:2}" = "fd" ]; } +is_ipv6_global() { [ "${1:0:4}" = "2001" ]; } +# is_ipv6_global() { is_ipv6 "$1" && ! is_ipv6_link_local "$1" && ! is_ipv6_link_local "$1"; } +is_list() { str_contains "$1" "," || str_contains "$1" " "; } +is_netmask() { local ip="${1%/*}"; [ "$ip" != "$1" ] && is_ipv4 "$ip"; } +is_domain() { str_contains "$1" '[a-zA-Z]'; } +is_phys_dev() { [ "${1:0:1}" = "@" ] && ip l show | grep -E -q "^\\d+\\W+${1: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; } +is_default_dev() { [ "$1" = "$($ip_full -4 r | grep -m1 'dev' | grep -Eso 'dev [^ ]*' | awk '{print $2}')" ]; } +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_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)" ]; } +# atomic +# is_service_running_nft() { [ -x "$nft" ] && [ -s "$nftPermFile" ]; } +is_service_running() { if is_nft; then is_service_running_nft; else is_service_running_iptables; fi; } +is_netifd_table() { local iface="$1"; [ "$(uci -q get "network.${iface}.ip4table")" = "${packageName}_${iface%6}" ]; } +get_rt_tables_id() { grep "${packageName}_${iface}" /etc/iproute2/rt_tables | awk '{print $1;}'; } +get_rt_tables_next_id() { echo "$(($(sort -r -n /etc/iproute2/rt_tables | grep -o -E -m 1 "^[0-9]+")+1))"; } +_check_config() { local en; config_get_bool en "$1" 'enabled' 1; [ "$en" -gt 0 ] && _cfg_enabled=0; } +is_config_enabled() { + local cfg="$1" _cfg_enabled=1 + [ -n "$1" ] || return 1 + config_load "$packageName" + config_foreach _check_config "$cfg" + return "$_cfg_enabled" +} +# shellcheck disable=SC2016 +resolveip_to_ipt() { resolveip "$@" | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d'; } +# 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 -ne "$value"; } +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 }'; } +is_ipset_type_supported() { ipset help hash:"$1" >/dev/null 2>&1; } +ubus_get_status() { ubus call service list "{ 'name': '$packageName' }" | jsonfilter -e "@.${packageName}.instances.main.data.status.${1}"; } +ubus_get_iface() { ubus call service list "{ 'name': '$packageName' }" | jsonfilter -e "@.${packageName}.instances.main.data.interfaces[@.name='${1}']${2:+.$2}"; } + +load_package_config() { + config_load "$packageName" + config_get boot_timeout 'config' 'boot_timeout' '30' + 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 procd_boot_delay 'config' 'procd_boot_delay' '0' + config_get resolver_set 'config' 'resolver_set' + config_get rule_create_option 'config' 'rule_create_option' 'add' + config_get_bool secure_reload 'config' 'secure_reload' '1' + config_get_bool strict_enforcement 'config' 'strict_enforcement' '0' + config_get supported_interface 'config' 'supported_interface' + config_get verbosity 'config' 'verbosity' '2' + 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}" + [ -n "$ipv6_enabled" ] && [ "$ipv6_enabled" -eq 0 ] && unset ipv6_enabled + . /lib/functions/network.sh + . /usr/share/libubox/jshn.sh + mkdir -p "${dnsmasqFile%/*}" + if is_nft; 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 +} + +load_environment() { + local param="$1" validation_result="$2" + load_package_config + + if [ "$param" = 'on_start' ]; then + 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 [ "$enabled" -eq 0 ]; then + state add 'errorSummary' 'errorServiceDisabled' + return 1 + fi + if [ ! -x "$ip_full" ]; then + state add 'errorSummary' 'errorNoIpFull' + return 1 + fi + resolver 'check_support' + fi + + load_network "$param" +} + +load_network() { + config_load 'network' + [ -z "$ifacesAll" ] && config_foreach _build_ifaces_all 'interface' + [ -z "$ifacesSupported" ] && config_foreach _build_ifaces_supported 'interface' + pbr_find_iface wanIface4 'wan' + [ -n "$ipv6_enabled" ] && pbr_find_iface wanIface6 'wan6' + [ -n "$wanIface4" ] && network_get_gateway wanGW4 "$wanIface4" + [ -n "$wanIface6" ] && network_get_gateway6 wanGW6 "$wanIface6" + wanGW="${wanGW4:-$wanGW6}" +} + +is_wan_up() { + local sleepCount='1' + load_network + while [ -z "$wanGW" ] ; do + load_network + if [ $((sleepCount)) -gt $((boot_timeout)) ] || [ -n "$wanGW" ]; then break; fi + output "$serviceName waiting for wan 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 + [ -x "$ip6tables" ] && "$ip6tables" $d >/dev/null 2>&1 + 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" comment "$comment" && ipv4_error=0 + ips6 -q -! add "$ipset6" 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 + echo "ipset=/${param}/${ipset4}${ipset6:+,$ipset6} # $comment" >> "$dnsmasqFile" && 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 && ipv4_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 + return $ipv4_error || $ipv6_error +} + +# atomic +#nfta() { echo "$@" >> "$nftTempFile"; } +#nfta4() { echo "$@" >> "$nftTempFile"; } +#nfta6() { [ -z "$ipv6_enabled" ] || echo "$@" >> "$nftTempFile"; } +#nft() { nfta "$@"; [ -x "$nft" ] && "$nft" "$@" >/dev/null 2>&1; } +#nft4() { nfta "$@"; [ -x "$nft" ] && "$nft" "$@" >/dev/null 2>&1; } +#nft6() { nfta "$@"; [ -n "$ipv6_enabled" ] || return 0; [ -x "$nft" ] && [ -n "$*" ] && "$nft" "$@" >/dev/null 2>&1; } +nft() { [ -x "$nft" ] && "$nft" "$@" >/dev/null 2>&1; } +nft4() { [ -x "$nft" ] && "$nft" "$@" >/dev/null 2>&1; } +nft6() { [ -n "$ipv6_enabled" ] || return 0; [ -x "$nft" ] && [ -n "$*" ] && "$nft" "$@" >/dev/null 2>&1; } +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_netmask "$param" || is_ipv4 "$param" || is_ipv6 "$param" \ + || 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 + else +# elif is_domain "$param"; then + 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")" + nft4 add element inet "$nftTable" "$nftset4" "{ $param4 }" && ipv4_error=0 + nft6 add element inet "$nftTable" "$nftset6" "{ $param6 }" && ipv6_error=0 + fi + ;; + add_dnsmasq_element) + [ -n "$ipv6_enabled" ] || unset nftset6 + echo "nftset=/${param}/4#inet#${nftTable}#${nftset4}${nftset6:+,6#inet#${nftTable}#$nftset6} # $comment" >> "$dnsmasqFile" && ipv4_error=0 + ;; + create) + case "$type" in + ip|net) + nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; flags interval; auto-merge; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; flags interval; auto-merge; comment \"$comment\"; }" && ipv6_error=0 + ;; + mac) + nft4 add set inet "$nftTable" "$nftset4" "{ type ether_addr; flags interval; auto-merge; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ether_addr; flags interval; auto-merge; comment \"$comment\"; }" && ipv6_error=0 + ;; + esac + ;; + create_dnsmasq_set) + nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; flags interval; auto-merge; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; flags interval; auto-merge; comment \"$comment\"; }" && ipv6_error=0 + ;; + create_user_set) + case "$type" in + ip|net) + nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; flags interval; auto-merge; policy memory; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; flags interval; auto-merge; policy memory; comment \"$comment\"; }" && ipv6_error=0 + case "$target" in + dst) + nft add rule inet "$nftTable" "${nftPrefix}_prerouting" ip daddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 + nft add rule inet "$nftTable" "${nftPrefix}_prerouting" ip daddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 + ;; + src) + nft add rule inet "$nftTable" "${nftPrefix}_prerouting" ip saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 + nft add rule inet "$nftTable" "${nftPrefix}_prerouting" ip saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 + ;; + esac + ;; + mac) + nft4 add set inet "$nftTable" "$nftset4" "{ type ether_addr; flags interval; auto-merge; policy memory; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ether_addr; flags interval; auto-merge; policy memory; comment \"$comment\"; }" && ipv6_error=0 + nft add rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 + nft add rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 + ;; + esac + ;; + delete|destroy) + nft delete set inet "$nftTable" "$nftset4" && ipv4_error=0 + nft delete set inet "$nftTable" "$nftset6" && ipv6_error=0 + ;; + delete_user_set) + nft delete set inet "$nftTable" "$nftset4" && ipv4_error=0 + nft delete set inet "$nftTable" "$nftset6" && ipv6_error=0 + case "$type" in + ip|net) + case "$target" in + dst) + nft delete rule inet "$nftTable" "${nftPrefix}_prerouting" ip daddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 + nft delete rule inet "$nftTable" "${nftPrefix}_prerouting" ip daddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 + ;; + src) + nft delete rule inet "$nftTable" "${nftPrefix}_prerouting" ip saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 + nft delete rule inet "$nftTable" "${nftPrefix}_prerouting" ip saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 + ;; + esac + ;; + mac) + nft delete rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset4}" goto "${nftPrefix}_mark_${mark}" && ipv4_error=0 + nft delete rule inet "$nftTable" "${nftPrefix}_prerouting" ether saddr "@${nftset6}" goto "${nftPrefix}_mark_${mark}" && ipv6_error=0 + ;; + esac + ;; + flush|flush_user_set) + nft flush set inet "$nftTable" "$nftset4" && ipv4_error=0 + nft flush set inet "$nftTable" "$nftset6" && ipv6_error=0 + ;; + esac +# nft6 returns true if IPv6 support is not enabled + [ -z "$ipv6_enabled" ] && ipv6_error='1' + return $ipv4_error || $ipv6_error +} + +cleanup_dnsmasq() { [ -s "$dnsmasqFile" ] && resolverStoredHash="$(md5sum $dnsmasqFile | awk '{ print $1; }')" && rm "$dnsmasqFile" >/dev/null 2>&1; } +cleanup_main_chains() { + local i + for i in $chainsList; do + i="$(str_to_lower "$i")" + nft 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 + for i in $(get_mark_nft_chains); do + nft flush chain inet "$nftTable" "$i" + nft 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 flush set inet "$nftTable" "$i" + nft 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) + 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 +# url="${c##*|}" +# c="${c%|*}" + 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 <<EOF +$(eval echo "\$$param" | tr \# \\n) +EOF + fi + json_close_array + ;; + print) + [ -z "$(eval echo "\$$param")" ] && return 0 + case "$param" in + errorSummary) + label="${_ERROR_}:";; + warningSummary) + label="${_WARNING_}:";; + esac + while read -r line; do + if str_contains "$line" ' '; then + error_id="${line% *}" + error_extra="${line#* }" + printf "%b $(get_text "$error_id")\\n" "$label" "$error_extra" + else + error_id="$line" + printf "%b $(get_text "$error_id")\\n" "$label" + fi + done <<EOF +$(eval echo "\$$param" | tr \# \\n) +EOF + ;; + set) + eval "$param"='${value}${extras:+ $extras}' + ;; + esac +} + +resolver() { + local agh_version + local param="$1" + shift + + if [ "$param" = 'cleanup_all' ]; then + sed -i "/ipset_file: ${aghIpsetFile}/d" "$aghConfigFile" >/dev/null 2>&1 + rm -f "$aghIpsetFile" + rm -f "$dnsmasqFile" + 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|')" + 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) + [ -n "$resolver_set_supported" ] && rm -f "$dnsmasqFile";; + configure) + [ -n "$resolver_set_supported" ] && mkdir -p "${dnsmasqFile%/*}";; + 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) + [ -n "$resolver_set_supported" ] && rm -f "$dnsmasqFile";; + configure) + [ -n "$resolver_set_supported" ] && mkdir -p "${dnsmasqFile%/*}";; + 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() { +# verbosity=0 + 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 + [ "$secure_reload" -ne 0 ] || 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 'lan' + network_get_physdev wan_device 'wan' + if is_nft; then + nft add chain inet "$nftTable" "${nftPrefix}_killswitch" '{ type filter hook forward priority 0; policy accept; }' || s=1 + nft add rule inet "$nftTable" "${nftPrefix}_killswitch" oifname "$wan_device" ip saddr "$lan_subnet" counter reject || s=1 +# nft add rule inet "$nftTable" "${nftPrefix}_killswitch" oifname '$wan_devices' ip saddr '$lan_subnet' counter reject || s=1 + else + ipt -N "${iptPrefix}_KILLSWITCH" || s=1 + ipt -A "${iptPrefix}_KILLSWITCH" -s "$lan_subnet" -o "$wan_device" -j REJECT || s=1 + ipt -I FORWARD -j "${iptPrefix}_KILLSWITCH" || s=1 + fi + if [ "$s" -eq 0 ]; then + output_okn + else + output_failn + fi + ;; + remove) + if [ "$secure_reload" -ne 0 ]; then + output 3 'Deactivating traffic killswitch ' + fi + if is_nft; then + nft flush chain inet "$nftTable" "${nftPrefix}_killswitch" || s=1 + nft 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 [ "$secure_reload" -ne 0 ]; then + if [ "$s" -eq 0 ]; then + output_okn + else + output_failn + fi + fi +# shellcheck disable=SC2086 + trap - $serviceTrapSignals + ;; + esac +} + +policy_routing_tor() { if is_nft; then policy_routing_tor_nft "$@"; else policy_routing_tor_iptables "$@"; fi; } +policy_routing_tor_iptables() { + local comment="$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_upper "$8")" + chain="${chain:-PREROUTING}" + if [ -n "${src_addr}${src_port}${dest_port}" ]; then + processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'src_addr', 'src_port' and 'dest_port' for policy '$comment'\\n" + fi + if [ -n "$proto" ] && [ "$proto" != "all" ]; then + processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'proto' or set 'proto' to 'all' for policy '$comment'\\n" + fi + if [ "$chain" != "PREROUTING" ]; then + processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '$comment'\\n" + fi + resolver 'add_resolver_element' "$iface" 'dst' 'ip' '' "${comment}: $dest_addr" "$dest_addr" || \ + processPolicyError="${processPolicyError}${_ERROR_}: resolver 'add_resolver_element' '$iface' 'dst' 'ip' '${comment}: $dest_addr' '$dest_addr'\\n" + return 0 +} +policy_routing_tor_nft() { + local comment="$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}" + if [ -n "${src_addr}${src_port}${dest_port}" ]; then + processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'src_addr', 'src_port' and 'dest_port' for policy '$comment'\\n" + fi + if [ -n "$proto" ] && [ "$proto" != "all" ]; then + processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'proto' or set 'proto' to 'all' for policy '$comment'\\n" + fi + if [ "$chain" != "prerouting" ]; then + processPolicyWarning="${processPolicyWarning}${_WARNING_}: Please unset 'chain' or set 'chain' to 'prerouting' for policy '$comment'\\n" + fi + resolver 'add_resolver_element' "$iface" 'dst' 'ip' '' "${comment}: $dest_addr" "$dest_addr" || \ + processPolicyError="${processPolicyError}${_ERROR_}: resolver 'add_resolver_element' '$iface' 'dst' 'ip' '${comment}: $dest_addr' '$dest_addr'\\n" + return 0 +} + +policy_routing() { if is_nft; then policy_routing_nft "$@"; else policy_routing_iptables "$@"; fi; } +policy_routing_iptables() { + local mark param4 param6 i negation value dest 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="${processPolicyError}${_ERROR_}: Skipping IPv6 policy '$name' as IPv6 support is disabled\\n" + return 1 + fi + + if [ -n "$mark" ]; then + dest="-g ${iptPrefix}_MARK_${mark}" + elif [ "$iface" = "ignore" ]; then + dest="-j RETURN" + else + processPolicyError="${processPolicyError}${_ERROR_}: Unknown fw_mark for ${iface}\\n" + return 1 + fi + + if [ -z "$proto" ]; then + if [ -n "$lport" ] || [ -n "$rport" ]; then + proto='tcp udp' + else + proto='all' + fi + fi + + if is_family_mismatch "$laddr" "$raddr"; then + processPolicyError="${processPolicyError}${_ERROR_}: Mismatched IP family between '$laddr' and '$raddr' in policy '$name'\\n" + return 1 + fi + + for i in $proto; do + if [ "$i" = 'all' ]; then + param4="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest" + param6="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest" + elif ! is_supported_protocol "$i"; then + processPolicyError="${processPolicyError}${_ERROR_}: Unknown protocol '$i' in policy '$name'\\n" + return 1 + else + param4="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest -p $i" + param6="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest -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 -m physdev --physdev-in ${value:1}" + param6="$param6 $negation -m physdev --physdev-in ${value:1}" + elif is_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 --match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" + param6="$param6 -m set $negation --match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" + else + param4="$param4 $negation -s $value" + param6="$param6 $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 --match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" + param6="$param6 -m set $negation --match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" + else + param4="$param4 -m mac $negation --mac-source $value" + param6="$param6 -m mac $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 --match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" + param6="$param6 -m set $negation --match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" + else + param4="$param4 $negation -s $(resolveip_to_ipt -4 "$value")" + param6="$param6 $negation -s $(resolveip_to_ipt -6 "$value")" + 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 --sport ${value//-/:}" + param6="$param6 -m multiport $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_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 --match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" + param6="$param6 -m set $negation --match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" + else + param4="$param4 $negation -d $value" + param6="$param6 $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 --match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" + param6="$param6 -m set $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 --match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" + param6="$param6 -m set $negation --match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" + else + param4="$param4 $negation -d $(resolveip_to_ipt -4 "$value")" + param6="$param6 $negation -d $(resolveip_to_ipt -6 "$value")" + 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 --match-set ${ipsPrefix}_${iface}_4_${target}_${type}_${uid} $target" + param6="$param6 -m set $negation --match-set ${ipsPrefix}_${iface}_6_${target}_${type}_${uid} $target" + else + param4="$param4 $negation -d $value" + param6="$param6 $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 --dport ${value//-/:}" + param6="$param6 -m multiport $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 + +# ipt6 returns true if IPv6 support is not enabled + [ -z "$ipv6_enabled" ] && ipv6_error='1' + if [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then + if [ -n "$ipv6_enabled" ]; then + processPolicyError="${processPolicyError}${_ERROR_}: Policy insertion failed for both IPv4 and IPv6!\\n" + processPolicyError="${processPolicyError}${_ERROR_}: iptables $param4\\n" + processPolicyError="${processPolicyError}${_ERROR_}: iptables $param6\\n" + else + processPolicyError="${processPolicyError}${_ERROR_}: iptables $param4\\n" + fi + fi + + done +} +policy_routing_nft() { + local mark param4 param6 i negation value dest nftInsertOption='add' + local ip4Flag='ip' ip6Flag='ip6' + 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_lower "$8")" + chain="${chain:-prerouting}" + mark=$(eval echo "\$mark_${iface//-/_}") + + if [ -z "$ipv6_enabled" ] && { is_ipv6 "$src_addr" || is_ipv6 "$dest_addr"; }; then + processPolicyError="${processPolicyError}${_ERROR_}: Skipping IPv6 policy '$name' as IPv6 support is disabled\\n" + return 1 + fi + + if [ -n "$mark" ]; then + dest="goto ${nftPrefix}_mark_${mark}" + elif [ "$iface" = "ignore" ]; then + dest="return" + else + processPolicyError="${processPolicyError}${_ERROR_}: Unknown packet mark for ${iface}\\n" + return 1 + fi + + if is_family_mismatch "$src_addr" "$dest_addr"; then + processPolicyError="${processPolicyError}${_ERROR_}: Mismatched IP family between '$src_addr' and '$dest_addr' in policy '$name'\\n" + return 1 + fi + + if [ -n "$proto" ] && ! is_supported_protocol "$proto"; then + processPolicyError="${processPolicyError}${_ERROR_}: Unknown protocol '$i' in policy '$name'\\n" + 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 iifname $negation ${value:1}" + param6="$param6 iifname $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 ether saddr $negation @${nftPrefix}_${iface}_4_${target}_${type}_${uid}" + param6="$param6 ether saddr $negation @${nftPrefix}_${iface}_6_${target}_${type}_${uid}" + else + param4="$param4 ether saddr $negation $value" + param6="$param6 ether saddr $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 $ip4Flag saddr $negation @${nftPrefix}_${iface}_4_${target}_${type}_${uid}" + param6="$param6 $ip6Flag saddr $negation @${nftPrefix}_${iface}_6_${target}_${type}_${uid}" + else + param4="$param4 $ip4Flag saddr $negation $value" + param6="$param6 $ip6Flag saddr $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 oifname $negation ${value:1}" + param6="$param6 oifname $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 $ip4Flag daddr $negation @${nftPrefix}_${iface}_4_${target}_${type}_${uid}" + param6="$param6 $ip6Flag daddr $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 $ip4Flag daddr $negation @${nftPrefix}_${iface}_4_${target}_${type}_${uid}" + param6="$param6 $ip6Flag daddr $negation @${nftPrefix}_${iface}_6_${target}_${type}_${uid}" + else + param4="$param4 $ip4Flag daddr $negation {$(resolveip_to_nftset4 "$value")}" + param6="$param6 $ip6Flag daddr $negation {$(resolveip_to_nftset6 "$value")}" + 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 $ip4Flag daddr $negation @${nftPrefix}_${iface}_4_${target}_${type}_${uid}" + param6="$param6 $ip6Flag daddr $negation @${nftPrefix}_${iface}_6_${target}_${type}_${uid}" + else + param4="$param4 $ip4Flag daddr $negation $value" + param6="$param6 $ip6Flag daddr $negation $value" + fi + fi + fi + + if [ -n "${src_port}${dest_port}" ]; then + proto="${proto:-tcp}" + 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 ${proto:+$proto }sport $negation {$(ports_to_nftset "$value")}" + param6="$param6 ${proto:+$proto }sport $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 ${proto:+$proto }dport $negation {$(ports_to_nftset "$value")}" + param6="$param6 ${proto:+$proto }dport $negation {$(ports_to_nftset "$value")}" + fi + + param4="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} $param4 $dest comment \"$name\"" + param6="$nftInsertOption rule inet $nftTable ${nftPrefix}_${chain} $param6 $dest comment \"$name\"" + + local ipv4_error='0' ipv6_error='0' + if [ "$nftPrevParam4" != "$param4" ]; then + nft4 "$param4" || ipv4_error='1' + nftPrevParam4="$param4" + fi + if [ "$nftPrevParam6" != "$param6" ]; then + nft6 "$param6" || ipv6_error='1' + nftPrevParam6="$param6" + fi + +# nft6 returns true if IPv6 support is not enabled + [ -z "$ipv6_enabled" ] && ipv6_error='1' + if [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then + if [ -n "$ipv6_enabled" ]; then + processPolicyError="${processPolicyError}${_ERROR_}: Policy insertion failed for both IPv4 and IPv6!\\n" + processPolicyError="${processPolicyError}${_ERROR_}: nft '$param4'\\n" + processPolicyError="${processPolicyError}${_ERROR_}: nft '$param6'\\n" + else + processPolicyError="${processPolicyError}${_ERROR_}: nft '$param4'\\n" + fi + fi +} + +policy_process() { + local i j uid="$9" + if [ -z "$uid" ]; then # first non-recursive call + [ "$enabled" -gt 0 ] || return 0 + unset processPolicyWarning + unset processPolicyError + uid="$1" + if is_nft; 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//,\!/ !}"; + if is_nft; then + nftset 'flush' "$interface" "dst" "ip" "$uid" + nftset 'flush' "$interface" "src" "ip" "$uid" + nftset 'flush' "$interface" "src" "mac" "$uid" + else + ips 'flush' "$interface" "dst" "ip" "$uid" + ips 'flush' "$interface" "src" "ip" "$uid" + ips 'flush' "$interface" "src" "mac" "$uid" + fi + policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid" + if [ -n "$processPolicyWarning" ]; then + state add 'warningSummary' 'warningPolicyProcess' "$processPolicyWarning" + fi + if [ -n "$processPolicyError" ]; then + output_fail + state add 'errorSummary' 'errorPolicyProcess' "$processPolicyError" + 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 is_tor "$interface"; then + policy_routing_tor "$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" + fi + fi + fi +} + +interface_process_tor() { if is_nft; then interface_process_tor_nft "$@"; else interface_process_tor_iptables "$@"; fi; } +interface_process_tor_iptables() { + local s=0 iface="$1" action="$2" + local displayText set_name4 set_name6 + local dnsPort trafficPort + case "$action" in + reload) + displayText="${iface}/53->${dnsPort}/80,443->${trafficPort}" + gatewaySummary="${gatewaySummary}${displayText}\\n" + ;; + destroy) + for i in $chainsList; do + i="$(str_to_upper "$i")" + ipt -t nat -D "${i}" -m mark --mark "0x0/${fw_mask}" -j "${nftPrefix}_${i}" + ipt -t nat -F "${nftPrefix}_${i}"; ipt -t nat -X "${nftPrefix}_${i}"; + done + ;; + create) + output 2 "Creating TOR redirects " + dnsPort="$(grep -m1 DNSPort /etc/tor/torrc | awk -F: '{print $2}')" + trafficPort="$(grep -m1 TransPort /etc/tor/torrc | awk -F: '{print $2}')" + dnsPort="${dnsPort:-9053}"; trafficPort="${trafficPort:-9040}"; + for i in $chainsList; do + ipt -t nat -N "${nftPrefix}_${i}" + ipt -t nat -A "$i" -m mark --mark "0x0/${fw_mask}" -j "${nftPrefix}_${i}" + done + if resolver 'create_resolver_set' "$iface" 'dst' 'ip' && ips 'flush' "$iface" 'dst' 'ip'; then + set_name4="${ipsPrefix}_${iface}_4_dst_ip" + for i in $chainsList; do + i="$(str_to_lower "$i")" + ipt -t nat -I "${nftPrefix}_${i}" -p udp -m udp --dport 53 -m set --match-set "${set_name4}" dst -j REDIRECT --to-ports "$dnsPort" -m comment --comment "TorDNS-UDP" || s=1 + ipt -t nat -I "${nftPrefix}_${i}" -p tcp -m tcp --dport 80 -m set --match-set "${set_name4}" dst -j REDIRECT --to-ports "$trafficPort" -m comment --comment "TorHTTP-TCP" || s=1 + ipt -t nat -I "${nftPrefix}_${i}" -p udp -m udp --dport 80 -m set --match-set "${set_name4}" dst -j REDIRECT --to-ports "$trafficPort" -m comment --comment "TorHTTP-UDP" || s=1 + ipt -t nat -I "${nftPrefix}_${i}" -p tcp -m tcp --dport 443 -m set --match-set "${set_name4}" dst -j REDIRECT --to-ports "$trafficPort" -m comment --comment "TorHTTPS-TCP" || s=1 + ipt -t nat -I "${nftPrefix}_${i}" -p udp -m udp --dport 443 -m set --match-set "${set_name4}" dst -j REDIRECT --to-ports "$trafficPort" -m comment --comment "TorHTTPS-UDP" || s=1 + done + else + s=1 + fi + displayText="${iface}/53->${dnsPort}/80,443->${trafficPort}" + if [ "$s" -eq 0 ]; then + gatewaySummary="${gatewaySummary}${displayText}\\n" + output_ok + else + state add 'errorSummary' 'errorFailedSetup' "$displayText" + output_fail + fi + ;; + esac + return $s +} +interface_process_tor_nft() { + local s=0 iface="$1" action="$2" + local displayText set_name4 set_name6 + local dnsPort trafficPort + case "$action" in + reload) + displayText="${iface}/53->${dnsPort}/80,443->${trafficPort}" + gatewaySummary="${gatewaySummary}${displayText}\\n" + ;; + destroy) + ;; + create) + output 2 "Creating TOR redirects " + dnsPort="$(grep -m1 DNSPort /etc/tor/torrc | awk -F: '{print $2}')" + trafficPort="$(grep -m1 TransPort /etc/tor/torrc | awk -F: '{print $2}')" + dnsPort="${dnsPort:-9053}"; trafficPort="${trafficPort:-9040}"; + if resolver 'create_resolver_set' "$iface" 'dst' 'ip' && nftset 'flush' "$iface" 'dst' 'ip'; then + set_name4="${nftPrefix}_${iface}_4_dst_ip" + set_name6="${nftPrefix}_${iface}_6_dst_ip" + nft meta nfproto ipv4 udp daddr "@${set_name4}" dport 53 counter redirect to :"$dnsPort" comment "Tor-DNS-UDP-ipv4" || s=1 + nft meta nfproto ipv4 tcp daddr "@${set_name4}" dport 80 counter redirect to :"$trafficPort" comment "Tor-HTTP-TCP-ipv4" || s=1 + nft meta nfproto ipv4 udp daddr "@${set_name4}" dport 80 counter redirect to :"$trafficPort" comment "Tor-HTTP-UDP-ipv4" || s=1 + nft meta nfproto ipv4 tcp daddr "@${set_name4}" dport 443 counter redirect to :"$trafficPort" comment "Tor-HTTPS-TCP-ipv4" || s=1 + nft meta nfproto ipv4 udp daddr "@${set_name4}" dport 443 counter redirect to :"$trafficPort" comment "Tor-HTTPS-UDP-ipv4" || s=1 + nft6 meta nfproto ipv6 udp daddr "@${set_name6}" dport 53 counter redirect to :"$dnsPort" comment "Tor-DNS-UDP-ipv6" || s=1 + nft6 meta nfproto ipv6 tcp daddr "@${set_name6}" dport 80 counter redirect to :"$trafficPort" comment "Tor-HTTP-TCP-ipv6" || s=1 + nft6 meta nfproto ipv6 udp daddr "@${set_name6}" dport 80 counter redirect to :"$trafficPort" comment "Tor-HTTP-UDP-ipv6" || s=1 + nft6 meta nfproto ipv6 tcp daddr "@${set_name6}" dport 443 counter redirect to :"$trafficPort" comment "Tor-HTTPS-TCP-ipv6" || s=1 + nft6 meta nfproto ipv6 udp daddr "@${set_name6}" dport 443 counter redirect to :"$trafficPort" comment "Tor-HTTPS-UDP-ipv6" || s=1 + else + s=1 + fi + displayText="${iface}/53->${dnsPort}/80,443->${trafficPort}" + if [ "$s" -eq 0 ]; then + gatewaySummary="${gatewaySummary}${displayText}\\n" + output_ok + else + state add 'errorSummary' 'errorFailedSetup' "$displayText" + output_fail + fi + ;; + esac + return $s +} + +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 + return 1 + fi + case "$action" in + create) + if is_netifd_table "$iface"; then + ipv4_error=0 + $ip_full -4 rule del fwmark "${mark}/${fw_mask}" table "$tid" >/dev/null 2>&1 + $ip_full -4 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 + if is_nft; then + nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1 + nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} counter mark set mark and ${fw_maskXor} xor ${mark}" || ipv4_error=1 + 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_full -6 rule del fwmark "${mark}/${fw_mask}" table "$tid" >/dev/null 2>&1 + $ip_full -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1 + fi + else + sed -i "/${ipTablePrefix}_${iface}/d" /etc/iproute2/rt_tables + $ip_full -4 rule del fwmark "${mark}/${fw_mask}" table "$tid" >/dev/null 2>&1 + $ip_full -4 route flush table "$tid" >/dev/null 2>&1 + if [ -n "$gw4" ] || [ "$strict_enforcement" -ne 0 ]; then + ipv4_error=0 + echo "$tid ${ipTablePrefix}_${iface}" >> /etc/iproute2/rt_tables + if [ -z "$gw4" ]; then + $ip_full -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1 + else + $ip_full -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 + $ip_full -4 route add $i table "$tid" >/dev/null 2>&1 || ipv4_error=1 + fi + done << EOF + $($ip_full -4 route list table main) +EOF + $ip_full -4 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 + if is_nft; then + nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" || ipv4_error=1 + nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} counter mark set mark and ${fw_maskXor} xor ${mark}" || ipv4_error=1 + 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_full -6 rule del fwmark "${mark}/${fw_mask}" table "$tid" >/dev/null 2>&1 + $ip_full -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 + $ip_full -6 route add unreachable default table "$tid" || ipv6_error=1 + elif $ip_full -6 route list table main | grep -q " dev $dev6 "; then + while read -r i; do + i="$(echo "$i" | sed 's/ linkdown$//')" + i="$(echo "$i" | sed 's/ onlink$//')" + $ip_full -6 route add "$i" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + done << EOF + $($ip_full -6 route list table main | grep " dev $dev6 ") +EOF + else + $ip_full -6 route add "$($ip_full -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + $ip_full -6 route add default dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + fi + fi + $ip_full -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1 + fi + fi + if [ "$ipv4_error" -eq 0 ] || [ "$ipv6_error" -eq 0 ]; then + dscp="$(uci -q get "${packageName}".config."${iface}"_dscp)" + if is_nft; then + if [ "${dscp:-0}" -ge 1 ] && [ "${dscp:-0}" -le 63 ]; then + nft add rule inet "$nftTable" "${nftPrefix}_prerouting ip dscp ${dscp} goto ${nftPrefix}_mark_${mark}" || s=1 + fi + if [ "$iface" = "$icmp_interface" ]; then + nft add rule inet "$nftTable" "${nftPrefix}_output ip protocol icmp goto ${nftPrefix}_mark_${mark}" || s=1 + 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) + [ -z "$createUserSets" ] && return 0; + if is_nft; 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" 'src' 'ip' 'user' '' "$mark" || s=1 + ips 'create_user_set' "$iface" 'dst' 'net' '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_full rule del fwmark "${mark}/${fw_mask}" table "$tid" >/dev/null 2>&1 + if ! is_netifd_table "$iface"; then + $ip_full route flush table "$tid" >/dev/null 2>&1 + sed -i "/${ipTablePrefix}_${iface}/d" /etc/iproute2/rt_tables + fi + return "$s" + ;; + reload_interface) + is_netifd_table "$iface" && return 0; + ipv4_error=0 + $ip_full -4 rule del fwmark "${mark}/${fw_mask}" table "$tid" >/dev/null 2>&1 + $ip_full -4 route flush table "$tid" >/dev/null 2>&1 + if [ -n "$gw4" ] || [ "$strict_enforcement" -ne 0 ]; then + if [ -z "$gw4" ]; then + $ip_full -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1 + else + $ip_full -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1 + fi + $ip_full rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 + fi + if [ -n "$ipv6_enabled" ]; then + ipv6_error=0 + $ip_full -6 rule del fwmark "${mark}/${fw_mask}" table "$tid" >/dev/null 2>&1 + $ip_full -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 + $ip_full -6 route add unreachable default table "$tid" || ipv6_error=1 + elif $ip_full -6 route list table main | grep -q " dev $dev6 "; then + while read -r i; do + $ip_full -6 route add "$i" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + done << EOF + $($ip_full -6 route list table main | grep " dev $dev6 ") +EOF + else + $ip_full -6 route add "$($ip_full -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + $ip_full -6 route add default dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + fi + fi + $ip_full -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 true + else + json_add_boolean default false + 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" + return 0 + fi + + is_supported_interface "$iface" || return 0 + is_wan6 "$iface" && return 0 + [ $((ifaceMark)) -gt $((fw_mask)) ] && return 1 + + network_get_device dev "$iface" + if is_wan "$iface" && [ -n "$wanIface6" ] && str_contains "$wanIface6" "$iface"; then + network_get_device dev6 "$wanIface6" + fi + + [ -z "$dev6" ] && dev6="$dev" + [ -z "$ifaceMark" ] && ifaceMark="$(printf '0x%06x' "$wan_mark")" + [ -z "$ifacePriority" ] && ifacePriority="$wan_ip_rules_priority" + + ifaceTableID="$(get_rt_tables_id "$iface")" + [ -z "$ifaceTableID" ] && ifaceTableID="$(get_rt_tables_next_id)" + eval "mark_${iface//-/_}"='$ifaceMark' + eval "tid_${iface//-/_}"='$ifaceTableID' + pbr_get_gateway gw4 "$iface" "$dev" + pbr_get_gateway6 gw6 "$iface" "$dev6" + dispGw4="${gw4:-0.0.0.0}" + dispGw6="${gw6:-::/0}" + [ "$iface" != "$dev" ] && dispDev="$dev" + is_default_dev "$dev" && dispStatus="${__OK__}" + displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}" + + case "$action" in + create) + output 2 "Setting up routing for '$displayText' " + if interface_routing 'create' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority" && \ + interface_routing 'create_user_set' "$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" + output_ok + else + state add 'errorSummary' 'errorFailedSetup' "$displayText" + output_fail + fi + ;; +# create_user_set) +# interface_routing 'create_user_set' "$ifaceTableID" "$ifaceMark" "$iface" "$gw4" "$dev" "$gw6" "$dev6" "$ifacePriority" +# ;; + destroy) + displayText="${iface}/${dispDev:+$dispDev/}${dispGw4}${ipv6_enabled:+/$dispGw6}" + output 2 "Removing routing for '$displayText' " + interface_routing 'destroy' "${ifaceTableID}" "${ifaceMark}" "${iface}" + output_ok + ;; + reload) + gatewaySummary="${gatewaySummary}${displayText}${dispStatus:+ $dispStatus}\\n" + ;; + reload_interface) + 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" + output_ok + 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 + 1))" + 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' 'ererrorUserFileSyntax' "$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 +} + +on_firewall_reload() { + if [ -z "$(ubus_get_status 'gateways')" ]; then # service is not running, do not start it on firewall reload + logger -t "$packageName" "Reload on firewall action aborted: service not running." + return 0; + else + rc_procd start_service 'on_firewall_reload' "$1" + fi +} +on_interface_reload() { rc_procd start_service 'on_interface_reload' "$1"; } + +start_service() { + local resolverStoredHash resolverNewHash i reloadedIface param="$1" + local createUserSets + + load_environment 'on_start' "$(load_validate_config)" || return 1 + is_wan_up || return 1 + rm -f "$nftTempFile" + + case "$param" in + on_boot) + serviceStartTrigger='on_start' + ;; + on_firewall_reload) + serviceStartTrigger='on_start' + ;; + on_interface_reload) + serviceStartTrigger='on_interface_reload' + reloadedIface="$2" + ;; + 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 gateway)" ]; then + serviceStartTrigger='on_start' + unset reloadedIface + elif [ "$serviceStartTrigger" = 'on_interface_reload' ] && \ + [ -z "$(ubus_get_interface "$reloadedIface" 'gateway_4')" ] && \ + [ -z "$(ubus_get_interface "$reloadedIface" 'gateway_6')" ]; then + serviceStartTrigger='on_start' + unset reloadedIface + else + serviceStartTrigger="${serviceStartTrigger:-on_start}" + fi + + if is_config_enabled 'include'; then + createUserSets='true' + 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 + if ! is_nft; 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' + interface_process_tor 'tor' 'destroy' + is_tor_running && interface_process_tor '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 'include'; then + traffic_killswitch 'remove' + output 1 'Processing user file(s) ' + config_load "$packageName" + config_foreach load_validate_include 'include' user_file_process + output 1 '\n' + resolver 'init_end' + resolver 'compare_hash' && resolver 'restart' + else + resolver 'init_end' + resolver 'compare_hash' && resolver 'restart' + traffic_killswitch 'remove' + fi + ;; + on_start|*) + traffic_killswitch 'insert' + resolver 'store_hash' + resolver 'cleanup_all' + resolver 'configure' + resolver 'init' + cleanup_main_chains + cleanup_sets + cleanup_marking_chains + if ! is_nft; 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' + config_foreach interface_process 'interface' 'create' + interface_process_tor 'tor' 'destroy' + is_tor_running && interface_process_tor '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 'include'; then + traffic_killswitch 'remove' + output 1 'Processing user file(s) ' + config_load "$packageName" + config_foreach load_validate_include 'include' user_file_process + output 1 '\n' + resolver 'init_end' + resolver 'compare_hash' && resolver 'restart' + else + resolver 'init_end' + resolver 'compare_hash' && resolver 'restart' + traffic_killswitch 'remove' + fi + ;; + 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 is_nft; then + [ -n "$gatewaySummary" ] && output "$serviceName (nft) started with gateways:\\n${gatewaySummary}" + else + [ -n "$gatewaySummary" ] && output "$serviceName (iptables) started with gateways:\\n${gatewaySummary}" + fi + state print 'errorSummary' + state print 'warningSummary' + 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_reload_trigger 'openvpn' + 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 + if [ "$serviceStartTrigger" = 'on_start' ]; then + output 3 "$serviceName monitoring interfaces: ${ifacesSupported}\\n" + fi +} + +stop_service() { + local i + load_environment 'on_stop' + is_service_running || return 0 + traffic_killswitch 'insert' + cleanup_main_chains + cleanup_sets + cleanup_marking_chains + output 1 'Resetting interfaces ' + config_load 'network' + config_foreach interface_process 'interface' 'destroy' + interface_process_tor 'tor' 'destroy' + 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 is_nft; then + output "$serviceName (nft) stopped "; output_okn; + else + output "$serviceName (iptables) stopped "; output_okn; + fi + fi +} + +status_service() { + local _SEPARATOR_='============================================================' + load_environment 'on_status' + if is_nft; 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 + 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_full -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}') + [ "$wanGW6" = "default" ] && wanGW6=$($ip_full -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' + echo "$_SEPARATOR_" + echo "$packageName chains - policies" + for i in forward input output prerouting postrouting; do + "$nft" 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" 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" list table inet "$nftTable" | sed -n "/set ${i} {/,/\t}/p" + done + if [ -s "$dnsmasqFile" ]; then + echo "$_SEPARATOR_" + echo "dnsmasq sets" + cat "$dnsmasqFile" + fi +# echo "$_SEPARATOR_" +# ip rule list | grep "${packageName}_" + echo "$_SEPARATOR_" + tableCount="$(grep -c "${packageName}_" /etc/iproute2/rt_tables)" || 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_full -4 route show table $((wan_tid + i)) | grep default)" + echo "IPv4 table $((wan_tid + i)) rule(s):" + $ip_full -4 rule list table "$((wan_tid + i))" + 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_full -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}') + [ "$wanGW6" = "default" ] && wanGW6=$($ip_full -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}_" /etc/iproute2/rt_tables)" || 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_full -4 route show table $((wan_tid + i)) | grep default)" + echo "IPv4 table $((wan_tid + i)) rule(s):" + $ip_full -4 rule list table "$((wan_tid + i))" + i=$((i + 1)) + done + + if [ -n "$ipv6_enabled" ]; then + i=0; while [ $i -lt $tableCount ]; do + $ip_full -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 is_present 'curl' && is_variant_installed 'libopenssl' && is_installed 'ca-bundle'; then + json_init; json_add_string "description" "${packageName}-support" + json_add_array "sections"; json_add_object '0' + json_add_string "name" "$(uci -q 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}" \ + 'enabled:bool:0' \ + 'procd_boot_delay:integer:0' \ + 'strict_enforcement:bool:1' \ + 'secure_reload:bool:0' \ + 'ipv6_enabled:bool:0' \ + 'resolver_set:or("", "none", "dnsmasq.ipset", "dnsmasq.nftset")' \ + 'verbosity:range(0,2):1' \ + "wan_mark:regex('0x[A-Fa-f0-9]{8}'):0x010000" \ + "fw_mask:regex('0x[A-Fa-f0-9]{8}'):0xff0000" \ + 'icmp_interface:or("","ignore", uci("network", "@interface"))' \ + 'ignored_interface:list(uci("network", "@interface"))' \ + 'supported_interface:list(uci("network", "@interface"))' \ + 'boot_timeout:integer:30' \ + 'wan_ip_rules_priority:uinteger:30000' \ + 'rule_create_option:or("", "add", "insert"):add' \ + 'procd_reload_delay:integer:0' \ + 'webui_supported_protocol:list(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(uci("network", "@interface"),"ignore"):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/90-pbr b/net/pbr/files/etc/uci-defaults/90-pbr new file mode 100644 index 000000000..237ebac58 --- /dev/null +++ b/net/pbr/files/etc/uci-defaults/90-pbr @@ -0,0 +1,34 @@ +#!/bin/sh +# shellcheck disable=SC1091,SC3037,SC3043 + +readonly __OK__='\033[0;32m[\xe2\x9c\x93]\033[0m' + +# Transition from vpn-policy-routing +if [ -s '/etc/config/vpn-policy-routing' ] && [ ! -s '/etc/config/pbr-opkg' ]; then + echo "Migrating vpn-policy-routing config file." + mv '/etc/config/pbr' '/etc/config/pbr-opkg' + sed 's/vpn-policy-routing/pbr/g' /etc/config/vpn-policy-routing > /etc/config/pbr + uci set vpn-policy-routing.config.enabled=0; uci commit vpn-policy-routing; +fi + +# Transition from older versions of pbr +sed -i 's/resolver_ipset/resolver_set/g' /etc/config/pbr +sed -i 's/iptables_rule_option/rule_create_option/g' /etc/config/pbr +sed -i "s/'FORWARD'/'forward'/g" /etc/config/pbr +sed -i "s/'INPUT'/'input'/g" /etc/config/pbr +sed -i "s/'OUTPUT'/'output'/g" /etc/config/pbr +sed -i "s/'PREROUTING'/'prerouting'/g" /etc/config/pbr +sed -i "s/'POSTROUTING'/'postrouting'/g" /etc/config/pbr +sed -i "s/option fw_mask '0x\(.*\)'/option fw_mask '\1'/g" /etc/config/pbr +sed -i "s/option wan_mark '0x\(.*\)'/option wan_mark '\1'/g" /etc/config/pbr + +uci -q batch <<-EOT + delete firewall.pbr + set firewall.pbr='include' + set firewall.pbr.fw4_compatible='1' + set firewall.pbr.type='script' + set firewall.pbr.path='/usr/share/pbr/pbr.firewall.include' + commit firewall +EOT + +exit 0 diff --git a/net/pbr/files/etc/uci-defaults/91-pbr b/net/pbr/files/etc/uci-defaults/91-pbr new file mode 100644 index 000000000..0d759c278 --- /dev/null +++ b/net/pbr/files/etc/uci-defaults/91-pbr @@ -0,0 +1,58 @@ +#!/bin/sh +# shellcheck disable=SC1091,SC3037,SC3043 + +readonly packageName='pbr' +readonly __OK__='\033[0;32m[\xe2\x9c\x93]\033[0m' + +pbr_iface_setup() { + local iface="${1}" + local proto + config_get proto "${iface}" proto + case "${iface}" in + (lan|loopback) return 0 ;; + esac + case "${proto}" in + (gre*|nebula|relay|vti*|vxlan|xfrm) return 0 ;; + (none) + uci -q set "network.${iface}_rt=route" + uci -q set "network.${iface}_rt.interface=${iface}" + uci -q set "network.${iface}_rt.target=0.0.0.0/0" + uci -q set "network.${iface}_rt6=route6" + uci -q set "network.${iface}_rt6.interface=${iface}" + uci -q set "network.${iface}_rt6.target=::/0" + ;; + esac + echo -en "Setting up ${packageName} routing tables for ${iface}... " + uci -q set "network.${iface}.ip4table=${packageName}_${iface%6}" + uci -q set "network.${iface}.ip6table=${packageName}_${iface%6}" + if ! grep -q -E -e "^[0-9]+\s+${packageName}_${iface%6}$" /etc/iproute2/rt_tables; then + sed -i -e "\$a $(($(sort -r -n /etc/iproute2/rt_tables | grep -o -E -m 1 "^[0-9]+")+1))\t${packageName}_${iface%6}" \ + /etc/iproute2/rt_tables + fi + echo -e "${__OK__}" +} + +. /lib/functions.sh +. /lib/functions/network.sh +config_load network +config_foreach pbr_iface_setup interface +network_flush_cache +network_find_wan iface +network_find_wan6 iface6 +# shellcheck disable=SC2154 +[ -n "$iface" ] && uci -q batch << EOF +set network.default='rule' +set network.default.lookup='${packageName}_${iface%6}' +set network.default.priority='80000' +EOF +[ -n "$iface6" ] && uci -q batch << EOF +set network.default6='rule6' +set network.default6.lookup='${packageName}_${iface6%6}' +set network.default6.priority='80000' +EOF +uci commit network +echo -en "Restarting network... " +/etc/init.d/network restart +echo -e "${__OK__}" + +exit 0 diff --git a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_forward/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_forward/30-pbr.nft new file mode 100644 index 000000000..d11ad8472 --- /dev/null +++ b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_forward/30-pbr.nft @@ -0,0 +1 @@ +jump pbr_forward comment "Jump into pbr forward chain"; diff --git a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_input/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_input/30-pbr.nft new file mode 100644 index 000000000..b3ce9db95 --- /dev/null +++ b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_input/30-pbr.nft @@ -0,0 +1 @@ +jump pbr_input comment "Jump into pbr input chain"; diff --git a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_output/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_output/30-pbr.nft new file mode 100644 index 000000000..c98514bc5 --- /dev/null +++ b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_output/30-pbr.nft @@ -0,0 +1 @@ +jump pbr_output comment "Jump into pbr output chain"; diff --git a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_postrouting/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_postrouting/30-pbr.nft new file mode 100644 index 000000000..cd5d1b4d3 --- /dev/null +++ b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_postrouting/30-pbr.nft @@ -0,0 +1 @@ +jump pbr_postrouting comment "Jump into pbr postrouting chain"; diff --git a/net/pbr/files/usr/share/nftables.d/chain-post/mangle_prerouting/30-pbr.nft b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_prerouting/30-pbr.nft new file mode 100644 index 000000000..a4471d376 --- /dev/null +++ b/net/pbr/files/usr/share/nftables.d/chain-post/mangle_prerouting/30-pbr.nft @@ -0,0 +1 @@ +jump pbr_prerouting comment "Jump into pbr prerouting 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 new file mode 100644 index 000000000..4dd9b2813 --- /dev/null +++ b/net/pbr/files/usr/share/nftables.d/table-post/30-pbr.nft @@ -0,0 +1,5 @@ +chain pbr_forward {} +chain pbr_input {} +chain pbr_output {} +chain pbr_prerouting {} +chain pbr_postrouting {} diff --git a/net/pbr/files/usr/share/pbr/pbr.firewall.include b/net/pbr/files/usr/share/pbr/pbr.firewall.include new file mode 100644 index 000000000..3fe906ee1 --- /dev/null +++ b/net/pbr/files/usr/share/pbr/pbr.firewall.include @@ -0,0 +1,5 @@ +#!/bin/sh +if [ -x /etc/init.d/pbr ] && /etc/init.d/pbr enabled; then + logger -t "pbr" "Reloading pbr due to $ACTION of firewall" + /etc/init.d/pbr on_firewall_reload "$ACTION" +fi diff --git a/net/pbr/files/usr/share/pbr/pbr.user.aws b/net/pbr/files/usr/share/pbr/pbr.user.aws new file mode 100644 index 000000000..bf398dd66 --- /dev/null +++ b/net/pbr/files/usr/share/pbr/pbr.user.aws @@ -0,0 +1,33 @@ +#!/bin/sh +# This file is heavily based on code from https://github.com/Xentrk/netflix-vpn-bypass/blob/master/IPSET_Netflix.sh + +TARGET_SET='pbr_wan_4_dst_ip_user' +TARGET_IPSET='pbr_wan_4_dst_net_user' +TARGET_TABLE='inet fw4' +TARGET_URL="https://ip-ranges.amazonaws.com/ip-ranges.json" +TARGET_DL_FILE="/var/pbr_tmp_aws_ip_ranges" +TARGET_NFT_FILE="/var/pbr_tmp_aws_ip_ranges.nft" +[ -z "$nft" ] && nft="$(command -v nft)" +_ret=1 + +if [ ! -s "$TARGET_DL_FILE" ]; then + uclient-fetch --no-check-certificate -qO- "$TARGET_URL" 2>/dev/null | grep "ip_prefix" | sed 's/^.*\"ip_prefix\": \"//; s/\",//' > "$TARGET_DL_FILE" +fi + +if [ -s "$TARGET_DL_FILE" ]; then + if ipset -q list "$TARGET_IPSET" >/dev/null 2>&1; then + if awk -v ipset="$TARGET_IPSET" '{print "add " ipset " " $1}' "$TARGET_DL_FILE" | ipset restore -!; then + _ret=0 + fi + elif [ -n "$nft" ] && [ -x "$nft" ] && "$nft" list set "$TARGET_TABLE" "$TARGET_SET" >/dev/null 2>&1; then + printf "add element %s %s { " "$TARGET_TABLE" "$TARGET_SET" > "$TARGET_NFT_FILE" + awk '{printf $1 ", "}' "$TARGET_DL_FILE" >> "$TARGET_NFT_FILE" + printf " } " >> "$TARGET_NFT_FILE" + if "$nft" -f "$TARGET_NFT_FILE"; then + rm -f "$TARGET_NFT_FILE" + _ret=0 + 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 new file mode 100644 index 000000000..54f54e0d3 --- /dev/null +++ b/net/pbr/files/usr/share/pbr/pbr.user.netflix @@ -0,0 +1,49 @@ +#!/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_SET='pbr_wan_4_dst_ip_user' +TARGET_IPSET='pbr_wan_4_dst_net_user' +TARGET_TABLE='inet fw4' +TARGET_ASN='2906' +TARGET_DL_FILE="/var/pbr_tmp_AS${TARGET_ASN}" +TARGET_NFT_FILE="/var/pbr_tmp_AS${TARGET_ASN}.nft" +#DB_SOURCE='ipinfo.io' +#DB_SOURCE='api.hackertarget.com' +DB_SOURCE='api.bgpview.io' +[ -z "$nft" ] && nft="$(command -v nft)" +_ret=1 + +if [ ! -s "$TARGET_DL_FILE" ]; 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 | grep -E "a href.*${TARGET_ASN}\/" | grep -v ":" | sed "s/^.*<a href=\"\/AS${TARGET_ASN}\///; s/\" >//" > "$TARGET_DL_FILE" + 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" + 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" + fi +fi + +if [ -s "$TARGET_DL_FILE" ]; then + if ipset -q list "$TARGET_IPSET" >/dev/null 2>&1; then + if awk -v ipset="$TARGET_IPSET" '{print "add " ipset " " $1}' "$TARGET_DL_FILE" | ipset restore -!; then + _ret=0 + fi + elif [ -n "$nft" ] && [ -x "$nft" ] && "$nft" list set "$TARGET_TABLE" "$TARGET_SET" >/dev/null 2>&1; then + printf "add element %s %s { " "$TARGET_TABLE" "$TARGET_SET" > "$TARGET_NFT_FILE" + awk '{printf $1 ", "}' "$TARGET_DL_FILE" >> "$TARGET_NFT_FILE" + printf " } " >> "$TARGET_NFT_FILE" + if "$nft" -f "$TARGET_NFT_FILE"; then + rm -f "$TARGET_NFT_FILE" + _ret=0 + fi + fi +fi + +return $_ret diff --git a/net/pbr/test.sh b/net/pbr/test.sh new file mode 100644 index 000000000..45469ed96 --- /dev/null +++ b/net/pbr/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/etc/init.d/"$1" version 2>&1 | grep "$2" |