diff --git a/net/adblock-fast/Makefile b/net/adblock-fast/Makefile new file mode 100644 index 00000000..efcb4960 --- /dev/null +++ b/net/adblock-fast/Makefile @@ -0,0 +1,81 @@ +# Copyright 2023-2024 MOSSDeF, Stan Grishin (stangri@melmac.ca). +# TLD optimization written by Dirk Brenken (dev@brenken.org). +# This is free software, licensed under AGPL-3.0-or-later. + +include $(TOPDIR)/rules.mk + +PKG_NAME:=adblock-fast +PKG_VERSION:=1.1.1 +PKG_RELEASE:=11 +PKG_MAINTAINER:=Stan Grishin +PKG_LICENSE:=AGPL-3.0-or-later + +include $(INCLUDE_DIR)/package.mk + +define Package/adblock-fast + SECTION:=net + CATEGORY:=Network + TITLE:=AdBlock Fast Service + URL:=https://docs.openwrt.melmac.net/adblock-fast/ + DEPENDS:=+jshn +curl + DEPENDS+=+!BUSYBOX_DEFAULT_AWK:gawk + DEPENDS+=+!BUSYBOX_DEFAULT_GREP:grep + DEPENDS+=+!BUSYBOX_DEFAULT_SED:sed + DEPENDS+=+!BUSYBOX_DEFAULT_SORT:coreutils-sort + CONFLICTS:=simple-adblock + PROVIDES:=simple-adblock + PKGARCH:=all +endef + +define Package/adblock-fast/description +Fast AdBlocking script to block ad or abuse/malware domains with DNSMASQ or Unbound. +Script supports local/remote list of domains and hosts-files for both block-listing and allow-listing. +Please see https://docs.openwrt.melmac.net/adblock-fast/ for more information. +endef + +define Package/adblock-fast/conffiles +/etc/config/adblock-fast +endef + +define Build/Configure +endef + +define Build/Compile +endef + +define Package/adblock-fast/install + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/adblock-fast $(1)/etc/init.d/adblock-fast + $(SED) "s|^\(readonly PKG_VERSION\).*|\1='$(PKG_VERSION)-$(PKG_RELEASE)'|" $(1)/etc/init.d/adblock-fast + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/adblock-fast $(1)/etc/config/adblock-fast + $(INSTALL_DIR) $(1)/tmp + $(INSTALL_DATA) ./files/adblock-fast.config.update $(1)/tmp/adblock-fast.config.update + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_BIN) ./files/etc/uci-defaults/90-adblock-fast $(1)/etc/uci-defaults/90-adblock-fast +endef + +define Package/adblock-fast/postinst + #!/bin/sh + # check if we are on real system + if [ -z "$${IPKG_INSTROOT}" ]; then + sed -f /tmp/adblock-fast.config.update -i /etc/config/adblock-fast || true + /etc/init.d/adblock-fast enable + fi + exit 0 +endef + +define Package/adblock-fast/prerm + #!/bin/sh + # check if we are on real system + if [ -z "$${IPKG_INSTROOT}" ]; then + echo -n "Stopping adblock-fast service... " + { /etc/init.d/adblock-fast stop && \ + /etc/init.d/adblock-fast killcache; } >/dev/null 2>&1 && echo "OK" || echo "FAIL" + echo -n "Removing rc.d symlink for adblock-fast... " + /etc/init.d/adblock-fast disable >/dev/null 2>&1 && echo "OK" || echo "FAIL" + fi + exit 0 +endef + +$(eval $(call BuildPackage,adblock-fast)) diff --git a/net/adblock-fast/files/README.md b/net/adblock-fast/files/README.md new file mode 100644 index 00000000..827255ab --- /dev/null +++ b/net/adblock-fast/files/README.md @@ -0,0 +1,3 @@ +# README + +README has been moved to [https://docs.openwrt.melmac.net/adblock-fast/](https://docs.openwrt.melmac.net/adblock-fast/). diff --git a/net/adblock-fast/files/adblock-fast.config.update b/net/adblock-fast/files/adblock-fast.config.update new file mode 100644 index 00000000..08f625ea --- /dev/null +++ b/net/adblock-fast/files/adblock-fast.config.update @@ -0,0 +1,15 @@ +s|http://winhelp2002.mvps.org/hosts.txt|https://winhelp2002.mvps.org/hosts.txt|g +s|list blocked_domains_url 'https://dbl.oisd.nl/basic'|list blocked_adblockplus_url 'https://small.oisd.nl/'|g +s|list blocked_domains_url 'https://dbl.oisd.nl/nsfw'|list blocked_adblockplus_url 'https://nsfw.oisd.nl/'|g +s|list blocked_domains_url 'https://dbl.oisd.nl/'|list blocked_adblockplus_url 'https://big.oisd.nl/'|g +s|list blocked_hosts_url 'https://hosts.oisd.nl/basic'|list blocked_adblockplus_url 'https://small.oisd.nl/'|g +s|list blocked_hosts_url 'https://hosts.oisd.nl/nsfw'|list blocked_adblockplus_url 'https://nsfw.oisd.nl/'|g +s|list blocked_hosts_url 'https://hosts.oisd.nl/'|list blocked_adblockplus_url 'https://big.oisd.nl/'|g +\|dshield.org|d +\|www.malwaredomainlist.com/hostslist/hosts.txt|d +\|https://mirror1.malwaredomains.com/files/justdomains|d +\|lists.disconnect.me|d +\|https://cdn.jsdelivr.net/gh/paulgb/BarbBlock/blacklists/domain-list.txt|d +\|dnsmasq.oisd.nl|d +\|dnsmasq2.oisd.nl|d +\|https://cdn.jsdelivr.net/gh/AdguardTeam/cname-trackers@master/combined_disguised_trackers_justdomains.txt|d diff --git a/net/adblock-fast/files/etc/config/adblock-fast b/net/adblock-fast/files/etc/config/adblock-fast new file mode 100644 index 00000000..68a6392f --- /dev/null +++ b/net/adblock-fast/files/etc/config/adblock-fast @@ -0,0 +1,120 @@ +config adblock-fast 'config' + option enabled '0' + list allowed_domain 'cdn.jsdelivr.net' + option allow_non_ascii '0' + option canary_domains_icloud '0' + option canary_domains_mozilla '0' + option compressed_cache '0' + option compressed_cache_dir '/etc' + option config_update_enabled '0' + option config_update_url 'https://cdn.jsdelivr.net/gh/openwrt/packages/net/adblock-fast/files/adblock-fast.config.update' + option curl_additional_param '' + option curl_max_file_size '30000000' + option curl_retry '3' + option debug '0' + option dns 'dnsmasq.servers' + list dnsmasq_instance '*' +# option dnsmasq_config_file_url 'https://big.oisd.nl/dnsmasq2' + option download_timeout '10' + option force_dns '1' + list force_dns_port '53' + list force_dns_port '853' +# ports listed below are used by some +# of the dnscrypt-proxy v1 resolvers +# list force_dns_port '553' +# list force_dns_port '1443' +# list force_dns_port '4343' +# list force_dns_port '4434' +# list force_dns_port '5443' +# list force_dns_port '8443' + option led 'none' + option parallel_downloads '1' + option pause_timeout '20' + option procd_trigger_wan6 '0' + option procd_boot_delay '0' + option procd_boot_wan_timeout '60' + option verbosity '2' + +config file_url + option url 'https://cdn.jsdelivr.net/gh/StevenBlack/hosts/hosts' + option size '6770929' + option action 'block' + option enabled '0' + +config file_url + option url 'https://raw.githubusercontent.com/AdguardTeam/cname-trackers/master/data/combined_disguised_trackers_justdomains.txt' + option size '6241707' + option action 'block' + option enabled '0' + +config file_url + option url 'https://big.oisd.nl/' + option size '6163363' + option action 'block' + option enabled '0' + +config file_url + option url 'https://cdn.jsdelivr.net/gh/bongochong/CombinedPrivacyBlockLists/NoFormatting/cpbl-ctld.txt' + option size '2608152' + option action 'block' + option enabled '0' + +config file_url + option url 'http://sysctl.org/cameleon/hosts' + option size '638545' + option action 'block' + option enabled '0' + +config file_url + option url 'https://cdn.jsdelivr.net/gh/kboghdady/youTube_ads_4_pi-hole/black.list' + option size '553006' + option action 'block' + option enabled '0' + +config file_url + option url 'https://raw.githubusercontent.com/AdguardTeam/cname-trackers/master/data/combined_disguised_clickthroughs_justdomains.txt' + option size '362170' + option action 'block' + option enabled '0' + +config file_url + option url 'https://someonewhocares.org/hosts/hosts' + option size '347410' + option action 'block' + option enabled '0' + +config file_url + option url 'https://winhelp2002.mvps.org/hosts.txt' + option size '334861' + option action 'block' + option enabled '0' + +config file_url + option url 'https://adaway.org/hosts.txt' + option size '243454' + option action 'block' + option enabled '0' + +config file_url + option url 'https://raw.githubusercontent.com/AdguardTeam/cname-trackers/master/data/combined_disguised_ads_justdomains.txt' + option size '222595' + option action 'block' + option enabled '0' + +config file_url + option url 'https://raw.githubusercontent.com/AdguardTeam/cname-trackers/master/data/combined_disguised_microsites_justdomains.txt' + option size '123275' + option action 'block' + option enabled '0' + +config file_url + option url 'https://pgl.yoyo.org/as/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext' + option size '99588' + option action 'block' + option enabled '0' + +config file_url + option url 'https://cdn.jsdelivr.net/gh/hoshsadiq/adblock-nocoin-list/hosts.txt' + option size '11149' + option action 'block' + option enabled '0' diff --git a/net/adblock-fast/files/etc/init.d/adblock-fast b/net/adblock-fast/files/etc/init.d/adblock-fast new file mode 100755 index 00000000..ccef6768 --- /dev/null +++ b/net/adblock-fast/files/etc/init.d/adblock-fast @@ -0,0 +1,2155 @@ +#!/bin/sh /etc/rc.common +# Copyright 2023 MOSSDeF, Stan Grishin (stangri@melmac.ca) +# shellcheck disable=SC3043 + +# shellcheck disable=SC2034 +START=94 +# shellcheck disable=SC2034 +USE_PROCD=1 +LC_ALL=C + +readonly PKG_VERSION='dev-test' +readonly packageName='adblock-fast' +readonly serviceName="$packageName $PKG_VERSION" +readonly packageConfigFile="/etc/config/${packageName}" +readonly dnsmasqAddnhostsFile="/var/run/${packageName}/dnsmasq.addnhosts" +readonly dnsmasqAddnhostsCache="/var/run/${packageName}/dnsmasq.addnhosts.cache" +readonly dnsmasqAddnhostsGzip="${packageName}.dnsmasq.addnhosts.gz" +readonly dnsmasqAddnhostsFilter='s|^|127.0.0.1 |;s|$||' +readonly dnsmasqAddnhostsFilterIPv6='s|^|:: |;s|$||' +readonly dnsmasqConfFile="/tmp/dnsmasq.d/${packageName}" +readonly dnsmasqConfCache="/var/run/${packageName}/dnsmasq.conf.cache" +readonly dnsmasqConfGzip="${packageName}.dnsmasq.conf.gz" +readonly dnsmasqConfFilter='s|^|local=/|;s|$|/|' +readonly dnsmasqIpsetFile="/tmp/dnsmasq.d/${packageName}.ipset" +readonly dnsmasqIpsetCache="/var/run/${packageName}/dnsmasq.ipset.cache" +readonly dnsmasqIpsetGzip="${packageName}.dnsmasq.ipset.gz" +readonly dnsmasqIpsetFilter='s|^|ipset=/|;s|$|/adb|' +readonly dnsmasqNftsetFile="/tmp/dnsmasq.d/${packageName}.nftset" +readonly dnsmasqNftsetCache="/var/run/${packageName}/dnsmasq.nftset.cache" +readonly dnsmasqNftsetGzip="${packageName}.dnsmasq.nftset.gz" +readonly dnsmasqNftsetFilter='s|^|nftset=/|;s|$|/4#inet#fw4#adb4|' +readonly dnsmasqNftsetFilterIPv6='s|^|nftset=/|;s|$|/4#inet#fw4#adb4,6#inet#fw4#adb6|' +readonly dnsmasqServersFile="/var/run/${packageName}/dnsmasq.servers" +readonly dnsmasqServersCache="/var/run/${packageName}/dnsmasq.servers.cache" +readonly dnsmasqServersGzip="${packageName}.dnsmasq.servers.gz" +readonly dnsmasqServersFilter='s|^|server=/|;s|$|/|' +readonly smartdnsDomainSetFile="/var/run/${packageName}/smartdns.domainset" +readonly smartdnsDomainSetCache="/var/run/${packageName}/smartdns.domainset.cache" +readonly smartdnsDomainSetConfig="/var/run/${packageName}/smartdns.domainset.conf" +readonly smartdnsDomainSetGzip="${packageName}.smartdns.domainset.gz" +readonly smartdnsDomainSetFilter=';' +readonly smartdnsIpsetFile="/var/run/${packageName}/smartdns.ipset" +readonly smartdnsIpsetCache="/var/run/${packageName}/smartdns.ipset.cache" +readonly smartdnsIpsetConfig="/var/run/${packageName}/smartdns.ipset.conf" +readonly smartdnsIpsetGzip="${packageName}.smartdns.ipset.gz" +readonly smartdnsIpsetFilter=';' +readonly smartdnsNftsetFile="/var/run/${packageName}/smartdns.nftset" +readonly smartdnsNftsetCache="/var/run/${packageName}/smartdns.nftset.cache" +readonly smartdnsNftsetConfig="/var/run/${packageName}/smartdns.nftset.conf" +readonly smartdnsNftsetGzip="${packageName}.smartdns.nftset.gz" +readonly smartdnsNftsetFilter=';' +readonly unboundFile="/var/lib/unbound/adb_list.${packageName}" +readonly unboundCache="/var/run/${packageName}/unbound.cache" +readonly unboundGzip="${packageName}.unbound.gz" +readonly unboundFilter='s|^|local-zone: "|;s|$|." always_nxdomain|' +readonly A_TMP="/var/${packageName}.a.tmp" +readonly B_TMP="/var/${packageName}.b.tmp" +readonly SED_TMP="/var/${packageName}.sed.tmp" +readonly uciConfigFile="/etc/config/${packageName}" +readonly runningConfigFile="/dev/shm/${packageName}.config" +readonly runningErrorFile="/dev/shm/${packageName}.error" +readonly runningStatusFile="/dev/shm/${packageName}.status" +readonly hostsFilter='/localhost/d;/^#/d;/^[^0-9]/d;s/^0\.0\.0\.0.//;s/^127\.0\.0\.1.//;s/[[:space:]]*#.*$//;s/[[:cntrl:]]$//;s/[[:space:]]//g;/[`~!@#\$%\^&\*()=+;:"'\'',<>?/\|[{}]/d;/]/d;/\./!d;/^$/d;/[^[:alnum:]_.-]/d;' +readonly domainsFilter='/^#/d;s/[[:space:]]*#.*$//;s/[[:space:]]*$//;s/[[:cntrl:]]$//;/[[:space:]]/d;/[`~!@#\$%\^&\*()=+;:"'\'',<>?/\|[{}]/d;/]/d;/^$/d;/[^[:alnum:]_.-]/d;' +readonly adBlockPlusFilter='/^#/d;/^!/d;s/[[:space:]]*#.*$//;s/^||//;s/\^$//;s/[[:space:]]*$//;s/[[:cntrl:]]$//;/[[:space:]]/d;/[`~!@#\$%\^&\*()=+;:"'\'',<>?/\|[{}]/d;/]/d;/\./!d;/^$/d;/[^[:alnum:]_.-]/d;' +readonly dnsmasqFileFilter='\|^server=/[[:alnum:]_.-].*/|!d;s|server=/||;s|/.*$||' +readonly dnsmasq2FileFilter='\|^local=/[[:alnum:]_.-].*/|!d;s|local=/||;s|/.*$||' +readonly dnsmasq3FileFilter='\|^address=/[[:alnum:]_.-].*/|!d;s|address=/||;s|/.*$||' +readonly _ERROR_='\033[0;31mERROR\033[0m' +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 _WARNING_='\033[0;33mWARNING\033[0m' +# shellcheck disable=SC2155 +readonly ipset="$(command -v ipset)" +# shellcheck disable=SC2155 +readonly nft="$(command -v nft)" +readonly canaryDomainsMozilla='use-application-dns.net' +readonly canaryDomainsiCloud='mask.icloud.com mask-h2.icloud.com' +readonly triggersReload='parallel_downloads debug download_timeout allowed_domain blocked_domain allowed_url blocked_url dns config_update_enabled config_update_url dnsmasq_config_file_url curl_additional_param curl_max_file_size curl_retry' +readonly triggersRestart='compressed_cache compressed_cache_dir force_dns led force_dns_port' + +dl_command= +dl_flag= +isSSLSupported= +outputFilter= +outputFilterIPv6= +outputFile= +outputGzip= +outputCache= +awk='awk' +load_environment_flag= +allowed_url= +blocked_url= +fw4_restart_flag= + +# shellcheck disable=SC1091 +. /lib/functions.sh +# shellcheck disable=SC1091 +. /lib/functions/network.sh +# shellcheck disable=SC1091 +. /usr/share/libubox/jshn.sh + +append_newline() { is_newline_ending "$1" || echo '' >> "$1"; } +check_ipset() { { command -v ipset && /usr/sbin/ipset help hash:net; } >/dev/null 2>&1; } +check_nft() { command -v nft >/dev/null 2>&1; } +check_dnsmasq() { command -v dnsmasq >/dev/null 2>&1; } +check_dnsmasq_ipset() { + local o; + check_dnsmasq || return 1 + o="$(dnsmasq -v 2>/dev/null)" + check_ipset && ! echo "$o" | grep -q 'no-ipset' && echo "$o" | grep -q 'ipset' +} +check_dnsmasq_nftset() { + local o; + check_dnsmasq || return 1 + o="$(dnsmasq -v 2>/dev/null)" + check_nft && ! echo "$o" | grep -q 'no-nftset' && echo "$o" | grep -q 'nftset' +} +check_smartdns() { command -v smartdns >/dev/null 2>&1; } +check_smartdns_ipset() { check_smartdns && check_ipset; } +check_smartdns_nftset() { check_smartdns && check_nft; } +check_unbound() { command -v unbound >/dev/null 2>&1; } +config_cache() { + local param="$1" var="$2" + local _reload="$triggersReload" + local _restart="$triggersRestart" + local i ret + case "$param" in + create|set) + cp -f "$uciConfigFile" "$runningConfigFile" + ;; + get) + case "$var" in + trigger_fw4) + ret='false' + if [ -s "$runningConfigFile" ]; then + local UCI_CONFIG_DIR="${runningConfigFile%/*}" + is_fw4_restart_needed && ret='true' + fi + printf "%b" "$ret" + return + ;; + trigger_service) + local old_allowed_url old_blocked_url + if [ ! -s "$runningConfigFile" ]; then + ret='on_boot' + elif cmp -s "$uciConfigFile" "$runningConfigFile"; then + ret='restart' + else + for i in $_reload; do + local val_current val_old UCI_CONFIG_DIR + case "$i" in + allowed_url) + val_current="$allowed_url" + config_load "$runningConfigFile" + config_foreach append_url 'file_url' old_allowed_url old_blocked_url + val_old="$old_allowed_url" + ;; + blocked_url) + val_current="$blocked_url" + config_load "$runningConfigFile" + config_foreach append_url 'file_url' old_allowed_url old_blocked_url + val_old="$old_blocked_url" + ;; + *) + UCI_CONFIG_DIR= + val_current="$(uci_get "$packageName" 'config' "$i")" + UCI_CONFIG_DIR="${runningConfigFile%/*}" + val_old="$(uci_get "$packageName" 'config' "$i")" + ;; + esac + if [ "$val_current" != "$val_old" ]; then + ret='download' + unset _restart + break + fi + done + for i in $_restart; do + local val_current val_old UCI_CONFIG_DIR + UCI_CONFIG_DIR= + val_current="$(uci_get "$packageName" 'config' "$i")" + UCI_CONFIG_DIR="${runningConfigFile%/*}" + val_old="$(uci_get "$packageName" 'config' "$i")" + if [ "$val_current" != "$val_old" ]; then + ret='restart' + break + fi + done + fi + printf "%b" "$ret" + return + ;; + *) + local UCI_CONFIG_DIR="${runningConfigFile%/*}" + ret="$(uci_get "$packageName" 'config' "$var")" + printf "%b" "$ret" + return + ;; + esac + ;; + esac +} +debug() { local __i __j; for __i in "$@"; do eval "__j=\$$__i"; echo "${__i}: ${__j} "; done; } +dns_set_output_values() { + case "$1" in + dnsmasq.addnhosts) + outputFilter="$dnsmasqAddnhostsFilter" + outputFile="$dnsmasqAddnhostsFile" + outputCache="$dnsmasqAddnhostsCache" + outputGzip="${compressed_cache_dir}/${dnsmasqAddnhostsGzip}" + if [ "$ipv6_enabled" -ne '0' ]; then + outputFilterIPv6="$dnsmasqAddnhostsFilterIPv6" + fi + ;; + dnsmasq.conf) + outputFilter="$dnsmasqConfFilter" + outputFile="$dnsmasqConfFile" + outputCache="$dnsmasqConfCache" + outputGzip="${compressed_cache_dir}/${dnsmasqConfGzip}" + ;; + dnsmasq.ipset) + outputFilter="$dnsmasqIpsetFilter" + outputFile="$dnsmasqIpsetFile" + outputCache="$dnsmasqIpsetCache" + outputGzip="${compressed_cache_dir}/${dnsmasqIpsetGzip}" + ;; + dnsmasq.nftset) + if [ "$ipv6_enabled" -ne '0' ]; then + outputFilter="$dnsmasqNftsetFilterIPv6" + else + outputFilter="$dnsmasqNftsetFilter" + fi + outputFile="$dnsmasqNftsetFile" + outputCache="$dnsmasqNftsetCache" + outputGzip="${compressed_cache_dir}/${dnsmasqNftsetGzip}" + ;; + dnsmasq.servers) + outputFilter="$dnsmasqServersFilter" + outputFile="$dnsmasqServersFile" + outputCache="$dnsmasqServersCache" + outputGzip="${compressed_cache_dir}/${dnsmasqServersGzip}" + ;; + smartdns.domainset) + outputFilter="$smartdnsDomainSetFilter" + outputFile="$smartdnsDomainSetFile" + outputCache="$smartdnsDomainSetCache" + outputGzip="${compressed_cache_dir}/${smartdnsDomainSetGzip}" + outputConfig="$smartdnsDomainSetConfig" + ;; + smartdns.ipset) + outputFilter="$smartdnsIpsetFilter" + outputFile="$smartdnsIpsetFile" + outputCache="$smartdnsIpsetCache" + outputGzip="${compressed_cache_dir}/${smartdnsIpsetGzip}" + outputConfig="$smartdnsIpsetConfig" + ;; + smartdns.nftset) + outputFilter="$smartdnsNftsetFilter" + outputFile="$smartdnsNftsetFile" + outputCache="$smartdnsNftsetCache" + outputGzip="${compressed_cache_dir}/${smartdnsNftsetGzip}" + outputConfig="$smartdnsNftsetConfig" + ;; + unbound.adb_list) + outputFilter="$unboundFilter" + outputFile="$unboundFile" + outputCache="$unboundCache" + outputGzip="${compressed_cache_dir}/${unboundGzip}" + ;; + esac +} +dnsmasq_hup() { killall -q -s HUP dnsmasq; } +dnsmasq_kill() { killall -q -s KILL dnsmasq; } +dnsmasq_restart() { /etc/init.d/dnsmasq restart >/dev/null 2>&1; } +is_enabled() { uci_get "$1" 'config' 'enabled' '0'; } +is_fw4_restart_needed() { + [ "$fw4_restart_flag" = 'true' ] && return 0 + local dns force_dns + dns="$(uci_get "$packageName" 'config' 'dns' 'dnsmasq.servers')" + force_dns="$(uci_get "$packageName" 'config' 'force_dns' '1')" + if [ "$force_dns" = '1' ]; then + return 0 + elif [ "$dns" = 'dnsmasq.ipset' ]; then + return 0 + elif [ "$dns" = 'dnsmasq.nftset' ]; then + return 0 + elif [ "$dns" = 'smartdns.ipset' ]; then + return 0 + elif [ "$dns" = 'smartdns.nftset' ]; then + return 0 + else + return 1 + fi +} +is_integer() { + case "$1" in + (*[!0123456789]*) return 1;; + ('') return 1;; + (*) return 0;; + esac +} +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"; } +# shellcheck disable=SC3057 +is_https_url() { [ "${1:0:8}" = "https://" ]; } +is_newline_ending() { [ "$(tail -c1 "$1" | wc -l)" -ne '0' ]; } +is_present() { command -v "$1" >/dev/null 2>&1; } +is_running() { + local i j + i="$(json 'get' 'status')" + j="$(ubus_get_data 'status')" + if [ "$i" = 'statusStopped' ] || [ -z "${i}${j}" ]; then + return 1 + else + return 0 + fi +} +ipset() { "$ipset" "$@" >/dev/null 2>&1; } +get_ram_free() { ubus call system info | jsonfilter -e '@.memory.free'; } +get_ram_total() { ubus call system info | jsonfilter -e '@.memory.total'; } +led_on(){ if [ -n "${1}" ] && [ -e "${1}/trigger" ]; then echo 'default-on' > "${1}/trigger" 2>&1; fi; } +led_off(){ if [ -n "${1}" ] && [ -e "${1}/trigger" ]; then echo 'none' > "${1}/trigger" 2>&1; fi; } +logger() { /usr/bin/logger -t "$packageName" "$@"; } +nft() { "$nft" "$@" >/dev/null 2>&1; } +output_ok() { output 1 "$_OK_"; output 2 "$__OK__\\n"; } +output_okn() { output 1 "$_OK_\\n"; output 2 "$__OK__\\n"; } +output_fail() { output 1 "$_FAIL_"; output 2 "$__FAIL__\\n"; } +output_failn() { output 1 "$_FAIL_\\n"; output 2 "$__FAIL__\\n"; } +print_json_bool() { json_init; json_add_boolean "$1" "$2"; json_dump; json_cleanup; } +print_json_int() { json_init; json_add_int "$1" "$2"; json_dump; json_cleanup; } +print_json_string() { json_init; json_add_string "$1" "$2"; json_dump; json_cleanup; } +sanitize_dir() { [ -d "$(readlink -fn "$1")" ] && readlink -fn "$1"; } +smartdns_restart() { /etc/init.d/smartdns restart >/dev/null 2>&1; } +str_contains() { test "$1" != "$(str_replace "$1" "$2" '')"; } +str_contains_word() { echo "$1" | grep -q -w "$2"; } +# shellcheck disable=SC2018,SC2019 +str_to_lower() { echo "$1" | tr 'A-Z' 'a-z'; } +# shellcheck disable=SC2018,SC2019 +str_to_upper() { echo "$1" | tr 'a-z' 'A-Z'; } +str_replace() { printf "%b" "$1" | sed -e "s/$(printf "%b" "$2")/$(printf "%b" "$3")/g"; } +ubus_get_data() { ubus call service list "{ 'name': '$packageName' }" | jsonfilter -e "@['${packageName}'].instances.main.data.${1}"; } +ubus_get_ports() { ubus call service list "{ 'name': '$packageName' }" | jsonfilter -e "@['${packageName}'].instances.main.data.firewall.*.dest_port"; } +uci_get_protocol() { uci_get 'network' "$1" 'proto'; } +unbound_restart() { /etc/init.d/unbound restart >/dev/null 2>&1; } + +json() { +# shellcheck disable=SC2034 + local action="$1" param="$2" value="$3" + shift 3 +# shellcheck disable=SC2124 + local extras="$@" line + local status message error stats + local ret i + if [ -s "$runningStatusFile" ]; then + json_load_file "$runningStatusFile" 2>/dev/null + json_select 'data' 2>/dev/null + for i in status message error stats; do + json_get_var "$i" "$i" 2>/dev/null + done + fi + case "$action" in + get) + printf "%b" "$(eval echo "\$$param")" + return + ;; + add) + line="$(eval echo "\$$param")" + eval "$param"='${line:+$line }${value}${extras:+|$extras}' + ;; + del) + case "$param" in + all) + unset status message error stats;; + *) + unset "$param";; + esac + ;; + set) + eval "$param"='${value}${extras:+|$extras}' + ;; + esac + json_init + json_add_object 'data' + json_add_string version "$PKG_VERSION" + json_add_string status "$status" + json_add_string message "$message" + json_add_string error "$error" + json_add_string stats "$stats" + json_close_object + mkdir -p "${runningStatusFile%/*}" + json_dump > "$runningStatusFile" + sync +} + +get_local_filesize() { + local file="$1" size + [ -f "$file" ] || return 0 + if is_present stat; then + size="$(stat -c%s "$file")" + elif is_present wc; then + size="$(wc -c < "$file")" + fi +# shellcheck disable=SC3037 + echo -en "$size" +} + +get_url_filesize() { + local url="$1" size size_command + [ -n "$url" ] || return 0 + is_present 'curl' || return 0 + size_command='curl --silent --insecure --fail --head --request GET' +# size="$($size_command "$url" | grep -Po '^[cC]ontent-[lL]ength: \K\w+')" + size="$($size_command "$url" | grep -Eo '^[cC]ontent-[lL]ength: (.*)' | awk '{print $2}')" +# shellcheck disable=SC3037 + echo -en "$size" +} + +output() { +# Target verbosity level with the first parameter being an integer + is_integer() { + case "$1" in + (*[!0123456789]*) return 1;; + ('') return 1;; + (*) return 0;; + esac + } + local msg memmsg logmsg text + local sharedMemoryOutput="/dev/shm/$packageName-output" + if [ -z "$verbosity" ] && [ -n "$packageName" ]; then + verbosity="$(uci_get "$packageName" 'config' 'verbosity' '2')" + fi + if [ $# -ne 1 ] && is_integer "$1"; then + if [ $((verbosity & $1)) -gt 0 ] || [ "$verbosity" = "$1" ]; then shift; text="$*"; else return 0; fi + fi + text="${text:-$*}"; + [ -t 1 ] && printf "%b" "$text" +# shellcheck disable=SC3060 + msg="${text//$serviceName /service }"; + if [ "$(printf "%b" "$msg" | wc -l)" -gt 0 ]; then + [ -s "$sharedMemoryOutput" ] && memmsg="$(cat "$sharedMemoryOutput")" + logmsg="$(printf "%b" "${memmsg}${msg}" | sed 's/\x1b\[[0-9;]*m//g')" + logger -t "${packageName:-service} [$$]" "$(printf "%b" "$logmsg")" + rm -f "$sharedMemoryOutput" + else + printf "%b" "$msg" >> "$sharedMemoryOutput" + fi +} + +uci_add_list_if_new() { + local PACKAGE="$1" + local CONFIG="$2" + local OPTION="$3" + local VALUE="$4" + local i + [ -n "$PACKAGE" ] && [ -n "$CONFIG" ] && [ -n "$OPTION" ] && [ -n "$VALUE" ] || return 1 + for i in $(uci_get "$PACKAGE" "$CONFIG" "$OPTION"); do + [ "$i" = "$VALUE" ] && return 0 + done + uci_add_list "$PACKAGE" "$CONFIG" "$OPTION" "$VALUE" +} + +uci_changes() { + local PACKAGE="$1" + local CONFIG="$2" + local OPTION="$3" + if [ -s "${UCI_CONFIG_DIR:-/etc/config/}${PACKAGE}" ]; then + /sbin/uci ${UCI_CONFIG_DIR:+-c $UCI_CONFIG_DIR} changes "$PACKAGE${CONFIG:+.$CONFIG}${OPTION:+.$OPTION}" + fi +} + +if type extra_command 1>/dev/null 2>&1; then + extra_command 'allow' 'Allows domain in current block-list and config' + extra_command 'check' 'Checks if specified domain is found in current block-list' + extra_command 'check_lists' 'Checks if specified domain is found in enabled block-lists' + extra_command 'dl' 'Force-downloads all enabled block-list' + extra_command 'killcache' 'Delete all cached files' + extra_command 'pause' 'Pauses AdBlocking for specified number of seconds (default: 60)' + extra_command 'sizes' 'Displays the file-sizes of enabled block-lists' + extra_command 'version' 'Show version information' +else +# shellcheck disable=SC2034 + EXTRA_COMMANDS='allow check dl killcache pause sizes status_service version' +# shellcheck disable=SC2034 + EXTRA_HELP=' allow Allows domain(s) in current block-list and config + check Checks if specified domain is found in current block-list + dl Force-downloads all enabled block-list + pause Pauses AdBlocking for specified number of seconds (default: 60) + sizes Displays the file-sizes of enabled block-lists' +fi + +get_text() { + local r + case "$1" in + errorConfigValidationFail) r="$packageName config validation failed";; + errorServiceDisabled) r="$packageName is currently disabled";; + errorNoDnsmasqIpset) + r="dnsmasq ipset support is enabled in $packageName, but dnsmasq is either not installed or installed dnsmasq does not support ipset";; + errorNoIpset) + r="dnsmasq ipset support is enabled in $packageName, but ipset is either not installed or installed ipset does not support 'hash:net' type";; + errorNoDnsmasqNftset) + r="dnsmasq nft set support is enabled in $packageName, but dnsmasq is either not installed or installed dnsmasq does not support nft set";; + errorNoNft) r="dnsmasq nft sets support is enabled in $packageName, but nft is not installed";; + errorNoWanGateway) r="The ${serviceName} failed to discover WAN gateway";; + errorOutputDirCreate) r="failed to create directory for %s file";; + errorOutputFileCreate) r="failed to create $outputFile file";; + errorFailDNSReload) r="failed to restart/reload DNS resolver";; + errorSharedMemory) r="failed to access shared memory";; + errorSorting) r="failed to sort data file";; + errorOptimization) r="failed to optimize data file";; + errorAllowListProcessing) r="failed to process allow-list";; + errorDataFileFormatting) r="failed to format data file";; + errorMovingDataFile) r="failed to move data file '${A_TMP}' to '${outputFile}'";; + errorCreatingCompressedCache) r="failed to create compressed cache";; + errorRemovingTempFiles) r="failed to remove temporary files";; + errorRestoreCompressedCache) r="failed to unpack compressed cache";; + errorRestoreCache) r="failed to move '$outputCache' to '$outputFile'";; + errorOhSnap) r="failed to create block-list or restart DNS resolver";; + errorStopping) r="failed to stop $serviceName";; + errorDNSReload) r="failed to reload/restart DNS resolver";; + errorDownloadingConfigUpdate) r="failed to download Config Update file";; + errorDownloadingList) r="failed to download";; + errorParsingConfigUpdate) r="failed to parse Config Update file";; + errorParsingList) r="failed to parse";; + errorNoSSLSupport) r="no HTTPS/SSL support on device";; + errorCreatingDirectory) r="failed to create output/cache/gzip file directory";; + errorDetectingFileType) r="failed to detect format";; + errorNothingToDo) r="no blocked list URLs nor blocked-domains enabled";; + errorTooLittleRam) r="free ram (%s) is not enough to process all enabled block-lists";; + + statusNoInstall) r="$serviceName is not installed or not found";; + statusStopped) r="Stopped";; + statusStarting) r="Starting";; + statusRestarting) r="Restarting";; + statusForceReloading) r="Force Reloading";; + statusDownloading) r="Downloading";; + statusProcessing) r="Processing";; + statusFail) r="failed to start";; + statusSuccess) r="Success";; + + warningExternalDnsmasqConfig) + r="use of external dnsmasq config file detected, please set 'dns' option to 'dnsmasq.conf'";; + warningMissingRecommendedPackages) r="some recommended packages are missing";; + warningInvalidCompressedCacheDir) r="invalid compressed cache directory '%s'";; + warningFreeRamCheckFail) r="can't detect free RAM";; + esac + shift +# shellcheck disable=SC2059 + printf "$r" "$@" +} + +load_network() { + local param="$1" + local i j wan_if wan_gw + local counter wan_if_timeout="$procd_boot_wan_timeout" wan_gw_timeout='5' + counter=0 + while [ -z "$wan_if" ]; do + network_flush_cache + network_find_wan wan_if + if [ -n "$wan_if" ]; then + output "WAN interface found: '${wan_if}'.\\n" + break + fi + if [ "$counter" -gt "$wan_if_timeout" ]; then + output "WAN interface timeout, assuming 'wan'.\\n" + wan_if='wan' + break + fi + counter=$((counter+1)) + output "Waiting to discover WAN Interface...\\n" + sleep 1 + done + + counter=0 + if [ "$(uci_get_protocol "$wan_if")" = 'pppoe' ]; then + wan_gw_timeout=$((wan_gw_timeout+10)) + fi + while [ "$counter" -le "$wan_gw_timeout" ]; do + network_flush_cache + network_get_gateway wan_gw "$wan_if" + if [ -n "$wan_gw" ]; then + output "WAN gateway found: '${wan_gw}.'\\n" + return 0 + fi + counter=$((counter+1)) + output "Waiting to discover $wan_if gateway...\\n" + sleep 1 + done + json add error 'errorNoWanGateway' + output "${_ERROR_}: $(get_text 'errorNoWanGateway')!\\n"; return 1; +} + +append_url() { + local cfg="$1" allow_var="${2:-allowed_url}" block_var="${3:-blocked_url}" + local old_value + local en action url + config_get_bool en "$cfg" enabled '1' + config_get action "$cfg" action 'block' + config_get url "$cfg" url + if [ "$en" = '1' ]; then + if [ "$action" = 'allow' ]; then + old_value=$(eval echo "\$$allow_var") + old_value="${old_value:+$old_value }${url}" + eval "$allow_var"="\$old_value" + else + old_value=$(eval echo "\$$block_var") + old_value="${old_value:+$old_value }${url}" + eval "$block_var"="\$old_value" + fi + fi +} + +detect_file_type() { + local file="$1" + if [ "$(head -1 "$file")" = '[Adblock Plus]' ] || \ + grep -q '^||' "$file"; then + echo 'adblockplus' + elif grep -q '^server=' "$file"; then + echo 'dnsmasq' + elif grep -q '^local=' "$file"; then + echo 'dnsmasq2' + elif grep -q '^address=' "$file"; then + echo 'dnsmasq3' + elif grep -q '^0\.0\.0\.0' "$file" || grep -q '^127\.0\.0\.1' "$file"; then + echo 'hosts' + elif [ -n "$(sed "$domainsFilter" "$file" | head -1)" ]; then + echo 'domains' + fi +} + +load_environment() { + local i j + local validation_result="$1" param="$2" + + [ -z "$load_environment_flag" ] || return 0 + + if [ "$validation_result" != '0' ]; then + json add error 'errorConfigValidationFail' + output "${_ERROR_}: $(get_text 'errorConfigValidationFail')!\\n" + output "Please check if the '$packageConfigFile' contains correct values for config options.\\n" + return 1 + fi + + if [ "$enabled" -eq 0 ]; then + json add error 'errorServiceDisabled' + output "${_ERROR_}: $(get_text 'errorServiceDisabled')!\\n" + output "Run the following commands before starting service again:\\n" + output "uci set ${packageName}.config.enabled='1'; uci commit $packageName;\\n" + return 1 + fi + + if [ "$debug" -ne '0' ]; then + exec 1>>"/tmp/$packageName.log" + exec 2>&1 + set -x + fi + +# TODO: check for resolver and error out on start + + if [ -n "$dnsmasq_config_file_url" ]; then + case "$dns" in + dnsmasq.conf) :;; + *) + if [ "$param" != 'quiet' ]; then + json add warning 'warningExternalDnsmasqConfig' + output "${_WARNING_}: $(get_text 'warningExternalDnsmasqConfig')!\\n" + fi + ;; + esac + fi + + case "$dns" in + dnsmasq.*) + if dnsmasq -v 2>/dev/null | grep -q 'no-IDN' || ! dnsmasq -v 2>/dev/null | grep -q -w 'IDN'; then + allow_non_ascii='0' + fi + ;; + smartdns.*) + allow_non_ascii='0' + ;; + unbound.*) + allow_non_ascii='1' + ;; + esac + + case "$dns" in + dnsmasq.ipset) + if dnsmasq -v 2>/dev/null | grep -q 'no-ipset' || ! dnsmasq -v 2>/dev/null | grep -q -w 'ipset'; then + if [ "$param" != 'quiet' ]; then + json add error 'errorNoDnsmasqIpset' + output "${_ERROR_}: $(get_text 'errorNoDnsmasqIpset')!\\n" + fi + dns='dnsmasq.servers' + fi + if ! ipset help hash:net; then + if [ "$param" != 'quiet' ]; then + json add error 'errorNoIpset' + output "${_ERROR_}: $(get_text 'errorNoIpset')!\\n" + fi + dns='dnsmasq.servers' + fi + ;; + dnsmasq.nftset) + if dnsmasq -v 2>/dev/null | grep -q 'no-nftset' || ! dnsmasq -v 2>/dev/null | grep -q -w 'nftset'; then + if [ "$param" != 'quiet' ]; then + json add error 'errorNoDnsmasqNftset' + output "${_ERROR_}: $(get_text 'errorNoDnsmasqNftset')!\\n" + fi + dns='dnsmasq.servers' + fi + if [ -z "$nft" ]; then + if [ "$param" != 'quiet' ]; then + json add error 'errorNoNft' + output "${_ERROR_}: $(get_text 'errorNoNft')!\\n" + fi + dns='dnsmasq.servers' + fi + ;; + smartdns.ipset) + if ! ipset help hash:net; then + if [ "$param" != 'quiet' ]; then + json add error 'errorNoIpset' + output "${_ERROR_}: $(get_text 'errorNoIpset')!\\n" + fi + dns='smartdns.domainset' + fi + ;; + smartdns.nftset) + if [ -z "$nft" ]; then + if [ "$param" != 'quiet' ]; then + json add error 'errorNoNft' + output "${_ERROR_}: $(get_text 'errorNoNft')!\\n" + fi + dns='smartdns.domainset' + fi + ;; + esac + + if [ "$(sanitize_dir "$compressed_cache_dir")" = '/' ]; then + compressed_cache_dir='' + elif [ -n "$(sanitize_dir "$compressed_cache_dir")" ]; then + compressed_cache_dir="$(sanitize_dir "$compressed_cache_dir")" + else + json add warning 'warningInvalidCompressedCacheDir' "$compressed_cache_dir" + output "${_WARNING_}: $(get_text 'warningInvalidCompressedCacheDir' "$compressed_cache_dir")!\\n" + compressed_cache_dir="/etc" + fi + + dns_set_output_values "$dns" + + [ "$dns" = 'dnsmasq.addnhosts' ] || rm -f "$dnsmasqAddnhostsFile" "$dnsmasqAddnhostsCache" "${compressed_cache_dir}/${dnsmasqAddnhostsGzip}" + [ "$dns" = 'dnsmasq.conf' ] || rm -f "$dnsmasqConfFile" "$dnsmasqConfCache" "${compressed_cache_dir}/${dnsmasqConfGzip}" + [ "$dns" = 'dnsmasq.ipset' ] || rm -f "$dnsmasqIpsetFile" "$dnsmasqIpsetCache" "${compressed_cache_dir}/${dnsmasqIpsetGzip}" + [ "$dns" = 'dnsmasq.nftset' ] || rm -f "$dnsmasqNftsetFile" "$dnsmasqNftsetCache" "${compressed_cache_dir}/${dnsmasqNftsetGzip}" + [ "$dns" = 'dnsmasq.servers' ] || rm -f "$dnsmasqServersFile" "$dnsmasqServersCache" "${compressed_cache_dir}/${dnsmasqServersGzip}" + [ "$dns" = 'smartdns.domainset' ] || rm -f "$smartdnsDomainSetFile" "$smartdnsDomainSetCache" "${compressed_cache_dir}/${smartdnsDomainSetGzip}" "$smartdnsDomainSetConfig" + [ "$dns" = 'smartdns.ipset' ] || rm -f "$smartdnsIpsetFile" "$smartdnsIpsetCache" "${compressed_cache_dir}/${smartdnsIpsetGzip}" "$smartdnsIpsetConfig" + [ "$dns" = 'smartdns.nftset' ] || rm -f "$smartdnsNftsetFile" "$smartdnsNftsetCache" "${compressed_cache_dir}/${smartdnsNftsetGzip}" "$smartdnsNftsetConfig" + [ "$dns" = 'unbound.adb_list' ] || rm -f "$unboundFile" "$unboundCache" "${compressed_cache_dir}/${unboundGzip}" + + for i in "$runningConfigFile" "$runningErrorFile" "$runningStatusFile" "$outputFile" "$outputCache" "$outputGzip" "$outputConfig"; do + [ -n "$i" ] || continue + if ! mkdir -p "${i%/*}"; then + if [ "$param" != 'quiet' ]; then + json add error 'errorOutputDirCreate' "$i" + output "${_ERROR_}: $(get_text 'errorOutputDirCreate' "$i")!\\n" + fi + fi + done + + is_present 'gawk' && awk='gawk' + if ! is_present '/usr/libexec/grep-gnu' || ! is_present '/usr/libexec/sed-gnu' || \ + ! is_present '/usr/libexec/sort-coreutils' || ! is_present 'gawk'; then + local s + is_present 'gawk' || s="${s:+$s }gawk" + is_present '/usr/libexec/grep-gnu' || s="${s:+$s }grep" + is_present '/usr/libexec/sed-gnu' || s="${s:+$s }sed" + is_present '/usr/libexec/sort-coreutils' || s="${s:+$s }coreutils-sort" + if [ "$param" != 'quiet' ]; then + json add warning 'warningMissingRecommendedPackages' "$s" + output "${_WARNING_}: $(get_text 'warningMissingRecommendedPackages'), install them by running:\\n" + output "opkg update; opkg --force-overwrite install $s;\\n" + fi + fi + # Prefer curl because it supports the file:// scheme. + if is_present 'curl'; then + dl_command='curl --silent --insecure' + dl_command="${dl_command}${curl_additional_param:+ $curl_additional_param}" + dl_command="${dl_command}${curl_max_file_size:+ --max-filesize $curl_max_file_size}" + dl_command="${dl_command}${curl_retry:+ --retry $curl_retry}" + dl_command="${dl_command}${download_timeout:+ --connect-timeout $download_timeout}" + dl_flag='-o' + elif is_present '/usr/libexec/wget-ssl'; then + dl_command='/usr/libexec/wget-ssl --no-check-certificate -q' + dl_command="${dl_command}${download_timeout:+ --timeout $download_timeout}" + dl_flag="-O" + size_command='/usr/libexec/wget-ssl --no-check-certificate -q -O /dev/null --server-response' + size_command="${size_command}${download_timeout:+ --timeout $download_timeout}" + elif is_present wget && wget --version 2>/dev/null | grep -q "+https"; then + dl_command="wget --no-check-certificate -q" + dl_command="${dl_command}${download_timeout:+ --timeout $download_timeout}" + dl_flag="-O" + size_command='wget --no-check-certificate -q -O /dev/null --server-response' + size_command="${size_command}${download_timeout:+ --timeout $download_timeout}" + else + dl_command="uclient-fetch --no-check-certificate -q" + dl_command="${dl_command}${download_timeout:+ --timeout $download_timeout}" + dl_flag="-O" + fi + led="${led:+/sys/class/leds/$led}" + if curl --version 2>/dev/null | grep -q "Protocols: .*https.*" \ + || wget --version 2>/dev/null | grep -q "+ssl"; then + isSSLSupported=1 + else + unset isSSLSupported + fi + config_load "$packageName" + config_foreach append_url 'file_url' + load_environment_flag=1 + cache 'test' && return 0 + cache 'test_gzip' && return 0 + if [ "$param" = 'on_boot' ]; then + load_network "$param" + return "$?" + else + return 0 + fi +} + +resolver() { + _dnsmasq_instance_config() { + local cfg="$1" param="$2" + [ -s "/etc/config/dhcp" ] || return 0 + case "$param" in + dnsmasq.addnhosts) + if [ "$(uci_get 'dhcp' "$cfg" 'serversfile')" = "$dnsmasqServersFile" ]; then + uci_remove 'dhcp' "$cfg" 'serversfile' + fi + uci_add_list_if_new 'dhcp' "$cfg" 'addnhosts' "$dnsmasqAddnhostsFile" + ;; + cleanup|dnsmasq.conf|dnsmasq.ipset|dnsmasq.nftset|unbound.adb_list) + uci_remove_list 'dhcp' "$cfg" 'addnhosts' "$dnsmasqAddnhostsFile" + if [ "$(uci_get 'dhcp' "$cfg" 'serversfile')" = "$dnsmasqServersFile" ]; then + uci_remove 'dhcp' "$cfg" 'serversfile' + fi + ;; + dnsmasq.servers) + uci_remove_list 'dhcp' "$cfg" 'addnhosts' "$dnsmasqAddnhostsFile" + if [ "$(uci_get 'dhcp' "$cfg" 'serversfile')" != "$dnsmasqServersFile" ]; then + uci_set 'dhcp' "$cfg" 'serversfile' "$dnsmasqServersFile" + fi + ;; + esac + } + _smartdns_instance_config() { + [ -s "/etc/config/smartdns" ] || return 0 + local cfg="$1" param="$2" + case "$param" in + cleanup) + uci_remove_list 'smartdns' "$cfg" 'conf_files' "$outputConfig" + rm -f "$outputConfig" + ;; + smartdns.domainset) + { echo "domain-set -name adblock-fast -file $outputFile"; \ + echo "domain-rules /domain-set:adblock-fast/ -a #"; } > "$outputConfig" + uci_add_list_if_new 'smartdns' "$cfg" 'conf_files' "$outputConfig" + ;; + smartdns.ipset) + { echo "domain-set -name adblock-fast -file $outputFile"; \ + echo "domain-rules /domain-set:adblock-fast/ -ipset adb"; } > "$outputConfig" + uci_add_list_if_new 'smartdns' "$cfg" 'conf_files' "$outputConfig" + ;; + smartdns.nftset) + local nftset="#4:inet#fw4#adb4" + [ "$ipv6_enabled" -ne '0' ] && nftset="${nftset},#6:inet#fw4#adb6" + { echo "domain-set -name adblock-fast -file $outputFile"; \ + echo "domain-rules /domain-set:adblock-fast/ -nftset $nftset"; } > "$outputConfig" + uci_add_list_if_new 'smartdns' "$cfg" 'conf_files' "$outputConfig" + ;; + esac + } + + local param output_text i + case $1 in + cleanup) + rm -f "$dnsmasqAddnhostsFile" "$dnsmasqAddnhostsCache" "${compressed_cache_dir}/${dnsmasqAddnhostsGzip}" + rm -f "$dnsmasqConfFile" "$dnsmasqConfCache" "${compressed_cache_dir}/${dnsmasqConfGzip}" + rm -f "$dnsmasqIpsetFile" "$dnsmasqIpsetCache" "${compressed_cache_dir}/${dnsmasqIpsetGzip}" + rm -f "$dnsmasqNftsetFile" "$dnsmasqNftsetCache" "${compressed_cache_dir}/${dnsmasqNftsetGzip}" + rm -f "$dnsmasqServersFile" "$dnsmasqServersCache" "${compressed_cache_dir}/${dnsmasqServersGzip}" + rm -f "$smartdnsDomainSetFile" "$smartdnsDomainSetCache" "${compressed_cache_dir}/${smartdnsDomainSetGzip}" "$smartdnsDomainSetConfig" + rm -f "$smartdnsIpsetFile" "$smartdnsIpsetCache" "${compressed_cache_dir}/${smartdnsIpsetGzip}" "$smartdnsIpsetConfig" + rm -f "$smartdnsNftsetFile" "$smartdnsNftsetCache" "${compressed_cache_dir}/${smartdnsNftsetGzip}" "$smartdnsNftsetConfig" + rm -f "$unboundFile" "$unboundCache" "${compressed_cache_dir}/${unboundGzip}" + if [ -s "/etc/config/dhcp" ]; then + config_load 'dhcp' + config_foreach _dnsmasq_instance_config 'dnsmasq' 'cleanup' + [ -n "$(uci_changes 'dhcp')" ] && uci_commit 'dhcp' + fi + if [ -s "/etc/config/smartdns" ]; then + config_load 'smartdns' + config_foreach _smartdns_instance_config 'smartdns' 'cleanup' + [ -n "$(uci_changes 'smartdns')" ] && uci_commit 'smartdns' + fi + ;; + on_start) + if [ ! -s "$outputFile" ]; then + json set status 'statusFail' + json add error 'errorOutputFileCreate' + output "${_ERROR_}: $(get_text 'errorOutputFileCreate')!\\n" + return 1 + fi + + config_load 'dhcp' + if [ "$dnsmasq_instance" = "*" ]; then + config_foreach _dnsmasq_instance_config 'dnsmasq' "$dns" + elif [ -n "$dnsmasq_instance" ]; then + for i in $dnsmasq_instance; do + _dnsmasq_instance_config "@dnsmasq[$i]" "$dns" || _dnsmasq_instance_config "$i" "$dns" + done + fi + config_load 'smartdns' + if [ "$smartdns_instance" = "*" ]; then + config_foreach _smartdns_instance_config 'smartdns' "$dns" + elif [ -n "$smartdns_instance" ]; then + for i in $smartdns_instance; do + _smartdns_instance_config "@smartdns[$i]" "$dns" || _smartdns_instance_config "$i" "$dns" + done + fi + + case "$dns" in + dnsmasq.*) + chmod 660 "$outputFile" + chown root:dnsmasq "$outputFile" >/dev/null 2>/dev/null + param='dnsmasq_restart' + output_text='Restarting dnsmasq' + ;; + smartdns.*) + chmod 660 "$outputFile" "$outputConfig" + chown root:root "$outputFile" "$outputConfig" >/dev/null 2>/dev/null + param='smartdns_restart' + output_text='Restarting SmartDNS' + ;; + unbound.*) + chmod 660 "$outputFile" + chown root:unbound "$outputFile" >/dev/null 2>/dev/null + param='unbound_restart' + output_text='Restarting Unbound' + ;; + esac + + if [ -n "$(uci_changes dhcp)" ]; then + uci_commit 'dhcp' + if ! str_contains "$param" 'dnsmasq_restart'; then + param="${param:+"$param; dnsmasq_restart"}" + output_text="${output_text}/dnsmasq" + fi + fi + if [ -n "$(uci_changes smartdns)" ]; then + uci_commit 'smartdns' + if ! str_contains "$param" 'smartdns_restart'; then + param="${param:+"$param; "}smartdns_restart" + output_text="${output_text}/smartDNS" + fi + fi + output 1 "$output_text " + output 2 "$output_text " + json set message "$output_text" + if eval "$param"; then + json set status 'statusSuccess' + led_on "$led" + output_okn + else + output_fail + json set status 'statusFail' + json add error 'errorDNSReload' + output "${_ERROR_}: $(get_text 'errorDNSReload')!\\n" + return 1 + fi + ;; + on_stop) + case "$dns" in + dnsmasq.*) + param='dnsmasq_restart' + ;; + smartdns.*) + param='smartdns_restart' + ;; + unbound.*) + param='unbound_restart' + ;; + esac + if [ -n "$(uci_changes dhcp)" ]; then + uci_commit 'dhcp' + str_contains "$param" 'dnsmasq_restart' || param="${param:+"$param; dnsmasq_restart"}" + fi + if [ -n "$(uci_changes smartdns)" ]; then + uci_commit 'smartdns' + str_contains "$param" 'smartdns_restart' || param="${param:+"$param; "}smartdns_restart" + fi + eval "$param" + return $? + ;; + quiet|quiet_restart) + case "$dns" in + dnsmasq.*) + param='dnsmasq_restart' + ;; + smartdns.*) + param='smartdns_restart' + ;; + unbound.*) + param='unbound_restart' + ;; + esac + eval "$param" + return $? + ;; + esac +} + +cache() { + local R_TMP + case "$1" in + create|backup) + [ -s "$outputFile" ] && { mv -f "$outputFile" "$outputCache"; } >/dev/null 2>/dev/null + return $? + ;; + restore|use) + [ -s "$outputCache" ] && mv "$outputCache" "$outputFile" >/dev/null 2>/dev/null + return $? + ;; + test) + [ -s "$outputCache" ] + return $? + ;; + test_gzip) + [ -s "$outputGzip" ] && gzip -t -c "$outputGzip" >/dev/null 2>/dev/null + return $? + ;; + create_gzip) + rm -f "$outputGzip" >/dev/null 2>/dev/null + R_TMP="$(mktemp -u -q -t ${packageName}_tmp.XXXXXXXX)" + if gzip < "$outputFile" > "$R_TMP"; then + if mv "$R_TMP" "$outputGzip"; then + rm -f "$R_TMP" + return 0 + else + rm -f "$R_TMP" + return 1 + fi + else + return 1 + fi + ;; + expand|unpack|unpack_gzip) + [ -s "$outputGzip" ] && gzip -dc < "$outputGzip" > "$outputCache" + return $? + ;; + esac +} + +process_file_url_wrapper() { + if [ "$2" != '0' ]; then + json add error 'errorConfigValidationFail' + output "${_ERROR_}: $(get_text 'errorConfigValidationFail')!\\n" + output "Please check if the '$packageConfigFile' contains correct values for config options.\\n" + fi + if [ "$parallel_downloads" -gt 0 ]; then + process_file_url "$1" & + else + process_file_url "$1" + fi +} + +process_file_url() { + local cfg="$1" new_size + local label type D_TMP R_TMP filter + if [ -z "$cfg" ] || [ -n "${2}${3}" ]; then + url="$2" + action="$3" + fi + + [ "$enabled" = '1' ] || return 0 + [ -n "$url" ] || return 1 + + label="${url##*//}" + label="${label%%/*}" + label="File: $label" + case "$action" in + allow) type='Allowed'; D_TMP="$A_TMP" + ;; + block) type='Blocked'; D_TMP="$B_TMP" + ;; + file) type='File'; D_TMP="$B_TMP" + ;; + esac + if is_https_url "$url" && [ -z "$isSSLSupported" ]; then + output 1 "$_FAIL_" + output 2 "[DL] $type $label $__FAIL__\\n" + echo "errorNoSSLSupport|${1}" >> "$runningErrorFile" + return 0 + fi + while [ -z "$R_TMP" ] || [ -e "$R_TMP" ]; do + R_TMP="$(mktemp -u -q -t ${packageName}_tmp.XXXXXXXX)" + done + if [ -z "$url" ] || ! $dl_command "$url" "$dl_flag" "$R_TMP" 2>/dev/null || \ + [ ! -s "$R_TMP" ]; then + output 1 "$_FAIL_" + output 2 "[DL] $type $label $__FAIL__\\n" + echo "errorDownloadingList|${url}" >> "$runningErrorFile" + else + append_newline "$R_TMP" + [ -n "$cfg" ] && new_size="$(get_local_filesize "$R_TMP")" + if [ -n "$new_size" ] && [ "$size" != "$new_size" ]; then + uci_set "$packageName" "$cfg" 'size' "$size" + fi + format="$(detect_file_type "$R_TMP")" + case "$format" in + adblockplus) filter="$adBlockPlusFilter";; + dnsmasq) filter="$dnsmasqFileFilter";; + dnsmasq2) filter="$dnsmasq2FileFilter";; + dnsmasq3) filter="$dnsmasq3FileFilter";; + domains) filter="$domainsFilter";; + hosts) filter="$hostsFilter";; + *) + output 1 "$_FAIL_" + output 2 "[DL] $type $label $__FAIL__\\n" + echo "errorDetectingFileType|${url}" >> "$runningErrorFile" + rm -f "$R_TMP" + return 0 + ;; + esac + if [ -n "$filter" ] && [ "$action" != 'file' ]; then + sed -i "$filter" "$R_TMP" + fi + if [ ! -s "$R_TMP" ]; then + output 1 "$_FAIL_" + output 2 "[DL] $type $label ($format) $__FAIL__\\n" + echo "errorParsingList|${url}" >> "$runningErrorFile" + else + append_newline "$R_TMP" + cat "${R_TMP}" >> "$D_TMP" + output 1 "$_OK_" + output 2 "[DL] $type $label ($format) $__OK__\\n" + fi + fi + rm -f "$R_TMP" + return 0 +} + +download_dnsmasq_file() { + json set message "$(get_text 'statusDownloading')..." + json set status 'statusDownloading' + + rm -f "$A_TMP" "$B_TMP" "$SED_TMP" "$outputFile" "$outputCache" "$outputGzip" + if [ "$(get_ram_free)" -lt 32 ]; then + output 3 'Low free memory, restarting resolver ' + if resolver 'quiet_restart'; then + output_okn + else + output_failn + fi + fi + touch "$A_TMP" "$B_TMP" "$SED_TMP" + output 1 'Downloading dnsmasq file ' + rm -f "$runningErrorFile" + process_file_url '' "$dnsmasq_config_file_url" 'file' + if [ -s "$runningErrorFile" ]; then + while IFS= read -r line; do + json add error "$line" + done < "$runningErrorFile" + rm -f "$runningErrorFile" + fi + output 2 'Moving dnsmasq file ' + if mv "$B_TMP" "$outputFile"; then + output 2 "$__OK__\\n" + else + output 2 "$__FAIL__\\n" + json add error 'errorMovingDataFile' + fi + output 1 '\n' +} + +download_lists() { + _ram_check() { + _config_calculate_sizes() { + local cfg="$1" + local en size url + config_get_bool en "$cfg" enabled '1' + config_get size "$cfg" size + config_get url "$cfg" url + [ "$en" = '0' ] && return 0 + [ -n "$size" ] || size="$(get_url_filesize "$url")" + [ -n "$size" ] && total_sizes=$((total_sizes+size)) + } + local i free_mem total_sizes + free_mem="$(get_ram_free)" + if [ -z "$free_mem" ]; then + json add warnning 'warningFreeRamCheckFail' + output "${_WARNING_}: $(get_text 'warningFreeRamCheckFail')!\\n" + return 0 + fi + config_load "$packageName" + config_foreach _config_calculate_sizes 'file_url' + if [ $((free_mem)) -lt $((total_sizes * 2)) ]; then + json add error 'errorTooLittleRam' "$free_mem" + output "${_ERROR_}: $(get_text 'errorTooLittleRam' "$free_mem")!\\n" + return 1 + else + return 0 + fi + } + local hf j=0 R_TMP + + _ram_check || return 1 + + json set message "$(get_text 'statusDownloading')..." + json set status 'statusDownloading' + + rm -f "$A_TMP" "$B_TMP" "$SED_TMP" "$outputFile" "$outputCache" "$outputGzip" + if [ "$(get_ram_total)" -lt 33554432 ]; then + output 3 'Low free memory, restarting resolver ' + if resolver 'quiet_restart'; then + output_okn + else + output_failn + fi + fi + touch "$A_TMP" "$B_TMP" "$SED_TMP" + output 1 'Downloading lists ' + rm -f "$runningErrorFile" + config_load "$packageName" + config_foreach load_validate_file_url_section 'file_url' process_file_url_wrapper + wait + if [ -n "$(uci_changes "$packageName")" ]; then + output 2 "Saving updated file size(s) " + if uci_commit "$packageName"; then output_okn; else output_failn; fi + fi + output 1 '\n' + + if [ -s "$runningErrorFile" ]; then + while IFS= read -r line; do + json add error "$line" + done < "$runningErrorFile" + rm -f "$runningErrorFile" + fi + + if [ "$canary_domains_icloud" -ne '0' ]; then + canaryDomains="${canaryDomains:+$canaryDomains }${canaryDomainsiCloud}" + fi + if [ "$canary_domains_mozilla" -ne '0' ]; then + canaryDomains="${canaryDomains:+$canaryDomains }${canaryDomainsMozilla}" + fi + + append_newline "$B_TMP" + for hf in $blocked_domain $canaryDomains; do + printf "%s\n" "$(echo "$hf" | sed "$domainsFilter")" >> "$B_TMP" + done + allowed_domain="${allowed_domain} +$(sed '/^[[:space:]]*$/d' "$A_TMP")" + for hf in ${allowed_domain}; do + hf="$(echo "$hf" | sed 's/\./\\./g')" + echo "/(^|\.)${hf}$/d;" >> "$SED_TMP" + done + + sed -i '/^[[:space:]]*$/d' "$B_TMP" + [ ! -s "$B_TMP" ] && return 1 + + output 1 'Processing downloads ' + output 2 'Sorting combined list ' + json set status 'statusProcessing' + json set message "$(get_text 'statusProcessing'): sorting combined list" + if [ "$allow_non_ascii" -gt 0 ]; then + if sort -u "$B_TMP" > "$A_TMP"; then + output_ok + else + output_failn + json add error 'errorSorting' + fi + else + if sort -u "$B_TMP" | grep -E -v '[^a-zA-Z0-9=/.-]' > "$A_TMP"; then + output_ok + else + output_failn + json add error 'errorSorting' + fi + fi + + if [ "$dns" = 'dnsmasq.conf' ] || \ + [ "$dns" = 'dnsmasq.ipset' ] || \ + [ "$dns" = 'dnsmasq.nftset' ] || \ + [ "$dns" = 'dnsmasq.servers' ] || \ + [ "$dns" = 'smartdns.domainset' ] || \ + [ "$dns" = 'smartdns.ipset' ] || \ + [ "$dns" = 'smartdns.nftset' ] || \ + [ "$dns" = 'unbound.adb_list' ]; then + # TLD optimization written by Dirk Brenken (dev@brenken.org) + output 2 'Optimizing combined list ' + json set message "$(get_text 'statusProcessing'): optimizing combined list" +# sed -E 'G;:t;s/(.*)(\.)(.*)(\n)(.*)/\1\4\5\2\3/;tt;s/(.*)\n(\.)(.*)/\3\2\1/' is actually slower than command below +# shellcheck disable=SC2016 + if $awk -F "." '{for(f=NF;f>1;f--)printf "%s.",$f;print $1}' "$A_TMP" > "$B_TMP"; then + if sort "$B_TMP" > "$A_TMP"; then + if $awk '{if(NR=1){tld=$NF};while(getline){if($NF!~tld"\\."){print tld;tld=$NF}}print tld}' "$A_TMP" > "$B_TMP"; then + if $awk -F "." '{for(f=NF;f>1;f--)printf "%s.",$f;print $1}' "$B_TMP" > "$A_TMP"; then + if sort -u "$A_TMP" > "$B_TMP"; then + output_ok + else + output_failn + json add error 'errorOptimization' + mv "$A_TMP" "$B_TMP" + fi + else + output_failn + json add error 'errorOptimization' + fi + else + output_failn + json add error 'errorOptimization' + mv "$A_TMP" "$B_TMP" + fi + else + output_failn + json add error 'errorOptimization' + fi + else + output_failn + json add error 'errorOptimization' + mv "$A_TMP" "$B_TMP" + fi + else + mv "$A_TMP" "$B_TMP" + fi + + if [ -s "$SED_TMP" ]; then + output 2 'Allowing domains ' + json set message "$(get_text 'statusProcessing'): allowing domains" + if sed -i -E -f "$SED_TMP" "$B_TMP"; then + output_ok + else + output_failn + json add error 'errorAllowListProcessing' + fi + fi + output 2 'Formatting merged file ' + json set message "$(get_text 'statusProcessing'): formatting merged file" + if [ -z "$outputFilterIPv6" ]; then + if sed "$outputFilter" "$B_TMP" > "$A_TMP"; then + output_ok + else + output_failn + json add error 'errorDataFileFormatting' + fi + else + case "$dns" in + dnsmasq.addnhosts) + if sed "$outputFilter" "$B_TMP" > "$A_TMP" && \ + sed "$outputFilterIPv6" "$B_TMP" >> "$A_TMP"; then + output_ok + else + output_failn + json add error 'errorDataFileFormatting' + fi + ;; + esac + fi + + case "$dns" in + dnsmasq.addnhosts) + output 2 'Creating dnsmasq addnhosts file ' + json set message "$(get_text 'statusProcessing'): creating dnsmasq addnhosts file" + ;; + dnsmasq.conf) + output 2 'Creating dnsmasq config file ' + json set message "$(get_text 'statusProcessing'): creating dnsmasq config file" + ;; + dnsmasq.ipset) + output 2 'Creating dnsmasq ipset file ' + json set message "$(get_text 'statusProcessing'): creating dnsmasq ipset file" + ;; + dnsmasq.nftset) + output 2 'Creating dnsmasq nft set file ' + json set message "$(get_text 'statusProcessing'): creating dnsmasq nft set file" + ;; + dnsmasq.servers) + output 2 'Creating dnsmasq servers file ' + json set message "$(get_text 'statusProcessing'): creating dnsmasq servers file" + ;; + smartdns.domainset) + output 2 'Creating smartdns domain-set file ' + json set message "$(get_text 'statusProcessing'): creating smartdns domain-set file" + ;; + smartdns.ipset) + output 2 'Creating smartdns domain-set file ' + json set message "$(get_text 'statusProcessing'): creating smartdns ipset file" + ;; + smartdns.nftset) + output 2 'Creating smartdns domain-set file ' + json set message "$(get_text 'statusProcessing'): creating smartdns nft set file" + ;; + unbound.adb_list) + output 2 'Creating Unbound adb_list file ' + json set message "$(get_text 'statusProcessing'): creating Unbound adb_list file" + ;; + esac + + if mv "$A_TMP" "$outputFile"; then + output_ok + else + output_failn + json add error 'errorMovingDataFile' + fi + case "$dns" in + unbound.adb_list) + sed -i '1 i\server:' "$outputFile" + ;; + esac + if [ "$compressed_cache" -gt 0 ]; then + output 2 'Creating compressed cache ' + json set message "$(get_text 'statusProcessing'): creating compressed cache" + if cache 'create_gzip'; then + output_ok + else + output_failn + json add error 'errorCreatingCompressedCache' + fi + else + rm -f "$outputGzip" + fi + output 2 'Removing temporary files ' + json set message "$(get_text 'statusProcessing'): removing temporary files" + rm -f "/tmp/${packageName}_tmp.*" "$A_TMP" "$B_TMP" "$SED_TMP" "$outputCache" || j=1 + if [ $j -eq 0 ]; then + output_ok + else + output_failn + json add error 'errorRemovingTempFiles' + fi + output 1 '\n' +} + +adb_allow() { + local c hf string="$1" + local validation_result="$3" + load_environment "$validation_result" 'quiet' || return 1 + if [ ! -s "$outputFile" ]; then + output "No block-list ('$outputFile') found.\\n" + return 0 + elif [ -z "$string" ]; then + output "Usage: /etc/init.d/${packageName} allow 'domain' ...\\n" + return 0 + elif [ -n "$dnsmasq_config_file_url" ]; then + output "Allowing individual domains is not possible when using external dnsmasq config file.\\n" + return 0 + fi + case "$dns" in + dnsmasq.*) + output 1 "Allowing domain(s) and restarting dnsmasq " + output 2 "Allowing domain(s) \\n" + for c in $string; do + output 2 " $c " + hf="$(echo "$c" | sed 's/\./\\./g')" + if sed -i "\:\(/\|\.\)${hf}/:d" "$outputFile" && \ + uci_add_list_if_new "${packageName}" 'config' 'allowed_domain' "$c"; then + output_ok + else + output_fail + fi + done + if [ "$compressed_cache" -gt 0 ]; then + output 2 'Creating compressed cache ' + if cache 'create_gzip'; then + output_ok + else + output_failn + fi + fi + output 2 "Committing changes to config " + if uci_commit "$packageName"; then + allowed_domain="$(uci_get "$packageName" 'config' 'allowed_domain')" + config_cache 'create' + json set stats "$serviceName is blocking $(wc -l < "$outputFile") domains (with ${dns})" + output_ok; + if [ "$dns" = 'dnsmasq.ipset' ]; then + output 2 "Flushing adb ipset " + if ipset -q -! flush adb; then output_ok; else output_fail; fi + fi + if [ "$dns" = 'dnsmasq.nftset' ]; then + output 2 "Flushing adb nft sets " + nft flush set inet fw4 adb6 + if nft flush set inet fw4 adb4; then output_ok; else output_fail; fi + fi + output 2 "Restarting dnsmasq " + if dnsmasq_restart; then output_okn; else output_failn; fi + else + output_fail; + fi + ;; + smartdns.*) + output 1 "Allowing domain(s) and restarting smartdns " + output 2 "Allowing domain(s) \\n" + for c in $string; do + output 2 " $c " + hf="$(echo "$c" | sed 's/\./\\./g')" + if sed -i "\:\(\"\|\.\)${hf}\":d" "$outputFile" && \ + uci_add_list_if_new "$packageName" 'config' 'allowed_domain' "$string"; then + output_ok + else + output_fail + fi + done + if [ "$compressed_cache" -gt 0 ]; then + output 2 'Creating compressed cache ' + if cache 'create_gzip'; then + output_ok + else + output_failn + fi + fi + output 2 "Committing changes to config " + if uci_commit "$packageName"; then + allowed_domain="$(uci_get "$packageName" 'config' 'allowed_domain')" + config_cache 'create' + json set stats "$serviceName is blocking $(wc -l < "$outputFile") domains (with ${dns})" + output_ok; + output 2 "Restarting Unbound " + if unbound_restart; then output_okn; else output_failn; fi + else + output_fail; + fi + ;; + unbound.*) + output 1 "Allowing domain(s) and restarting Unbound " + output 2 "Allowing domain(s) \\n" + for c in $string; do + output 2 " $c " + hf="$(echo "$c" | sed 's/\./\\./g')" + if sed -i "\:\(\"\|\.\)${hf}\":d" "$outputFile" && \ + uci_add_list_if_new "$packageName" 'config' 'allowed_domain' "$string"; then + output_ok + else + output_fail + fi + done + if [ "$compressed_cache" -gt 0 ]; then + output 2 'Creating compressed cache ' + if cache 'create_gzip'; then + output_ok + else + output_failn + fi + fi + output 2 "Committing changes to config " + if uci_commit "$packageName"; then + allowed_domain="$(uci_get "$packageName" 'config' 'allowed_domain')" + config_cache 'create' + json set stats "$serviceName is blocking $(wc -l < "$outputFile") domains (with ${dns})" + output_ok; + output 2 "Restarting Unbound " + if unbound_restart; then output_okn; else output_failn; fi + else + output_fail; + fi + ;; + esac +} + +adb_check() { + local c param="$1" + local validation_result="$3" + load_environment "$validation_result" 'quiet' || return 1 + if [ ! -s "$outputFile" ]; then + output "No block-list ('$outputFile') found.\\n" + return 0 + elif [ -z "$param" ]; then + output "Usage: /etc/init.d/${packageName} check 'domain' ...\\n" + return 0 + fi + for string in ${param}; do + c="$(grep -c "$string" "$outputFile")" + if [ "$c" -gt 0 ]; then + if [ "$c" -eq 1 ]; then + output "Found 1 match for '$string' in '$outputFile'.\\n" + else + output "Found $c matches for '$string' in '$outputFile'.\\n" + fi + if [ "$c" -le 20 ]; then + case "$dns" in + dnsmasq.addnhosts) + grep "$string" "$outputFile" | sed 's|^127.0.0.1 ||;s|^:: ||;';; + dnsmasq.conf) + grep "$string" "$outputFile" | sed 's|local=/||;s|/$||;';; + dnsmasq.ipset) + grep "$string" "$outputFile" | sed 's|ipset=/||;s|/adb$||;';; + dnsmasq.nftset) + grep "$string" "$outputFile" | sed 's|nftset=/||;s|/4#inet#adb#adb4||;';; + dnsmasq.servers) + grep "$string" "$outputFile" | sed 's|server=/||;s|/$||;';; + smartdns.*) + grep "$string" "$outputFile";; + unbound.adb_list) + grep "$string" "$outputFile" | sed 's|^local-zone: "||;s|." always_nxdomain$||;';; + esac + fi + else + output "The '$string' is not found in current block-list ('$outputFile').\\n" + fi + done +} + +adb_check_lists() { + _check_list() { + local cfg="$1" + local en size url R_TMP string c + config_get_bool en "$cfg" enabled '1' + config_get action "$cfg" action 'block' + config_get url "$cfg" url + [ "$en" = '0' ] && return 0 + [ "$action" != 'block' ] && return 0 + if is_https_url "$url" && [ -z "$isSSLSupported" ]; then + output "[DL] $url $__FAIL__\\n" + fi + while [ -z "$R_TMP" ] || [ -e "$R_TMP" ]; do + R_TMP="$(mktemp -u -q -t ${packageName}_tmp.XXXXXXXX)" + done + if [ -z "$url" ] || ! $dl_command "$url" "$dl_flag" "$R_TMP" 2>/dev/null || \ + [ ! -s "$R_TMP" ]; then + output "[DL] $url $__FAIL__\\n" + else + append_newline "$R_TMP" + for string in ${param}; do + c="$(grep -c "$string" "$R_TMP")" + if [ "$c" -gt 0 ]; then + if [ "$c" -eq 1 ]; then + output "Found 1 match for '$string' in '$url'.\\n" + else + output "Found $c matches for '$string' in '$url'.\\n" + fi + grep "$string" "$R_TMP" + else + output "The '$string' is not found in '$url'.\\n" + fi + done + rm -f "$R_TMP" + fi + } + local param="$1" + local validation_result="$3" + load_environment "$validation_result" 'quiet' || return 1 + if [ -z "$param" ]; then + output "Usage: /etc/init.d/${packageName} check_lists 'domain' ...\\n" + return 0 + fi + config_load "$packageName" + config_foreach _check_list 'file_url' + return 0 +} + +adb_config_update() { + local R_TMP label + local param validation_result="$3" + case "$1" in + on_boot) param="$1";; + *) param='quiet';; + esac + load_environment "$validation_result" "$param" || return 1 + label="${config_update_url##*//}" + label="${label%%/*}"; + [ "$config_update_enabled" -ne '0' ] || return 0 + + if [ "$param" != 'download' ]; then + cache 'test' && return 0 + cache 'test_gzip' && return 0 + fi + output 1 'Updating config ' + while [ -z "$R_TMP" ] || [ -e "$R_TMP" ]; do + R_TMP="$(mktemp -u -q -t ${packageName}_tmp.XXXXXXXX)" + done + if ! $dl_command "$config_update_url" "$dl_flag" "$R_TMP" 2>/dev/null || [ ! -s "$R_TMP" ]; then + append_newline "$R_TMP" + output 1 "$_FAIL_\\n" + output 2 "[DL] Config Update: $label $__FAIL__\\n" + json add error 'errorDownloadingConfigUpdate' + else + if [ -s "$R_TMP" ] && sed -f "$R_TMP" -i "$packageConfigFile" 2>/dev/null; then + output 1 "$_OK_\\n" + output 2 "[DL] Config Update: $label $__OK__\\n" + else + output 1 "$_FAIL_\\n" + output 2 "[DL] Config Update: $label $__FAIL__\\n" + json add error 'errorParsingConfigUpdate' + fi + fi + rm -f "$R_TMP" + return 0 +} + +adb_sizes() { + _config_add_url_size() { + local cfg="$1" url size + config_get url "$cfg" url + size="$(get_url_filesize "$url")" + output "$url${size:+: $size} " + if [ -n "$size" ]; then + uci_set "$packageName" "$cfg" 'size' "$size" + output_okn + else + output_failn + fi + } + local i + local validation_result="$3" + load_environment "$validation_result" 'quiet' || return 1 + config_load "$packageName" + config_foreach _config_add_url_size 'file_url' + uci_commit "$packageName" +} + +# shellcheck disable=SC2120 +adb_start() { + local action status error message stats c iface + local param="$1" validation_result="$3" + + load_environment "$validation_result" "$param" || return 1 + + status="$(json get 'status')" + error="$(json get 'error')" + message="$(json get 'message')" + stats="$(json get 'stats')" + action="$(config_cache get 'trigger_service')" + fw4_restart_flag="$(config_cache get 'trigger_fw4')" + + if [ "$action" = 'on_boot' ] || [ "$param" = 'on_boot' ] || [ "$param" = 'on_pause' ]; then + if cache 'test_gzip' || cache 'test'; then + action='restore' + else + action='download' + fi + elif [ "$action" = 'download' ] || [ "$param" = 'download' ] || [ -n "$error" ]; then + action='download' + elif [ ! -s "$outputFile" ]; then + if cache 'test_gzip' || cache 'test'; then + action='restore' + else + action='download' + fi + elif [ "$action" = 'restart' ] || [ "$param" = 'restart' ]; then + action='restart' + elif [ -s "$outputFile" ] && [ "$status" = "statusSuccess" ] && [ -z "$error" ]; then + status_service 'quiet' + return 0 + else + action='download' + fi + + json del all + config_cache 'create' + + if [ "$action" = 'restore' ]; then + output 0 "Starting $serviceName... " + output 3 "Starting $serviceName...\\n" + json set status 'statusStarting' + if cache 'test_gzip' && ! cache 'test' && [ ! -s "$outputFile" ]; then + output 3 'Found compressed cache file, unpacking it ' + json set message 'found compressed cache file, unpacking it.' + if cache 'unpack_gzip'; then + output_okn + else + output_failn + json add error 'errorRestoreCompressedCache' + output "${_ERROR_}: $(get_text 'errorRestoreCompressedCache')!\\n" + action='download' + fi + fi + if cache 'test' && [ ! -s "$outputFile" ]; then + output 3 'Found cache file, reusing it ' + json set message 'found cache file, reusing it.' + if cache 'restore'; then + output_okn + resolver 'on_start' + else + output_failn + json add error 'errorRestoreCache' + output "${_ERROR_}: $(get_text 'errorRestoreCache')!\\n" + action='download' + fi + fi + fi + if [ "$action" = 'download' ]; then + if [ -z "$blocked_url" ] && [ -z "$blocked_domain" ]; then + json set status 'statusFail' + json add error 'errorNothingToDo' + output "${_ERROR_}: $(get_text 'errorNothingToDo')!\\n" + else + if [ -s "$outputFile" ] || cache 'test' || cache 'test_gzip'; then + output 0 "Force-reloading $serviceName... " + output 3 "Force-reloading $serviceName...\\n" + json set status 'statusForceReloading' + else + output 0 "Starting $serviceName... " + output 3 "Starting $serviceName...\\n" + json set status 'statusStarting' + fi + resolver 'cleanup' + if [ "$dns" = 'dnsmasq.conf' ] && [ -n "$dnsmasq_config_file_url" ]; then + download_dnsmasq_file + else + download_lists + fi + resolver 'on_start' + fi + fi + if [ "$action" = 'restart' ]; then + output 0 "Restarting $serviceName... " + output 3 "Restarting $serviceName...\\n" + json set status 'statusRestarting' + resolver 'on_start' + fi + if [ "$action" = 'start' ]; then + output 0 "Starting $serviceName... " + output 3 "Starting $serviceName...\\n" + json set status 'statusStarting' + resolver 'on_start' + fi + if [ -s "$outputFile" ] && [ "$(json get status)" != "statusFail" ]; then + output 0 "$__OK__\\n"; + json del message + json set status 'statusSuccess' + json set stats "$serviceName is blocking $(wc -l < "$outputFile") domains (with ${dns})" + status_service 'quiet' + + else + output 0 "$__FAIL__\\n"; + json set status 'statusFail' + json add error 'errorOhSnap' + status_service 'quiet' + fi + + procd_open_instance 'main' + procd_set_param command /bin/true + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_open_data + json_add_string 'status' "$(json get status)" + json_add_string 'errors' "$(json get error)" + json_add_string 'warnings' "$(json get warning)" + if [ -s "$outputFile" ]; then + json_add_int 'entries' "$(wc -l < "$outputFile")" + else + json_add_int 'entries' '0' + fi + json_add_array firewall + if [ "$force_dns" -ne '0' ]; then +# shellcheck disable=SC3060 + for c in ${force_dns_port/,/ }; do + if netstat -tuln | grep LISTEN | grep ":${c}" >/dev/null 2>&1; then + for iface in $force_dns_interface; do + json_add_object "" + json_add_string type redirect + json_add_string target DNAT + json_add_string src "$iface" + json_add_string proto "tcp udp" + json_add_string src_dport "$c" + json_add_string dest_port "$c" + json_add_string family any + json_add_boolean reflection 0 + json_close_object + done + else + for iface in $force_dns_interface; do + json_add_object "" + json_add_string type rule + json_add_string src "$iface" + json_add_string dest "*" + json_add_string proto "tcp udp" + json_add_string dest_port "$c" + json_add_string target REJECT + json_close_object + done + fi + done + fi + case "$dns" in + dnsmasq.ipset|smartdns.ipset) + json_add_object "" + json_add_string type ipset + json_add_string name adb + json_add_string match dest_net + json_add_string storage hash + json_close_object + for iface in $force_dns_interface; do + json_add_object "" + json_add_string type rule + json_add_string ipset adb + json_add_string src "$iface" + json_add_string dest "*" + json_add_string proto "tcp udp" + json_add_string target REJECT + json_close_object + done + ;; + dnsmasq.nftset|smartdns.nftset) + json_add_object "" + json_add_string type ipset + json_add_string name adb4 + json_add_string family 4 + json_add_string match dest_net + json_close_object + for iface in $force_dns_interface; do + json_add_object "" + json_add_string type rule + json_add_string ipset adb4 + json_add_string src "$iface" + json_add_string dest "*" + json_add_string proto "tcp udp" + json_add_string target REJECT + json_close_object + done + if [ "$ipv6_enabled" -ne '0' ]; then + json_add_object "" + json_add_string type ipset + json_add_string name adb6 + json_add_string family 6 + json_add_string match dest_net + json_close_object + for iface in $force_dns_interface; do + json_add_object "" + json_add_string type rule + json_add_string ipset adb6 + json_add_string src "$iface" + json_add_string dest "*" + json_add_string proto "tcp udp" + json_add_string target REJECT + json_close_object + done + fi + ;; + esac + json_close_array + procd_close_data + procd_close_instance + return 0 +} + +adb_status() { + local param="$1" + local c status message error warning stats text + status="$(json get status)" + message="$(json get message)" + error="$(json get error)" + warning="$(json get warning)" + stats="$(json get stats)" + if [ "$status" = "statusSuccess" ]; then + output "$stats "; output_okn; + else + [ -n "$status" ] && status="$(get_text "$status")" + if [ -n "$status" ] && [ -n "$message" ]; then + status="${status}: $message" + fi + [ -n "$status" ] && output "$serviceName $status!\\n" + fi + if [ "$param" != 'quiet' ] && [ -n "$error" ]; then + for c in $error; do + local error_param="${c##*|}" + local error_code="${c%|*}" + output "${_ERROR_}: $(get_text "$error_code" "$error_param")!\\n" + done + fi + if [ "$param" != 'quiet' ] && [ -n "$warning" ]; then + for c in $warning; do + local warning_param="${c##*|}" + local warning_code="${c%|*}" + output "${_WARNING_}: $(get_text "$warning_code" "$warning_param").\\n" + done + fi + return 0 +} + +# shellcheck disable=SC2120 +adb_stop() { + local validation_result="$3" + load_environment "$validation_result" 'quiet' || return 0 + if [ -s "$outputFile" ]; then + output "Stopping $serviceName... " + cache 'create' + if resolver 'on_stop'; then + ipset -q -! flush adb + ipset -q -! destroy adb + nft delete set inet fw4 adb4 + nft delete set inet fw4 adb6 + led_off "$led" + output 0 "$__OK__\\n"; output_okn; + json set status 'statusStopped' + json del message + else + output 0 "$__FAIL__\\n"; output_fail; + json set status 'statusFail' + json add error 'errorStopping' + output "${_ERROR_}: $(get_text 'errorStopping')!\\n" + fi + fi + return 0 +} + +adb_pause() { + local timeout="${1:-$pause_timeout}" + local validation_result="$3" + adb_stop 'on_pause' '' "$validation_result" + output "Sleeping for $timeout seconds... " + if is_integer "$timeout" && sleep "$timeout"; then + output_okn + else + output_failn + fi + adb_start 'on_pause' '' "$validation_result" +} + +allow() { load_validate_config 'config' adb_allow "'$*'"; } +boot() { + local procd_boot_delay + ubus -t 30 wait_for network.interface 2>/dev/null + config_load "$packageName" + config_get procd_boot_delay 'config' 'procd_boot_delay' '0' +# shellcheck disable=SC2154 + { is_integer "$procd_boot_delay" && sleep "$procd_boot_delay"; \ + rc_procd start_service 'on_boot' && service_started 'on_boot'; } & +} +check() { load_validate_config 'config' adb_check "'$*'"; } +check_lists() { load_validate_config 'config' adb_check_lists "'$*'"; } +dl() { rc_procd start_service 'download'; } +killcache() { + local compressed_cache_dir + config_load "$packageName" + config_get compressed_cache_dir 'config' 'compressed_cache_dir' '/etc' + if [ "$(sanitize_dir "$compressed_cache_dir")" = '/' ]; then + compressed_cache_dir='' + elif [ -n "$(sanitize_dir "$compressed_cache_dir")" ]; then + compressed_cache_dir="$(sanitize_dir "$compressed_cache_dir")" + else + compressed_cache_dir="/etc" + fi + rm -f "$dnsmasqAddnhostsCache" "${compressed_cache_dir}/${dnsmasqAddnhostsGzip}" + rm -f "$dnsmasqConfCache" "${compressed_cache_dir}/${dnsmasqConfGzip}" + rm -f "$dnsmasqIpsetCache" "${compressed_cache_dir}/${dnsmasqIpsetGzip}" + rm -f "$dnsmasqNftsetCache" "${compressed_cache_dir}/${dnsmasqNftsetGzip}" + rm -f "$dnsmasqServersCache" "${compressed_cache_dir}/${dnsmasqServersGzip}" + rm -f "$smartdnsDomainSetCache" "${compressed_cache_dir}/${smartdnsDomainSetGzip}" + rm -f "$smartdnsIpsetCache" "${compressed_cache_dir}/${smartdnsIpsetGzip}" + rm -f "$smartdnsNftsetCache" "${compressed_cache_dir}/${smartdnsNftsetGzip}" + rm -f "$unboundCache" "${compressed_cache_dir}/${unboundGzip}" + resolver 'cleanup' + return 0 +} +reload_service() { rc_procd start_service 'restart'; } +restart_service() { rc_procd start_service 'restart'; } +service_started() { is_fw4_restart_needed && procd_set_config_changed firewall; } +service_stopped() { is_fw4_restart_needed && procd_set_config_changed firewall; } +service_triggers() { + local wan wan6 i + local procd_trigger_wan6 + config_load "$packageName" + config_get_bool procd_trigger_wan6 'config' 'procd_trigger_wan6' '0' + network_flush_cache + network_find_wan wan + wan="${wan:-wan}" + if [ "$procd_trigger_wan6" -ne '0' ]; then + network_find_wan6 wan6 + wan6="${wan6:-wan6}" + fi + for i in "$wan" "$wan6"; do + [ -n "$i" ] && procd_add_interface_trigger "interface.*" "$i" "/etc/init.d/${packageName}" start + done + procd_add_config_trigger "config.change" "$packageName" "/etc/init.d/${packageName}" reload +} +sizes() { load_validate_config 'config' adb_sizes "''"; } +start_service() { + load_validate_config 'config' adb_config_update "'$*'" + load_validate_config 'config' adb_start "'$*'" +} +status_service() { adb_status "$@"; } +stop_service() { load_validate_config 'config' adb_stop "'$*'"; } +pause() { load_validate_config 'config' adb_pause "'$*'"; } +version() { echo "$PKG_VERSION"; } + +load_validate_file_url_section() { + uci_load_validate "$packageName" "$packageName" "$1" "$2" \ + 'enabled:bool:1' \ + 'action:or("allow", "block"):block' \ + 'size:or(uinteger, "")' \ + 'url:string' +} + +load_validate_config() { + local enabled + local force_dns + local force_dns_interface + local force_dns_port + local parallel_downloads + local debug + local compressed_cache + local compressed_cache_dir + local ipv6_enabled + local allow_non_ascii + local canary_domains_icloud + local canary_domains_mozilla + local config_update_enabled + local config_update_url + local download_timeout + local pause_timeout + local curl_additional_param + local curl_max_file_size + local curl_retry + local verbosity + local procd_trigger_wan6 + local procd_boot_wan_timeout + local procd_lan_interface_name + local led + local dns + local dnsmasq_instance + local allowed_domain + local blocked_domain + local dnsmasq_config_file_url + uci_load_validate "$packageName" "$packageName" "$1" "${2}${3:+ $3}" \ + 'enabled:bool:0' \ + 'force_dns:bool:1' \ + 'force_dns_interface:list(network):lan' \ + 'force_dns_port:list(integer):53,853' \ + 'parallel_downloads:bool:1' \ + 'debug:bool:0' \ + 'compressed_cache:bool:0' \ + 'compressed_cache_dir:directory:/etc' \ + 'ipv6_enabled:bool:0' \ + 'allow_non_ascii:bool:0' \ + 'canary_domains_icloud:bool:0' \ + 'canary_domains_mozilla:bool:0' \ + 'config_update_enabled:bool:0' \ + 'config_update_url:string:https://cdn.jsdelivr.net/gh/openwrt/packages/net/adblock-fast/files/adblock-fast.config.update' \ + 'download_timeout:range(1,60):20' \ + 'pause_timeout:range(1,60):20' \ + 'curl_additional_param:or("", string)' \ + 'curl_max_file_size:or("", uinteger)' \ + 'curl_retry:range(0,30):3' \ + 'verbosity:range(0,2):2' \ + 'procd_trigger_wan6:bool:0' \ + 'procd_boot_wan_timeout:integer:60' \ + 'led:or("", "none", file, device, string)' \ + 'dns:or("dnsmasq.addnhosts", "dnsmasq.conf", "dnsmasq.ipset", "dnsmasq.nftset", "dnsmasq.servers", "smartdns.domainset", "smartdns.ipset", "smartdns.nftset", "unbound.adb_list"):dnsmasq.servers' \ + 'dnsmasq_instance:list(or(integer, string)):*' \ + 'smartdns_instance:list(or(integer, string)):*' \ + 'allowed_domain:list(string)' \ + 'blocked_domain:list(string)' \ + 'dnsmasq_config_file_url:string' +} diff --git a/net/adblock-fast/files/etc/uci-defaults/90-adblock-fast b/net/adblock-fast/files/etc/uci-defaults/90-adblock-fast new file mode 100644 index 00000000..0ba1f9a3 --- /dev/null +++ b/net/adblock-fast/files/etc/uci-defaults/90-adblock-fast @@ -0,0 +1,128 @@ +#!/bin/sh +# Copyright 2023 MOSSDeF, Stan Grishin (stangri@melmac.ca) +# shellcheck disable=SC2015,SC3043,SC3060 + +readonly adbFunctionsFile='/etc/init.d/adblock-fast' +if [ -s "$adbFunctionsFile" ]; then +# shellcheck source=../../etc/init.d/adblock-fast + . "$adbFunctionsFile" +else + printf "%b: adblock-fast init.d file (%s) not found! \n" '\033[0;31mERROR\033[0m' "$adbFunctionsFile" +fi + +# Transition from simple-adblock +_enable_url() { + local cfg="$1" url="$2" action="$3" + local u a + config_get u "$cfg" 'url' + config_get a "$cfg" 'action' 'block' + if [ "$u" = "$url" ] && [ "$a" = "$action" ]; then + uci del "${packageName}.${cfg}.enabled" && _found=1 + fi +} + +enable_add_url() { + local url="$1" action="$2" _found + config_load "$packageName" + config_foreach _enable_url 'file_url' "$url" "$action" + if [ -z "$_found" ]; then + uci add "${packageName}" 'file_url' >/dev/null 2>&1 + uci set "${packageName}.@file_url[-1].url=$url" + uci set "${packageName}.@file_url[-1].size=$(get_url_filesize "$url")" + uci set "${packageName}.@file_url[-1].action=$action" + fi +} + +if [ -s '/etc/config/simple-adblock' ] \ + && [ ! -s '/etc/config/adblock-fast-opkg' ] \ + && [ "$(uci get adblock-fast.config.enabled)" = '0' ]; then + cp -f '/etc/config/adblock-fast' '/etc/config/adblock-fast-opkg' + enabled="$(uci get simple-adblock.config.enabled)" + if [ -x '/etc/init.d/simple-adblock' ]; then + output "Stopping and disabling simple-adblock " + if /etc/init.d/simple-adblock stop >/dev/null 2>&1 \ + && /etc/init.d/simple-adblock disable \ + && uci set simple-adblock.config.enabled=0 \ + && uci commit simple-adblock; then + output_okn + else + output_failn + fi + else + output "Disabling simple-adblock." + if uci set simple-adblock.config.enabled=0 \ + && uci commit simple-adblock; then + output_okn + else + output_failn + fi + fi + output "Migrating simple-adblock config file " + for i in allow_non_ascii canary_domains_icloud canary_domains_mozilla \ + compressed_cache compressed_cache_dir config_update_enabled \ + curl_additional_param curl_max_file_size curl_retry download_timeout \ + debug dns dns_instance dnsmasq_config_file_url force_dns led \ + parallel_downloads procd_trigger_wan6 procd_boot_wan_timeout verbosity; do + j="$(uci -q get simple-adblock.config.${i})" + [ -n "$j" ] && uci set "${packageName}.config.${i}=${j}" + done + [ -n "$enabled" ] && uci set "${packageName}.config.enabled=${enabled}" + j="$(uci -q get simple-adblock.config.config_update_url)" + if [ "${j//simple-adblock/}" = "$j" ]; then + uci set "${packageName}.config.config_update_url=$j" + fi + ccd="$(uci get simple-adblock.config.compressed_cache_dir)" + ccd="${ccd:-/etc}" + for j in $(uci -q get simple-adblock.config.allowed_domain); do + [ -n "$j" ] && uci add_list "${packageName}.config.allowed_domain=${j}" + done + for j in $(uci -q get simple-adblock.config.blocked_domain); do + [ -n "$j" ] && uci add_list "${packageName}.config.blocked_domain=${j}" + done + for j in $(uci -q get simple-adblock.config.force_dns_port); do + [ -n "$j" ] && uci add_list "${packageName}.config.force_dns_port=${j}" + done + output_okn + + for i in allowed_domains_url blocked_adblockplus_url blocked_domains_url \ + blocked_hosts_url; do + output "Migrating simple-adblock ${i} " + for j in $(uci -q get simple-adblock.config.${i}); do + if [ "$i" = 'allowed_domains_url' ]; then + enable_add_url "$j" 'allow' + else + enable_add_url "$j" 'block' + fi + done + output_okn + done + uci commit "$packageName" + output "Migrating simple-adblock cache file(s) " + for i in '/var/run/simple-adblock/dnsmasq.addnhosts.cache' \ + '/var/run/simple-adblock/dnsmasq.conf.cache' \ + '/var/run/simple-adblock/dnsmasq.ipset.cache' \ + '/var/run/simple-adblock/dnsmasq.nftset.cache' \ + '/var/run/simple-adblock/dnsmasq.servers.cache' \ + '/var/run/simple-adblock/unbound.cache'; do + if [ -s "$i" ]; then + current_dir="$(dirname "$i")" + mkdir -p "${current_dir//simple-adblock/adblock-fast}" + mv -f "$i" "${i//simple-adblock/adblock-fast}" && output_okn || output_failn + fi + done + for i in 'simple-adblock.dnsmasq.addnhosts.gz' \ + 'simple-adblock.dnsmasq.conf.gz' \ + 'simple-adblock.dnsmasq.ipset.gz' \ + 'simple-adblock.dnsmasq.nftset.gz' \ + 'simple-adblock.dnsmasq.servers.gz' \ + 'simple-adblock.unbound.gz'; do + i="${ccd}/${i}" + if [ -s "$i" ]; then + mkdir -p "${ccd//simple-adblock/adblock-fast}" + mv -f "$i" "${i//simple-adblock/adblock-fast}" && output_okn || output_failn + fi + done + output_okn +fi + +exit 0 diff --git a/net/adblock-fast/test.sh b/net/adblock-fast/test.sh new file mode 100644 index 00000000..45469ed9 --- /dev/null +++ b/net/adblock-fast/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/etc/init.d/"$1" version 2>&1 | grep "$2" diff --git a/net/apinger/Makefile b/net/apinger/Makefile index f6837cec..b2f53727 100644 --- a/net/apinger/Makefile +++ b/net/apinger/Makefile @@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=apinger PKG_SOURCE_DATE:=2015-04-09 PKG_SOURCE_VERSION:=78eb328721ba1a10571c19df95acddcb5f0c17c8 -PKG_RELEASE:=2 +PKG_RELEASE:=5 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/Jajcus/apinger @@ -46,7 +46,8 @@ define Package/apinger/description endef define Package/apinger/conffiles -/etc/apinger.conf +/etc/config/apinger +/etc/apinger.user endef define Package/apinger/install @@ -56,7 +57,35 @@ define Package/apinger/install $(INSTALL_DATA) $(PKG_BUILD_DIR)/src/apinger.conf $(1)/etc/apinger.conf $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/apinger.init $(1)/etc/init.d/apinger + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_DATA) ./files/apinger.config $(1)/etc/config/apinger + $(INSTALL_DIR) $(1)/usr/libexec + $(INSTALL_BIN) ./files/apinger-hotplug $(1)/usr/libexec/apinger-hotplug + $(INSTALL_DIR) $(1)/etc/hotplug.d/apinger + $(INSTALL_DATA) ./files/user.hotplug $(1)/etc/hotplug.d/apinger/01-user + $(INSTALL_DIR) $(1)/etc/hotplug.d/iface + $(INSTALL_DATA) ./files/iface.hotplug $(1)/etc/hotplug.d/iface/25-apinger + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./files/apinger.rpc $(1)/usr/libexec/rpcd/apinger +endef + +define Package/apinger-rrd + SECTION:=net + CATEGORY:=Network + DEPENDS:=+apinger +rrdtool1 +rrdcgi1 + TITLE:=Apinger RRD Graphs + URL:=https://github.com/Jajcus/apinger +endef + +define Package/apinger-rrd/description + Generate RRD Graphs from Apinger Data +endef + +define Package/apinger-rrd/install + $(INSTALL_DIR) $(1)/usr/libexec/apinger/rpc + $(INSTALL_DATA) ./files/graphs.sh $(1)/usr/libexec/apinger/rpc endef $(eval $(call BuildPackage,apinger)) +$(eval $(call BuildPackage,apinger-rrd)) diff --git a/net/apinger/files/apinger-hotplug b/net/apinger/files/apinger-hotplug new file mode 100644 index 00000000..70a29daf --- /dev/null +++ b/net/apinger/files/apinger-hotplug @@ -0,0 +1,22 @@ +#!/bin/sh + +usage_help() +{ + echo "$0 " +} + +export ACTION=$1 +export INSTANCE=$2 +export APINGER_TARGET=$3 +export APINGER_SRCIP=$4 +export APINGER_TARGET_ID=$5 +export APINGER_ALARM=$6 +export APINGER_ALARM_TYPE=$7 +export APINGER_ALARM_MESSAGE=$8 +export APINGER_PROBES_SENT=$9 +export APINGER_PROBES_RECEIVED=${10} +export APINGER_LOSS=${11} +export APINGER_DELAY=${12} +export APINGER_TIMESTAMP=${13} + +exec /sbin/hotplug-call apinger $@ diff --git a/net/apinger/files/apinger.config b/net/apinger/files/apinger.config new file mode 100644 index 00000000..40190b3f --- /dev/null +++ b/net/apinger/files/apinger.config @@ -0,0 +1,28 @@ +config interface 'wan' + option debug '0' + option status_interval '5' + +#config interface 'wan2' +# option debug '0' +# option status_interval '5' + +# delay is in ms +#config alarm_delay 'delay200' +# option delay_low '100' +# option delay_high '200' + +# loss is in % +#config alarm_loss 'loss50' +# option percent_low '30' +# option percent_high '50' + +#config target 'target1' +# option interface 'wan' +# option address '8.8.8.8' +# option alarm_delay 'a1' +# option alarm_loss 'loss50' +# option probe_interval '5' + +#config target 'target2' +# option interface 'wan2' +# option address '8.8.8.8' diff --git a/net/apinger/files/apinger.init b/net/apinger/files/apinger.init index 8caac386..7a287c03 100644 --- a/net/apinger/files/apinger.init +++ b/net/apinger/files/apinger.init @@ -2,17 +2,241 @@ # Copyright (C) 2006-2011 OpenWrt.org START=80 +USE_PROCD=1 +BIN=/usr/sbin/apinger +APINGER_RRD=/apinger/rrd -SERVICE_USE_PID=1 +. /lib/functions/network.sh -start() { - service_start /usr/sbin/apinger +set_config_file() { + export CONFIG_FILE="/var/run/apinger-$instance.conf" } -stop() { - service_stop /usr/sbin/apinger +set_status_file() { + export STATUS_FILE="/var/run/apinger-$instance.status" } -reload() { - service_reload /usr/sbin/apinger +write_config_block() { + local cfg_var="CONFIG_BLOCK_$instance" + eval echo -e "\$$cfg_var" >> "$CONFIG_FILE" +} + +start_config_block() { + eval "export CONFIG_BLOCK_$instance=''" + append CONFIG_BLOCK_$instance "$* {" "\n" +} + +close_config_block() { + append CONFIG_BLOCK_$instance "}" "\n" +} + +append_config_line() { + append CONFIG_BLOCK_$instance "\t$*" "\n" +} + +append_target() { + local target=$1 + local interface address probe_interval srcip + local avg_delay_samples avg_loss_samples avg_loss_delay_samples + local alarm_down alarm_delay alarm_loss alarms rrd + + config_get interface "$target" interface wan + [ "$interface" != "$instance" ] && return 0 + + config_get address "$target" address + config_get probe_interval "$target" probe_interval + config_get avg_delay_samples "$target" avg_delay_samples + config_get avg_loss_samples "$target" avg_loss_samples + config_get avg_loss_delay_samples "$target" avg_loss_delay_samples + config_get alarm_down "$target" alarm_down + config_get alarm_delay "$target" alarm_delay + config_get alarm_loss "$target" alarm_loss + config_get_bool rrd "$target" rrd 0 + + [ -z "$address" ] && return 0 + + srcip=$(uci_get network "$interface" ipaddr) + [ -z "$srcip" ] && network_get_ipaddr srcip "$interface" + srcip="${srcip:-0.0.0.0}" + + alarms=${alarm_down:+\"${alarm_down}\"} + alarms=${alarm_delay:+${alarms:+${alarms}, }}${alarm_delay:+\"${alarm_delay}\"} + alarms=${alarm_loss:+${alarms:+${alarms}, }}${alarm_loss:+\"${alarm_loss}\"} + + start_config_block "target \"$address\"" + append_config_line "srcip \"$srcip\"" + append_config_line "description \"$target\"" + + [ -n "$probe_interval" ] && append_config_line "interval ${probe_interval}s" + [ -n "$avg_delay_samples" ] && append_config_line "avg_delay_samples ${avg_delay_samples}" + [ -n "$avg_loss_samples" ] && append_config_line "avg_loss_samples ${avg_loss_samples}" + [ -n "$avg_loss_delay_samples" ] && append_config_line "avg_loss_delay_samples ${avg_loss_delay_samples}" + [ -n "$alarms" ] && append_config_line "alarms override ${alarms}" + [ "$rrd" = "1" ] && append_config_line "rrd file \"$APINGER_RRD/apinger-target-$target.rrd\"" + + close_config_block + write_config_block +} + +append_alarm_down() { + local alarm=$1 + local time + + config_get time "$alarm" time 5 + + [ -z "$time" ] && return + + start_config_block "alarm down \"$alarm\"" + append_config_line "time ${time}s" + close_config_block + write_config_block +} + +append_alarm_delay() { + local alarm=$1 + local delay_low delay_high + + config_get delay_low "$alarm" delay_low + config_get delay_high "$alarm" delay_high + + if [ -z "$delay_low" ] || [ -z "$delay_high" ]; then + return + fi + + start_config_block "alarm delay \"$alarm\"" + append_config_line "delay_low ${delay_low}ms" + append_config_line "delay_high ${delay_high}ms" + close_config_block + write_config_block +} + +append_alarm_loss() { + local alarm=$1 + local percent_low percent_high + + config_get percent_low "$alarm" percent_low + config_get percent_high "$alarm" percent_low + + if [ -z "$percent_low" ] || [ -z "$percent_high" ]; then + return + fi + + start_config_block "alarm loss \"$alarm\"" + append_config_line "percent_low ${percent_low}" + append_config_line "percent_high ${percent_high}" + close_config_block + write_config_block +} + +init_apinger_config() { + local debug status_interval rrd_interval instance + instance=$1 + + config_get_bool debug apinger debug 0 + config_get status_interval apinger status_interval 1 + config_get rrd_interval apinger rrd_interval 30 + + [ "$debug" = "1" ] && debug=on || debug=off + + set_config_file + set_status_file + + cat << EOF > "$CONFIG_FILE" +user "root" +group "root" +debug ${debug} + +rrd interval ${rrd_interval}s + +status { + scriptformat on + file "$STATUS_FILE" + interval ${status_interval}s +} +alarm down "down" { + time 30s +} +alarm delay "delay" { + delay_low 5ms + delay_high 20ms +} +alarm loss "loss" { + percent_low 3 + percent_high 5 +} +alarm default { + command on "/usr/libexec/apinger-hotplug up $instance '%t' '%i' '%T' '%a' '%A' '%r' '%p' '%P' '%l' '%d' '%s'" + command off "/usr/libexec/apinger-hotplug down $instance '%t' '%i' '%T' '%a' '%A' '%r' '%p' '%P' '%l' '%d' '%s'" +} +target default { + interval 1s + avg_delay_samples 10 + avg_loss_samples 50 + avg_loss_delay_samples 20 + alarms "down", "delay", "loss" +} +EOF +} + +start_instance() { + export instance=$1 + + local enabled + config_get_bool enabled "$instance" enabled 1 + [ "$enabled" != "1" ] && return 0 + + init_apinger_config "$instance" + config_foreach append_alarm_down alarm_down + config_foreach append_alarm_delay alarm_delay + config_foreach append_alarm_loss alarm_loss + config_foreach append_target target + + procd_open_instance "$instance" + procd_set_param command $BIN -f -c $CONFIG_FILE + procd_set_param stderr 1 + procd_close_instance +} + +start_service() { + local instance=$1 + + config_load apinger + + [ ! -d "$APINGER_RRD" ] && mkdir -p "$APINGER_RRD" + + if [ -n "$instance" ]; then + start_instance "$instance" + else + config_foreach start_instance interface + fi +} + +service_triggers() { + procd_add_reload_trigger apinger +} + +clean_instance() { + local instance=$1 + + set_config_file + set_status_file + + [ -e $CONFIG_FILE ] && rm -f $CONFIG_FILE + [ -e $STATUS_FILE ] && rm -f $STATUS_FILE +} + +service_stopped() { + local instance=$1 + + config_load apinger + + if [ -n "$instance" ]; then + clean_instance "$instance" + else + config_foreach clean_instance interface + fi +} + +reload_service() { + restart } diff --git a/net/apinger/files/apinger.rpc b/net/apinger/files/apinger.rpc new file mode 100644 index 00000000..0be6e165 --- /dev/null +++ b/net/apinger/files/apinger.rpc @@ -0,0 +1,117 @@ +#!/bin/sh + +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +RPC_SCRIPTS=/usr/libexec/apinger/rpc + +[ -d $RPC_SCRIPTS ] && include $RPC_SCRIPTS + +__function__() { + type "$1" > /dev/null 2>&1 +} + +foreach_extra() { + local file obj + + [ ! -d $RPC_SCRIPTS ] && return + + for file in $RPC_SCRIPTS/*; do + obj="${file##*/}" + $1 "${obj%%.*}" + done +} + +apinger_status() { + interface_list() { + append iface_list $1 + } + config_load apinger + config_foreach interface_list interface + + json_init + json_add_array targets + + for iface in $iface_list; do + local status_file="/var/run/apinger-$iface.status" + + if [ -f "$status_file" ]; then + _IFS="$IFS" + IFS="|" + while read -r address srcip target received sent timestamp latency loss alarm; do + json_add_object targets + json_add_string interface "$iface" + json_add_string target "$target" + json_add_string address "$address" + json_add_string srcip "$srcip" + json_add_int sent "$sent" + json_add_int received "$received" + json_add_string latency "$latency" + json_add_string loss "$loss" + json_add_string alarm "$alarm" + json_add_int timestamp "$timestamp" + json_close_object + done < "$status_file" + IFS="$_IFS" + fi + done + + json_close_array + json_dump +} + +call_extra() { + if __function__ "$1"; then + $1 + else + json_init + json_add_string error "invalid call $1" + json_dump + fi +} + +call_method() { + case "$1" in + status) + apinger_status + ;; + *) + call_extra $1 + ;; + esac +} + +list_extra() { + if __function__ "${1}_help"; then + ${1}_help + else + json_add_object "$1" + json_close_object + fi +} + +list_methods() { + local file + + json_init + + json_add_object status + json_close_object + + foreach_extra list_extra ${1} + + json_dump +} + +main () { + case "$1" in + list) + list_methods + ;; + call) + call_method $2 + ;; + esac +} + +main "$@" diff --git a/net/apinger/files/graphs.sh b/net/apinger/files/graphs.sh new file mode 100644 index 00000000..442dfbec --- /dev/null +++ b/net/apinger/files/graphs.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +APINGER="/usr/sbin/apinger" +RRDCGI="/www/cgi-bin/apinger" +GRAPH_DIR="/apinger/graphs" +WWW_LOCATION="/www${GRAPH_DIR}" + +update_interface_graphs() { + local iface cfg cmd + + iface=$1 + cfg=/var/run/apinger-$iface.conf + + [ ! -f $cfg ] && return + + cmd="$APINGER -c $cfg -g $WWW_LOCATION -l $GRAPH_DIR" + + if [ -x $RRDCGI ]; then + $cmd 2>/dev/null | sed -e '/\(HTML\|TITLE\|H1\|H2\|by\|^#\)/d' >> $RRDCGI + else + $cmd 2>/dev/null | sed -e '/\(HTML\|TITLE\|H1\|H2\|by\)/d' > $RRDCGI + chmod 755 $RRDCGI + fi +} + +update_graphs() { + [ ! -d $WWW_LOCATION ] && mkdir -p $WWW_LOCATION + [ -e $RRDCGI ] && rm -f $RRDCGI + + config_load apinger + config_foreach update_interface_graphs interface + + json_init + json_add_string rrdcgi "$RRDCGI" + json_dump +} + +graphs_help() { + json_add_object update_graphs + json_close_object +} diff --git a/net/apinger/files/iface.hotplug b/net/apinger/files/iface.hotplug new file mode 100644 index 00000000..f97de0ae --- /dev/null +++ b/net/apinger/files/iface.hotplug @@ -0,0 +1,12 @@ +#!/bin/sh + +. /lib/functions.sh + +/etc/init.d/apinger enabled && { + [ "$(uci_get apinger $INTERFACE)" == "interface" ] || exit 0 + + [ "$ACTION" = "ifup" ] && { + /etc/init.d/apinger restart $INTERFACE + } + +} diff --git a/net/apinger/files/user.hotplug b/net/apinger/files/user.hotplug new file mode 100644 index 00000000..50bee5e6 --- /dev/null +++ b/net/apinger/files/user.hotplug @@ -0,0 +1,7 @@ +#!/bin/sh + +[ -e /etc/apinger.user ] && { + sh /etc/apinger.user +} + +exit 0 diff --git a/net/apinger/patches/030-apinger-no_exit.patch b/net/apinger/patches/030-apinger-no_exit.patch new file mode 100644 index 00000000..d058b3d1 --- /dev/null +++ b/net/apinger/patches/030-apinger-no_exit.patch @@ -0,0 +1,34 @@ +--- a/src/apinger.c ++++ b/src/apinger.c +@@ -786,7 +786,6 @@ struct alarm_cfg *a; + time_t tm; + int i,qp,really_lost; + char *buf1,*buf2; +-int err=0; + + if (config->status_file==NULL) return; + +@@ -849,12 +848,10 @@ int err=0; + } + } + buf2[i]=0; +- fprintf(f,"Received packets buffer: %s %s\n",buf2,buf1); + if (t->recently_lost!=really_lost){ +- fprintf(f," lost packet count mismatch (%i!=%i)!\n",t->recently_lost,really_lost); +- logit("%s: Lost packet count mismatch (%i!=%i)!",t->name,t->recently_lost,really_lost); +- logit("%s: Received packets buffer: %s %s\n",t->name,buf2,buf1); +- err=1; ++ logit("Target \"%s\": Lost packet count mismatch (%i(recently_lost) != %i(really_lost))!",t->name,t->recently_lost,really_lost); ++ logit("Target \"%s\": Received packets buffer: %s %s\n",t->name,buf2,buf1); ++ t->recently_lost = really_lost = 0; + } + free(buf1); + free(buf2); +@@ -862,7 +859,6 @@ int err=0; + fprintf(f,"\n"); + } + fclose(f); +- if (err) abort(); + } + + #ifdef FORKED_RECEIVER diff --git a/net/apinger/patches/040-srcip.patch b/net/apinger/patches/040-srcip.patch new file mode 100644 index 00000000..461201e6 --- /dev/null +++ b/net/apinger/patches/040-srcip.patch @@ -0,0 +1,564 @@ +--- a/src/apinger.c ++++ b/src/apinger.c +@@ -161,6 +161,9 @@ time_t tim; + case 't': + values[n]=t->name; + break; ++ case 'i': ++ values[n]=t->config->srcip; ++ break; + case 'T': + values[n]=t->description; + break; +@@ -276,6 +279,7 @@ time_t tm; + else + fprintf(f,"alarm canceled: %s\n",a->name); + fprintf(f,"Target: %s\n",t->name); ++ fprintf(f,"Source: %s\n",t->config->srcip); + fprintf(f,"Description: %s\n",t->description); + fprintf(f,"Probes sent: %i\n",t->last_sent+1); + fprintf(f,"Replies received: %i\n",t->received); +@@ -645,7 +649,7 @@ void configure_targets(void){ + struct target *t,*pt,*nt; + struct target_cfg *tc; + struct active_alarm_list *al,*nal; +-union addr addr; ++union addr addr, srcaddr; + int r; + int l; + +@@ -665,6 +669,8 @@ int l; + nal=al->next; + free(al); + } ++ if (t->socket) ++ close(t->socket); + free(t->queue); + free(t->rbuf); + free(t->name); +@@ -681,20 +687,16 @@ int l; + break; + if (t==NULL) { /* new target */ + memset(&addr,0,sizeof(addr)); ++ logit("Checking target IP %s", tc->srcip); + r=inet_pton(AF_INET,tc->name,&addr.addr4.sin_addr); + if (r){ +- if (icmp_sock<0){ +- logit("Sorry, IPv4 is not available\n"); +- logit("Ignoring target %s\n",tc->name); +- continue; +- } + addr.addr.sa_family=AF_INET; + }else{ + #ifdef HAVE_IPV6 + r=inet_pton(AF_INET6,tc->name,&addr.addr6.sin6_addr); + if (r==0){ + #endif +- logit("Bad host address: %s\n",tc->name); ++ logit("Bad target IP address: %s\n",tc->name); + logit("Ignoring target %s\n",tc->name); + continue; + #ifdef HAVE_IPV6 +@@ -707,12 +709,38 @@ int l; + addr.addr.sa_family=AF_INET6; + #endif + } ++ memset(&srcaddr,0,sizeof(srcaddr)); ++ logit("Checking source IP %s", tc->srcip); ++ r=inet_pton(AF_INET,tc->srcip,&srcaddr.addr4.sin_addr); ++ if (r){ ++ srcaddr.addr.sa_family=AF_INET; ++ }else{ ++#ifdef HAVE_IPV6 ++ r=inet_pton(AF_INET6,tc->srcip,&srcaddr.addr6.sin6_addr); ++ if (r==0){ ++#endif ++ logit("Bad source IP address %s for target %s\n", tc->srcip, tc->name); ++ logit("Ignoring target %s\n",tc->name); ++ continue; ++#ifdef HAVE_IPV6 ++ } ++ if (icmp6_sock<0){ ++ logit("Sorry, IPv6 is not available\n"); ++ logit("Ignoring target %s\n",tc->name); ++ continue; ++ } ++ srcaddr.addr.sa_family=AF_INET6; ++#endif ++ } + t=NEW(struct target,1); + memset(t,0,sizeof(struct target)); + t->name=strdup(tc->name); + t->description=strdup(tc->description); + t->addr=addr; ++ t->ifaddr=srcaddr; + t->next=targets; ++ if(t->addr.addr.sa_family==AF_INET) make_icmp_socket(t); ++ if(t->addr.addr.sa_family==AF_INET6) make_icmp6_socket(t); + targets=t; + } + t->config=tc; +@@ -752,6 +780,8 @@ struct active_alarm_list *al,*nal; + nal=al->next; + free(al); + } ++ if (t->socket) ++ close(t->socket); + free(t->queue); + free(t->rbuf); + free(t->name); +@@ -799,6 +829,7 @@ char *buf1,*buf2; + fprintf(f,"%s\n",ctime(&tm)); + for(t=targets;t;t=t->next){ + fprintf(f,"Target: %s\n",t->name); ++ fprintf(f,"Source: %s\n",t->config->srcip); + fprintf(f,"Description: %s\n",t->description); + fprintf(f,"Last reply received: #%i %s",t->last_received, + ctime(&t->last_received_tv.tv_sec)); +@@ -909,7 +940,7 @@ int i; + void main_loop(void){ + struct target *t; + struct timeval cur_time,next_status={0,0},tv,next_report={0,0},next_rrd_update={0,0}; +-struct pollfd pfd[2]; ++struct pollfd pfd[1024]; + int timeout; + int npfd=0; + int i; +@@ -946,18 +977,8 @@ struct piped_info pi; + pfd[npfd].events=POLLIN|POLLERR|POLLHUP|POLLNVAL; + pfd[npfd].revents=0; + pfd[npfd++].fd=recv_pipe[0]; +-#else +- if (icmp_sock){ +- pfd[npfd].events=POLLIN|POLLERR|POLLHUP|POLLNVAL; +- pfd[npfd].revents=0; +- pfd[npfd++].fd=icmp_sock; +- } +- if (icmp6_sock){ +- pfd[npfd].events=POLLIN|POLLERR|POLLHUP|POLLNVAL; +- pfd[npfd++].fd=icmp6_sock; +- pfd[npfd].revents=0; +- } + #endif ++ memset(&pfd, '\0', sizeof pfd); + if (config->status_interval){ + gettimeofday(&cur_time,NULL); + tv.tv_sec=config->status_interval/1000; +@@ -965,10 +986,16 @@ struct piped_info pi; + timeradd(&cur_time,&tv,&next_status); + } + while(!interrupted_by){ ++ npfd = 0; + gettimeofday(&cur_time,NULL); + if ( !timercmp(&next_probe,&cur_time,>) ) + timerclear(&next_probe); + for(t=targets;t;t=t->next){ ++ if (t->socket){ ++ pfd[npfd].events=POLLIN|POLLERR|POLLHUP|POLLNVAL; ++ pfd[npfd].revents=0; ++ pfd[npfd++].fd=t->socket; ++ } + for(al=t->config->alarms;al;al=nal){ + a=al->alarm; + nal=al->next; +@@ -1051,8 +1078,20 @@ struct piped_info pi; + analyze_reply(pi.recv_timestamp,pi.icmp_seq,&pi.ti); + } + #else +- if (pfd[i].fd==icmp_sock) recv_icmp(); +- else if (pfd[i].fd==icmp6_sock) recv_icmp6(); ++ for(t=targets;t;t=t->next){ ++ if (t->addr.addr.sa_family==AF_INET) { ++ if (t->socket == pfd[i].fd) { ++ recv_icmp(t); ++ break; ++ } ++ } ++ if (t->addr.addr.sa_family==AF_INET6) { ++ if (t->socket == pfd[i].fd) { ++ recv_icmp6(t); ++ break; ++ } ++ } ++ } + #endif + pfd[i].revents=0; + } +--- a/src/apinger.conf ++++ b/src/apinger.conf +@@ -47,6 +47,7 @@ alarm default { + + ## Following "macros" may be used in options below: + ## %t - target name (address) ++ ## %i - source name (address) + ## %T - target description + ## %a - alarm name + ## %A - alarm type ("down"/"loss"/"delay") +--- a/src/apinger.h ++++ b/src/apinger.h +@@ -46,6 +46,8 @@ + #endif + #include "conf.h" + ++#include ++ + union addr { + struct sockaddr addr; + struct sockaddr_in addr4; +@@ -70,6 +72,7 @@ struct target { + char *queue; /* + contains info about recently sent packets + "1" means it was received */ ++ int socket; + int last_sent; /* sequence number of the last ping sent */ + int last_received; /* sequence number of the last ping received */ + struct timeval last_received_tv; /* timestamp of the last ping received */ +@@ -89,6 +92,7 @@ struct target { + struct target_cfg *config; + + struct target *next; ++ union addr ifaddr; /* iface address */ + }; + + #define AVG_DELAY_KNOWN(t) (t->upsent >= t->config->avg_delay_samples) +@@ -118,16 +122,16 @@ extern char *config_file; + + extern int icmp_sock; + extern int icmp6_sock; +-extern int ident; ++extern uint16_t ident; + + extern struct timeval next_probe; + +-int make_icmp_socket(void); +-void recv_icmp(void); ++int make_icmp_socket(struct target *t); ++void recv_icmp(struct target *t); + void send_icmp_probe(struct target *t,int seq); + +-int make_icmp6_socket(void); +-void recv_icmp6(void); ++int make_icmp6_socket(struct target *t); ++void recv_icmp6(struct target *t); + void send_icmp6_probe(struct target *t,int seq); + + #ifdef FORKED_RECEIVER +--- a/src/cfgparser1.y ++++ b/src/cfgparser1.y +@@ -96,6 +96,7 @@ struct target_cfg *cur_target; + %token DELAY_HIGH + + %token DESCRIPTION ++%token SRCIP + %token ALARMS + %token INTERVAL + %token AVG_DELAY_SAMPLES +@@ -247,6 +248,8 @@ target: TARGET getdeftarget DEFAULT '{' + targetcfg: /* */ + | DESCRIPTION string + { cur_target->description=$2; } ++ | SRCIP string ++ { cur_target->srcip = $2; } + | ALARMS alarmlist + { cur_target->alarms=$2; } + | ALARMS OVERRIDE alarmlist +--- a/src/cfgparser2.l ++++ b/src/cfgparser2.l +@@ -81,6 +81,7 @@ delay { LOC; LOCINC; return DELAY; } + delay_high { LOC; LOCINC; return DELAY_HIGH; } + delay_low { LOC; LOCINC; return DELAY_LOW; } + description { LOC; LOCINC; return DESCRIPTION; } ++srcip { LOC; LOCINC; return SRCIP; } + down { LOC; LOCINC; return DOWN; } + false { LOC; LOCINC; return FALSE; } + file { LOC; LOCINC; return FILE_; } +--- a/src/conf.c ++++ b/src/conf.c +@@ -174,6 +174,14 @@ int ret; + } + } + for(t=cur_config.targets;t;t=t->next){ ++ if (t->name==NULL || strlen(t->name)==0){ ++ logit("Target name can't be empty."); ++ return 1; ++ } ++ else if (t->srcip==NULL){ ++ logit("No source IP defined for target \"%s\".", t->name); ++ return 1; ++ } + if (t->description==NULL) + t->description=cur_config.target_defaults.description; + if (t->interval<=0) +--- a/src/conf.h ++++ b/src/conf.h +@@ -71,6 +71,7 @@ struct alarm_list { + struct target_cfg { + char *name; + char *description; ++ char *srcip; + int interval; + int avg_delay_samples; + int avg_loss_delay_samples; +--- a/src/icmp6.c ++++ b/src/icmp6.c +@@ -112,14 +112,14 @@ int ret; + memcpy(p+1,&ti,sizeof(ti)); + size=sizeof(*p)+sizeof(ti); + +- ret=sendto(icmp6_sock,p,size,MSG_DONTWAIT, ++ ret=sendto(t->socket,p,size,MSG_DONTWAIT, + (struct sockaddr *)&t->addr.addr6,sizeof(t->addr.addr6)); + if (ret<0){ + if (config->debug) myperror("sendto"); + } + } + +-void recv_icmp6(void){ ++void recv_icmp6(struct target *t){ + int len,icmplen,datalen; + char buf[1024]; + char abuf[100]; +@@ -133,6 +133,7 @@ char ans_data[4096]; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *c; ++reloophack6: + + iov.iov_base=buf; + iov.iov_len=1000; +@@ -142,12 +143,13 @@ struct cmsghdr *c; + msg.msg_iovlen=1; + msg.msg_control=ans_data; + msg.msg_controllen=sizeof(ans_data); +- len=recvmsg(icmp6_sock, &msg, MSG_DONTWAIT); ++ len=recvmsg(t->socket, &msg, MSG_DONTWAIT); + #else + socklen_t sl; ++reloophack6: + + sl=sizeof(from); +- len=recvfrom(icmp6_sock,buf,1024,0,(struct sockaddr *)&from,&sl); ++ len=recvfrom(t->socket,buf,1024,0,(struct sockaddr *)&from,&sl); + #endif + if (len<0){ + if (errno==EAGAIN) return; +@@ -169,7 +171,7 @@ socklen_t sl; + #endif + if (time_recvp==NULL){ + #ifdef SIOCGSTAMP +- if (!ioctl(icmp6_sock, SIOCGSTAMP, &time_recv)){ ++ if (!ioctl(t->socket, SIOCGSTAMP, &time_recv)){ + debug("Got timestamp from ioctl()"); + }else + #endif +@@ -182,8 +184,11 @@ socklen_t sl; + icmplen=len; + icmp=(struct icmp6_hdr *)buf; + if (icmp->icmp6_type != ICMP6_ECHO_REPLY) return; +- if (icmp->icmp6_id != ident) return; +- ++ if (icmp->icmp6_id != ident){ ++ debug("Alien echo-reply received from xxx. Expected %i, received %i", ident, icmp->icmp6_id); ++ goto reloophack6; ++ return; ++ } + name=inet_ntop(AF_INET6,&from.sin6_addr,abuf,100); + debug("Ping reply from %s",name); + datalen=icmplen-sizeof(*icmp); +@@ -199,33 +204,36 @@ socklen_t sl; + } + + +-int make_icmp6_socket(void){ ++int make_icmp6_socket(struct target *t){ + int opt; + +- icmp6_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); +- if (icmp6_sock<0) ++ t->socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); ++ if (t->socket <0) + myperror("socket"); + else { + opt=2; + #if defined(SOL_RAW) && defined(IPV6_CHECKSUM) +- if (setsockopt(icmp6_sock, SOL_RAW, IPV6_CHECKSUM, &opt, sizeof(int))) ++ if (setsockopt(t->socket, SOL_RAW, IPV6_CHECKSUM, &opt, sizeof(int))) + myperror("setsockopt(IPV6_CHECKSUM)"); + #endif + #ifdef SO_TIMESTAMP + opt=1; +- if (setsockopt(icmp6_sock, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt))) ++ if (setsockopt(t->socket, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt))) + myperror("setsockopt(SO_TIMESTAMP)"); + #endif + /*install_filter6();*/ + } +- return icmp6_sock; ++ if (bind(t->socket, (struct sockaddr *)&t->ifaddr.addr6, sizeof(t->ifaddr.addr6)) < 0) ++ myperror("bind socket"); ++ ++ return t->socket; + } + + #else /*HAVE_IPV6*/ + #include "apinger.h" + +-int make_icmp6_socket(void){ return -1; } +-void recv_icmp6(void){} ++int make_icmp6_socket(struct target *t){ return -1; } ++void recv_icmp6(struct target *t){} + void send_icmp6_probe(struct target *t,int seq){} + + #endif /*HAVE_IPV6*/ +--- a/src/icmp.c ++++ b/src/icmp.c +@@ -150,14 +150,14 @@ int ret; + size=sizeof(*p)+sizeof(ti); + + p->icmp_cksum = in_cksum((u_short *)p,size,0); +- ret=sendto(icmp_sock,p,size,MSG_DONTWAIT, ++ ret=sendto(t->socket,p,size,MSG_DONTWAIT, + (struct sockaddr *)&t->addr.addr4,sizeof(t->addr.addr4)); + if (ret<0){ + if (config->debug) myperror("sendto"); + } + } + +-void recv_icmp(void){ ++void recv_icmp(struct target *t){ + int len,hlen,icmplen,datalen; + char buf[1024]; + struct sockaddr_in from; +@@ -170,6 +170,7 @@ char ans_data[4096]; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *c; ++reloophack: + + iov.iov_base=buf; + iov.iov_len=1000; +@@ -179,12 +180,13 @@ struct cmsghdr *c; + msg.msg_iovlen=1; + msg.msg_control=ans_data; + msg.msg_controllen=sizeof(ans_data); +- len=recvmsg(icmp_sock, &msg, MSG_DONTWAIT); ++ len=recvmsg(t->socket, &msg, MSG_DONTWAIT); + #else + socklen_t sl; ++reloophack: + + sl=sizeof(from); +- len=recvfrom(icmp_sock,buf,1024,MSG_DONTWAIT,(struct sockaddr *)&from,&sl); ++ len=recvfrom(t->socket,buf,1024,MSG_DONTWAIT,(struct sockaddr *)&from,&sl); + #endif + if (len<0){ + if (errno==EAGAIN) return; +@@ -196,7 +198,7 @@ socklen_t sl; + debug("checking CMSG..."); + for (c = CMSG_FIRSTHDR(&msg); c; c = CMSG_NXTHDR(&msg, c)) { + debug("CMSG level: %i type: %i",c->cmsg_level,c->cmsg_type); +- if (c->cmsg_level != SOL_SOCKET || c->cmsg_type != SO_TIMESTAMP) ++ if (c->cmsg_level != SOL_SOCKET || c->cmsg_type != SCM_TIMESTAMP) + continue; + if (c->cmsg_len < CMSG_LEN(sizeof(struct timeval))) + continue; +@@ -206,7 +208,7 @@ socklen_t sl; + #endif + if (time_recvp==NULL){ + #ifdef SIOCGSTAMP +- if (!ioctl(icmp_sock, SIOCGSTAMP, &time_recv)){ ++ if (!ioctl(t->socket, SIOCGSTAMP, &time_recv)){ + debug("Got timestampt from ioctl()"); + }else + #endif +@@ -226,7 +228,8 @@ socklen_t sl; + return; + } + if (icmp->icmp_id != ident){ +- debug("Alien echo-reply received"); ++ debug("Alien echo-reply received from %s. Expected %i, received %i",inet_ntoa(from.sin_addr), ident, icmp->icmp_id); ++ goto reloophack; + return; + } + debug("Ping reply from %s",inet_ntoa(from.sin_addr)); +@@ -242,19 +245,23 @@ socklen_t sl; + #endif + } + +-int make_icmp_socket(void){ ++int make_icmp_socket(struct target *t){ + int on; + +- icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); +- if (icmp_sock<0) ++ t->socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); ++ if (t->socket < 0) + myperror("socket"); + #ifdef SO_TIMESTAMP + else{ + on=1; +- if (setsockopt(icmp_sock, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof(on))) ++ if (setsockopt(t->socket, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof(on))) + myperror("setsockopt(SO_TIMESTAMP)"); + } + #endif +- return icmp_sock; ++ ++ if (bind(t->socket, (struct sockaddr *)&t->ifaddr.addr4, sizeof(t->ifaddr.addr4)) < 0) ++ myperror("bind socket"); ++ ++ return t->socket; + } + +--- a/src/main.c ++++ b/src/main.c +@@ -71,6 +71,7 @@ struct config default_config={ + { /* target defaults */ + "default", /* name */ + "", /* description */ ++ "", /* interface */ + 1000, /* interval */ + 20, /* avg_delay_samples */ + 5, /* avg_loss_delay_samples */ +@@ -95,7 +96,7 @@ char *config_file=CONFIG; + + int icmp_sock; + int icmp6_sock; +-int ident; ++uint16_t ident; + + struct timeval next_probe={0,0}; + +@@ -216,12 +217,6 @@ char *graph_location="/apinger/"; + } + } + +- make_icmp_socket(); +- make_icmp6_socket(); +- if (icmp6_sock<0 && icmp_sock<0){ +- return 1; +- } +- + pw=getpwnam(config->user); + if (!pw) { + debug("getpwnam(\"%s\") failed.",config->user); +@@ -276,7 +271,7 @@ char *graph_location="/apinger/"; + return 1; + } + +- ident=getpid(); ++ ident=getpid() & 0xFFFF; + signal(SIGTERM,signal_handler); + signal(SIGINT,signal_handler); + signal(SIGHUP,signal_handler); +@@ -285,9 +280,8 @@ char *graph_location="/apinger/"; + #ifdef FORKED_RECEIVER + signal(SIGCHLD,sigchld_handler); + #endif ++ logit("Starting Alarm Pinger, apinger(%i)", ident); + main_loop(); +- if (icmp_sock>=0) close(icmp_sock); +- if (icmp6_sock>=0) close(icmp6_sock); + + logit("Exiting on signal %i.",interrupted_by); + diff --git a/net/apinger/patches/050-statusformat.patch b/net/apinger/patches/050-statusformat.patch new file mode 100644 index 00000000..dee865b8 --- /dev/null +++ b/net/apinger/patches/050-statusformat.patch @@ -0,0 +1,126 @@ +--- a/src/apinger.c ++++ b/src/apinger.c +@@ -826,26 +826,49 @@ char *buf1,*buf2; + return; + } + tm=time(NULL); +- fprintf(f,"%s\n",ctime(&tm)); ++ if(!config->status_format) fprintf(f,"%s\n",ctime(&tm)); + for(t=targets;t;t=t->next){ +- fprintf(f,"Target: %s\n",t->name); +- fprintf(f,"Source: %s\n",t->config->srcip); +- fprintf(f,"Description: %s\n",t->description); +- fprintf(f,"Last reply received: #%i %s",t->last_received, +- ctime(&t->last_received_tv.tv_sec)); +- fprintf(f,"Average delay: %0.3fms\n",AVG_DELAY(t)); ++ if(config->status_format){ ++ fprintf(f,"%s|%s|%s|%i|%i|%u|",t->name, t->config->srcip, t->description, t->last_sent+1, ++ t->received, t->last_received_tv.tv_sec); ++ fprintf(f,"%0.3fms|", AVG_DELAY(t)); ++ } ++ else{ ++ fprintf(f,"Target: %s\n",t->name); ++ fprintf(f,"Source: %s\n",t->config->srcip); ++ fprintf(f,"Description: %s\n",t->description); ++ fprintf(f,"Last reply received: #%i %s",t->last_received, ++ ctime(&t->last_received_tv.tv_sec)); ++ fprintf(f,"Average delay: %0.3fms\n",AVG_DELAY(t)); ++ } + if (AVG_LOSS_KNOWN(t)){ +- fprintf(f,"Average packet loss: %0.1f%%\n",AVG_LOSS(t)); ++ if(config->status_format){ ++ fprintf(f,"%0.1f%%",AVG_LOSS(t)); ++ } ++ else{ ++ fprintf(f,"Average packet loss: %0.1f%%\n",AVG_LOSS(t)); ++ } ++ } ++ if(config->status_format){ ++ fprintf(f, "|"); ++ } ++ else{ ++ fprintf(f,"Active alarms: "); + } +- fprintf(f,"Active alarms:"); + if (t->active_alarms){ + for(al=t->active_alarms;al;al=al->next){ + a=al->alarm; +- fprintf(f," \"%s\"",a->name); ++ if(config->status_format){ ++ fprintf(f,"%s",a->name); ++ } ++ else{ ++ fprintf(f," \"%s\"",a->name); ++ } + } +- fprintf(f,"\n"); ++ if(!config->status_format) fprintf(f,"\n"); + } +- else fprintf(f," None\n"); ++ else fprintf(f,"none"); ++ if(!config->status_format) fprintf(f,"\n"); + + buf1=NEW(char,t->config->avg_loss_delay_samples+1); + buf2=NEW(char,t->config->avg_loss_samples+1); +--- a/src/apinger.conf ++++ b/src/apinger.conf +@@ -29,6 +29,10 @@ group "nogroup" + # ## Interval between file updates + # ## when 0 or not set, file is written only when SIGUSR1 is received + # interval 5m ++# ++# ## Create status file in script parseable format (on) or human ++# ## readable format (off) ++# scriptformat off + #} + + ######################################## +--- a/src/cfgparser1.y ++++ b/src/cfgparser1.y +@@ -104,6 +104,7 @@ struct target_cfg *cur_target; + %token AVG_LOSS_DELAY_SAMPLES + + %token FILE_ ++%token SCRIPTFORMAT + + %token ERROR + +@@ -282,6 +283,8 @@ statuscfg: /* */ + { cur_config.status_interval=$2; } + | INTERVAL TIME + { cur_config.status_interval=$2; } ++ | SCRIPTFORMAT boolean ++ { cur_config.status_format=$2; } + | statuscfg separator statuscfg + ; + +--- a/src/cfgparser2.l ++++ b/src/cfgparser2.l +@@ -85,6 +85,7 @@ srcip { LOC; LOCINC; return SRCIP; } + down { LOC; LOCINC; return DOWN; } + false { LOC; LOCINC; return FALSE; } + file { LOC; LOCINC; return FILE_; } ++scriptformat { LOC; LOCINC; return SCRIPTFORMAT; } + group { LOC; LOCINC; return GROUP; } + interval { LOC; LOCINC; return INTERVAL; } + loss { LOC; LOCINC; return LOSS; } +--- a/src/conf.h ++++ b/src/conf.h +@@ -97,6 +97,7 @@ struct config { + char *pid_file; + char *status_file; + int status_interval; ++ int status_format; + char *timestamp_format; + }; + +--- a/src/main.c ++++ b/src/main.c +@@ -88,6 +88,7 @@ struct config default_config={ + "/var/run/apinger.pid", /* pid file */ + NULL, /* status file */ + 0, /* status interval */ ++ 0, /* status format */ + "%b %d %H:%M:%S" /* timestamp format */ + }; + diff --git a/net/apinger/patches/060-format-alarm-list.patch b/net/apinger/patches/060-format-alarm-list.patch new file mode 100644 index 00000000..815202f3 --- /dev/null +++ b/net/apinger/patches/060-format-alarm-list.patch @@ -0,0 +1,12 @@ +--- a/src/apinger.c ++++ b/src/apinger.c +@@ -860,6 +860,9 @@ char *buf1,*buf2; + a=al->alarm; + if(config->status_format){ + fprintf(f,"%s",a->name); ++ if(al->next){ ++ fprintf(f,","); ++ } + } + else{ + fprintf(f," \"%s\"",a->name); diff --git a/net/basicstation/Makefile b/net/basicstation/Makefile new file mode 100644 index 00000000..c405f5b0 --- /dev/null +++ b/net/basicstation/Makefile @@ -0,0 +1,60 @@ +# +# Copyright (C) 2022 TDT AG +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# +# +include $(TOPDIR)/rules.mk + +PKG_NAME:=basicstation +PKG_VERSION:=2.0.6 +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/lorabasics/basicstation/tar.gz/v$(PKG_VERSION)? +PKG_HASH:=7e285de94bae1174b4c3496fc3ab15fe67c72f92c0693d2320bafc654a9dfb43 + +PKG_MAINTAINER:=Marcus Schref +PKG_LICENSE:=BSD-3-Clause +PKG_LICENSE_FILES:=LICENSE + +PKG_BUILD_DEPENDS:=sx1302_hal mbedtls + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +define Package/basicstation + SECTION:=net + CATEGORY:=Network + SUBMENU:=LoRaWAN + TITLE:=LoRa Basic Station + DEPENDS:=+kmod-usb-acm +endef + +define Package/basicstation/description + LoRa Basic Station. The LoRaWAN Gateway Software. +endef + +define Package/basicstation/conffiles +/etc/config/basicstation +endef + +define Build/Prepare + $(call Build/Prepare/Default) + rm -rf $(PKG_BUILD_DIR)/deps/ + rm -f $(PKG_BUILD_DIR)/makefile + rm -f $(PKG_BUILD_DIR)/makefile.s2core + rm -f $(PKG_BUILD_DIR)/setup.gmk +endef + +define Package/basicstation/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/station $(1)/usr/bin + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/basicstation $(1)/etc/config/basicstation + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/basicstation $(1)/etc/init.d/basicstation +endef + +$(eval $(call BuildPackage,basicstation)) diff --git a/net/basicstation/files/etc/config/basicstation b/net/basicstation/files/etc/config/basicstation new file mode 100644 index 00000000..3b6a33c7 --- /dev/null +++ b/net/basicstation/files/etc/config/basicstation @@ -0,0 +1,146 @@ +config auth 'auth' + option cred '' + option mode '' + option addr '' + option port '' + option token '' + option key '' + option crt '' + option trust '' + +config sx130x 'sx130x' + option comif '' + option devpath '' + option pps '' + option public '' + option clksrc '' + option radio0 '' + option radio1 '' + +config rfconf 'rfconf0' + option type 'SX1250' + option txEnable '1' + option freq '' + option antennaGain '3' + option rssiOffset '-215.4' + option useRssiTcomp 'std' + +config rfconf 'rfconf1' + option type 'SX1250' + option txEnable '0' + option freq '' + option antennaGain '3' + option rssiOffset '-215.4' + option useRssiTcomp 'std' + +config rssitcomp 'std' + option coeff_a '0' + option coeff_b '0' + option coeff_c '20.41' + option coeff_d '2162.56' + option coeff_e '0' + +config txlut + option rfPower '12' + option paGain '0' + option pwrIdx '15' + list usedBy 'rfconf0' + +config txlut + option rfPower '13' + option paGain '0' + option pwrIdx '16' + list usedBy 'rfconf0' + +config txlut + option rfPower '14' + option paGain '0' + option pwrIdx '17' + list usedBy 'rfconf0' + +config txlut + option rfPower '15' + option paGain '0' + option pwrIdx '19' + list usedBy 'rfconf0' + +config txlut + option rfPower '16' + option paGain '0' + option pwrIdx '20' + list usedBy 'rfconf0' + +config txlut + option rfPower '17' + option paGain '0' + option pwrIdx '22' + list usedBy 'rfconf0' + +config txlut + option rfPower '18' + option paGain '1' + option pwrIdx '1' + list usedBy 'rfconf0' + +config txlut + option rfPower '19' + option paGain '1' + option pwrIdx '2' + list usedBy 'rfconf0' + +config txlut + option rfPower '20' + option paGain '1' + option pwrIdx '3' + list usedBy 'rfconf0' + +config txlut + option rfPower '21' + option paGain '1' + option pwrIdx '4' + list usedBy 'rfconf0' + +config txlut + option rfPower '22' + option paGain '1' + option pwrIdx '5' + list usedBy 'rfconf0' + +config txlut + option rfPower '23' + option paGain '1' + option pwrIdx '6' + list usedBy 'rfconf0' + +config txlut + option rfPower '24' + option paGain '1' + option pwrIdx '7' + list usedBy 'rfconf0' + +config txlut + option rfPower '25' + option paGain '1' + option pwrIdx '9' + list usedBy 'rfconf0' + +config txlut + option rfPower '26' + option paGain '1' + option pwrIdx '11' + list usedBy 'rfconf0' + +config txlut + option rfPower '27' + option paGain '1' + option pwrIdx '14' + list usedBy 'rfconf0' + +config station 'station' + option idGenIf '' + option routerid '' + option stationid '' + option logFile '' + option logLevel '' + option logSize '' + option logRotate '' diff --git a/net/basicstation/files/etc/init.d/basicstation b/net/basicstation/files/etc/init.d/basicstation new file mode 100644 index 00000000..638de353 --- /dev/null +++ b/net/basicstation/files/etc/init.d/basicstation @@ -0,0 +1,254 @@ +#!/bin/sh /etc/rc.common +# +# Copyright (C) 2022 TDT AG +# +# This is free software, licensed under the GNU General Public License v2. +# See https://www.gnu.org/licenses/gpl-2.0.txt for more information. + +START=85 +STOP=25 + +USE_PROCD=1 + +#radioconf +DEFAULT_COMIF='usb' +DEFAULT_DEVPATH='/dev/ttyACM0' +DEFAULT_PPS=0 +DEFAULT_PUBLIC=1 +DEFAULT_CLKSRC=0 +DEFAULT_RADIO_0='rfconf0' +DEFAULT_RADIO_1='rfconf1' + +#rfconf +DEFAULT_TYPE='SX1250' +DEFAULT_TX_ENABLE=0 +DEFAULT_FREQ=0 +DEFAULT_RSSI_OFFSET=0 +DEFAULT_ANTENNA_GAIN=0 +DEFAULT_RSSI_TCOMP='std' + +#rssi tcomp +DEFAULT_RSSI_TCOMP_A=0 +DEFAULT_RSSI_TCOMP_B=0 +DEFAULT_RSSI_TCOMP_C=20.41 +DEFAULT_RSSI_TCOMP_D=2162.56 +DEFAULT_RSSI_TCOMP_E=0 + +#tx gain lut +DEFAULT_RF_POWER=0 +DEFAULT_PA_GAIN=0 +DEFAULT_PWR_IDX=0 + +#station +DEFAULT_RADIO_INIT='' +DEFAULT_ID_GEN_IF='eth0' +DEFAULT_ROUTER_ID='/sys/class/net/eth0/address' +DEFAULT_LOG_FILE='/tmp/basicstation/log' +DEFAULT_LOG_LEVEL='DEBUG' +DEFAULT_LOG_SIZE=1 +DEFAULT_LOG_ROTATE=1 + +#auth +DEFAULT_CREDENTIALS='tc' +DEFAULT_MODE='no' + +parse_txlut() { + local section="$1" + local buffer + + config_get buffer "$section" usedBy + + if [[ "$buffer" == *"$2"* ]]; then + json_add_object + + config_get buffer "$section" rfPower "$DEFAULT_RF_POWER" + json_add_int 'rf_power' "$buffer" + config_get_bool buffer "$section" paGain "$DEFAULT_PA_GAIN" + json_add_int 'pa_gain' "$buffer" + config_get buffer "$section" pwrIdx "$DEFAULT_PWR_IDX" + json_add_int 'pwr_idx' "$buffer" + + json_close_object + fi +} + +parse_rssitcomp() { + local section="$1" + local buffer + + if [ "$section" = "$2" ]; then + json_add_object 'rssi_tcomp' + + config_get buffer "$section" coeff_a "$DEFAULT_RSSI_TCOMP_A" + json_add_double 'coeff_a' "$buffer" + config_get buffer "$section" coeff_b "$DEFAULT_RSSI_TCOMP_B" + json_add_double 'coeff_b' "$buffer" + config_get buffer "$section" coeff_c "$DEFAULT_RSSI_TCOMP_C" + json_add_double 'coeff_c' "$buffer" + config_get buffer "$section" coeff_d "$DEFAULT_RSSI_TCOMP_D" + json_add_double 'coeff_d' "$buffer" + config_get buffer "$section" coeff_e "$DEFAULT_RSSI_TCOMP_E" + json_add_double 'coeff_e' "$buffer" + + json_close_object + fi +} + +parse_rfconf() { + local section="$1" + local buffer + + if [ "$section" = "$2" ]; then + json_add_object "radio_"$3"" + + config_get buffer "$section" type "$DEFAULT_TYPE" + json_add_string 'type' "$buffer" + config_get buffer "$section" freq "$DEFAULT_FREQ" + json_add_int 'freq' "$buffer" + config_get buffer "$section" antennaGain "$DEFAULT_ANTENNA_GAIN" + json_add_int 'antenna_gain' "$buffer" + config_get buffer "$section" rssiOffset "$DEFAULT_RSSI_OFFSET" + json_add_double 'rssi_offset' "$buffer" + config_get buffer "$section" useRssiTcomp "$DEFAULT_RSSI_TCOMP" + config_foreach parse_rssitcomp rssitcomp "$buffer" + config_get_bool buffer "$section" txEnable "$DEFAULT_TX_ENABLE" + json_add_boolean 'tx_enable' "$buffer" + + if [ "$buffer" -eq 1 ]; then + json_add_array 'tx_gain_lut' + config_foreach parse_txlut txlut "$section" + json_close_array + fi + + json_close_object + fi +} + +parse_sx130x() { + local section="$1" + local comif + local devpath + local buffer + + json_add_object 'radio_conf' + + config_get comif "$section" comif "$DEFAULT_COMIF" + config_get devpath "$section" devpath "$DEFAULT_DEVPATH" + json_add_string 'device' ""$comif":"$devpath"" + config_get_bool buffer "$section" pps "$DEFAULT_PPS" + json_add_boolean 'pps' "$buffer" + config_get_bool buffer "$section" public "$DEFAULT_PUBLIC" + json_add_boolean 'lorawan_public' "$buffer" + config_get buffer "$section" clksrc "$DEFAULT_CLKSRC" + json_add_int 'clksrc' "$buffer" + json_add_boolean 'full_duplex' 0 + config_get buffer "$section" radio0 "$DEFAULT_RADIO_0" + config_foreach parse_rfconf rfconf "$buffer" 0 + config_get buffer "$section" radio1 "$DEFAULT_RADIO_1" + config_foreach parse_rfconf rfconf "$buffer" 1 + + json_close_object +} + +parse_station() { + local section="$1" + local buffer + local mac + + config_get buffer "$section" idGenIf "$DEFAULT_ID_GEN_IF" + mac=$(head -n 1 /sys/class/net/"$buffer"/address) + uci_set basicstation "$section" stationid "${mac::8}:ff:fe:${mac:9:8}" + uci_commit basicstation + + json_add_object 'station_conf' + + config_get buffer "$section" routerid "$DEFAULT_ROUTER_ID" + json_add_string 'routerid' "$buffer" + config_get buffer "$section" radioInit "$DEFAULT_RADIO_INIT" + json_add_string 'radio_init' "$buffer" + config_get buffer "$section" logFile "$DEFAULT_LOG_FILE" + json_add_string 'log_file' "$buffer" + config_get buffer "$section" logLevel "$DEFAULT_LOG_LEVEL" + json_add_string 'log_level' "$buffer" + config_get buffer "$section" logSize "$DEFAULT_LOG_SIZE" + json_add_int 'log_size' $(( 1000000*buffer )) + config_get buffer "$section" logRotate "$DEFAULT_LOG_ROTATE" + json_add_int 'log_rotate' "$buffer" + + json_close_object +} + +parse_auth() { + local section="$1" + local cred + local mode + local addr + local port + local uri + local buffer + + config_get cred "$section" cred "$DEFAULT_CREDENTIALS" + config_get mode "$section" mode "$DEFAULT_MODE" + config_get addr "$section" addr + config_get port "$section" port + + if [ "$mode" != 'no' ]; then + config_get buffer "$section" trust + [ -f "$buffer" ] && cp "$buffer" /tmp/basicstation/"$cred".trust + if [ "$mode" = 'serverAndClient' ]; then + config_get buffer "$section" key + echo "$buffer" > /tmp/basicstation/"$cred".key + config_get buffer "$section" crt + [ -f "$buffer" ] && cp "$buffer" /tmp/basicstation/"$cred".crt + elif [ "$mode" = 'serverAndClientToken' ]; then + config_get buffer "$section" token + echo "$buffer" > /tmp/basicstation/"$cred".key + fi + if [ "$cred" = "tc" ]; then + uri="wss://${addr}:${port}" + else + uri="https://${addr}:${port}" + fi + else + if [ "$cred" = "tc" ]; then + uri="ws://${addr}:${port}" + else + uri="http://${addr}:${port}" + fi + fi + + echo "$uri" > /tmp/basicstation/"$cred".uri +} + +process_config() { + . /usr/share/libubox/jshn.sh + json_init + + config_load basicstation + config_foreach parse_sx130x sx130x + config_foreach parse_station station + json_dump -i > /tmp/basicstation/station.conf + config_foreach parse_auth auth +} + +service_triggers() { + procd_add_reload_trigger "basicstation" +} + +start_service() { + rm -rf /tmp/basicstation/ + mkdir -p /tmp/basicstation/ + + process_config + + procd_open_instance + procd_set_param command /usr/bin/station + procd_append_param command --home /tmp/basicstation/ + procd_append_param command --force + procd_set_param respawn + procd_close_instance +} + +reload_service() { + restart "$@" +} diff --git a/net/basicstation/patches/000-include_sys_time.patch b/net/basicstation/patches/000-include_sys_time.patch new file mode 100644 index 00000000..f8571d1c --- /dev/null +++ b/net/basicstation/patches/000-include_sys_time.patch @@ -0,0 +1,24 @@ +From ca1eb77bf89697e1bacbb21ed7aae8b5134a7bf5 Mon Sep 17 00:00:00 2001 +From: Marcus Schref +Date: Mon, 22 Aug 2022 10:13:52 +0200 +Subject: [PATCH] basicstation: include sys/time.h + +Change time.h include directory to add compatibility with musl and fix +compilation error + +Signed-off-by: Marcus Schref +--- + src/aio.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/aio.c ++++ b/src/aio.c +@@ -27,7 +27,7 @@ + */ + + #include +-#include ++#include + #include + #include + #include "rt.h" diff --git a/net/basicstation/src/CMakeLists.txt b/net/basicstation/src/CMakeLists.txt new file mode 100644 index 00000000..8c1dfd46 --- /dev/null +++ b/net/basicstation/src/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.18.4) +project(basicstation) + +add_compile_definitions(CFG_linux) +add_compile_definitions(CFG_lgw1) +add_compile_definitions(CFG_sx1302) +add_compile_definitions(CFG_ral_lgw) +add_compile_definitions(CFG_no_leds) +add_compile_definitions(CFG_argp) +add_compile_definitions(CFG_platform="linux") +add_compile_definitions(CFG_bdate=${PKG_SOURCE_DATE}) +file(STRINGS "VERSION.txt" VERSION_NUMBER) +add_compile_definitions(CFG_version="${VERSION_NUMBER}") + + +include_directories(src) +include_directories(src-linux) + +find_path(SX1302_HAL_INCLUDE_DIR NAMES lgw/loragw_hal.h) +find_path(MBEDTLS_INCLUDE_DIR NAMES mbedtls/x509.h) +find_library(SX1302_HAL_LIBRARY NAMES libloragw.a PATH /usr/lib/) +find_library(TINYMT32_LIBRARY NAMES libtinymt32.a PATH /usr/lib/) +find_library(MBEDTLS_LIBRARY NAMES libmbedtls.a PATH /usr/lib/) +find_library(MBEDCRYPTO_LIBRARY NAMES libmbedcrypto.a PATH /usr/lib/) +find_library(MBEDX509_LIBRARY NAMES libmbedx509.a PATH /usr/lib/) + +set(basicstation_SOURCE_FILES + src/aio.c + src/argp.c + src/crc32.c + src/cups.c + src/flashsim.c + src/fs.c + src/genkwcrcs.c + src/lgwsim.c + src/log.c + src/lora.c + src/net.c + src/ral.c + src/ral_lgw.c + src/ral_lgw2.c + src/rt.c + src/s2conf.c + src/s2e.c + src/selftest_fs.c + src/selftest_lora.c + src/selftest_rt.c + src/selftest_ujdec.c + src/selftest_ujenc.c + src/selftest_xprintf.c + src/selftest_xq.c + src/selftests.c + src/sx1301v2conf.c + src/sx130xconf.c + src/sys.c + src/sys.h + src/tc.c + src/timesync.c + src/tls.c + src/uj.c + src/web.c + src/xq.c + src-linux/cmdfifo.c + src-linux/commands.c + src-linux/gps.c + src-linux/leds.c + src-linux/ral_master.c + src-linux/ral_slave.c + src-linux/rmtsh.c + src-linux/sys_linux.c + src-linux/sys_log.c + src-linux/web_linux.c) + +add_library(s2core ${basicstation_SOURCE_FILES}) +target_include_directories(s2core PRIVATE ${SX1302_HAL_INCLUDE_DIR}) +target_include_directories(s2core PRIVATE ${MBEDTLS_INCLUDE_DIR}) +target_link_libraries(s2core ${SX1302_HAL_LIBRARY} ${TINYMT32_LIBRARY} m) +target_link_libraries(s2core ${MBEDTLS_LIBRARY}) +target_link_libraries(s2core ${MBEDCRYPTO_LIBRARY}) +target_link_libraries(s2core ${MBEDX509_LIBRARY}) + +add_executable(station src-linux/station_main.c) + +target_link_libraries(station s2core) + +install(TARGETS station RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/net/basicstation/test.sh b/net/basicstation/test.sh new file mode 100644 index 00000000..ca28b501 --- /dev/null +++ b/net/basicstation/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +station --version 2>&1 | grep "$2" diff --git a/net/external-protocol/Makefile b/net/external-protocol/Makefile new file mode 100644 index 00000000..2123060b --- /dev/null +++ b/net/external-protocol/Makefile @@ -0,0 +1,87 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=external-protocol +PKG_VERSION:=20231119 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=Oskari Rauta + +include $(INCLUDE_DIR)/package.mk + +define Package/external-protocol + SECTION:=net + CATEGORY:=Network + TITLE:=externally managed protocol + PKGARCH:=all +endef + +define Package/external-protocol/description + external protocol is a general protocol for assisting + setup of many virtual devices that lack proper + protocol support in openwrt. Such as netavark, cni and + netbird for example. External protocol is supposed + to be managed with external software, not directly. + + external protocol works automaticly on the background + and sets up netifd details when interface comes up or + goes down. This allows one to easily add interface to + a firewall zone. + + as a example use case, podman, with network where it's + internal firewall and portmapper are disabled, control + of firewalling, whether it was exposing ports or + limiting/accepting access between networks, such as + lan can be made through openwrt's own firewalling + configuration if you used external protocol. + + podman example configuration could be as following: + - lan network: 10.0.0.0/16 (255.255.0.0) + - container network: 10.129.0.1/24 (255.255.255.0) + + Add a network configuration for your container network + using external protocol. Then create firewall zone for it. + + You could create a new container/pod with static ip + address 10.129.0.2 (as 10.129.0.1 as container network's + gateway). + + Easily define permissions so that local networks can + connect to container network, but not the other way around. + Also you want to allow forwarding from/to wan. + + Now, as container cannot access local dns, make a rule for + your firewall to accept connections from container network + to port 53 (dns). + + Now all you have to do, is make redirects to your firewall + and point them to 10.129.0.2 and connections from wan are + redirectered to containers/pods. + + external protocol also works for other applications as + well that are using veth/tun/etc devices and don't have + a hand-tailored protocol available, such as vpn service + netbird. + + Protocol has 3 settings: device, searchdomain and delay. + Sometimes polling interfaces takes some time, and in + that case you might want to add few seconds to delay. + Otherwise, it can be excluded from configuration. + Option for searchdomain is also completely optional. + + package was previously known as cni protocol but as + it can be used on so many other things, naming became + mis-leading and it was renamed to external protocol. +endef + +define Build/Configure +endef + +define Build/Compile +endef + +define Package/external-protocol/install + $(INSTALL_DIR) $(1)/lib/netifd/proto + $(INSTALL_BIN) ./files/external.sh $(1)/lib/netifd/proto/external.sh +endef + +$(eval $(call BuildPackage,external-protocol)) diff --git a/net/external-protocol/files/external.sh b/net/external-protocol/files/external.sh new file mode 100755 index 00000000..53261595 --- /dev/null +++ b/net/external-protocol/files/external.sh @@ -0,0 +1,141 @@ +#!/bin/sh + +[ -n "$INCLUDE_ONLY" ] || { + . /lib/functions.sh + . ../netifd-proto.sh + init_proto "$@" +} + +proto_external_init_config() { + no_device=0 + available=0 + + proto_config_add_string "device:device" + proto_config_add_string "searchdomain" + proto_config_add_int "delay" +} + +proto_external_setup() { + local cfg="$1" + local iface="$2" + local device searchdomain delay + + json_get_vars device searchdomain delay + + [ -n "$device" ] || { + echo "External protocol interface is not specified" + proto_notify_error "$cfg" NO_DEVICE + proto_set_available "$cfg" 0 + return 1 + } + + [ -n "$delay" ] && sleep "$delay" + + [ -L "/sys/class/net/${iface}" ] || { + echo "External protocol interface $iface is not present" + proto_notify_error "$cfg" NO_DEVICE + proto_set_available "$cfg" 0 + return 1 + } + + IP4ADDRS= + IP6ADDRS= + + local addresses="$(ip -json address list dev "$iface")" + json_init + json_load "{\"addresses\":${addresses}}" + + if json_is_a addresses array; then + json_select addresses + json_select 1 + + if json_is_a addr_info array; then + json_select addr_info + + local i=1 + while json_is_a ${i} object; do + json_select ${i} + json_get_vars scope family local prefixlen broadcast + + if [ "${scope}" == "global" ]; then + case "${family}" in + inet) + append IP4ADDRS "$local/$prefixlen/$broadcast/" + ;; + + inet6) + append IP6ADDRS "$local/$prefixlen/$broadcast///" + ;; + esac + fi + + json_select .. + i=$(( i + 1 )) + done + fi + fi + + IP4ROUTES= + IP6ROUTES= + + local routes="$(ip -json route list dev "$iface")" + json_init + json_load "{\"routes\":${routes}}" + + if json_is_a routes array;then + json_select routes + + local i=1 + while json_is_a ${i} object; do + json_select ${i} + json_get_vars dst gateway metric prefsrc + + case "${dst}" in + *:*/*) + append IP6ROUTES "$dst/$gateway/$metric///$prefsrc" + ;; + *.*/*) + append IP4ROUTES "$dst/$gateway/$metric///$prefsrc" + ;; + *:*) + append IP6ROUTES "$dst/128/$gateway/$metric///$prefsrc" + ;; + *.*) + append IP4ROUTES "$dst/32/$gateway/$metric///$prefsrc" + ;; + esac + + json_select .. + i=$(( i + 1 )) + done + fi + + [ -z "${IP4ADDRS}" -a -z "${IP6ADDRS}" ] && { + echo "interface $iface does not have ip address" + proto_notify_error "$cfg" NO_IPADDRESS + return 1 + } + + proto_init_update "$iface" 1 + + PROTO_IPADDR="${IP4ADDRS}" + PROTO_IP6ADDR="${IP6ADDRS}" + + PROTO_ROUTE="${IP4ROUTES}" + PROTO_ROUTE6="${IP6ROUTES}" + + [ -n "$searchdomain" ] && proto_add_dns_search "$searchdomain" + + echo "$iface is up" + + proto_send_update "$cfg" +} + +proto_external_teardown() { + local cfg="$1" + return 0 +} + +[ -n "$INCLUDE_ONLY" ] || { + add_protocol external +} diff --git a/net/keepalived/Config.in b/net/keepalived/Config.in index 0cddf09f..92ef9031 100644 --- a/net/keepalived/Config.in +++ b/net/keepalived/Config.in @@ -66,11 +66,30 @@ config KEEPALIVED_IPTABLES depends on KEEPALIVED_VRRP bool default y + select KEEPALIVED_IP6TABLES prompt "Enable iptables for VIP filtering" help Builds support for using iptables/ipsets for filtering packets to VIPs +config KEEPALIVED_IP6TABLES + depends on KEEPALIVED_VRRP && KEEPALIVED_IPTABLES && IPV6 + bool + default y + prompt "Enable ip6tables for VIP filtering" + help + Builds support for using ip6tables/ipsets for filtering packets + to VIPs + +config KEEPALIVED_NFTABLES + depends on KEEPALIVED_VRRP + bool + default y + prompt "Enable nftables for VIP filtering" + help + Builds support for using nftables for filtering packets + to VIPs + config KEEPALIVED_SNMP_VRRP depends on KEEPALIVED_VRRP bool @@ -111,14 +130,6 @@ config KEEPALIVED_DBUS help Builds support for using DBus with VRRP -config KEEPALIVED_JSON - depends on KEEPALIVED_VRRP - bool - default n - prompt "Enable JSON support with VRRP" - help - Builds support for using JSON output for VRRP - config KEEPALIVED_VRRP_AUTH depends on KEEPALIVED_VRRP bool diff --git a/net/keepalived/Makefile b/net/keepalived/Makefile index ab77087b..217b12c1 100644 --- a/net/keepalived/Makefile +++ b/net/keepalived/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=keepalived PKG_VERSION:=2.2.7 -PKG_RELEASE:=$(AUTORELEASE) +PKG_RELEASE:=10 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://www.keepalived.org/software @@ -21,20 +21,20 @@ PKG_LICENSE_FILES:=COPYING PKG_MAINTAINER:=Ben Kelly \ Florian Eckert -PKG_CONFIG_DEPENDS += \ - KEEPALIVED_VRRP \ - KEEPALIVED_LVS \ - KEEPALIVED_IPTABLES \ - KEEPALIVED_BFD \ - KEEPALIVED_SNMP_VRRP \ - KEEPALIVED_SNMP_CHECKER \ - KEEPALIVED_SNMP_RFC2 \ - KEEPALIVED_SNMP_RFC3 \ - KEEPALIVED_SNMP_REPLY_V3_FOR_V2 \ - KEEPALIVED_DBUS \ - KEEPALIVED_JSON \ - KEEPALIVED_ROUTES \ - IPV6 +PKG_CONFIG_DEPENDS:= \ + CONFIG_KEEPALIVED_NFTABLES \ + CONFIG_KEEPALIVED_VRRP \ + CONFIG_KEEPALIVED_LVS \ + CONFIG_KEEPALIVED_IPTABLES \ + CONFIG_KEEPALIVED_BFD \ + CONFIG_KEEPALIVED_SNMP_VRRP \ + CONFIG_KEEPALIVED_SNMP_CHECKER \ + CONFIG_KEEPALIVED_SNMP_RFC2 \ + CONFIG_KEEPALIVED_SNMP_RFC3 \ + CONFIG_KEEPALIVED_SNMP_REPLY_V3_FOR_V2 \ + CONFIG_KEEPALIVED_DBUS \ + CONFIG_KEEPALIVED_ROUTES \ + CONFIG_IPV6 PKG_INSTALL:=1 @@ -45,11 +45,6 @@ define Package/keepalived/config source "$(SOURCE)/Config.in" endef -# specifying +(IPV6&&KEEPALIVED_IPTABLES) in the DEPENDS definition doesn't work -ifeq ($(CONFIG_KEEPALIVED_IPTABLES)$(CONFIG_IPV6),yy) - KEEPALIVED_DEPENDS_LIBIP6TC=+libip6tc -endif - # The +kmod-nf-ipvs line should be +KEEPALIVED_LVS:kmod-nf-ipvs, # but make menuconfig then reports :error: recursive dependency detected! # !!FIXME DEPENDS:= +KEEPALIVED_LVS:kmod-nf-ipvs @@ -62,16 +57,18 @@ define Package/keepalived +libnl-genl \ +libmagic \ +libkmod \ + +kmod-nf-ipvs \ + +libjson-c \ + +KEEPALIVED_NFTABLES:libnftnl \ +KEEPALIVED_VRRP:kmod-macvlan \ +KEEPALIVED_VRRP:libnl-route \ +KEEPALIVED_VRRP:libnfnetlink \ +KEEPALIVED_SHA1:libopenssl \ +KEEPALIVED_IPTABLES:libip4tc \ - $(KEEPALIVED_DEPENDS_LIBIP6TC) \ + +KEEPALIVED_IP6TABLES:libip6tc \ +KEEPALIVED_IPTABLES:libxtables \ +KEEPALIVED_IPTABLES:libipset \ +(KEEPALIVED_SNMP_VRRP||KEEPALIVED_SNMP_CHECKER||KEEPALIVED_SNMP_RFC2||KEEPALIVED_SNMP_RFC3):libnetsnmp \ - +KEEPALIVED_JSON:libjson-c \ +KEEPALIVED_DBUS:glib2 endef @@ -86,8 +83,8 @@ define Package/keepalived/conffiles endef CONFIGURE_ARGS+= \ + --enable-json \ --with-init=SYSV \ - --disable-nftables \ --disable-track-process \ --runstatedir="/var/run" @@ -113,6 +110,12 @@ endif ifeq ($(CONFIG_KEEPALIVED_VRRP),y) + +ifeq ($(CONFIG_KEEPALIVED_NFTABLES),) +CONFIGURE_ARGS += \ + --disable-nftables +endif + ifeq ($(CONFIG_KEEPALIVED_IPTABLES),) CONFIGURE_ARGS += \ --disable-iptables @@ -145,11 +148,6 @@ CONFIGURE_ARGS += \ endif endif # CONFIG_KEEPALIVED_SNMP_RFC3 -ifeq ($(CONFIG_KEEPALIVED_JSON),y) -CONFIGURE_ARGS += \ - --enable-json -endif - ifeq ($(CONFIG_KEEPALIVED_VRRP_AUTH),) CONFIGURE_ARGS += \ --disable-vrrp-auth @@ -239,6 +237,10 @@ endif $(INSTALL_DATA) ./files/hotplug-user \ $(1)/etc/hotplug.d/keepalived/01-user + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./files/usr/libexec/rpcd/keepalived \ + $(1)/usr/libexec/rpcd/keepalived + ifneq ($(CONFIG_KEEPALIVED_SNMP_VRRP)$(CONFIG_KEEPALIVED_SNMP_CHECKER)$(CONFIG_KEEPALIVED_SNMP_RFC2)$(CONFIG_KEEPALIVED_SNMP_RFC3),) $(INSTALL_DIR) $(1)/usr/share/snmp/mibs endif @@ -272,4 +274,103 @@ endif endef +define Package/keepalived-sync + SECTION:=net + CATEGORY:=Network + TITLE:=Keepalived Master and Backup Synchronization + DEPENDS:= +keepalived +rsync +inotifywait +sudo +coreutils-timeout +endef + +define Package/keepalived-sync/description + Keepalived HA with Master to Backup files and data Synchronization +endef + +define Package/keepalived-sync/conffiles +/etc/keepalived/scripts +/etc/keepalived/keys +endef + +define Package/keepalived-sync/install + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/keepalived-inotify \ + $(1)/etc/init.d/keepalived-inotify + + $(INSTALL_DIR) $(1)/usr/share/keepalived/scripts + $(INSTALL_BIN) ./files/usr/share/keepalived/scripts/rsync.sh \ + $(1)/usr/share/keepalived/scripts/rsync.sh + + $(INSTALL_DIR) $(1)/etc/keepalived/scripts + $(LN) /usr/share/keepalived/scripts/rsync.sh \ + $(1)/etc/keepalived/scripts/rsync.sh + + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) ./files/usr/bin/keepalived-rsync-inotify \ + $(1)/usr/bin/keepalived-rsync-inotify + + $(INSTALL_DIR) $(1)/lib/functions/keepalived + $(INSTALL_DATA) ./files/lib/functions/keepalived/hotplug.sh \ + $(1)/lib/functions/keepalived/hotplug.sh + $(INSTALL_DATA) ./files/lib/functions/keepalived/common.sh \ + $(1)/lib/functions/keepalived/common.sh + + $(INSTALL_DIR) $(1)/usr/libexec/keepalived/rpc + $(INSTALL_DATA) ./files/usr/libexec/keepalived/rpc/sync.sh \ + $(1)/usr/libexec/keepalived/rpc/sync.sh + + $(INSTALL_DIR) $(1)/etc/hotplug.d/keepalived + $(CP) ./files/etc/hotplug.d/keepalived/* \ + $(1)/etc/hotplug.d/keepalived +endef + +USER=keepalived +USER_ID=60001 +USER_HOME=/usr/share/keepalived/rsync +SUDO_DIR=/etc/sudoers.d +SUDO_FILE=$(SUDO_DIR)/$(USER) +KEYS_DIR=/etc/keepalived/keys + +define Package/keepalived-sync/postinst + #!/bin/sh + + mkdir -p "$${IPKG_INSTROOT}/etc/uci-defaults" + DEFAULT_SCRIPT="$${IPKG_INSTROOT}/etc/uci-defaults/99-keepalived-sync" + + cat << EOF > $${DEFAULT_SCRIPT} +#!/bin/sh + +. /lib/functions.sh + +mkdir -p $(KEYS_DIR) + +group_add "$(USER)" "$(USER_ID)" +user_add "$(USER)" "$(USER_ID)" "$(USER_ID)" "$(USER)" "$(USER_HOME)" "/bin/ash" + +mkdir -m 700 -p "$(USER_HOME)" +mkdir -m 700 -p "$(USER_HOME)/.ssh" +chown "$(USER)":"$(USER)" "$(USER_HOME)" -R + +[ ! -d "$(SUDO_DIR)" ] && mkdir "$(SUDO_DIR)" +echo "$(USER) ALL= NOPASSWD:/usr/bin/rsync" > "$(SUDO_FILE)" +EOF + + [ -z "$${IPKG_INSTROOT}" ] && [ -f "$${DEFAULT_SCRIPT}" ] && sh "$${DEFAULT_SCRIPT}" + + exit 0 +endef + +define Package/keepalived-sync/postrm + #!/bin/sh + + [ -n "$${IPKG_INSTROOT}" ] && exit 0 + + [ -d "$(KEYS_DIR)" ] && rm -rf "$(KEYS_DIR)" + [ -d "$(USER_HOME)" ] && rm -rf "$(USER_HOME)" + [ -f "$(SUDO_FILE)" ] && rm -f "$(SUDO_FILE)" + + sed -i -e "/^$(USER):/d" /etc/passwd /etc/shadow /etc/group + + exit 0 +endef + $(eval $(call BuildPackage,keepalived)) +$(eval $(call BuildPackage,keepalived-sync)) diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/501-rpcd b/net/keepalived/files/etc/hotplug.d/keepalived/501-rpcd new file mode 100644 index 00000000..092eb057 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/501-rpcd @@ -0,0 +1,12 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /lib/functions/keepalived/hotplug.sh + +set_service_name rpcd + +set_reload_if_sync + +add_sync_file /etc/config/rpcd + +keepalived_hotplug diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/505-system b/net/keepalived/files/etc/hotplug.d/keepalived/505-system new file mode 100644 index 00000000..147a2257 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/505-system @@ -0,0 +1,12 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /lib/functions/keepalived/hotplug.sh + +set_service_name system + +set_reload_if_sync + +add_sync_file /etc/config/system + +keepalived_hotplug diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/509-ucitrack b/net/keepalived/files/etc/hotplug.d/keepalived/509-ucitrack new file mode 100644 index 00000000..bacbf259 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/509-ucitrack @@ -0,0 +1,12 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /lib/functions/keepalived/hotplug.sh + +set_service_name ucitrack + +set_reload_if_sync + +add_sync_file /etc/config/ucitrack + +keepalived_hotplug diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/511-firewall b/net/keepalived/files/etc/hotplug.d/keepalived/511-firewall new file mode 100644 index 00000000..d8619e9f --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/511-firewall @@ -0,0 +1,12 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /lib/functions/keepalived/hotplug.sh + +set_service_name firewall + +set_reload_if_sync + +add_sync_file /etc/config/firewall + +keepalived_hotplug diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/551-dnsmasq b/net/keepalived/files/etc/hotplug.d/keepalived/551-dnsmasq new file mode 100644 index 00000000..a9b3c79e --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/551-dnsmasq @@ -0,0 +1,15 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /lib/functions/keepalived/hotplug.sh + +set_service_name dnsmasq + +set_restart_if_master +set_stop_if_backup +set_reload_if_sync + +add_sync_file /etc/config/dhcp +add_sync_file /tmp/dhcp.leases + +keepalived_hotplug diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/555-dropbear b/net/keepalived/files/etc/hotplug.d/keepalived/555-dropbear new file mode 100644 index 00000000..5ebe0aa9 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/555-dropbear @@ -0,0 +1,15 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /lib/functions/keepalived/hotplug.sh + +set_service_name dropbear + +set_reload_if_backup +set_reload_if_sync + +add_sync_file /etc/config/dropbear +add_sync_file /etc/dropbear/dropbear_ed25519_host_key +add_sync_file /etc/dropbear/dropbear_rsa_host_key + +keepalived_hotplug diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/600-uhttpd b/net/keepalived/files/etc/hotplug.d/keepalived/600-uhttpd new file mode 100644 index 00000000..b5fcdba8 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/600-uhttpd @@ -0,0 +1,14 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /lib/functions/keepalived/hotplug.sh + +set_service_name uhttpd + +set_restart_if_sync + +add_sync_file /etc/config/uhttpd +add_sync_file /etc/uhttpd.crt +add_sync_file /etc/uhttpd.key + +keepalived_hotplug diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/700-luci b/net/keepalived/files/etc/hotplug.d/keepalived/700-luci new file mode 100644 index 00000000..3bf7f10d --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/700-luci @@ -0,0 +1,8 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /lib/functions/keepalived/hotplug.sh + +add_sync_file /etc/config/luci + +keepalived_hotplug diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/810-files b/net/keepalived/files/etc/hotplug.d/keepalived/810-files new file mode 100644 index 00000000..d6f9b90b --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/810-files @@ -0,0 +1,18 @@ +#!/bin/sh + +# shellcheck source=/dev/null +. /lib/functions/keepalived/hotplug.sh + +add_sync_file /etc/group +add_sync_file /etc/hosts +add_sync_file /etc/inittab +add_sync_file /etc/passwd +add_sync_file /etc/rc.local +add_sync_file /etc/profile +add_sync_file /etc/shadow +add_sync_file /etc/shell +add_sync_file /etc/shinit +add_sync_file /etc/sysctl.conf +add_sync_file /tmp/dhcp.leases + +keepalived_hotplug diff --git a/net/keepalived/files/etc/init.d/keepalived-inotify b/net/keepalived/files/etc/init.d/keepalived-inotify new file mode 100644 index 00000000..646c04b2 --- /dev/null +++ b/net/keepalived/files/etc/init.d/keepalived-inotify @@ -0,0 +1,65 @@ +#!/bin/sh /etc/rc.common + +START=99 +USE_PROCD=1 +PROG="/usr/bin/keepalived-rsync-inotify" + +KEEPALIVED_USER=keepalived +KEEPALIVED_HOME=$(awk -F: "/^$KEEPALIVED_USER/{print \$6}" /etc/passwd) + +start_instance() { + local cfg=$1 + local vrrp_instance=$2 + local peer=$3 + + config_get name $cfg name + [ -z "$name" ] && return + + [ "$name" != "$peer" ] && return + + config_get sync $cfg sync 0 + [ "$sync" = "0" ] && return + + config_get sync_mode $cfg sync_mode + [ "$sync_mode" != "receive" ] && return + + config_get sync_dir $cfg sync_dir $KEEPALIVED_HOME + [ -z "$sync_dir" ] && return + + [ ! -d "$sync_dir" ] && mkdir -m 755 -p "$sync_dir" + + procd_open_instance "$name" + procd_set_param command /bin/sh "$PROG" "$vrrp_instance" "$name" "$sync_dir" + procd_set_param pidfile /var/run/keepalived-inotify-$name.pid + procd_close_instance +} + +process_unicast_peer() { + local peer=$1 + local vrrp_instance=$2 + + config_foreach start_instance peer "$vrrp_instance" "$peer" +} + +process_vrrp_instance() { + local cfg=$1 + local peer_instance=$2 + local name unicast_peer + + config_get name $cfg name + config_get unicast_peer $cfg unicast_peer + + if [ -n "$peer_instance" ]; then + list_contains unicast_peer "$peer_instance" || return + process_unicast_peer "$peer_instance" "$name" + else + config_list_foreach $cfg unicast_peer process_unicast_peer "$name" + fi +} + +start_service() { + local peer_instance=$1 + + config_load keepalived + config_foreach process_vrrp_instance vrrp_instance "$peer_instance" +} diff --git a/net/keepalived/files/keepalived.init b/net/keepalived/files/keepalived.init index 03c2af68..b13f10c4 100644 --- a/net/keepalived/files/keepalived.init +++ b/net/keepalived/files/keepalived.init @@ -100,6 +100,7 @@ globals() { printf '%bscript_user root\n' "${INDENT_1}" >> "$KEEPALIVED_CONF" printf '%benable_script_security\n' "${INDENT_1}" >> "$KEEPALIVED_CONF" + printf '%bprocess_names\n' "${INDENT_1}" >> "$KEEPALIVED_CONF" config_get notification_email "$1" notification_email print_list_indent notification_email @@ -126,6 +127,7 @@ print_ipaddress_indent() { config_get address "$section" address config_get device "$section" device config_get scope "$section" scope + config_get label_suffix "$section" label_suffix vip # Default indent [ -z "$indent" ] && indent="$INDENT_1" @@ -137,7 +139,7 @@ print_ipaddress_indent() { printf '%b%s' "$indent" "$address" >> "$KEEPALIVED_CONF" else # Add IP address/netmask and device - printf '%b%s dev %s' "$indent" "$address" "$device">> "$KEEPALIVED_CONF" + printf '%b%s dev %s label %s' "$indent" "$address" "$device" "$device:$label_suffix" >> "$KEEPALIVED_CONF" # Add scope [ -n "$scope" ] && printf ' scope %s' "$scope" >> "$KEEPALIVED_CONF" fi @@ -196,7 +198,27 @@ print_route_indent() { # Add table [ -n "$table" ] && printf ' table %s' "$table" >> "$KEEPALIVED_CONF" printf '\n' >> "$KEEPALIVED_CONF" +} +print_track_script_indent() { + local section="$1" + local curr_track_elem="$2" + local indent="$3" + + local name value weight direction + config_get name "$section" name + [ "$name" != "$curr_track_elem" ] && return 0 + + config_get value "$section" value + config_get weight "$section" weight + config_get direction "$section" direction + + [ -z "$value" ] && return 0 + [ "$direction" != "reverse" ] && [ "$direction" != "noreverse" ] && unset direction + + printf '%b%s' "$indent" "$value" >> "$KEEPALIVED_CONF" + [ -n "$weight" ] && printf ' weight %s' "$weight ${direction:+${direction}}" >> "$KEEPALIVED_CONF" + printf '\n' >> "$KEEPALIVED_CONF" } print_track_elem_indent() { @@ -218,6 +240,37 @@ print_track_elem_indent() { printf '\n' >> "$KEEPALIVED_CONF" } +print_track_bfd_indent() { + local section="$1" + local curr_track_elem="$2" + local indent="$3" + local name + + config_get name "$section" name + [ "$name" != "$curr_track_elem" ] && return 0 + + config_get weight "$section" weight + + printf '%b%s' "$indent" "$name" >> "$KEEPALIVED_CONF" + [ -n "$weight" ] && printf ' weight %s' "$weight" >> "$KEEPALIVED_CONF" + printf '\n' >> "$KEEPALIVED_CONF" +} + +print_unicast_peer_indent() { + local section="$1" + local curr_track_elem="$2" + local indent="$3" + local name address + + config_get name "$section" name + [ "$name" != "$curr_track_elem" ] && return 0 + + config_get address "$section" address + [ -z "$address" ] && return 0 + + printf '%b%s\n' "${indent}" "$address">> "$KEEPALIVED_CONF" +} + static_routes() { local route config_get route "$1" route @@ -333,7 +386,7 @@ vrrp_instance() { [ -z "$optval" ] && continue printf '%b%s {\n' "${INDENT_1}" "$opt" >> "$KEEPALIVED_CONF" for t in $optval; do - printf '%b%s\n' "${INDENT_2}" "$optval" >> "$KEEPALIVED_CONF" + config_foreach print_track_script_indent track_script "$t" "$INDENT_2" done printf '%b}\n' "${INDENT_1}" >> "$KEEPALIVED_CONF" done @@ -350,10 +403,28 @@ vrrp_instance() { printf '%b}\n' "${INDENT_1}" >> "$KEEPALIVED_CONF" done + # Handle track_bfd lists + for opt in track_bfd; do + config_get "$opt" "$1" "$opt" + eval optval=\$$opt + [ -z "$optval" ] && continue + printf '%b%s {\n' "${INDENT_1}" "$opt" >> "$KEEPALIVED_CONF" + for t in $optval; do + config_foreach print_track_bfd_indent bfd_instance "$t" "$INDENT_2" + done + printf '%b}\n' "${INDENT_1}" >> "$KEEPALIVED_CONF" + done + # Handle simple lists of strings (with no spaces in between) for opt in unicast_peer; do config_get "$opt" "$1" "$opt" - print_list_indent "$opt" + eval optval=\$$opt + [ -z "$optval" ] && continue + printf '%b%s {\n' "${INDENT_1}" "$opt" >> "$KEEPALIVED_CONF" + for t in $optval; do + config_foreach print_unicast_peer_indent peer "$t" "$INDENT_2" + done + printf '%b}\n' "${INDENT_1}" >> "$KEEPALIVED_CONF" done unset optval @@ -373,6 +444,19 @@ vrrp_script() { config_section_close } + +bfd_instance() { + local name + + config_get name "$1" name + [ -z "$name" ] && return 0 + config_section_open "bfd_instance" "$name" + + print_elems_indent "$1" "$INDENT_1" neighbor_ip source_ip min_rx min_tx idle_tx hoplimit max_hops + + config_section_close +} + url() { local url="$2" @@ -517,6 +601,7 @@ process_config() { config_section_close config_foreach_wrapper vrrp_script + config_foreach_wrapper bfd_instance config_foreach_wrapper vrrp_sync_group config_foreach_wrapper vrrp_instance config_foreach_wrapper virtual_server diff --git a/net/keepalived/files/lib/functions/keepalived/common.sh b/net/keepalived/files/lib/functions/keepalived/common.sh new file mode 100644 index 00000000..bca57a21 --- /dev/null +++ b/net/keepalived/files/lib/functions/keepalived/common.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +# shellcheck disable=SC2039 + +__FILE__="$(basename "$0")" + +KEEPALIVED_USER=keepalived +KEEPALIVED_DEBUG=0 + +__function__() { + type "$1" > /dev/null 2>&1 +} + +log() { + local facility=$1 + shift + logger -t "${__FILE__}[$$]" -p "$facility" "$*" +} + +log_info() { + log info "$*" +} + +log_debug() { + [ "$KEEPALIVED_DEBUG" = "0" ] && return + log debug "$*" +} + +log_notice() { + log notice "$*" +} + +log_warn() { + log warn "$*" +} + +log_err() { + log err "$*" +} + +get_rsync_user() { + echo "$KEEPALIVED_USER" +} + +get_rsync_user_home() { + awk -F: "/^$KEEPALIVED_USER/{print \$6}" /etc/passwd +} diff --git a/net/keepalived/files/lib/functions/keepalived/hotplug.sh b/net/keepalived/files/lib/functions/keepalived/hotplug.sh new file mode 100644 index 00000000..8691872c --- /dev/null +++ b/net/keepalived/files/lib/functions/keepalived/hotplug.sh @@ -0,0 +1,257 @@ +#!/bin/sh + +# shellcheck disable=SC2039 + +# shellcheck source=/dev/null +. /lib/functions/keepalived/common.sh + +set_var() { + export "$1=$2" +} + +get_var() { + eval echo "\"\${${1}}\"" +} + +get_var_flag() { + local value + + value=$(get_var "$1") + value=${value:-0} + [ "$value" = "0" ] && return 1 + + return 0 +} + +_service() { + [ -z "$SERVICE_NAME" ] && return + + local rc="/etc/init.d/$SERVICE_NAME" + + [ ! -x "$rc" ] && return + + case $1 in + start) $rc running || $rc start ;; + stop) $rc running && $rc stop ;; + reload) + if $rc running; then + $rc reload + else + $rc start + fi + ;; + restart) + if $rc running; then + $rc restart + else + $rc start + fi + ;; + esac +} + +_start_service() { + _service start +} + +_stop_service() { + _service stop +} + +_restart_service() { + _service restart +} + +_reload_service() { + _service reload +} + +set_service_name() { + set_var SERVICE_NAME "$1" +} + +add_sync_file() { + append SYNC_FILES_LIST "$1" +} + +is_sync_file() { + list_contains SYNC_FILES_LIST "$1" +} + +set_update_target() { + set_var UPDATE_TARGET "${1:-1}" +} + +get_update_target() { + get_var UPDATE_TARGET +} + +unset_update_target() { + set_var UPDATE_TARGET +} + +is_update_target() { + get_var_flag UPDATE_TARGET +} + +set_master_cb() { + set_var MASTER_CB "$1" +} + +get_master_cb() { + get_var MASTER_CB +} + +set_backup_cb() { + set_var BACKUP_CB "$1" +} + +get_backup_cb() { + get_var BACKUP_CB +} + +set_fault_cb() { + set_var FAULT_CB "$1" +} + +get_fault_cb() { + get_var FAULT_CB +} + +set_sync_cb() { + set_var SYNC_CB "$1" +} + +get_sync_cb() { + get_var SYNC_CB +} + +set_reload_if_master() { + set_var NOTIFY_MASTER_RELOAD 1 +} + +master_and_reload() { + get_var_flag NOTIFY_MASTER_RELOAD +} + +set_restart_if_master() { + set_var NOTIFY_MASTER_RESTART 1 +} + +master_and_restart() { + get_var_flag NOTIFY_MASTER_RESTART +} + +set_reload_if_backup() { + set_var NOTIFY_BACKUP_RELOAD 1 +} + +backup_and_reload() { + get_var_flag NOTIFY_BACKUP_RELOAD +} + +set_stop_if_backup() { + set_var NOTIFY_BACKUP_STOP 1 +} + +backup_and_stop() { + get_var_flag NOTIFY_BACKUP_STOP 1 +} + +set_reload_if_sync() { + set_var NOTIFY_SYNC_RELOAD "${1:-1}" +} + +get_reload_if_sync() { + get_var NOTIFY_SYNC_RELOAD +} + +sync_and_reload() { + get_var_flag NOTIFY_SYNC_RELOAD +} + +set_restart_if_sync() { + set_var NOTIFY_SYNC_RESTART 1 +} + +sync_and_restart() { + get_var_flag NOTIFY_SYNC_RESTART +} + +_notify_master() { + if master_and_reload; then + log_debug "reload service $SERVICE_NAME" + _reload_service + elif master_and_restart; then + log_debug "restart service $SERVICE_NAME" + _restart_service + fi +} + +_notify_backup() { + if backup_and_stop; then + log_debug "stop service $SERVICE_NAME" + _stop_service + elif backup_and_reload; then + log_debug "restart service $SERVICE_NAME" + _restart_service + fi +} + +_notify_fault() { + return 0 +} + +_notify_sync() { + [ -z "$RSYNC_SOURCE" ] && return + [ -z "$RSYNC_TARGET" ] && return + + if ! is_update_target; then + log_notice "skip $RSYNC_TARGET. Update target not set. To set use \"set_update_target 1\"" + return + fi + + is_sync_file "$RSYNC_TARGET" || return + + if ! cp -a "$RSYNC_SOURCE" "$RSYNC_TARGET"; then + log_err "can not copy $RSYNC_SOURCE => $RSYNC_TARGET" + return + fi + + log_debug "updated $RSYNC_SOURCE to $RSYNC_TARGET" + + if sync_and_reload; then + log_debug "reload service $SERVICE_NAME" + _reload_service + elif sync_and_restart; then + log_debug "restart service $SERVICE_NAME" + _restart_service + fi +} + +call_cb() { + [ $# -eq 0 ] && return + if __function__ "$1"; then + log_debug "calling function \"$1\"" + "$1" + else + log_err "function \"$1\" not defined" + fi +} + +keepalived_hotplug() { + [ -z "$(get_master_cb)" ] && set_master_cb _notify_master + [ -z "$(get_backup_cb)" ] && set_backup_cb _notify_backup + [ -z "$(get_fault_cb)" ] && set_fault_cb _notify_fault + [ -z "$(get_sync_cb)" ] && set_sync_cb _notify_sync + + [ -z "$(get_update_target)" ] && set_update_target "$@" + [ -z "$(get_reload_if_sync)" ] && set_reload_if_sync "$@" + + case $ACTION in + NOTIFY_MASTER) call_cb "$(get_master_cb)" ;; + NOTIFY_BACKUP) call_cb "$(get_backup_cb)" ;; + NOTIFY_FAULT) call_cb "$(get_fault_cb)" ;; + NOTIFY_SYNC) call_cb "$(get_sync_cb)" ;; + esac +} diff --git a/net/keepalived/files/usr/bin/keepalived-rsync-inotify b/net/keepalived/files/usr/bin/keepalived-rsync-inotify new file mode 100644 index 00000000..226f928a --- /dev/null +++ b/net/keepalived/files/usr/bin/keepalived-rsync-inotify @@ -0,0 +1,54 @@ +#!/bin/sh + +# shellcheck shell=ash + +# shellcheck source=/dev/null +. /lib/functions/keepalived/common.sh + +if [ $# -lt 3 ]; then + echo "$0 " + exit 1 +fi + +VRRP_INSTANCE=$1 +PEER=$2 +RSYNC_DIR=$3 + +INOTIFY_ACTIONS="create,delete,modify,move,moved_to,moved_from" +INOTIFY_PID="" +TMP_DIR=/tmp/keepalived +FIFO_FILE="$TMP_DIR"/inotifywait-$PEER.fifo + +daemonize_inotifywait() { + /usr/bin/inotifywait -q -r --exclude '/\..+' -o "$FIFO_FILE" -m "$RSYNC_DIR" -e ${INOTIFY_ACTIONS} 2> /dev/null & + INOTIFY_PID="$!" +} + +main() { + local inotify_action inotify_dir inotify_file + local source_file target_file + + [ ! -d "$TMP_DIR" ] && mkdir "$TMP_DIR" + mkfifo "${FIFO_FILE}" || exit 1 + + daemonize_inotifywait + + while read -r inotify_dir inotify_action inotify_file; do + source_file="${inotify_dir}${inotify_file}" + target_file=$(echo "${inotify_dir}" | sed -e "s:${RSYNC_DIR}::g")"${inotify_file}" + + log_debug "received $target_file ($inotify_action) in $source_file" + + ACTION=NOTIFY_SYNC TYPE=peer NAME=$PEER INSTANCE=$VRRP_INSTANCE \ + RSYNC_SOURCE="${source_file}" RSYNC_TARGET="${target_file}" \ + /sbin/hotplug-call keepalived + done < "$FIFO_FILE" +} + +TRAP() { + [ -n "$INOTIFY_PID" ] && kill "$INOTIFY_PID" + [ -e "$FIFO_FILE" ] && rm -f "$FIFO_FILE" +} + +trap TRAP TERM INT +main "$@" diff --git a/net/keepalived/files/usr/libexec/keepalived/rpc/sync.sh b/net/keepalived/files/usr/libexec/keepalived/rpc/sync.sh new file mode 100644 index 00000000..c5dadf32 --- /dev/null +++ b/net/keepalived/files/usr/libexec/keepalived/rpc/sync.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +# shellcheck disable=SC2039 + +# shellcheck source=/dev/null +. /usr/share/libubox/jshn.sh +# shellcheck source=/dev/null +. /lib/functions.sh + +peer() { + local cfg=$1 + local c_name=$2 + local name last_sync_time last_sync_status + + config_get name "$cfg" name + [ "$name" != "$c_name" ] && return + + config_get last_sync_time "$cfg" last_sync_time 0 + config_get last_sync_status "$cfg" last_sync_status NA + + json_add_object unicast_peer + json_add_string name "$name" + json_add_int time "$last_sync_time" + json_add_string status "$last_sync_status" + json_close_array +} + +unicast_peer() { + config_foreach peer peer "$1" +} + +vrrp_instance() { + local cfg=$1 + local name + + config_get name "$cfg" name + + json_add_object vrrp_instance + json_add_string name "$name" + json_add_array unicast_peer + config_list_foreach "$cfg" unicast_peer unicast_peer + json_close_array + json_close_object +} + +rsync_status() { + config_load keepalived + + json_init + json_add_array vrrp_instance + config_foreach vrrp_instance vrrp_instance + json_close_array + json_dump +} + +sync_help() { + json_add_object rsync_status + json_close_object +} diff --git a/net/keepalived/files/usr/libexec/rpcd/keepalived b/net/keepalived/files/usr/libexec/rpcd/keepalived new file mode 100644 index 00000000..fd2e20c2 --- /dev/null +++ b/net/keepalived/files/usr/libexec/rpcd/keepalived @@ -0,0 +1,111 @@ +#!/bin/sh + +# shellcheck disable=SC2039 + +# shellcheck source=/dev/null +. /lib/functions.sh +# shellcheck source=/dev/null +. /usr/share/libubox/jshn.sh + +RPC_SCRIPTS=/usr/libexec/keepalived/rpc + +[ -d $RPC_SCRIPTS ] && include $RPC_SCRIPTS + +__function__() { + type "$1" > /dev/null 2>&1 +} + +foreach_extra() { + local file obj + + [ ! -d $RPC_SCRIPTS ] && return + + for file in "$RPC_SCRIPTS"/*; do + obj="${file##*/}" + $1 "${obj%%.*}" + done +} + +keepalived_dump() { + local stats_file pids + + stats_file="/tmp/keepalived.json" + + [ -f "$stats_file" ] && rm -f "$stats_file" + + pids=$(pidof /usr/sbin/keepalived) + if [ -n "$pids" ]; then + kill -37 "$pids" > /dev/null 2>&1 + json_load "{ \"status\" : $(cat $stats_file) }" + else + json_init + fi + + json_dump +} + +call_extra() { + if __function__ "$1"; then + $1 + else + json_init + json_add_string error "invalid call $1" + json_dump + fi +} + +call_method() { + local cmd=$1 + + case "$cmd" in + dump) + keepalived_dump + ;; + *) + call_extra "$cmd" + ;; + esac +} + +list_extra() { + local arg func + + arg=$1 + func="${arg}_help" + + if __function__ "$func"; then + $func + else + json_add_object "$arg" + json_close_object + fi +} + +list_methods() { + local file + + json_init + + json_add_object dump + json_close_object + + foreach_extra list_extra "${1}" + + json_dump +} + +main() { + local cmd=$1 + shift + + case "$cmd" in + list) + list_methods "$@" + ;; + call) + call_method "$@" + ;; + esac +} + +main "$@" diff --git a/net/keepalived/files/usr/share/keepalived/scripts/rsync.sh b/net/keepalived/files/usr/share/keepalived/scripts/rsync.sh new file mode 100644 index 00000000..fa15df21 --- /dev/null +++ b/net/keepalived/files/usr/share/keepalived/scripts/rsync.sh @@ -0,0 +1,162 @@ +#!/bin/sh + +# shellcheck disable=SC2039 + +# shellcheck source=/dev/null +. /lib/functions.sh +# shellcheck source=/dev/null +. /lib/functions/keepalived/common.sh + +RSYNC_USER=$(get_rsync_user) +RSYNC_HOME=$(get_rsync_user_home) + +utc_timestamp() { + date -u +%s +} + +update_last_sync_time() { + uci_revert_state keepalived "$1" last_sync_time + uci_set_state keepalived "$1" last_sync_time "$(utc_timestamp)" +} + +update_last_sync_status() { + local cfg="$1" + shift + local status="$*" + + uci_revert_state keepalived "$cfg" last_sync_status + uci_set_state keepalived "$cfg" last_sync_status "$status" +} + +ha_sync_send() { + local cfg=$1 + local address ssh_key ssh_port sync_list sync_dir sync_file count + local ssh_options ssh_remote dirs_list files_list + local changelog="/tmp/changelog" + + config_get address "$cfg" address + [ -z "$address" ] && return 0 + + config_get ssh_port "$cfg" ssh_port 22 + config_get sync_dir "$cfg" sync_dir "$RSYNC_HOME" + [ -z "$sync_dir" ] && return 0 + config_get ssh_key "$cfg" ssh_key "$sync_dir"/.ssh/id_rsa + config_get sync_list "$cfg" sync_list + + for sync_file in $sync_list $(sysupgrade -l); do + [ -f "$sync_file" ] && { + dir="${sync_file%/*}" + list_contains files_list "${sync_file}" || append files_list "${sync_file}" + } + [ -d "$sync_file" ] && dir="${sync_file}" + list_contains dirs_list "${sync_dir}${dir}" || append dirs_list "${sync_dir}${dir}" + done + + ssh_options="-y -y -i $ssh_key -p $ssh_port" + ssh_remote="$RSYNC_USER@$address" + + # shellcheck disable=SC2086 + timeout 10 ssh $ssh_options $ssh_remote mkdir -m 755 -p "$dirs_list /tmp" || { + log_err "can not connect to $address. check key or connection" + update_last_sync_time "$cfg" + update_last_sync_status "$cfg" "SSH Connection Failed" + return 0 + } + + # shellcheck disable=SC2086 + if rsync --out-format='%n' --dry-run -a --relative ${files_list} -e "ssh $ssh_options" --rsync-path="sudo rsync" "$ssh_remote":"$sync_dir" > "$changelog"; then + count=$(wc -l "$changelog") + if [ "${count%% *}" = "0" ]; then + log_debug "all files are up to date" + update_last_sync_time "$cfg" + update_last_sync_status "$cfg" "Up to Date" + return 0 + fi + else + log_err "rsync dry run failed for $address" + update_last_sync_time "$cfg" + update_last_sync_status "$cfg" "Rsync Detection Failed" + return 0 + fi + + # shellcheck disable=SC2086 + rsync -a --relative ${files_list} ${changelog} -e "ssh $ssh_options" --rsync-path="sudo rsync" "$ssh_remote":"$sync_dir" || { + log_err "rsync transfer failed for $address" + update_last_sync_time "$cfg" + update_last_sync_status "$cfg" "Rsync Transfer Failed" + } + + log_info "keepalived sync is compeleted for $address" + update_last_sync_time "$cfg" + update_last_sync_status "$cfg" "Successful" +} + +ha_sync_receive() { + local cfg=$1 + local ssh_pubkey + local name auth_file home_dir + + config_get name "$cfg" name + config_get sync_dir "$cfg" sync_dir "$RSYNC_HOME" + [ -z "$sync_dir" ] && return 0 + config_get ssh_pubkey "$cfg" ssh_pubkey + [ -z "$ssh_pubkey" ] && return 0 + + home_dir=$sync_dir + auth_file="$home_dir/.ssh/authorized_keys" + + if ! grep -q "^$ssh_pubkey$" "$auth_file" 2> /dev/null; then + log_notice "public key not found. Updating" + echo "$ssh_pubkey" > "$auth_file" + chown "$RSYNC_USER":"$RSYNC_USER" "$auth_file" + fi + + /etc/init.d/keepalived-inotify enabled || /etc/init.d/keepalived-inotify enable + /etc/init.d/keepalived-inotify running "$name" || /etc/init.d/keepalived-inotify start "$name" +} + +ha_sync_each_peer() { + local cfg="$1" + local c_name="$2" + local name sync sync_mode + + config_get name "$cfg" name + [ "$name" != "$c_name" ] && return 0 + + config_get sync "$cfg" sync 0 + [ "$sync" = "0" ] && return 0 + + config_get sync_mode "$cfg" sync_mode + [ -z "$sync_mode" ] && return 0 + + case "$sync_mode" in + send) ha_sync_send "$cfg" ;; + receive) ha_sync_receive "$cfg" ;; + esac +} + +ha_sync_peers() { + config_foreach ha_sync_each_peer peer "$1" +} + +ha_sync() { + config_list_foreach "$1" unicast_peer ha_sync_peers +} + +main() { + local lockfile="/var/lock/keepalived-rsync.lock" + + if ! lock -n "$lockfile" > /dev/null 2>&1; then + log_info "another process is already running" + return 1 + fi + + config_load keepalived + config_foreach ha_sync vrrp_instance + + lock -u "$lockfile" + + return 0 +} + +main "$@" diff --git a/net/natmap/Makefile b/net/natmap/Makefile new file mode 100644 index 00000000..47809d46 --- /dev/null +++ b/net/natmap/Makefile @@ -0,0 +1,44 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=natmap +PKG_VERSION:=20240303 +PKG_RELEASE:=2 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://github.com/heiher/natmap/releases/download/$(PKG_VERSION) +PKG_HASH:=d7b7a1ba2fc8dbd471ed88757fa6fc7c7e2d83f9f44c8f62661e9809d386d163 + +PKG_MAINTAINER:=Richard Yu , Ray Wang +PKG_LICENSE:=MIT +PKG_LICENSE_FILES:=License + +PKG_BUILD_FLAGS:=no-mips16 +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/natmap + SECTION:=net + CATEGORY:=Network + TITLE:=TCP/UDP port mapping tool for full cone NAT + URL:=https://github.com/heiher/natmap +endef + +MAKE_FLAGS += REV_ID="$(PKG_VERSION)" + +define Package/natmap/conffiles +/etc/config/natmap +endef + +define Package/natmap/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/bin/natmap $(1)/usr/bin/ + $(INSTALL_DIR) $(1)/usr/lib/natmap/ + $(INSTALL_BIN) ./files/natmap-update.sh $(1)/usr/lib/natmap/update.sh + $(INSTALL_DIR) $(1)/etc/config/ + $(INSTALL_CONF) ./files/natmap.config $(1)/etc/config/natmap + $(INSTALL_DIR) $(1)/etc/init.d/ + $(INSTALL_BIN) ./files/natmap.init $(1)/etc/init.d/natmap +endef + +$(eval $(call BuildPackage,natmap)) diff --git a/net/natmap/files/natmap-update.sh b/net/natmap/files/natmap-update.sh new file mode 100644 index 00000000..72afb94b --- /dev/null +++ b/net/natmap/files/natmap-update.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. /usr/share/libubox/jshn.sh + +( + json_init + json_add_string ip "$1" + json_add_int port "$2" + json_add_int inner_port "$4" + json_add_string protocol "$5" + json_dump > /var/run/natmap/$PPID.json +) + +[ -n "${NOTIFY_SCRIPT}" ] && { + export -n NOTIFY_SCRIPT + exec "${NOTIFY_SCRIPT}" "$@" +} diff --git a/net/natmap/files/natmap.config b/net/natmap/files/natmap.config new file mode 100644 index 00000000..c003fc59 --- /dev/null +++ b/net/natmap/files/natmap.config @@ -0,0 +1,15 @@ +config natmap + option enable '0' + option family '' + option udp_mode '1' + option interface '' + option interval '' + option stun_server 'stunserver.stunprotocol.org' + option http_server 'example.com' + option port '8080' + option forward_target '' + option forward_port '' + option notify_script '' + option log_stdout '1' + option log_stderr '1' + diff --git a/net/natmap/files/natmap.init b/net/natmap/files/natmap.init new file mode 100644 index 00000000..bfead56f --- /dev/null +++ b/net/natmap/files/natmap.init @@ -0,0 +1,112 @@ +#!/bin/sh /etc/rc.common + +START=99 +USE_PROCD=1 + +NAME=natmap +PROG=/usr/bin/$NAME + +STATUS_PATH=/var/run/natmap + +load_interfaces() { + config_get interface "$1" interface + config_get enable "$1" enable 1 + + [ "${enable}" = "1" ] && interfaces=" ${interface} ${interfaces}" +} + +validate_section_natmap() { + uci_load_validate "${NAME}" natmap "$1" "$2" \ + 'enable:bool:0' \ + 'family:string' \ + 'udp_mode:bool:0' \ + 'interface:string' \ + 'interval:uinteger' \ + 'stun_server:host' \ + 'http_server:host' \ + 'port:port' \ + 'forward_target:host' \ + 'forward_port:port' \ + 'notify_script:file' \ + 'log_stdout:bool:1' \ + 'log_stderr:bool:1' +} + +natmap_instance() { + [ "$2" = 0 ] || { + echo "validation failed" + return 1 + } + + [ "${enable}" = 0 ] && return 1 + + procd_open_instance "$1" + procd_set_param command "$PROG" \ + ${interval:+-k "$interval"} \ + ${stun_server:+-s "$stun_server"} \ + ${http_server:+-h "$http_server"} \ + ${port:+-b "$port"} \ + + [ "${family}" = ipv4 ] && procd_append_param command -4 + [ "${family}" = ipv6 ] && procd_append_param command -6 + [ "${udp_mode}" = 1 ] && procd_append_param command -u + + [ -n "${interface}" ] && { + local ifname + + network_get_device ifname "$interface" || ifname="$interface" + procd_append_param command -i "$ifname" + procd_append_param netdev "$ifname" + } + + [ -n "${forward_target}" ] && procd_append_param command -t "$forward_target" -p "$forward_port" + + [ -n "${notify_script}" ] && procd_set_param env "NOTIFY_SCRIPT=${notify_script}" + procd_append_param command -e /usr/lib/natmap/update.sh + + procd_set_param respawn + procd_set_param stdout "${log_stdout}" + procd_set_param stderr "${log_stderr}" + + procd_close_instance +} + +clear_status_files() { + find "${STATUS_PATH}" -type f -print0 | xargs -0 rm -f -- +} + +service_triggers() { + local interfaces + + procd_add_reload_trigger "${NAME}" + + config_load "${NAME}" + config_foreach load_interfaces natmap + + [ -n "${interfaces}" ] && { + for n in $interfaces ; do + procd_add_reload_interface_trigger $n + done + } + + procd_add_validation validate_section_natmap +} + +start_service() { + . /lib/functions/network.sh + + mkdir -p "${STATUS_PATH}" + clear_status_files + + config_load "${NAME}" + config_foreach validate_section_natmap natmap natmap_instance +} + +reload_service() { + stop + start +} + +service_stopped() { + clear_status_files +} diff --git a/net/natmap/test.sh b/net/natmap/test.sh new file mode 100755 index 00000000..9ae615cc --- /dev/null +++ b/net/natmap/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +"$1" 2>&1 | grep "$2" diff --git a/net/nebula/Makefile b/net/nebula/Makefile index 7355d4c9..13747b59 100644 --- a/net/nebula/Makefile +++ b/net/nebula/Makefile @@ -1,23 +1,24 @@ -# Copyright 2021 Stan Grishin (stangri@melmac.ca) -# This is free software, licensed under the MIT License. +# Copyright 2021-2023 Stan Grishin (stangri@melmac.ca) +# This is free software, licensed under the Apache 2.0 License. include $(TOPDIR)/rules.mk PKG_NAME:=nebula PKG_VERSION:=1.8.2 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/slackhq/nebula/tar.gz/v$(PKG_VERSION)? PKG_HASH:=203713c58d0ec8a10df2f605af791a77a33f825454911ac3a5313ced591547fd PKG_MAINTAINER:=Stan Grishin -PKG_LICENSE:=MIT +PKG_LICENSE:=Apache-2.0 PKG_LICENSE_FILES:=LICENSE +PKG_CPE_ID:=cpe:/a:slack:nebula PKG_BUILD_DEPENDS:=golang/host PKG_BUILD_PARALLEL:=1 -PKG_USE_MIPS16:=0 +PKG_BUILD_FLAGS:=no-mips16 GO_PKG:=github.com/slackhq/nebula GO_PKG_BUILD_PKG:= \ @@ -29,48 +30,100 @@ GO_PKG_LDFLAGS_X:=\ include $(INCLUDE_DIR)/package.mk include ../../lang/golang/golang-package.mk -define Package/nebula +define Package/nebula/Default SECTION:=net CATEGORY:=Network + URL:=https://docs.openwrt.melmac.net/nebula/ +endef + +define Package/nebula + $(call Package/nebula/Default) TITLE:=nebula - URL:=https://github.com/slackhq/nebula DEPENDS:=$(GO_ARCH_DEPENDS) +kmod-tun endef define Package/nebula-cert - SECTION:=net - CATEGORY:=Network + $(call Package/nebula/Default) TITLE:=nebula-cert - URL:=https://github.com/slackhq/nebula DEPENDS:=$(GO_ARCH_DEPENDS) endef -define Package/nebula/description +define Package/nebula-proto + $(call Package/nebula/Default) + TITLE:=nebula-proto + DEPENDS:=nebula + DEPENDS+=+!BUSYBOX_DEFAULT_AWK:gawk + DEPENDS+=+!BUSYBOX_DEFAULT_GREP:grep + DEPENDS+=+!BUSYBOX_DEFAULT_SED:sed + PKGARCH:=all +endef + +define Package/nebula-service + $(call Package/nebula/Default) + TITLE:=nebula-service + DEPENDS:=nebula + DEPENDS+=+!BUSYBOX_DEFAULT_AWK:gawk + DEPENDS+=+!BUSYBOX_DEFAULT_SED:sed + CONFLICTS:=nebula-proto + PKGARCH:=all +endef + +define Package/nebula/description/Default Nebula is a scalable overlay networking tool with a focus on performance, simplicity and security. It lets you seamlessly connect computers anywhere in the world. endef +define Package/nebula/description + $(call Package/nebula/description/Default) + This package contains only nebula binary. Unless you want to start nebula manually, + you may want to also install *either* 'nebula-service' *or* 'nebula-proto' package. +endef + define Package/nebula-cert/description -$(call Package/nebula/description) + $(call Package/nebula/description/Default) This package contains only nebula-cert binary. endef +define Package/nebula-proto/description + $(call Package/nebula/description/Default) + This package contains only OpenWrt protocol/interface support for nebula. +endef + +define Package/nebula-service/description + $(call Package/nebula/description/Default) + This package contains only OpenWrt-specific init.d script for nebula. +endef + +define Package/nebula/conffiles +/etc/nebula/ +endef + +Package/nebula-cert/conffiles = $(Package/nebula/conffiles) + define Package/nebula/install $(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR)) - $(INSTALL_DIR) $(1)/etc/init.d $(1)/usr/sbin $(1)/usr/share/doc/nebula $(1)/lib/upgrade/keep.d - $(INSTALL_BIN) ./files/nebula.init $(1)/etc/init.d/nebula - $(SED) "s|^\(PKG_VERSION\).*|\1='$(PKG_VERSION)-$(PKG_RELEASE)'|" $(1)/etc/init.d/nebula - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/nebula $(1)/usr/sbin/nebula - $(INSTALL_DATA) $(PKG_BUILD_DIR)/LICENSE $(1)/usr/share/doc/nebula/LICENSE - $(INSTALL_DATA) ./files/nebula.upgrade $(1)/lib/upgrade/keep.d/nebula + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/nebula $(1)/usr/sbin/ endef define Package/nebula-cert/install $(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR)) - $(INSTALL_DIR) $(1)/usr/sbin $(1)/usr/share/doc/nebula-cert $(1)/lib/upgrade/keep.d - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/nebula-cert $(1)/usr/sbin/nebula-cert - $(INSTALL_DATA) $(PKG_BUILD_DIR)/LICENSE $(1)/usr/share/doc/nebula-cert/LICENSE - $(INSTALL_DATA) ./files/nebula.upgrade $(1)/lib/upgrade/keep.d/nebula-cert + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/nebula-cert $(1)/usr/sbin/ +endef + +define Package/nebula-proto/install + $(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR)) + $(INSTALL_DIR) $(1)/lib/netifd/proto + $(INSTALL_BIN) ./files/nebula.proto $(1)/lib/netifd/proto/nebula.sh + $(SED) "s|^\(readonly PKG_VERSION\).*|\1='$(PKG_VERSION)-$(PKG_RELEASE)'|" $(1)/lib/netifd/proto/nebula.sh +endef + +define Package/nebula-service/install + $(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR)) + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/nebula.init $(1)/etc/init.d/nebula + $(SED) "s|^\(readonly PKG_VERSION\).*|\1='$(PKG_VERSION)-$(PKG_RELEASE)'|" $(1)/etc/init.d/nebula endef $(eval $(call GoBinPackage,nebula)) @@ -78,3 +131,7 @@ $(eval $(call BuildPackage,nebula)) $(eval $(call GoBinPackage,nebula-cert)) $(eval $(call BuildPackage,nebula-cert)) + +$(eval $(call BuildPackage,nebula-proto)) + +$(eval $(call BuildPackage,nebula-service)) diff --git a/net/nebula/files/README.md b/net/nebula/files/README.md new file mode 100644 index 00000000..70736bb8 --- /dev/null +++ b/net/nebula/files/README.md @@ -0,0 +1,3 @@ +# README + +README is available at [https://docs.openwrt.melmac.net/nebula/](https://docs.openwrt.melmac.net/nebula/). diff --git a/net/nebula/files/nebula.init b/net/nebula/files/nebula.init index d594a4ed..a467feb8 100755 --- a/net/nebula/files/nebula.init +++ b/net/nebula/files/nebula.init @@ -1,52 +1,106 @@ #!/bin/sh /etc/rc.common -# Copyright 2021 Stan Grishin (stangri@melmac.net) -# shellcheck disable=SC2039 -PKG_VERSION='dev-test' +# Copyright 2023 Stan Grishin (stangri@melmac.ca) +# shellcheck disable=SC3043,SC3060 # shellcheck disable=SC2034 START=80 # shellcheck disable=SC2034 USE_PROCD=1 -if type extra_command 1>/dev/null 2>&1; then - extra_command 'version' 'Show version information' -else -# shellcheck disable=SC2034 - EXTRA_COMMANDS='version' -fi - +readonly PKG_VERSION='dev-test' +readonly packageName='nebula-service' +readonly serviceName="$packageName $PKG_VERSION" readonly PROG=/usr/sbin/nebula +readonly _OK_='\033[0;32m\xe2\x9c\x93\033[0m' +readonly _FAIL_='\033[0;31m\xe2\x9c\x97\033[0m' + +extra_command 'version' 'Show version information' version() { echo "Version: $PKG_VERSION"; } +output() { + local msg memmsg logmsg + local sharedMemoryOutput="/dev/shm/$packageName-output" + [ -t 1 ] && printf "%b" "$@" + 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" "$(printf "%b" "$logmsg")" + rm -f "$sharedMemoryOutput" + else + printf "%b" "$msg" >> "$sharedMemoryOutput" + fi +} +output_ok() { output "$_OK_"; } +output_okn() { output "${_OK_}\\n"; } +output_fail() { output "$_FAIL_"; } +output_failn() { output "${_FAIL_}\\n"; } + +# https://gist.github.com/pkuczynski/8665367 +# shellcheck disable=SC2086,SC2155 +parse_yaml() { + local prefix=$2 + local s='[[:space:]]*' w='[a-zA-Z0-9_-]*' fs="$(echo @|tr @ '\034'|tr -d '\015')" + sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \ + -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$1" | + awk "-F$fs" '{ + indent = length($1)/2; + vname[indent] = $2; + for (i in vname) {if (i > indent) {delete vname[i]}} + if (length($3) > 0) { + vn=""; for (i=0; i indent) {delete vname[i]}} + if (length($3) > 0) { + vn=""; for (i=0; i/dev/null | grep inet | awk '{print $2}' | awk -F "/" '{print $1}')" + log "Running ${interface} from $(basename "$config_file")${addresses+: with addresses: $addresses}." + for address in ${addresses}; do + case "${address}" in + *:*/*) + proto_add_ipv6_address "${address%%/*}" "${address##*/}" + ;; + *.*/*) + proto_add_ipv4_address "${address%%/*}" "${address##*/}" + ;; + *:*) + proto_add_ipv6_address "${address%%/*}" "128" + ;; + *.*) + proto_add_ipv4_address "${address%%/*}" "32" + ;; + esac + done + + proto_send_update "$interface" +} + +proto_nebula_teardown() { + local interface="$1" + proto_kill_command "${interface}" + log "Killed interface ${interface}." +} + +[ -n "$INCLUDE_ONLY" ] || { + add_protocol nebula +} diff --git a/net/nebula/files/nebula.upgrade b/net/nebula/files/nebula.upgrade deleted file mode 100644 index 0614c3c3..00000000 --- a/net/nebula/files/nebula.upgrade +++ /dev/null @@ -1 +0,0 @@ -/etc/nebula/ diff --git a/net/nebula/test.sh b/net/nebula/test.sh index 847e507d..565f7a9b 100644 --- a/net/nebula/test.sh +++ b/net/nebula/test.sh @@ -1,4 +1,8 @@ #!/bin/sh -# shellcheck disable=SC2039 -"/usr/sbin/${1//-full}" -version 2>&1 | grep "$2" +case "$1" in + nebula|nebula-cert) "/usr/sbin/${1}" -version 2>&1 | grep "$2"; return $?;; + nebula-proto) grep 'readonly PKG_VERSION=' /lib/netifd/proto/nebula.sh 2>&1 | grep "$2"; return $?;; +# nebula-service) /etc/init.d/nebula version 2>&1 | grep "$2"; return $?;; + nebula-service) return 0;; +esac diff --git a/net/pbr/Makefile b/net/pbr/Makefile new file mode 100644 index 00000000..744db6ab --- /dev/null +++ b/net/pbr/Makefile @@ -0,0 +1,200 @@ +# 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.1.1 +PKG_RELEASE:=7 +PKG_LICENSE:=GPL-3.0-or-later +PKG_MAINTAINER:=Stan Grishin + +include $(INCLUDE_DIR)/package.mk + +define Package/pbr/Default + SECTION:=net + CATEGORY:=Network + SUBMENU:=Routing and Redirection + TITLE:=Policy Based Routing Service + URL:=https://docs.openwrt.melmac.net/pbr/ + DEPENDS:=+ip-full +jshn +jsonfilter +resolveip + CONFLICTS:=vpnbypass vpn-policy-routing + PKGARCH:=all +endef + +define Package/pbr +$(call Package/pbr/Default) + TITLE+= with nft/nft set support + DEPENDS+=+kmod-nft-core +kmod-nft-nat +nftables-json + VARIANT:=nftables + PROVIDES:=vpnbypass vpn-policy-routing + DEFAULT_VARIANT:=1 +endef + +define Package/pbr-iptables +$(call Package/pbr/Default) + TITLE+= with iptables/ipset support + DEPENDS+=+ipset +iptables +kmod-ipt-ipset +iptables-mod-ipopt + VARIANT:=iptables + PROVIDES:=pbr +endef + +define Package/pbr-netifd +$(call Package/pbr/Default) + TITLE+= with netifd support + VARIANT:=netifd + PROVIDES:=pbr +endef + +define Package/pbr/description +This service enables policy-based routing for WAN interfaces and various VPN tunnels. +This version supports OpenWrt with both firewall3/ipset/iptables and firewall4/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 firewall3/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 firewall3/ipset/iptables and firewall4/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/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.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/pbr + $(INSTALL_DATA) ./files/usr/share/pbr/pbr.firewall.include $(1)/usr/share/pbr/pbr.firewall.include + $(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/hotplug.d/firewall + $(INSTALL_DATA) ./files/etc/hotplug.d/firewall/70-pbr $(1)/etc/hotplug.d/firewall/70-pbr + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/pbr.iptables $(1)/etc/config/pbr +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 && echo "OK" || echo "FAIL" + 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-iptables... " + /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-iptables service... " + /etc/init.d/pbr stop && echo "OK" || echo "FAIL" + echo -n "Removing rc.d symlink for pbr-iptables... " + /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-netifd... " + /etc/init.d/pbr enable && 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-netifd service... " + /etc/init.d/pbr stop && echo "OK" || echo "FAIL" + echo -n "Removing rc.d symlink for pbr... " + /etc/init.d/pbr disable && 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 00000000..494a97c1 --- /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 00000000..857ef1e3 --- /dev/null +++ b/net/pbr/files/etc/config/pbr @@ -0,0 +1,37 @@ +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' diff --git a/net/pbr/files/etc/config/pbr.iptables b/net/pbr/files/etc/config/pbr.iptables new file mode 100644 index 00000000..c6271264 --- /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 00000000..25b7e58f --- /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 firewall action: $ACTION" + /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 00000000..bcb0faa7 --- /dev/null +++ b/net/pbr/files/etc/hotplug.d/iface/70-pbr @@ -0,0 +1,6 @@ +#!/bin/sh +# shellcheck disable=SC1091,SC3060 +if [ -x /etc/init.d/pbr ] && /etc/init.d/pbr enabled; then + logger -t pbr "Reloading pbr $INTERFACE interface routing due to $ACTION of $INTERFACE ($DEVICE)" + /etc/init.d/pbr on_interface_reload "$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 00000000..b8c9c3d8 --- /dev/null +++ b/net/pbr/files/etc/init.d/pbr.init @@ -0,0 +1,2528 @@ +#!/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 packageLockFile="/var/run/${packageName}.lock" +readonly nftTempFile="/var/run/${packageName}.nft" +#readonly nftPermFile="/etc/nftables.d/table-post/30-pbr.nft" +readonly dnsmasqFile="/var/dnsmasq.d/${packageName}" +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' +# shellcheck disable=SC2155 +readonly ip_bin="$(command -v ip)" +readonly ipTablePrefix='pbr' +# shellcheck disable=SC2155 +readonly iptables="$(command -v iptables)" +# shellcheck disable=SC2155 +readonly ip6tables="$(command -v ip6tables)" +# shellcheck disable=SC2155 +readonly ipset="$(command -v ipset)" +readonly ipsPrefix='pbr' +readonly iptPrefix='PBR' +# shellcheck disable=SC2155 +readonly agh="$(command -v AdGuardHome)" +readonly 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= +nft_user_set_policy= +nft_user_set_counter= +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= +firewallWanZone= +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!";; + errorNoIptables) r="iptables binary cannot be found!";; + errorNoIpset) r="Resolver set support (${resolver_set}) requires ipset, but ipset binary cannot be found!";; + errorNoNft) r="Resolver set support (${resolver_set}) requires nftables, but nft binary cannot be found!";; + errorResolverNotSupported) r="Resolver set (${resolver_set}) is not supported on this system!";; + errorServiceDisabled) r="The ${packageName} service is currently disabled!";; + errorNoWanGateway) r="The ${serviceName} service failed to discover WAN gateway!";; + 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!";; + errorPolicyProcessCMD) r="'%s'!";; + errorFailedSetup) r="Failed to set up '%s'!";; + errorFailedReload) r="Failed to reload '%s'!";; + errorUserFileNotFound) r="Custom user file '%s' not found or empty!";; + errorUserFileSyntax) r="Syntax error in custom user file '%s'!";; + errorUserFileRunning) r="Error running custom user file '%s'!";; + errorUserFileNoCurl) r="Use of 'curl' is detected in custom user file '%s', but 'curl' isn't installed!";; + errorNoGateways) r="Failed to set up any gateway!";; + errorResolver) r="Resolver '%s'!";; + errorPolicyProcessNoIpv6) r="Skipping IPv6 policy '%s' as IPv6 support is disabled!";; + errorPolicyProcessUnknownFwmark) r="Unknown packet mark for interface '%s'!";; + errorPolicyProcessMismatchFamily) r="Mismatched IP family between in policy '%s'!";; + errorPolicyProcessUnknownProtocol) r="Unknown protocol in policy '%s'!";; + errorPolicyProcessInsertionFailed) r="Insertion failed for both IPv4 and IPv6 for policy '%s'!";; + errorPolicyProcessInsertionFailedIpv4) r="Insertion failed for IPv4 for policy '%s'!";; + errorInterfaceRoutingEmptyValues) r="Received empty tid/mark or interface name when setting up routing!";; + errorFailedToResolve) r="Failed to resolve '%s'!";; + warningInvalidOVPNConfig) r="Invalid OpenVPN config for '%s' interface.";; + warningResolverNotSupported) r="Resolver set (${resolver_set}) is not supported on this system.";; + warningAGHVersionTooLow) r="Installed AdGuardHome ('%s') doesn't support 'ipset_file' option.";; + warningPolicyProcessCMD) r="'%s'";; + warningTorUnsetParams) r="Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'.";; + warningTorUnsetProto) r="Please unset 'proto' or set 'proto' to 'all' for policy '%s'.";; + warningTorUnsetChainIpt) r="Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'.";; + warningTorUnsetChainNft) r="Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'.";; + warningOutdatedWebUIApp) r="The WebUI application is outdated (version %s), please update it.";; + 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"; } +# shellcheck disable=SC2317 +str_replace() { printf "%b" "$1" | sed -e "s/$(printf "%b" "$2")/$(printf "%b" "$3")/g"; } +str_replace() { echo "${1//$2/$3}"; } +str_contains() { [ -n "$1" ] && [ -n "$2" ] && [ "${1//$2}" != "$1" ]; } +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 + local sharedMemoryOutput="/dev/shm/$packageName-output" + 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"; } +_find_firewall_wan_zone() { [ "$(uci -q get "firewall.${1}.name")" = "wan" ] && firewallWanZone="$1"; } +_build_ifaces_all() { ifacesAll="${ifacesAll}${1} "; } +_build_ifaces_supported() { is_supported_interface "$1" && ! str_contains "$ifacesSupported" "$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")" + gw="$($ip_bin -4 a list dev "$dev" 2>/dev/null | grep inet | awk '{print $2}' | awk -F "/" '{print $1}')" + fi + eval "$1"='$gw' +} +pbr_get_gateway6() { + local iface="$2" dev="$3" gw + network_get_gateway6 gw "$iface" true + if [ -z "$gw" ] || [ "$gw" = '::/0' ] || [ "$gw" = '::0/0' ] || [ "$gw" = '::' ]; then + gw="$($ip_bin -6 a list dev "$dev" 2>/dev/null | grep inet6 | grep 'scope global' | awk '{print $2}')" + fi + eval "$1"='$gw' +} +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_ovpn() { local dev; dev="$(uci -q get "network.${1}.device")"; [ -z "$dev" ] && dev="$(uci -q get "network.${1}.dev")"; [ "${dev:0:3}" = "tun" ] || [ "${dev:0:3}" = "tap" ] || [ -f "/sys/devices/virtual/net/${dev}/tun_flags" ]; } +is_valid_ovpn() { local dev_net dev_ovpn; dev_net="$(uci -q get "network.${1}.device")"; [ -z "$dev_net" ] && dev_net="$(uci -q get "network.${1}.dev")"; dev_ovpn="$(uci -q get "openvpn.${1}.dev")"; [ -n "$dev_net" ] && [ -n "$dev_ovpn" ] && [ "$dev_net" = "$dev_ovpn" ]; } +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() { ! is_ipv6 "$1" && 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_bin -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() { local iface="$1"; grep "${ipTablePrefix}_${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'; } +resolveip_to_ipt4() { resolveip_to_ipt -4 "$@"; } +resolveip_to_ipt6() { [ -n "$ipv6_enabled" ] && resolveip_to_ipt -6 "$@"; } +# shellcheck disable=SC2016 +resolveip_to_nftset() { resolveip "$@" | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; } +resolveip_to_nftset4() { resolveip_to_nftset -4 "$@"; } +resolveip_to_nftset6() { [ -n "$ipv6_enabled" ] && resolveip_to_nftset -6 "$@"; } +# shellcheck disable=SC2016 +ipv4_leases_to_nftset() { [ -s '/tmp/dhcp.leases' ] || return 1; grep "$1" '/tmp/dhcp.leases' | awk '{print $3}' | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; } +# shellcheck disable=SC2016 +ipv6_leases_to_nftset() { [ -s '/tmp/hosts/odhcpd' ] || return 1; grep -v '^\#' '/tmp/hosts/odhcpd' | grep "$1" | awk '{print $1}' | sed -n 'H;${x;s/\n/,/g;s/^,//;p;};d' | tr '\n' ' '; } +# shellcheck disable=SC3037 +ports_to_nftset() { echo -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}"; } +opkg_get_version() { grep -m1 -A1 "Package: $1$" '/usr/lib/opkg/status' | grep -m1 'Version: ' | sed 's|Version: \(.*\)|\1|'; } + +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 nft_user_set_policy 'config' 'nft_user_set_policy' 'memory' + config_get_bool nft_user_set_counter 'config' 'nft_user_set_counter' '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}" + if [ "$nft_user_set_counter" -eq '0' ]; then + unset nft_user_set_counter + fi + 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 + case "$param" in + on_start) + 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_bin" ]; then + state add 'errorSummary' 'errorNoIpFull' + return 1 + fi + if ! is_nft; then + if [ -z "$iptables" ] || [ ! -x "$iptables" ]; then + state add 'errorSummary' 'errorNoIptables' + return 1 + fi + fi + rm -f "$packageLockFile" + resolver 'check_support' + ;; + on_stop) + touch "$packageLockFile" + ;; + esac + load_network "$param" +} + +load_network() { + local i + config_load 'network' + [ -z "$ifacesAll" ] && config_foreach _build_ifaces_all 'interface' + if [ -z "$ifacesSupported" ]; then + config_load 'firewall' + config_foreach _find_firewall_wan_zone 'zone' + for i in $(uci -q get "firewall.${firewallWanZone}.network"); do + is_supported_interface "$i" && ! str_contains "$ifacesSupported" "$1" && ifacesSupported="${ifacesSupported}${i} " + done + config_load 'network' + config_foreach _build_ifaces_supported 'interface' + fi + 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" ["$param"] comment "$comment" && ipv4_error=0 + ips6 -q -! add "$ipset6" ["$param"] comment "$comment" && ipv6_error=0 + ;; + add_agh_element) + [ -n "$ipv6_enabled" ] || unset ipset6 + echo "${param}/${ipset4}${ipset6:+,$ipset6}" >> "$aghIpsetFile" && ipv4_error=0 + ;; + add_dnsmasq_element) + [ -n "$ipv6_enabled" ] || unset ipset6 + 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 && ipv6_error=0 + case "$target" in + dst) + ipt4 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" dst -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 + ipt6 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" dst -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 + ;; + src) + ipt4 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 + ipt6 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 + ;; + esac + ;; + mac) + ips4 -q -! create "$ipset4" "hash:$type" comment && ipv4_error=0 + ips6 -q -! create "$ipset6" "hash:$type" comment family inet6 && ipv4_error=0 + ipt4 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 + ipt6 -t mangle -A "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 + ;; + esac + ;; + delete|destroy) + ips4 -q -! destroy "$ipset4" && ipv4_error=0 + ips6 -q -! destroy "$ipset6" && ipv6_error=0 + ;; + delete_user_set) + ips4 -q -! destroy "$ipset4" && ipv4_error=0 + ips6 -q -! destroy "$ipset6" family inet6 && ipv6_error=0 + case "$type" in + ip|net) + case "$target" in + dst) + ipt4 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" dst -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 + ipt6 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" dst -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 + ;; + src) + ipt4 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 + ipt6 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 + ;; + esac + ;; + mac) + ipt4 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset4" src -g "${iptPrefix}_MARK_${mark}" && ipv4_error=0 + ipt6 -t mangle -D "${iptPrefix}_PREROUTING" -m set --match-set "$ipset6" src -g "${iptPrefix}_MARK_${mark}" && ipv6_error=0 + ;; + esac + ;; + flush|flush_user_set) + ips4 -q -! flush "$ipset4" && ipv4_error=0 + ips6 -q -! flush "$ipset6" && ipv6_error=0 + ;; + esac + if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then + return 0 + else + return 1 + fi +} + +# 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 + if [ "$target" = 'src' ]; then + param4="$(ipv4_leases_to_nftset "$param")" + param6="$(ipv6_leases_to_nftset "$param")" + fi + [ -z "$param4" ] && param4="$(resolveip_to_nftset4 "$param")" + [ -z "$param6" ] && param6="$(resolveip_to_nftset6 "$param")" + if [ -z "$param4" ] && [ -z "$param6" ]; then + state add 'errorSummary' 'errorFailedToResolve' "$param" + else + nft4 add element inet "$nftTable" "$nftset4" "{ $param4 }" && ipv4_error=0 + nft6 add element inet "$nftTable" "$nftset6" "{ $param6 }" && ipv6_error=0 + fi + 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; counter; flags interval; auto-merge; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; counter; flags interval; auto-merge; comment \"$comment\"; }" && ipv6_error=0 + ;; + mac) + nft4 add set inet "$nftTable" "$nftset4" "{ type ether_addr; counter; flags interval; auto-merge; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ether_addr; counter; flags interval; auto-merge; comment \"$comment\"; }" && ipv6_error=0 + ;; + esac + ;; + create_dnsmasq_set) + nft4 add set inet "$nftTable" "$nftset4" "{ type ipv4_addr; counter; flags interval; auto-merge; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; counter; 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; ${nft_user_set_counter:+counter;} flags interval; auto-merge; policy $nft_user_set_policy; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ipv6_addr; ${nft_user_set_counter:+counter;} flags interval; auto-merge; policy $nft_user_set_policy; 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; ${nft_user_set_counter:+counter;} flags interval; auto-merge; policy $nft_user_set_policy; comment \"$comment\"; }" && ipv4_error=0 + nft6 add set inet "$nftTable" "$nftset6" "{ type ether_addr; ${nft_user_set_counter:+counter;} flags interval; auto-merge; policy $nft_user_set_policy; 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' + if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then + return 0 + else + return 1 + fi +} + +cleanup_rt_tables() { sed -i '/pbr_/d' '/etc/iproute2/rt_tables'; sync; } +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) + json_init + json_add_object "$packageName" + case "$param" in + errorSummary) + json_add_array 'errors';; + warningSummary) + json_add_array 'warnings';; + esac + if [ -n "$(eval echo "\$$param")" ]; then + while read -r line; do + if str_contains "$line" ' '; then + error_id="${line% *}" + error_extra="${line#* }" + else + error_id="$line" + fi + json_add_object + json_add_string 'id' "$error_id" + json_add_string 'extra' "$error_extra" + json_close_object + done </dev/null 2>&1 + rm -f "$aghIpsetFile" + 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|' | sed 's|-.*||')" + if is_greater_or_equal "$agh_version" '0.107.13'; then + resolver_set_supported='true' + return 0 + else + state add 'warningSummary' 'warningAGHVersionTooLow' "$agh_version" + return 1 + fi + else + state add 'warningSummary' 'warningResolverNotSupported' + return 1 + fi + ;; + cleanup) + [ -z "$resolver_set_supported" ] && return 0 + rm -f "$aghIpsetFile" + sed -i "/ipset_file: ${aghIpsetFile}/d" "$aghConfigFile" >/dev/null 2>&1 + ;; + configure) + [ -z "$resolver_set_supported" ] && return 1 + mkdir -p "${aghIpsetFile%/*}" + touch "$aghIpsetFile" + sed -i '/ipset_file/d' "$aghConfigFile" >/dev/null 2>&1 + sed -i "/ ipset:/a \ \ ipset_file: $aghIpsetFile" "$aghConfigFile" + ;; + init) :;; + init_end) :;; + kill) + [ -n "$resolver_set_supported" ] && [ -n "$agh" ] && killall -q -s HUP "$agh";; + reload) + [ -z "$resolver_set_supported" ] && return 1 + output 3 'Reloading adguardhome ' + if /etc/init.d/adguardhome reload >/dev/null 2>&1; then + output_okn + return 0 + else + output_failn + return 1 + fi + ;; + restart) + [ -z "$resolver_set_supported" ] && return 1 + output 3 'Restarting adguardhome ' + if /etc/init.d/adguardhome restart >/dev/null 2>&1; then + output_okn + return 0 + else + output_failn + return 1 + fi + ;; + compare_hash) + [ -z "$resolver_set_supported" ] && return 1 + local resolverNewHash + if [ -s "$aghIpsetFile" ]; then + resolverNewHash="$(md5sum $aghIpsetFile | awk '{ print $1; }')" + fi + [ "$resolverNewHash" != "$resolverStoredHash" ] + ;; + store_hash) + [ -s "$aghIpsetFile" ] && resolverStoredHash="$(md5sum $aghIpsetFile | awk '{ print $1; }')";; + esac + ;; + dnsmasq.ipset) + case "$param" in + add_resolver_element) + [ -n "$resolver_set_supported" ] && ips 'add_dnsmasq_element' "$@";; + create_resolver_set) + [ -n "$resolver_set_supported" ] && ips 'create_dnsmasq_set' "$@";; + check_support) + if [ ! -x "$ipset" ]; then + state add 'errorSummary' 'errorNoIpset' + return 1 + fi + if ! dnsmasq -v 2>/dev/null | grep -q 'no-ipset' && dnsmasq -v 2>/dev/null | grep -q 'ipset'; then + resolver_set_supported='true' + return 0 + else + state add 'warningSummary' 'warningResolverNotSupported' + return 1 + fi + ;; + cleanup) + [ -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() { + 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 + 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 + state add 'warningSummary' 'warningTorUnsetParams' "$comment" + fi + if [ -n "$proto" ] && [ "$proto" != "all" ]; then + state add 'warningSummary' 'warningTorUnsetProto' "$comment" + fi + if [ "$chain" != "PREROUTING" ]; then + state add 'warningSummary' 'warningTorUnsetChainIpt' "$comment" + fi + if ! resolver 'add_resolver_element' "$iface" 'dst' 'ip' '' "${comment}: $dest_addr" "$dest_addr"; then + processPolicyError='true' + state add 'errorSummary' 'errorResolver' "'add_resolver_element' '$iface' 'dst' 'ip' '${comment}: $dest_addr' '$dest_addr'" + return 1 + fi + 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 + state add 'warningSummary' 'warningTorUnsetParams' "$comment" + fi + if [ -n "$proto" ] && [ "$proto" != "all" ]; then + state add 'warningSummary' 'warningTorUnsetProto' "$comment" + fi + if [ "$chain" != "prerouting" ]; then + state add 'warningSummary' 'warningTorUnsetChainNft' "$comment" + fi + if ! resolver 'add_resolver_element' "$iface" 'dst' 'ip' '' "${comment}: $dest_addr" "$dest_addr"; then + processPolicyError='true' + state add 'errorSummary' 'errorResolver' "'add_resolver_element' '$iface' 'dst' 'ip' '${comment}: $dest_addr' '$dest_addr'" + return 1 + fi + 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='true' + state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name" + return 1 + fi + + if [ -n "$mark" ]; then + dest="-g ${iptPrefix}_MARK_${mark}" + elif [ "$iface" = "ignore" ]; then + dest="-j RETURN" + else + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessUnknownFwmark' "$iface" + return 1 + fi + + if is_family_mismatch "$laddr" "$raddr"; then + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$laddr' '$raddr'" + return 1 + fi + + if [ -z "$proto" ]; then + if [ -n "${lport}${rport}" ]; then + proto='tcp udp' + else + proto='all' + fi + fi + + for i in $proto; do + if [ "$i" = 'all' ]; then + param4="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest" + param6="-t mangle ${ipInsertOption} ${iptPrefix}_${chain} $dest" + elif ! is_supported_protocol "$i"; then + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessUnknownProtocol' "${name}: '$i'" + 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 + local resolvedIP4 resolvedIP6 + resolvedIP4="$(resolveip_to_ipt4 "$value")" + resolvedIP6="$(resolveip_to_ipt6 "$value")" + if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then + state add 'errorSummary' 'errorFailedToResolve' "$value" + fi + param4="$param4 $negation -s $resolvedIP4" + param6="$param6 $negation -s $resolvedIP6" + fi + fi + fi + + if [ -n "$lport" ]; then + if [ "${lport:0:1}" = "!" ]; then + negation='!'; value="${lport:1}" + else + unset negation; value="$lport"; + fi + param4="$param4 -m multiport $negation --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 + local resolvedIP4 resolvedIP6 + resolvedIP4="$(resolveip_to_ipt4 "$value")" + resolvedIP6="$(resolveip_to_ipt6 "$value")" + if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then + state add 'errorSummary' 'errorFailedToResolve' "$value" + fi + param4="$param4 $negation -d $resolvedIP4" + param6="$param6 $negation -d $resolvedIP6" + fi + else + local target='dst' type='ip' + if ips 'create' "$iface" "$target" "$type" "$uid" "${name}: $raddr" && \ + ips 'add' "$iface" "$target" "$type" "$uid" "${name}: $raddr" "$value"; then + param4="$param4 -m set $negation --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 + + if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name" + state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param4" + state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param6" + logger -t "$packageName" "ERROR: iptables $param4" + logger -t "$packageName" "ERROR: iptables $param6" + elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name" + state add 'errorSummary' 'errorPolicyProcessCMD' "iptables $param4" + logger -t "$packageName" "ERROR: iptables $param4" + fi + + done +} +policy_routing_nft() { + local mark i nftInsertOption='add' + local param4 param6 proto_i negation value dest + 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='true' + state add 'errorSummary' 'errorPolicyProcessNoIpv6' "$name" + return 1 + fi + + if [ -n "$mark" ]; then + dest="goto ${nftPrefix}_mark_${mark}" + elif [ "$iface" = "ignore" ]; then + dest="return" + else + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessUnknownFwmark' "$iface" + return 1 + fi + + if is_family_mismatch "$src_addr" "$dest_addr"; then + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessMismatchFamily' "${name}: '$laddr' '$raddr'" + return 1 + fi + + if [ -z "$proto" ]; then + if [ -n "${src_port}${dest_port}" ]; then + proto='tcp udp' + else + proto='all' + fi + fi + + for proto_i in $proto; do + unset param4 + unset param6 + if [ "$proto_i" = 'all' ]; then + unset proto_i + elif ! is_supported_protocol "$proto_i"; then + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessUnknownProtocol' "${name}: '$proto_i'" + return 1 + fi + + if [ -n "$src_addr" ]; then + if [ "${src_addr:0:1}" = "!" ]; then + negation='!='; value="${src_addr:1}" + else + unset negation; value="$src_addr"; + fi + if is_phys_dev "$value"; then + param4="$param4 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 + local resolvedIP4 resolvedIP6 + resolvedIP4="$(resolveip_to_nftset4 "$value")" + resolvedIP6="$(resolveip_to_nftset6 "$value")" + if [ -z "$resolvedIP4" ] && [ -z "$resolvedIP6" ]; then + state add 'errorSummary' 'errorFailedToResolve' "$value" + fi + param4="$param4 $ip4Flag daddr $negation { $resolvedIP4 }" + param6="$param6 $ip6Flag daddr $negation { $resolvedIP6 }" + fi + else + local target='dst' type='ip' + if nftset 'create' "$iface" "$target" "$type" "$uid" "$name" && \ + nftset 'add' "$iface" "$target" "$type" "$uid" "$name" "$value"; then + param4="$param4 $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" ]; then + if [ "${src_port:0:1}" = "!" ]; then + negation='!='; value="${src_port:1}" + else + unset negation; value="$src_port"; + fi + param4="$param4 ${proto_i:+$proto_i }sport $negation {$(ports_to_nftset "$value")}" + param6="$param6 ${proto_i:+$proto_i }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_i:+$proto_i }dport $negation {$(ports_to_nftset "$value")}" + param6="$param6 ${proto_i:+$proto_i }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 + + if [ -n "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ] && [ "$ipv6_error" -eq '1' ]; then + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessInsertionFailed' "$name" + state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4" + state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param6" + logger -t "$packageName" "ERROR: nft $param4" + logger -t "$packageName" "ERROR: nft $param6" + elif [ -z "$ipv6_enabled" ] && [ "$ipv4_error" -eq '1' ]; then + processPolicyError='true' + state add 'errorSummary' 'errorPolicyProcessInsertionFailedIpv4' "$name" + state add 'errorSummary' 'errorPolicyProcessCMD' "nft $param4" + logger -t "$packageName" "ERROR: nft $param4" + fi + + done +} + +policy_process() { + local i j uid="$9" + if [ -z "$uid" ]; then # first non-recursive call + [ "$enabled" -gt 0 ] || return 0 + unset processPolicyError + uid="$1" + if is_nft; 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 "$processPolicyError" ]; then + output_fail + else + output_ok + fi + else # recursive call, get options from passed variables + local name="$1" interface="$2" src_addr="$3" src_port="$4" dest_addr="$5" dest_port="$6" proto="$7" chain="$8" + if str_contains "$src_addr" '[ ;\{\}]'; then + for i in $(str_extras_to_space "$src_addr"); do [ -n "$i" ] && policy_process "$name" "$interface" "$i" "$src_port" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"; done + elif str_contains "$src_port" '[ ;\{\}]'; then + for i in $(str_extras_to_space "$src_port"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$i" "$dest_addr" "$dest_port" "$proto" "$chain" "$uid"; done + elif str_contains "$dest_addr" '[ ;\{\}]'; then + for i in $(str_extras_to_space "$dest_addr"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$i" "$dest_port" "$proto" "$chain" "$uid"; done + elif str_contains "$dest_port" '[ ;\{\}]'; then + for i in $(str_extras_to_space "$dest_port"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$i" "$proto" "$chain" "$uid"; done + elif str_contains "$proto" '[ ;\{\}]'; then + for i in $(str_extras_to_space "$proto"); do [ -n "$i" ] && policy_process "$name" "$interface" "$src_addr" "$src_port" "$dest_addr" "$dest_port" "$i" "$chain" "$uid"; done + else + if 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 "${iptPrefix}_${i}" + ipt -t nat -F "${iptPrefix}_${i}"; ipt -t nat -X "${iptPrefix}_${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 + i="$(str_to_upper "$i")" + ipt -t nat -N "${iptPrefix}_${i}" + ipt -t nat -A "$i" -m mark --mark "0x0/${fw_mask}" -j "${iptPrefix}_${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_upper "$i")" + ipt -t nat -I "${iptPrefix}_${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 "${iptPrefix}_${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 "${iptPrefix}_${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 "${iptPrefix}_${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 "${iptPrefix}_${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 add rule inet "$nftTable" dstnat meta nfproto ipv4 ip daddr "@${set_name4}" udp dport 53 counter redirect to :"$dnsPort" comment "Tor-DNS-UDP-ipv4" || s=1 + nft add rule inet "$nftTable" dstnat meta nfproto ipv4 ip daddr "@${set_name4}" tcp dport 80 counter redirect to :"$trafficPort" comment "Tor-HTTP-TCP-ipv4" || s=1 + nft add rule inet "$nftTable" dstnat meta nfproto ipv4 ip daddr "@${set_name4}" udp dport 80 counter redirect to :"$trafficPort" comment "Tor-HTTP-UDP-ipv4" || s=1 + nft add rule inet "$nftTable" dstnat meta nfproto ipv4 ip daddr "@${set_name4}" tcp dport 443 counter redirect to :"$trafficPort" comment "Tor-HTTPS-TCP-ipv4" || s=1 + nft add rule inet "$nftTable" dstnat meta nfproto ipv4 ip daddr "@${set_name4}" udp dport 443 counter redirect to :"$trafficPort" comment "Tor-HTTPS-UDP-ipv4" || s=1 + nft6 add rule inet "$nftTable" dstnat meta nfproto ipv6 ip6 daddr "@${set_name6}" udp dport 53 counter redirect to :"$dnsPort" comment "Tor-DNS-UDP-ipv6" || s=1 + nft6 add rule inet "$nftTable" dstnat meta nfproto ipv6 ip6 daddr "@${set_name6}" tcp dport 80 counter redirect to :"$trafficPort" comment "Tor-HTTP-TCP-ipv6" || s=1 + nft6 add rule inet "$nftTable" dstnat meta nfproto ipv6 ip6 daddr "@${set_name6}" udp dport 80 counter redirect to :"$trafficPort" comment "Tor-HTTP-UDP-ipv6" || s=1 + nft6 add rule inet "$nftTable" dstnat meta nfproto ipv6 ip6 daddr "@${set_name6}" tcp dport 443 counter redirect to :"$trafficPort" comment "Tor-HTTPS-TCP-ipv6" || s=1 + nft6 add rule inet "$nftTable" dstnat meta nfproto ipv6 ip6 daddr "@${set_name6}" udp 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 + state add 'errorSummary' 'errorInterfaceRoutingEmptyValues' + return 1 + fi + case "$action" in + create) + if is_netifd_table "$iface"; then + ipv4_error=0 + $ip_bin rule del table "$tid" >/dev/null 2>&1 + $ip_bin -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_bin -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1 + fi + else + if ! grep -q "$tid ${ipTablePrefix}_${iface}" '/etc/iproute2/rt_tables'; then + sed -i "/${ipTablePrefix}_${iface}/d" '/etc/iproute2/rt_tables' + sync + echo "$tid ${ipTablePrefix}_${iface}" >> '/etc/iproute2/rt_tables' + sync + fi + $ip_bin rule del table "$tid" >/dev/null 2>&1 + $ip_bin route flush table "$tid" >/dev/null 2>&1 + if [ -n "$gw4" ] || [ "$strict_enforcement" -ne 0 ]; then + ipv4_error=0 + if [ -z "$gw4" ]; then + $ip_bin -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1 + else + $ip_bin -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1 + fi +# shellcheck disable=SC2086 + while read -r i; do + i="$(echo "$i" | sed 's/ linkdown$//')" + i="$(echo "$i" | sed 's/ onlink$//')" + idev="$(echo "$i" | grep -Eso 'dev [^ ]*' | awk '{print $2}')" + if ! is_supported_iface_dev "$idev"; then + $ip_bin -4 route add $i table "$tid" >/dev/null 2>&1 || ipv4_error=1 + fi + done << EOF + $($ip_bin -4 route list table main) +EOF + $ip_bin -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 + if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ "$strict_enforcement" -ne 0 ]; then + if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then + $ip_bin -6 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv6_error=1 + elif $ip_bin -6 route list table main | grep -q " dev $dev6 "; then + while read -r i; do + i="$(echo "$i" | sed 's/ linkdown$//')" + i="$(echo "$i" | sed 's/ onlink$//')" + # shellcheck disable=SC2086 + $ip_bin -6 route add $i table "$tid" >/dev/null 2>&1 || ipv6_error=1 + done << EOF + $($ip_bin -6 route list table main | grep " dev $dev6 ") +EOF + else + $ip_bin -6 route add "$($ip_bin -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + $ip_bin -6 route add default dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + fi + fi + $ip_bin -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" >/dev/null 2>&1 || 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) + 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_bin rule del table "$tid" >/dev/null 2>&1 + if ! is_netifd_table "$iface"; then + $ip_bin route flush table "$tid" >/dev/null 2>&1 + sed -i "/${ipTablePrefix}_${iface}\$/d" '/etc/iproute2/rt_tables' + sync + fi + return "$s" + ;; + reload_interface) + is_netifd_table "$iface" && return 0; + ipv4_error=0 + $ip_bin rule del table "$tid" >/dev/null 2>&1 + if ! is_netifd_table "$iface"; then + $ip_bin route flush table "$tid" >/dev/null 2>&1 + fi + if [ -n "$gw4" ] || [ "$strict_enforcement" -ne 0 ]; then + if [ -z "$gw4" ]; then + $ip_bin -4 route add unreachable default table "$tid" >/dev/null 2>&1 || ipv4_error=1 + else + $ip_bin -4 route add default via "$gw4" dev "$dev" table "$tid" >/dev/null 2>&1 || ipv4_error=1 + fi + $ip_bin rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 + fi + if [ -n "$ipv6_enabled" ]; then + ipv6_error=0 + if { [ -n "$gw6" ] && [ "$gw6" != "::/0" ]; } || [ "$strict_enforcement" -ne 0 ]; then + if [ -z "$gw6" ] || [ "$gw6" = "::/0" ]; then + $ip_bin -6 route add unreachable default table "$tid" || ipv6_error=1 + elif $ip_bin -6 route list table main | grep -q " dev $dev6 "; then + while read -r i; do + # shellcheck disable=SC2086 + $ip_bin -6 route add $i table "$tid" >/dev/null 2>&1 || ipv6_error=1 + done << EOF + $($ip_bin -6 route list table main | grep " dev $dev6 ") +EOF + else + $ip_bin -6 route add "$($ip_bin -6 -o a show "$dev6" | awk '{print $4}')" dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + $ip_bin -6 route add default dev "$dev6" table "$tid" >/dev/null 2>&1 || ipv6_error=1 + fi + fi + $ip_bin -6 rule add fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1 + fi + if [ "$ipv4_error" -eq 0 ] || [ "$ipv6_error" -eq 0 ]; then + s=0 + else + s=1 + fi + return "$s" + ;; + esac +} + +json_add_gateway() { + local action="$1" tid="$2" mark="$3" iface="$4" gw4="$5" dev4="$6" gw6="$7" dev6="$8" priority="$9" default="${10}" + json_add_object '' + json_add_string name "$iface" + json_add_string device_ipv4 "$dev4" + json_add_string gateway_ipv4 "$gw4" + json_add_string device_ipv6 "$dev6" + json_add_string gateway_ipv6 "$gw6" + if [ -n "$default" ]; then + json_add_boolean default 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 + + if is_ovpn "$iface" && ! is_valid_ovpn "$iface"; then + : || state add 'warningSummary' 'warningInvalidOVPNConfig' "$iface" + fi + + network_get_device dev "$iface" + [ -z "$dev" ] && network_get_physdev dev "$iface" + if is_wan "$iface" && [ -n "$wanIface6" ] && str_contains "$wanIface6" "$iface"; then + network_get_device dev6 "$wanIface6" + [ -z "$dev6" ] && network_get_physdev dev6 "$wanIface6" + fi + + [ -z "$dev6" ] && dev6="$dev" + [ -z "$ifaceMark" ] && ifaceMark="$(printf '0x%06x' "$wan_mark")" + [ -z "$ifacePriority" ] && ifacePriority="$wan_ip_rules_priority" + + 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"; 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' 'errorUserFileSyntax' "$path" + output_fail + return 1 + fi + output 2 "Running $path " +# shellcheck disable=SC1090 + if ! . "$path"; then + state add 'errorSummary' 'errorUserFileRunning' "$path" + if grep -q -w 'curl' "$path" && ! is_present 'curl'; then + state add 'errorSummary' 'errorUserFileNoCurl' "$path" + fi + output_fail + return 1 + else + output_ok + return 0 + fi +} + +boot() { + ubus -t 30 wait_for network.interface 2>/dev/null + rc_procd start_service 'on_boot' +} + +on_firewall_reload() { + if [ -e "$packageLockFile" ]; then # service is stopped, do not start it on firewall reload + logger -t "$packageName" "Reload on firewall action aborted: service is stopped." + return 0 + else + rc_procd start_service 'on_firewall_reload' "$1" + fi +} +on_interface_reload() { + if [ -e "$packageLockFile" ]; then # service is stopped, do not start it on interface change + logger -t "$packageName" "Reload on interface change aborted: service is stopped." + return 0 + else + rc_procd start_service 'on_interface_reload' "$1" + fi +} + +start_service() { + local resolverStoredHash resolverNewHash i param="$1" reloadedIface + + load_environment '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) + reloadedIface="$2" + if is_ovpn "$reloadedIface"; then + logger -t "$packageName" "Updated interface is an OpenVPN tunnel, restarting." + serviceStartTrigger='on_start' + unset reloadedIface + else + serviceStartTrigger='on_interface_reload' + fi + ;; + on_reload) + serviceStartTrigger='on_reload' + ;; + on_restart) + serviceStartTrigger='on_start' + ;; + esac + + if [ -n "$reloadedIface" ] && ! is_supported_interface "$reloadedIface"; then + return 0 + fi + + if [ -n "$(ubus_get_status error)" ] || [ -n "$(ubus_get_status warning)" ]; then + serviceStartTrigger='on_start' + unset reloadedIface + elif ! is_service_running; then + serviceStartTrigger='on_start' + unset reloadedIface + elif [ -z "$(ubus_get_status 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 + + 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 + interface_process 'all' 'prepare' + config_foreach interface_process 'interface' 'create_user_set' + output 1 'Processing user file(s) ' + config_load "$packageName" + config_foreach load_validate_include 'include' user_file_process + output 1 '\n' + fi + resolver 'init_end' + resolver 'compare_hash' && resolver 'restart' + traffic_killswitch 'remove' + ;; + on_start|*) + traffic_killswitch 'insert' + resolver 'store_hash' + resolver 'cleanup_all' + resolver 'configure' + resolver 'init' + cleanup_main_chains + cleanup_sets + cleanup_marking_chains + cleanup_rt_tables + 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 + interface_process 'all' 'prepare' + config_foreach interface_process 'interface' 'create_user_set' + output 1 'Processing user file(s) ' + config_load "$packageName" + config_foreach load_validate_include 'include' user_file_process + output 1 '\n' + fi + resolver 'init_end' + resolver 'compare_hash' && resolver 'restart' + traffic_killswitch 'remove' + ;; + esac + + if [ -z "$gatewaySummary" ]; then + state add 'errorSummary' 'errorNoGateways' + fi + json_add_object 'status' + [ -n "$gatewaySummary" ] && json_add_string 'gateways' "$gatewaySummary" + [ -n "$errorSummary" ] && json_add_string 'errors' "$errorSummary" + [ -n "$warningSummary" ] && json_add_string 'warnings' "$warningSummary" + if [ "$strict_enforcement" -ne 0 ] && str_contains "$gatewaySummary" '0.0.0.0'; then + json_add_string 'mode' "strict" + fi + json_close_object + procd_close_data + procd_close_instance +} + +service_started() { + if 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' + cleanup_rt_tables + output 1 "\\n" + ip route flush cache + unset ifaceMark + unset ifaceTableID + resolver 'store_hash' + resolver 'cleanup_all' + resolver 'compare_hash' && resolver 'restart' + traffic_killswitch 'remove' + if [ "$enabled" -ne 0 ]; then + if 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_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}') + [ "$wanGW6" = "default" ] && wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}') + fi + while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done + [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support" + status="$serviceName running on $dist $vers." + [ -n "$wanIface4" ] && status="$status WAN (IPv4): ${wanIface4}/${dev}/${wanGW4:-0.0.0.0}." + [ -n "$wanIface6" ] && status="$status WAN (IPv6): ${wanIface6}/${dev6}/${wanGW6:-::/0}." + + echo "$_SEPARATOR_" + echo "$packageName - environment" + echo "$status" + echo "$_SEPARATOR_" + dnsmasq --version 2>/dev/null | sed '/^$/,$d' + echo "$_SEPARATOR_" + echo "$packageName chains - policies" + for i in forward input output prerouting postrouting; do + "$nft" -a list table inet "$nftTable" | sed -n "/chain ${nftPrefix}_${i} {/,/\t}/p" + done + echo "$_SEPARATOR_" + echo "$packageName chains - marking" + for i in $(get_mark_nft_chains); do + "$nft" -a list table inet "$nftTable" | sed -n "/chain ${i} {/,/\t}/p" + done + echo "$_SEPARATOR_" + echo "$packageName nft sets" + for i in $(get_nft_sets); do + "$nft" -a list table inet "$nftTable" | sed -n "/set ${i} {/,/\t}/p" + done + if [ -s "$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_bin -4 route show table $((wan_tid + i)) | grep default)" + echo "IPv4 table $((wan_tid + i)) rule(s):" + $ip_bin -4 rule list table "$((wan_tid + i))" + if [ -n "$ipv6_enabled" ]; then + echo "IPv6 table $((wan_tid + i)) route: $($ip_bin -6 route show table $((wan_tid + i)) | grep default)" + echo "IPv6 table $((wan_tid + i)) rule(s):" + $ip_bin -6 route show table $((wan_tid + i)) + fi + i=$((i + 1)) + done +} + +status_service_iptables() { + local dist vers out id s param status set_d set_p tableCount i=0 dev dev6 j wan_tid + + json_load "$(ubus call system board)"; json_select release; json_get_var dist distribution; json_get_var vers version + if [ -n "$wanIface4" ]; then + network_get_gateway wanGW4 "$wanIface4" + network_get_device dev "$wanIface4" + fi + if [ -n "$wanIface6" ]; then + network_get_device dev6 "$wanIface6" + wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $1}') + [ "$wanGW6" = "default" ] && wanGW6=$($ip_bin -6 route show | grep -m1 " dev $dev6 " | awk '{print $3}') + fi + while [ "${1:0:1}" = "-" ]; do param="${1//-/}"; eval "set_$param=1"; shift; done + [ -e "/var/${packageName}-support" ] && rm -f "/var/${packageName}-support" + status="$serviceName running on $dist $vers." + [ -n "$wanIface4" ] && status="$status WAN (IPv4): ${wanIface4}/${dev}/${wanGW4:-0.0.0.0}." + [ -n "$wanIface6" ] && status="$status WAN (IPv6): ${wanIface6}/${dev6}/${wanGW6:-::/0}." + { + echo "$status" + echo "$_SEPARATOR_" + dnsmasq --version 2>/dev/null | sed '/^$/,$d' + if [ -n "$1" ]; then + echo "$_SEPARATOR_" + echo "Resolving domains" + for i in $1; do + echo "$i: $(resolveip "$i" | tr '\n' ' ')" + done + fi + + echo "$_SEPARATOR_" + echo "Routes/IP Rules" + tableCount="$(grep -c "${packageName}_" /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_bin -4 route show table $((wan_tid + i)) | grep default)" + echo "IPv4 table $((wan_tid + i)) rule(s):" + $ip_bin -4 rule list table "$((wan_tid + i))" + i=$((i + 1)) + done + + if [ -n "$ipv6_enabled" ]; then + i=0; while [ $i -lt "$tableCount" ]; do + $ip_bin -6 route show table $((wan_tid + i)) | while read -r param; do + echo "IPv6 Table $((wan_tid + i)): $param" + done + i=$((i + 1)) + done + fi + + for j in Mangle NAT; do + if [ -z "$set_d" ]; then + for i in $chainsList; do + i="$(str_to_upper "$i")" + if iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_${i}" >/dev/null 2>&1; then + echo "$_SEPARATOR_" + echo "$j IP Table: $i" + iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_${i}" + if [ -n "$ipv6_enabled" ]; then + echo "$_SEPARATOR_" + echo "$j IPv6 Table: $i" + iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_${i}" + fi + fi + done + else + echo "$_SEPARATOR_" + echo "$j IP Table" + iptables -L -t "$(str_to_lower $j)" + if [ -n "$ipv6_enabled" ]; then + echo "$_SEPARATOR_" + echo "$j IPv6 Table" + iptables -L -t "$(str_to_lower $j)" + fi + fi + i=0; ifaceMark="$wan_mark"; + while [ $i -lt "$tableCount" ]; do + if iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_MARK_${ifaceMark}" >/dev/null 2>&1; then + echo "$_SEPARATOR_" + echo "$j IP Table MARK Chain: ${iptPrefix}_MARK_${ifaceMark}" + iptables -v -t "$(str_to_lower $j)" -S "${iptPrefix}_MARK_${ifaceMark}" + ifaceMark="$(printf '0x%06x' $((ifaceMark + wan_mark)))"; + fi + i=$((i + 1)) + done + done + + echo "$_SEPARATOR_" + echo "Current ipsets" + ipset save + if [ -s "$dnsmasqFile" ]; then + echo "$_SEPARATOR_" + echo "DNSMASQ sets" + cat "$dnsmasqFile" + fi + if [ -s "$aghIpsetFile" ]; then + echo "$_SEPARATOR_" + echo "AdGuardHome sets" + cat "$aghIpsetFile" + fi + echo "$_SEPARATOR_" + } | tee -a /var/${packageName}-support + if [ -n "$set_p" ]; then + printf "%b" "Pasting to paste.ee... " + if 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("", "tor", uci("network", "@interface"))' \ + 'ignored_interface:list(or("tor", uci("network", "@interface")))' \ + 'supported_interface:list(or("tor", 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)' \ + 'nft_user_set_policy:or("", "memory", "performance")'\ + 'nft_user_set_counter:bool:0' +} + +# shellcheck disable=SC2120 +load_validate_policy() { + local name + local enabled + local interface + local proto + local chain + local src_addr + local src_port + local dest_addr + local dest_port + uci_load_validate "$packageName" 'policy' "$1" "${2}${3:+ $3}" \ + 'name:string:Untitled' \ + 'enabled:bool:1' \ + 'interface:or("ignore", "tor", uci("network", "@interface")):wan' \ + 'proto:or(string)' \ + 'chain:or("", "forward", "input", "output", "prerouting", "postrouting", "FORWARD", "INPUT", "OUTPUT", "PREROUTING", "POSTROUTING"):prerouting' \ + 'src_addr:list(neg(or(host,network,macaddr,string)))' \ + 'src_port:list(neg(or(portrange,string)))' \ + 'dest_addr:list(neg(or(host,network,string)))' \ + 'dest_port:list(neg(or(portrange,string)))' +} + +# shellcheck disable=SC2120 +load_validate_include() { + local path= + local enabled= + uci_load_validate "$packageName" 'include' "$1" "${2}${3:+ $3}" \ + 'path:file' \ + 'enabled:bool:0' +} diff --git a/net/pbr/files/etc/uci-defaults/90-pbr b/net/pbr/files/etc/uci-defaults/90-pbr new file mode 100644 index 00000000..95fe3776 --- /dev/null +++ b/net/pbr/files/etc/uci-defaults/90-pbr @@ -0,0 +1,41 @@ +#!/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 + if [ -x '/etc/init.d/vpn-policy-routing' ]; then + echo "Stopping and disabling vpn-policy-routing." + /etc/init.d/vpn-policy-routing stop + /etc/init.d/vpn-policy-routing disable + fi + 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 + +if [ -s '/usr/share/pbr/pbr.firewall.include' ]; then +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 +fi + +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 00000000..16693864 --- /dev/null +++ b/net/pbr/files/etc/uci-defaults/91-pbr @@ -0,0 +1,59 @@ +#!/bin/sh +# shellcheck disable=SC1091,SC3037,SC3043 + +readonly packageName='pbr' +readonly __OK__='\033[0;32m[\xe2\x9c\x93]\033[0m' + +# shellcheck disable=SC2317 +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 00000000..d11ad847 --- /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 00000000..b3ce9db9 --- /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 00000000..c98514bc --- /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 00000000..cd5d1b4d --- /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 00000000..a4471d37 --- /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 00000000..4dd9b281 --- /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 00000000..36b3cd80 --- /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 firewall action: $ACTION" + /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 00000000..bf398dd6 --- /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 00000000..54f54e0d --- /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/^.*//" > "$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 00000000..45469ed9 --- /dev/null +++ b/net/pbr/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/etc/init.d/"$1" version 2>&1 | grep "$2"