adblock: release 4.5.1
authorDirk Brenken <dev@brenken.org>
Sun, 8 Feb 2026 09:25:55 +0000 (10:25 +0100)
committerDirk Brenken <dev@brenken.org>
Sun, 8 Feb 2026 09:28:17 +0000 (10:28 +0100)
* Remote DNS Allow: this additional firewall feature lets selected client devices
   temporarily bypass local DNS blocking and use an external, unfiltered DNS resolver.
* LuCI: new Firewall options for remote Access
* readme update

Signed-off-by: Dirk Brenken <dev@brenken.org>
net/adblock/Makefile
net/adblock/files/README.md
net/adblock/files/adblock.cgi [new file with mode: 0644]
net/adblock/files/adblock.sh

index 63956b4c5749cce86d1753d845cd6fc00099e816..97638ee92f9abbc0378f65aea9ec5bc8fc42881c 100644 (file)
@@ -6,8 +6,8 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=adblock
-PKG_VERSION:=4.5.0
-PKG_RELEASE:=5
+PKG_VERSION:=4.5.1
+PKG_RELEASE:=1
 PKG_LICENSE:=GPL-3.0-or-later
 PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org>
 
@@ -62,6 +62,9 @@ define Package/adblock/install
        $(INSTALL_CONF) ./files/adblock.feeds $(1)/etc/adblock
        $(INSTALL_CONF) ./files/adblock.custom.feeds $(1)/etc/adblock
 
+       $(INSTALL_DIR) $(1)/www/cgi-bin
+       $(INSTALL_BIN) ./files/adblock.cgi $(1)/www/cgi-bin/adblock
+
        $(INSTALL_DIR) $(1)/etc/uci-defaults
        $(INSTALL_BIN) ./files/95-adblock-housekeeping $(1)/etc/uci-defaults
 endef
index 398ccad8fc8ba10e464cef52659420e092b82c6b..dee98e9aa7f84e211ef38b364b3ee074b75c65b0 100644 (file)
@@ -135,56 +135,61 @@ Available commands:
 ## Adblock Config Options
 * Usually the auto pre-configured adblock setup works quite well and no manual overrides are needed
 
-| Option             | Default                            | Description/Valid Values                                                                       |
-| :----------------- | :--------------------------------- | :--------------------------------------------------------------------------------------------- |
-| adb_enabled        | 1, enabled                         | set to 0 to disable the adblock service                                                        |
-| adb_feedfile       | /etc/adblock/adblock.feeds         | full path to the used adblock feed file                                                        |
-| adb_dns            | -, auto-detected                   | 'dnsmasq', 'unbound', 'named', 'kresd', 'smartdns' or 'raw'                                    |
-| adb_fetchcmd       | -, auto-detected                   | 'uclient-fetch', 'wget' or 'curl'                                                              |
-| adb_fetchparm      | -, auto-detected                   | manually override the config options for the selected download utility                         |
-| adb_fetchinsecure  | 0, disabled                        | don't check SSL server certificates during download                                            |
-| adb_trigger        | -, not set                         | trigger network interface or 'not set' to use a time-based startup                             |
-| adb_triggerdelay   | 5                                  | additional trigger delay in seconds before adblock processing begins                           |
-| adb_debug          | 0, disabled                        | set to 1 to enable the debug output                                                            |
-| adb_nicelimit      | 0, standard prio.                  | valid nice level range 0-19 of the adblock processes                                           |
-| adb_dnsshift       | 0, disabled                        | shift the blocklist to the backup directory and only set a soft link to this file in memory    |
-| adb_dnsdir         | -, auto-detected                   | path for the generated blocklist file 'adb_list.overall'                                       |
-| adb_dnstimeout     | 20                                 | timeout in seconds to wait for a successful DNS backend restart                                |
-| adb_dnsinstance    | 0, first instance                  | set the relevant dnsmasq backend instance used by adblock                                      |
-| adb_dnsflush       | 0, disabled                        | set to 1 to flush the DNS Cache before & after adblock processing                              |
-| adb_lookupdomain   | localhost                          | domain to check for a successful DNS backend restart                                           |
-| adb_report         | 0, disabled                        | set to 1 to enable the background tcpdump gathering process for reporting                      |
-| adb_map            | 0, disabled                        | enable a GeoIP Map with blocked domains                                                        |
-| adb_reportdir      | /tmp/adblock-report                | path for DNS related report files                                                              |
-| adb_repiface       | -, auto-detected                   | name of the reporting interface or 'any' used by tcpdump                                       |
-| adb_repport        | 53                                 | list of reporting port(s) used by tcpdump                                                      |
-| adb_repchunkcnt    | 5                                  | report chunk count used by tcpdump                                                             |
-| adb_repchunksize   | 1                                  | report chunk size used by tcpdump in MB                                                        |
-| adb_represolve     | 0, disabled                        | resolve reporting IP addresses using reverse DNS (PTR) lookups                                 |
-| adb_tld            | 1, enabled                         | set to 0 to disable the top level domain compression (tld) function                            |
-| adb_basedir        | /tmp                               | path for all adblock related runtime operations, e.g. downloading, sorting, merging etc.       |
-| adb_backupdir      | /tmp/adblock-backup                | path for adblock backups                                                                       |
-| adb_safesearch     | 0, disabled                        | enforce SafeSearch for google, bing, brave, duckduckgo, yandex, youtube and pixabay            |
-| adb_safesearchlist | -, not set                         | limit SafeSearch to certain provider (see above)                                               |
-| adb_mail           | 0, disabled                        | set to 1 to enable notification E-Mails in case of a processing errors                         |
-| adb_mailreceiver   | -, not set                         | receiver address for adblock notification E-Mails                                              |
-| adb_mailsender     | no-reply@adblock                   | sender address for adblock notification E-Mails                                                |
-| adb_mailtopic      | adblock notification               | topic for adblock notification E-Mails                                                         |
-| adb_mailprofile    | adb_notify                         | mail profile used in 'msmtp' for adblock notification E-Mails                                  |
-| adb_jail           | 0                                  | jail mode - only domains on the allowlist are permitted, all other DNS requests are rejected   |
-| adb_nftforce       | 0, disabled                        | redirect all local DNS queries from specified LAN zones to the local DNS resolver              |
-| adb_nftdevforce    | -, not set                         | firewall LAN Devices/VLANs that should be forced locally                                       |
-| adb_nftportforce   | -, not set                         | firewall ports that should be forced locally                                                   |
-| adb_nftallow       | 0, disabled                        | routes MACs or interfaces to an unfiltered external DNS resolver, bypassing local adblock      |
-| adb_nftmacallow    | -, not set                         | listed MAC addresses will always use the configured unfiltered DNS server                      |
-| adb_nftdevallow    | -, not set                         | entire interfaces or VLANs will be routed to the unfiltered DNS server                         |
-| adb_allowdnsv4     | -, not set                         | IPv4 DNS resolver applied to MACs and interfaces using the unfiltered DNS policy               |
-| adb_allowdnsv6     | -, not set                         | IPv6 DNS resolver applied to MACs and interfaces using the unfiltered DNS policy               |
-| adb_nftblock       | 0, disabled                        | routes MACs or interfaces to a filtered external DNS resolver, bypassing local adblock         |
-| adb_nftmacblock    | -, not set                         | listed MAC addresses will always use the configured filtered DNS server                        |
-| adb_nftdevblock    | -, not set                         | entire interfaces or VLANs will be routed to the filtered DNS server                           |
-| adb_blockdnsv4     | -, not set                         | IPv4 DNS resolver applied to MACs and interfaces using the filtered DNS policy                 |
-| adb_blockdnsv6     | -, not set                         | IPv6 DNS resolver applied to MACs and interfaces using the filtered DNS policy                 |
+| Option               | Default                            | Description/Valid Values                                                                           |
+| :------------------- | :--------------------------------- | :------------------------------------------------------------------------------------------------- |
+| adb_enabled          | 1, enabled                         | set to 0 to disable the adblock service                                                            |
+| adb_feedfile         | /etc/adblock/adblock.feeds         | full path to the used adblock feed file                                                            |
+| adb_dns              | -, auto-detected                   | 'dnsmasq', 'unbound', 'named', 'kresd', 'smartdns' or 'raw'                                        |
+| adb_fetchcmd         | -, auto-detected                   | 'uclient-fetch', 'wget' or 'curl'                                                                  |
+| adb_fetchparm        | -, auto-detected                   | manually override the config options for the selected download utility                             |
+| adb_fetchinsecure    | 0, disabled                        | don't check SSL server certificates during download                                                |
+| adb_trigger          | -, not set                         | trigger network interface or 'not set' to use a time-based startup                                 |
+| adb_triggerdelay     | 5                                  | additional trigger delay in seconds before adblock processing begins                               |
+| adb_debug            | 0, disabled                        | set to 1 to enable the debug output                                                                |
+| adb_nicelimit        | 0, standard prio.                  | valid nice level range 0-19 of the adblock processes                                               |
+| adb_dnsshift         | 0, disabled                        | shift the blocklist to the backup directory and only set a soft link to this file in memory        |
+| adb_dnsdir           | -, auto-detected                   | path for the generated blocklist file 'adb_list.overall'                                           |
+| adb_dnstimeout       | 20                                 | timeout in seconds to wait for a successful DNS backend restart                                    |
+| adb_dnsinstance      | 0, first instance                  | set the relevant dnsmasq backend instance used by adblock                                          |
+| adb_dnsflush         | 0, disabled                        | set to 1 to flush the DNS Cache before & after adblock processing                                  |
+| adb_lookupdomain     | localhost                          | domain to check for a successful DNS backend restart                                               |
+| adb_report           | 0, disabled                        | set to 1 to enable the background tcpdump gathering process for reporting                          |
+| adb_map              | 0, disabled                        | enable a GeoIP Map with blocked domains                                                            |
+| adb_reportdir        | /tmp/adblock-report                | path for DNS related report files                                                                  |
+| adb_repiface         | -, auto-detected                   | name of the reporting interface or 'any' used by tcpdump                                           |
+| adb_repport          | 53                                 | list of reporting port(s) used by tcpdump                                                          |
+| adb_repchunkcnt      | 5                                  | report chunk count used by tcpdump                                                                 |
+| adb_repchunksize     | 1                                  | report chunk size used by tcpdump in MB                                                            |
+| adb_represolve       | 0, disabled                        | resolve reporting IP addresses using reverse DNS (PTR) lookups                                     |
+| adb_tld              | 1, enabled                         | set to 0 to disable the top level domain compression (tld) function                                |
+| adb_basedir          | /tmp                               | path for all adblock related runtime operations, e.g. downloading, sorting, merging etc.           |
+| adb_backupdir        | /tmp/adblock-backup                | path for adblock backups                                                                           |
+| adb_safesearch       | 0, disabled                        | enforce SafeSearch for google, bing, brave, duckduckgo, yandex, youtube and pixabay                |
+| adb_safesearchlist   | -, not set                         | limit SafeSearch to certain provider (see above)                                                   |
+| adb_mail             | 0, disabled                        | set to 1 to enable notification E-Mails in case of a processing errors                             |
+| adb_mailreceiver     | -, not set                         | receiver address for adblock notification E-Mails                                                  |
+| adb_mailsender       | no-reply@adblock                   | sender address for adblock notification E-Mails                                                    |
+| adb_mailtopic        | adblock notification               | topic for adblock notification E-Mails                                                             |
+| adb_mailprofile      | adb_notify                         | mail profile used in 'msmtp' for adblock notification E-Mails                                      |
+| adb_jail             | 0                                  | jail mode - only domains on the allowlist are permitted, all other DNS requests are rejected       |
+| adb_nftforce         | 0, disabled                        | redirect all local DNS queries from specified LAN zones to the local DNS resolver                  |
+| adb_nftdevforce      | -, not set                         | firewall LAN Devices/VLANs that should be forced locally                                           |
+| adb_nftportforce     | -, not set                         | firewall ports that should be forced locally                                                       |
+| adb_nftallow         | 0, disabled                        | routes MACs or interfaces to an unfiltered external DNS resolver, bypassing local adblock          |
+| adb_nftmacallow      | -, not set                         | listed MAC addresses will always use the configured unfiltered DNS server                          |
+| adb_nftdevallow      | -, not set                         | entire interfaces or VLANs will be routed to the unfiltered DNS server                             |
+| adb_allowdnsv4       | -, not set                         | IPv4 DNS resolver applied to MACs and interfaces using the unfiltered DNS policy                   |
+| adb_allowdnsv6       | -, not set                         | IPv6 DNS resolver applied to MACs and interfaces using the unfiltered DNS policy                   |
+| adb_nftremote        | 0, disabled                        | routes MACs to an unfiltered external DNS resolver, bypassing local adblock                        |
+| adb_nftmacremote     | -, not set                         | Allows listed MACs to remotely access an unfiltered external DNS resolver, bypassing local adblock |
+| adb_nftremotetimeout | 15                                 | Time limit in minutes for remote DNS access of the listed MAC addresses                            |
+| adb_remotednsv4      | -, not set                         | IPv4 DNS resolver applied to MACs using the unfiltered remote DNS policy                           |
+| adb_remotednsv6      | -, not set                         | IPv6 DNS resolver applied to MACs using the unfiltered remote DNS policy                           |
+| adb_nftblock         | 0, disabled                        | routes MACs or interfaces to a filtered external DNS resolver, bypassing local adblock             |
+| adb_nftmacblock      | -, not set                         | listed MAC addresses will always use the configured filtered DNS server                            |
+| adb_nftdevblock      | -, not set                         | entire interfaces or VLANs will be routed to the filtered DNS server                               |
+| adb_blockdnsv4       | -, not set                         | IPv4 DNS resolver applied to MACs and interfaces using the filtered DNS policy                     |
+| adb_blockdnsv6       | -, not set                         | IPv6 DNS resolver applied to MACs and interfaces using the filtered DNS policy                     |
 
 <a id="examples"></a>
 ## Examples
@@ -292,6 +297,19 @@ This mode guarantees that adblock’s filtering pipeline is always applied.
 
 adblock's firewall rules are based on nftables in a separate isolated nftables table (inet adblock) and chains (prerouting), with MAC addresses stored in a nftables set. The configuration is carried out centrally in LuCI on the ‘Firewall Settings’ tab in adblock.  
 
+**Remote DNS Allow (Temporary MAC‑Based Bypass)**  
+This additional firewall feature lets selected client devices temporarily bypass local DNS blocking and use an external, unfiltered DNS resolver. It is designed for situations where a device needs short‑term access to content normally blocked by the adblock rules.  
+
+A lightweight CGI endpoint handles the workflow:  
+* the client opens the URL, e.g. https://\<ROUTER-IP\>cgi-bin/adblock (preferably transferred via QR code shown in LuCI)
+* the script automatically detects the device’s MAC address
+* if the MAC is authorized, the script displays the current status:
+  * not in the nftables set → option to request a temporary allow (“Renew”)
+  * already active → shows remaining timeout
+* when renewing, the CGI adds the MAC to an nftables Set with a per‑entry timeout
+
+The CGI interface is mobile‑friendly and includes a LuCI‑style loading spinner during the renew process, giving immediate visual feedback while the nftables entry is created. All operations are atomic and safe even when multiple devices renew access in parallel.  
+
 **Jail mode (allowlist-only):**  
 Enforces a strict allowlist‑only DNS policy in which only domains listed in the allowlist file are resolved, while every other query is rejected. This mode is intended for highly restrictive environments and depends on a carefully maintained allowlist, typically managed manually.  
 
diff --git a/net/adblock/files/adblock.cgi b/net/adblock/files/adblock.cgi
new file mode 100644 (file)
index 0000000..b58b89c
--- /dev/null
@@ -0,0 +1,231 @@
+#!/bin/sh
+# adblock cgi remote script - dns based ad/abuse domain blocking
+# Copyright (c) 2026 Dirk Brenken
+# This is free software, licensed under the GNU General Public License v3.
+
+# (s)hellcheck exceptions
+# shellcheck disable=all
+
+# load relevant uci options
+#
+nft_remote="$(uci -q get adblock.global.adb_nftremote)"
+nft_macremote="$(uci -q get adblock.global.adb_nftmacremote)"
+nft_remotetimeout="$(uci -q get adblock.global.adb_nftremotetimeout)"
+nft_authorized="0"
+
+# parse query
+#
+query_str="${QUERY_STRING}"
+query_mac="$(printf "%s" "${query_str}" | sed -n 's/.*mac=\([^&]*\).*/\1/p' 2>/dev/null)"
+query_mode="$(printf "%s" "${query_str}" | sed -n 's/.*mode=\([^&]*\).*/\1/p' 2>/dev/null)"
+
+# determine MAC if not provided
+#
+if [ -z "${query_mac}" ]; then
+       query_ip="${REMOTE_ADDR}"
+       query_mac="$(ip neigh show 2>/dev/null | awk -v ip="${query_ip}" '$1==ip {print $5}' 2>/dev/null)"
+fi
+
+# validate MAC address
+#
+printf '%s\n' "${query_mac}" | grep -Eq '^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$' 2>/dev/null \
+       && query_mac="$(printf '%s\n' "${query_mac}" | awk '{ print tolower($0) }' 2>/dev/null)" \
+       || query_mac=""
+
+# validate mode
+#
+[ "${query_mode}" = "renew" ] || query_mode=""
+
+# output header and start html
+#
+printf "%s\n\n" "Content-Type: text/html"
+printf "%s\n" "<!DOCTYPE html>
+<html>
+<head>
+<meta charset=\"utf-8\">
+<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
+<title>Adblock Remote Allow</title>
+
+<style>
+       body {
+               margin: 0;
+               padding: 0;
+               background: #1a1a1a;
+               color: #e5e5e5;
+               font-family: 'Open Sans', sans-serif;
+               text-align: center;
+       }
+
+       .container {
+               max-width: 600px;
+               margin: 2rem auto;
+               padding: 1.5rem;
+               background: #2b2b2b;
+               border-radius: 10px;
+               box-shadow: 0 0 10px rgba(0,0,0,0.4);
+       }
+
+       h1 {
+               font-size: 1.6rem;
+               margin-bottom: 1rem;
+               color: #5fa8ff;
+       }
+
+       .msg {
+               padding: 1rem;
+               border-radius: 8px;
+               margin-bottom: 1.5rem;
+               font-size: 1.2rem;
+               line-height: 1.5;
+       }
+
+       .ok {
+               background: #223344;
+               color: #8fc7ff;
+       }
+
+       .err {
+               background: #442222;
+               color: #ff8f8f;
+       }
+
+       .btn {
+               display: inline-block;
+               padding: 0.8rem 1.2rem;
+               background: #4da3ff;
+               color: #1a1a1a;
+               border-radius: 8px;
+               text-decoration: none;
+               font-size: 1.1rem;
+               font-weight: bold;
+               margin-top: 1rem;
+       }
+
+       .btn:hover {
+               background: #6bb6ff;
+       }
+
+       .spinner {
+               margin: 1.5rem auto;
+               width: 40px;
+               height: 40px;
+               border: 4px solid #4da3ff33;
+               border-top-color: #4da3ff;
+               border-radius: 50%;
+               animation: spin 0.8s linear infinite;
+       }
+
+       @keyframes spin {
+               to { transform: rotate(360deg); }
+       }
+</style>
+
+<script>
+let refreshTimer = null;
+
+function startRefresh() {
+       stopRefresh();
+       refreshTimer = setTimeout(function() { window.location.reload(); }, 5000);
+}
+
+function stopRefresh() {
+       if (refreshTimer !== null) {
+               clearTimeout(refreshTimer);
+               refreshTimer = null;
+       }
+}
+
+function setStatus(mac) {
+       window.location.href = '?mac=' + mac;
+}
+
+function showSpinner() {
+       var s = document.getElementById('spinner');
+       if (s) s.style.display = 'block';
+}
+</script>
+
+</head>
+<body>
+<div class=\"container\">
+<h1>Adblock Remote Allow</h1>
+"
+
+# check if remote allow is enabled
+#
+if [ "${nft_remote}" != "1" ] || [ -z "${nft_macremote}" ]; then
+       printf "%s\n" "<div class=\"msg err\">Remote allow is not enabled or no MAC addresses configured</div></div></body></html>"
+       exit 0
+fi
+if [ -z "${query_mac}" ]; then
+       printf "%s\n" "<div class=\"msg err\">Could not determine MAC address</div></div></body></html>"
+       exit 0
+fi
+
+# check MAC authorization
+#
+for mac in ${nft_macremote}; do
+       mac="$(printf '%s' "${mac}" | awk '{ print tolower($0) }')"
+       if [ "${mac}" = "${query_mac}" ]; then
+               nft_authorized="1"
+               break
+       fi
+done
+if [ "${nft_authorized}" = "0" ]; then
+       printf "%s\n" "<div class=\"msg err\">MAC ${query_mac} is not authorized to use remote allow</div></div></body></html>"
+       exit 0
+fi
+
+# extract remaining timeout
+#
+# extract remaining timeout (strip ms part)
+remaining="$(nft list set inet adblock mac_remote 2>/dev/null | \
+       awk -v mac="${query_mac}" '
+               $0 ~ mac {
+                       for (i = 1; i <= NF; i++) {
+                               if ($i == "expires") {
+                                       val = $(i+1)
+                                       gsub(/[,}]/, "", val)
+                                       sub(/s.*/, "s", val)
+                                       print val
+                                       exit
+                               }
+                       }
+               }
+       ')"
+
+# show renew option
+#
+if [ -z "${query_mode}" ] && [ -z "${remaining}" ]; then
+       printf "%s\n" "<script>stopRefresh();</script>
+               <div class=\"msg ok\">
+                       MAC ${query_mac} is currently not in the remote allow Set<br><br>
+                       <a class=\"btn\" href=\"?mac=${query_mac}&mode=renew\">Renew Set Entry</a>
+               </div>
+       </div></body></html>"
+       exit 0
+fi
+
+# add MAC
+#
+if [ -z "${remaining}" ]; then
+       printf "%s\n" "
+               <div class=\"msg ok\">
+                       Renewing remote allow for MAC ${query_mac}<br><br>
+                       <div class=\"spinner\"></div>
+               </div>
+       </div></body></html>"
+       nft add element inet adblock mac_remote "{ ${query_mac} }" >/dev/null 2>&1
+       printf "%s\n" "<script>setStatus('${query_mac}');</script>"
+fi
+
+# success message
+#
+printf "%s\n" "
+       <div class=\"msg ok\">
+               MAC ${query_mac} is temporarily allowed<br>
+               Remaining time: ${remaining:-${nft_remotetimeout}m}
+       </div>
+       <script>startRefresh();</script>
+</div></body></html>"
+exit 0
index cf628961d31451c484dbc0fd8f252df7ed593986..5f83b066ce1e1b88e00125daadbabd4a2c5d9a9b 100755 (executable)
@@ -22,8 +22,13 @@ adb_nftdevallow=""
 adb_nftblock="0"
 adb_nftmacblock=""
 adb_nftdevblock=""
+adb_nftremote="0"
+adb_nftremotetimeout="15"
+adb_nftmacremote=""
 adb_allowdnsv4=""
 adb_allowdnsv6=""
+adb_remotednsv4=""
+adb_remotednsv6=""
 adb_blockdnsv4=""
 adb_blockdnsv6=""
 adb_dnsshift="0"
@@ -716,7 +721,7 @@ f_nftadd() {
 
        # only proceed if at least one feature is enabled
        #
-       if [ "${adb_nftallow}" = "0" ] && [ "${adb_nftblock}" = "0" ] && [ "${adb_nftforce}" = "0" ]; then
+       if [ "${adb_nftallow}" = "0" ] && [ "${adb_nftblock}" = "0" ] && [ "${adb_nftremote}" = "0" ] && [ "${adb_nftforce}" = "0" ]; then
                return
        fi
 
@@ -728,9 +733,18 @@ f_nftadd() {
                        printf "%s\n" "delete table inet adblock"
                fi
                printf "%s\n" "add table inet adblock"
+               # allow Set
+               #
                if [ "${adb_nftallow}" = "1" ] && [ -n "${adb_nftmacallow}" ]; then
                        printf "%s\n" "add set inet adblock mac_allow { type ether_addr; flags interval; auto-merge; elements = { ${adb_nftmacallow// /, } }; }"
                fi
+               # remote allow Set with timeout, for MACs that should be temporary allowed to bypass dns blocking
+               #
+               if [ "${adb_nftremote}" = "1" ] && [ -n "${adb_nftmacremote}" ]; then
+                       printf "%s\n" "add set inet adblock mac_remote { type ether_addr; flags timeout; timeout ${adb_nftremotetimeout}m; }"
+               fi
+               # block Set
+               #
                if [ "${adb_nftblock}" = "1" ] && [ -n "${adb_nftmacblock}" ]; then
                        printf "%s\n" "add set inet adblock mac_block { type ether_addr; flags interval; auto-merge; elements = { ${adb_nftmacblock// /, } }; }"
                fi
@@ -755,6 +769,13 @@ f_nftadd() {
                        done
                fi
 
+               # external remote allow rules
+               #
+               if [ "${adb_nftremote}" = "1" ]; then
+                       [ -n "${adb_remotednsv4}" ] && printf "%s\n" "add rule inet adblock pre-routing meta nfproto ipv4 ether saddr @mac_remote meta l4proto { udp, tcp } th dport 53 counter dnat to ${adb_remotednsv4}:53"
+                       [ -n "${adb_remotednsv6}" ] && printf "%s\n" "add rule inet adblock pre-routing meta nfproto ipv6 ether saddr @mac_remote meta l4proto { udp, tcp } th dport 53 counter dnat to [${adb_remotednsv6}]:53"
+               fi
+
                # external block rules
                #
                if [ "${adb_nftblock}" = "1" ]; then
@@ -1191,7 +1212,7 @@ f_query() {
 #
 f_jsnup() {
        local pids object feeds end_time runtime dns dns_ver dns_mem free_mem custom_feed="0" status="${1:-"enabled"}"
-       local duration jail="0" nft_unfiltered="0" nft_filtered="0" nft_force="0"
+       local duration jail="0" nft_unfiltered="0" nft_filtered="0" nft_remote="0" nft_force="0"
 
        if [ -n "${adb_dnspid}" ]; then
                pids="$("${adb_pgrepcmd}" -P "${adb_dnspid}" 2>/dev/null)"
@@ -1231,6 +1252,10 @@ f_jsnup() {
                && { [ -n "${adb_blockdnsv4}" ] || [ -n "${adb_blockdnsv6}" ]; }; then
                nft_filtered="1"
        fi
+       if [ "${adb_nftremote}" = "1" ] && [ -n "${adb_nftmacremote}" ] \
+               && { [ -n "${adb_remotednsv4}" ] || [ -n "${adb_remotednsv6}" ]; }; then
+               nft_remote="1"
+       fi
        case "${status}" in
                "enabled")
                        if [ -n "${adb_starttime}" ] && [ "${adb_action}" != "boot" ]; then
@@ -1280,7 +1305,7 @@ f_jsnup() {
        json_add_string "dns_backend" "${adb_dns:-"-"} (${dns_ver:-"-"}), ${adb_finaldir:-"-"}, ${dns_mem:-"0"} MB"
        json_add_string "run_ifaces" "trigger: ${adb_trigger:-"-"}, report: ${adb_repiface:-"-"}"
        json_add_string "run_directories" "base: ${adb_basedir}, dns: ${adb_dnsdir}, backup: ${adb_backupdir}, report: ${adb_reportdir}"
-       json_add_string "run_flags" "shift: $(f_char ${adb_dnsshift}), custom feed: $(f_char ${custom_feed}), ext. DNS (std/prot): $(f_char ${nft_unfiltered})/$(f_char ${nft_filtered}), force: $(f_char ${nft_force}), flush: $(f_char ${adb_dnsflush}), tld: $(f_char ${adb_tld}), search: $(f_char ${adb_safesearch}), report: $(f_char ${adb_report}), mail: $(f_char ${adb_mail}), jail: $(f_char ${jail})"
+       json_add_string "run_flags" "shift: $(f_char ${adb_dnsshift}), custom feed: $(f_char ${custom_feed}), ext. DNS (std/prot/remote): $(f_char ${nft_unfiltered})/$(f_char ${nft_filtered})/$(f_char ${nft_remote}), force: $(f_char ${nft_force}), flush: $(f_char ${adb_dnsflush}), tld: $(f_char ${adb_tld}), search: $(f_char ${adb_safesearch}), report: $(f_char ${adb_report}), mail: $(f_char ${adb_mail}), jail: $(f_char ${jail})"
        json_add_string "last_run" "${runtime:-"-"}"
        json_add_string "system_info" "cores: ${adb_cores}, fetch: ${adb_fetchcmd##*/}, ${adb_sysver}"
        json_dump >"${adb_rtfile}"
@@ -1941,6 +1966,7 @@ case "${adb_action}" in
                f_main
                ;;
        "restart")
+               f_temp
                f_jsnup "processing"
                f_nftremove
                f_rmdns