adblock: update 4.5.0-2
authorDirk Brenken <redacted>
Sat, 24 Jan 2026 13:33:24 +0000 (14:33 +0100)
committerDirk Brenken <redacted>
Sat, 24 Jan 2026 13:35:07 +0000 (14:35 +0100)
* rework DNS reporting: more reliable, more information (request type), better performance
* fixed minor issues
* readme update
* LuCI: added new DNS page (incl. Allowed/Blocked canvas)

Signed-off-by: Dirk Brenken <redacted>
net/adblock/Makefile
net/adblock/files/95-adblock-housekeeping
net/adblock/files/README.md
net/adblock/files/adblock.sh

index 57565ebca436aab0f81ea6c901fe65477b4015b4..e09ca1cdf5c75a1e41a0f7fb2516eb872e77fcfd 100644 (file)
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
 
 PKG_NAME:=adblock
 PKG_VERSION:=4.5.0
-PKG_RELEASE:=1
+PKG_RELEASE:=2
 PKG_LICENSE:=GPL-3.0-or-later
 PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org>
 
index bb7c62070710ef7646b97e808f22242531913538..4451d166944b2c9056880c28ebf96159a1bbcaea 100755 (executable)
@@ -11,7 +11,7 @@ export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
 config="adblock"
 old_options="adb_sources adb_forcedns adb_fetchutil adb_hag_sources adb_hst_sources adb_stb_sources adb_utc_sources \
        adb_maxqueue adb_backup adb_dnsfilereset adb_tmpbase adb_mailcnt adb_safesearchmod adb_srcfile adb_srcarc adb_nice \
-       adb_hag_feed adb_jaildir adb_dnsdenyip adb_dnsallowip adb_zonelist adb_portlist adb_dnsforce"
+       adb_hag_feed adb_jaildir adb_dnsdenyip adb_dnsallowip adb_zonelist adb_portlist adb_dnsforce adb_replisten"
 
 for option in ${old_options}; do
        inplace="0"
@@ -44,9 +44,6 @@ for option in ${old_options}; do
                                                uci -q add_list ${config}.global.adb_utc_feed="${value}"
                                        fi
                                ;;
-                               "adb_forcedns")
-                                       uci -q set ${config}.global.adb_dnsforce="${value}"
-                               ;;
                                "adb_fetchutil")
                                        uci -q set ${config}.global.adb_fetchcmd="${value}"
                                ;;
@@ -63,14 +60,23 @@ for option in ${old_options}; do
                                                uci -q add_list ${config}.global.adb_hag_feed="wildcard/${value}"
                                        fi
                                ;;
-                               "adb_dnsforce")
+                               "adb_forcedns" | "adb_dnsforce")
                                        uci -q set ${config}.global.adb_nftforce="${value}"
                                ;;
                                "adb_zonelist")
-                                       uci -q set ${config}.global.adb_nftdevforce="${value}"
+                                       if ! uci -q get ${config}.global.adb_nftdevforce | grep -q "${value}"; then
+                                               uci -q add_list ${config}.global.adb_nftdevforce="${value}"
+                                       fi
                                ;;
                                "adb_portlist")
-                                       uci -q set ${config}.global.adb_nftportforce="${value}"
+                                       if ! uci -q get ${config}.global.adb_nftportforce | grep -q "${value}"; then
+                                               uci -q add_list ${config}.global.adb_nftportforce="${value}"
+                                       fi
+                               ;;
+                               "adb_replisten")
+                                       if ! uci -q get ${config}.global.adb_repport | grep -q "${value}"; then
+                                               uci -q add_list ${config}.global.adb_repport="${value}"
+                                       fi
                                ;;
                        esac
                done
index 27b52fcd05c13068263f301f4b4d67fcdc9f32a4..398ccad8fc8ba10e464cef52659420e092b82c6b 100644 (file)
@@ -66,6 +66,7 @@ A lot of people already use adblocker plugins within their desktop browsers, but
 * Overall duplicate removal in generated blocklist file 'adb_list.overall'
 * Additional local allowlist for manual overrides, located in '/etc/adblock/adblock.allowlist' (only exact matches).
 * Additional local blocklist for manual overrides, located in '/etc/adblock/adblock.blocklist'
+* Implements Firewall‑Based DNS Control to force DNS interfaces/ports and to redirect to external unfiltered/filtered DNS server
 * Connection checks during blocklist update to ensure a reliable DNS backend service
 * Minimal status & error logging to syslog, enable debug logging to receive more output
 * Procd based init system support ('start', 'stop', 'restart', 'reload', 'enable', 'disable', 'running', 'status', 'suspend', 'resume', 'query', 'report')
@@ -75,6 +76,7 @@ A lot of people already use adblocker plugins within their desktop browsers, but
 * Provides a detailed DNS Query Report with DNS related information about client requests, top (blocked) domains and more
 * Provides a powerful query function to quickly find blocked (sub-)domains, e.g. to allow certain domains
 * Contains an option to route DNS queries to the local resolver via corresponding firewall rules
+* Implements a jail mode - only domains on the allowlist are permitted, all other DNS requests are rejected
 * Automatic blocklist backup & restore, these backups will be used in case of download errors and during startup
 * Send notification E-Mails, see example configuration below
 * Add new adblock feeds on your own with the 'Custom Feed Editor' in LuCI or via CLI, see example below
@@ -155,7 +157,7 @@ Available commands:
 | 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_replisten      | 53                                 | space separated list of reporting port(s) 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                                 |
@@ -178,7 +180,7 @@ Available commands:
 | 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 an filtered external DNS resolver, bypassing local adblock        |
+| 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                 |
@@ -239,11 +241,12 @@ To get the status in the CLI, just call _/etc/init.d/adblock status_ or _/etc/in
 ## Best practise and tweaks
 
 **Recommendation for low memory systems**  
-Adblock does use RAM by default and never writes to the flash space of the router. To reduce the memory pressure on low memory systems (i.e. those with 128-256MB RAM), you should optimize your configuration with the following options:  
+adblock uses RAM by design and avoids writing to flash. On devices with 128–256 MB RAM, you can reduce memory pressure with the following optimizations:  
 
-* point 'adb_basedir', 'adb_backupdir' and 'adb_reportdir' to an external usb drive or ssd
-* set 'adb_cores' to '1' (only useful on a multicore system) to force sequential feed processing
-* enable the 'adb_dnsshift' option to shift the blocklist to the backup directory and only set a soft link to this file in memory
+* use external storage: point 'adb_basedir', 'adb_backupdir' and 'adb_reportdir' to an USB drive or SSD
+* limit CPU processing to one core: set 'adb_cores' to '1' to reduce peak memory usage during feed processing
+* enable blocklist shifting: activate 'adb_dnsshift' to store the blocklist in the backup directory and expose it via a symlink in RAM.
+* Firewall DNS redirection: use nftables based DNS routing to external filtered DNS serves and only use a minimal set of local blocklists
 
 **Sensible choice of blocklists**  
 The following feeds are just my personal recommendation as an initial setup:  
@@ -287,7 +290,7 @@ force DNS ensures that all DNS traffic on your network by specific devices or en
 * also prevents DNS bypassing by clients with hardcoded DNS settings on other ports, e.g. on port 853
 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 an nftables set. The configuration is carried out centrally in LuCI on the ‘Firewall Settings’ tab in adblock.  
+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.  
 
 **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.  
index b4504202c603c9e83a55fe571be77d092f46e4c1..de9fd26eb9720235ef10cc6bbd37c8d2d77e760f 100755 (executable)
@@ -59,7 +59,7 @@ adb_etagparm=""
 adb_geoparm=""
 adb_geourl="http://ip-api.com/json"
 adb_repiface=""
-adb_replisten="53"
+adb_repport="53"
 adb_repchunkcnt="5"
 adb_repchunksize="1"
 adb_represolve="0"
@@ -92,7 +92,7 @@ f_cmd() {
 # load adblock environment
 #
 f_load() {
-       local bg_pid iface port ports cpu core
+       local bg_pid iface port filter tcpdump_filter cpu core
 
        adb_packages="$("${adb_ubuscmd}" -S call rpc-sys packagelist '{ "all": true }' 2>/dev/null)"
        adb_bver="$(printf "%s" "${adb_packages}" | "${adb_jsoncmd}" -ql1 -e '@.packages.adblock')"
@@ -127,26 +127,21 @@ f_load() {
        fi
 
        if [ "${adb_report}" = "1" ] && [ ! -x "${adb_dumpcmd}" ]; then
-               f_log "info" "Please install the package 'tcpdump' or 'tcpdump-mini' to use the reporting feature"
+               f_log "info" "please install the package 'tcpdump' or 'tcpdump-mini' to use the reporting feature"
        elif [ -x "${adb_dumpcmd}" ]; then
-               bg_pid="$("${adb_pgrepcmd}" -f "^${adb_dumpcmd}.*adb_report\\.pcap$" | "${adb_awkcmd}" '{ORS=" "; print $1}')"
-               if [ "${adb_report}" = "0" ] || { [ -n "${bg_pid}" ] && [ "${adb_action}" = "stop" ]; }; then
-                       if [ -n "${bg_pid}" ]; then
-                               kill -HUP "${bg_pid}" 2>/dev/null
+               bg_pid="$("${adb_pgrepcmd}" -nf "${adb_reportdir}/adb_report.pcap")"
+               if [ "${adb_report}" = "0" ] || { [ -n "${bg_pid}" ] && { [ "${adb_action}" = "stop" ] || [ "${adb_action}" = "restart" ]; }; }; then
+                       if kill -HUP "${bg_pid}" 2>/dev/null; then
                                while kill -0 "${bg_pid}" 2>/dev/null; do
                                        sleep 1
                                done
-                               unset bg_pid
                        fi
+                       bg_pid="$("${adb_pgrepcmd}" -nf "${adb_reportdir}/adb_report.pcap")"
                        rm -f "${adb_reportdir}"/adb_report.pcap*
                fi
 
                if [ "${adb_report}" = "1" ] && [ -z "${bg_pid}" ] && [ "${adb_action}" != "report" ] && [ "${adb_action}" != "stop" ]; then
                        [ ! -d "${adb_reportdir}" ] && mkdir -p "${adb_reportdir}"
-
-                       for port in ${adb_replisten}; do
-                               [ -z "${ports}" ] && ports="port ${port}" || ports="${ports} or port ${port}"
-                       done
                        if [ -z "${adb_repiface}" ]; then
                                network_get_device iface "lan"
                                [ -z "${iface}" ] && network_get_physdev iface "lan"
@@ -156,12 +151,22 @@ f_load() {
                                        f_uci "adblock"
                                fi
                        fi
-
+                       for port in ${adb_repport}; do
+                               [ -n "${filter}" ] && filter="${filter} or "
+                               filter="${filter}(udp port ${port}) or (tcp port ${port})"
+                       done
+                       tcpdump_filter="(${filter}) and greater 28"
                        if [ -n "${adb_repiface}" ] && [ -d "${adb_reportdir}" ]; then
-                               ("${adb_dumpcmd}" --immediate-mode -nn -p -s0 -l -i ${adb_repiface} ${ports} -C${adb_repchunksize} -W${adb_repchunkcnt} -w "${adb_reportdir}/adb_report.pcap" >/dev/null 2>&1 &)
-                               bg_pid="$("${adb_pgrepcmd}" -f "^${adb_dumpcmd}.*adb_report\\.pcap$" | "${adb_awkcmd}" '{ORS=" "; print $1}')"
+                               (
+                                       "${adb_dumpcmd}" --immediate-mode -nn -p -s0 -i "${adb_repiface}" \
+                                       "${tcpdump_filter}" \
+                                       -C "${adb_repchunksize}" -W "${adb_repchunkcnt}" \
+                                       -w "${adb_reportdir}/adb_report.pcap" >/dev/null 2>&1 &
+                               )
+                               bg_pid="$("${adb_pgrepcmd}" -nf "${adb_reportdir}/adb_report.pcap")"
+                               f_log "info" "tcpdump backgound process started (interface: '${adb_repiface}', port: ${adb_repport}, pid: ${bg_pid})"
                        else
-                               f_log "info" "Please set the name of the reporting network device 'adb_repiface' manually"
+                               f_log "info" "please set the name of the reporting network device 'adb_repiface' manually"
                        fi
                fi
        fi
@@ -539,17 +544,12 @@ f_uci() {
 
        if [ -n "$(uci -q changes "${config}")" ]; then
                uci_commit "${config}"
-               case "${config}" in
-                       "firewall")
-                               "/etc/init.d/firewall" reload >/dev/null 2>&1
-                               ;;
-                       "resolver")
-                               printf "%b" "${adb_dnsheader}" >"${adb_finaldir}/${adb_dnsfile}"
-                               adb_cnt="0"
-                               f_jsnup "processing"
-                               "/etc/init.d/${adb_dns}" reload >/dev/null 2>&1
-                               ;;
-               esac
+               if [ "${config}" = "resolver" ]; then
+                       printf "%b" "${adb_dnsheader}" >"${adb_finaldir}/${adb_dnsfile}"
+                       adb_cnt="0"
+                       f_jsnup "processing"
+                       "/etc/init.d/${adb_dns}" reload >/dev/null 2>&1
+               fi
        fi
 }
 
@@ -1520,55 +1520,99 @@ f_report() {
        report_srt="${adb_reportdir}/adb_report.srt"
        report_jsn="${adb_reportdir}/adb_report.jsn"
        report_txt="${adb_reportdir}/adb_mailreport.txt"
+       top_tmpclients="${adb_reportdir}/top_clients.tmp"
+       top_tmpdomains="${adb_reportdir}/top_domains.tmp"
+       top_tmpblocked="${adb_reportdir}/top_blocked.tmp"
        map_jsn="${adb_reportdir}/adb_map.jsn"
 
-       # build json file
+
+       # build report
        #
        if [ "${action}" != "json" ]; then
                : >"${report_raw}" >"${report_srt}" >"${report_txt}" >"${report_jsn}"
+               : >"${top_tmpclients}" >"${top_tmpdomains}" >"${top_tmpblocked}"
                [ "${adb_represolve}" = "1" ] && resolve=""
                for file in "${adb_reportdir}/adb_report.pcap"*; do
                        (
-                               if [ "${adb_repiface}" = "any" ]; then
-                                       "${adb_dumpcmd}" "${resolve}" --immediate-mode -T domain -tttt -r "${file}" 2>/dev/null |
-                                               "${adb_awkcmd}" -v cnt="${cnt}" '!/\.lan\. |PTR\? | SOA\? | Flags /&&/ A[A]*\? |NXDomain|0\.0\.0\.0|[0-9]\/[0-9]\/[0-9]/{sub(/\.[0-9]+$/,"",$6);
-                                               type=substr($(NF-1),length($(NF-1)));
-                                               if(type=="."&&$(NF-2)!="CNAME")
-                                                       {domain=substr($(NF-1),1,length($(NF-1))-1);type="RQ"}
-                                               else
-                                                       {if($(NF-2)~/NXDomain/||$(NF-1)=="0.0.0.0"){type="NX"}else{type="OK"};domain=""};
-                                                       if(int($9)>0)
-                                                               printf "%08d\t%s\t%s\t%s\t%-25s\t%s\n",$9,type,$1,substr($2,1,8),$6,domain}' >>"${report_raw}"
-                               else
-                                       "${adb_dumpcmd}" "${resolve}" --immediate-mode -T domain -tttt -r "${file}" 2>/dev/null |
-                                               "${adb_awkcmd}" -v cnt="${cnt}" '!/\.lan\. |PTR\? | SOA\? | Flags /&&/ A[A]*\? |NXDomain|0\.0\.0\.0|[0-9]\/[0-9]\/[0-9]/{sub(/\.[0-9]+$/,"",$4);
-                                               type=substr($(NF-1),length($(NF-1)));
-                                               if(type=="."&&$(NF-2)!="CNAME")
-                                                       {domain=substr($(NF-1),1,length($(NF-1))-1);type="RQ"}
-                                               else
-                                                       {if($(NF-2)~/NXDomain/||$(NF-1)=="0.0.0.0"){type="NX"}else{type="OK"};domain=""};
-                                                       if(int($7)>0)
-                                                               printf "%08d\t%s\t%s\t%s\t%-25s\t%s\n",$7,type,$1,substr($2,1,8),$4,domain}' >>"${report_raw}"
-                               fi
+                               "${adb_dumpcmd}" ${resolve} --immediate-mode -tttt -T domain -r "${file}" 2>/dev/null |
+                               "${adb_awkcmd}" '
+                                       BEGIN {
+                                               pending = 0
+                                       }
+                                       # ignore Reverse DNS
+                                       /\.in-addr\.arpa/ || /\.ip6\.arpa/ { next }
+                                       # domain request parser
+                                       /\+[[:space:]]+(A\?|AAAA\?)/ {
+                                               # drop unresolved previous query
+                                               if (pending)
+                                                       pending = 0
+                                               date = $1
+                                               split($2, t, ":")
+                                               time = t[1] ":" t[2] ":" substr(t[3],1,2)
+                                               client = $4
+                                               sub(/\.[0-9]+$/, "", client)
+                                               domain = $(NF-1)
+                                               sub(/[,\.]+$/, "", domain)
+                                               if (domain ~ /\.lan$/) next
+                                               qtype = $(NF-2)
+                                               sub(/\?$/, "", qtype)
+                                               last_date = date
+                                               last_time = time
+                                               last_client = client
+                                               last_domain = domain
+                                               last_qtype  = qtype
+                                               pending  = 1
+                                               next
+                                       }
+                                       # ok answer
+                                       /[0-9]+[[:space:]]+[0-9]+\/[0-9]+\/[0-9]+[[:space:]]+(A|AAAA|CNAME)[[:space:]]/ {
+                                               if (pending) {
+                                                       printf "%s\t%s\t%s\t%s\t%s\tOK\n",
+                                                       last_date, last_time, last_client, last_qtype, last_domain
+                                                       pending = 0
+                                               }
+                                               next
+                                       }
+                                       # nxdomain answer
+                                       / NXDomain/ {
+                                               if (pending) {
+                                                       printf "%s\t%s\t%s\t%s\t%s\tNX\n",
+                                                       last_date, last_time, last_client, last_qtype, last_domain
+                                                       pending = 0
+                                               }
+                                               next
+                                       }
+                                       # servfail answer
+                                       / ServFail/ {
+                                               if (pending) {
+                                                       printf "%s\t%s\t%s\t%s\t%s\tSF\n",
+                                                       last_date, last_time, last_client, last_qtype, last_domain
+                                                       pending = 0
+                                               }
+                                               next
+                                       }
+                                       END {
+                                       # no fallback
+                                       }
+                               ' >> "${report_raw}"
                        ) &
                        [ "${cnt}" -gt "${adb_cores}" ] && wait -n
                        cnt="$((cnt + 1))"
                done
                wait
                if [ -s "${report_raw}" ]; then
-                       "${adb_sortcmd}" ${adb_srtopts} -k3,3 -k4,4 -k1,1 -k2,2 -u -r "${report_raw}" |
-                               "${adb_awkcmd}" '{currA=($1+0);currB=$1;currC=$2;if(reqA==currB){reqA=0;printf "%-90s\t%s\n",d,$2}else if(currC=="RQ"){reqA=currA;d=$3"\t"$4"\t"$5"\t"$6}}' |
-                               "${adb_grepcmd}" -v "RQ" | "${adb_sortcmd}" ${adb_srtopts} -u -r >"${report_srt}"
-                       : >"${report_raw}"
+                       "${adb_sortcmd}" ${adb_srtopts} -ru "${report_raw}" > "${report_srt}"
+                       rm -f "${report_raw}"
                fi
 
+               # build json
+               #
                if [ -s "${report_srt}" ]; then
                        start="$("${adb_awkcmd}" 'END{printf "%s_%s",$1,$2}' "${report_srt}")"
                        end="$("${adb_awkcmd}" 'NR==1{printf "%s_%s",$1,$2}' "${report_srt}")"
                        total="$(f_count tld "${report_srt}" "var")"
-                       blocked="$("${adb_awkcmd}" '{if($5=="NX")cnt++}END{printf "%s",cnt}' "${report_srt}")"
-                       percent="$("${adb_awkcmd}" -v t="${total}" -v b="${blocked}" 'BEGIN{printf "%.2f%s",b/t*100,"%"}')"
-                       : >"${report_jsn}"
+                       blocked="$("${adb_awkcmd}" '{if($6=="NX")cnt++}END{printf "%s",cnt}' "${report_srt}")"
+                       percent="$("${adb_awkcmd}" -v t="${total}" -v b="${blocked}" 'BEGIN{ if(t>0) printf "%.2f%s",b/t*100,"%"; else printf "0.00%%"}')"
                        {
                                printf "%s\n" "{ "
                                printf "\t%s\n" "\"start_date\": \"${start%_*}\", "
@@ -1578,33 +1622,137 @@ f_report() {
                                printf "\t%s\n" "\"total\": \"${total}\", "
                                printf "\t%s\n" "\"blocked\": \"${blocked}\", "
                                printf "\t%s\n" "\"percent\": \"${percent}\", "
-                       } >>"${report_jsn}"
+                       } >"${report_jsn}"
+
+                       # build top list counters
+                       #
+                       "${adb_awkcmd}" '
+                               {
+                                       client = $3
+                                       qtype = $4
+                                       domain = $5
+                                       rc = $6
+                                       # normalize domain
+                                       gsub(/[\.]+$/, "", domain)
+                                       domain = tolower(domain)
+                                       # total client counter
+                                       clients[client]++
+                                       # remember OK per domain
+                                       if (rc == "OK") {
+                                               ok_domain[domain] = 1
+                                               ok_rr[domain SUBSEP qtype] = 1
+                                       }
+                                       # remember NX per domain
+                                       if (rc == "NX") {
+                                               nx_domain[domain]++
+                                               nx_rr[domain SUBSEP qtype]++
+                                       }
+                                       # total queries per domain
+                                       all_domain[domain]++
+                               }
+                               END {
+                                       # top clients
+                                       for (c in clients)
+                                               printf "%d %s\n", clients[c], c > "'"${top_tmpclients}"'"
+                                       # domains & blocked domains
+                                       for (d in all_domain) {
+                                       if (ok_domain[d]) {
+                                               printf "%d %s\n", all_domain[d], d > "'"${top_tmpdomains}"'"
+                                               continue
+                                       }
+                                       if (nx_domain[d]) {
+                                               printf "%d %s\n", nx_domain[d], d > "'"${top_tmpblocked}"'"
+                                       }
+                               }
+                       }' "${report_srt}"
+
+                       # build json top lists
+                       #
                        top_list="top_clients top_domains top_blocked"
                        for top in ${top_list}; do
-                               printf "\t%s" "\"${top}\": [ " >>"${report_jsn}"
+                               printf "\t\"%s\": [ " "${top}" >>"${report_jsn}"
                                case "${top}" in
-                                       "top_clients")
-                                               "${adb_awkcmd}" '{print $3}' "${report_srt}" | "${adb_sortcmd}" ${adb_srtopts} | uniq -c |
-                                                       "${adb_sortcmd}" ${adb_srtopts} -nr |
-                                                       "${adb_awkcmd}" "{ORS=\" \";if(NR==1)printf \"\n\t\t{\n\t\t\t\\\"count\\\": \\\"%s\\\",\n\t\t\t\\\"address\\\": \\\"%s\\\"\n\t\t}\",\$1,\$2; else if(NR<=${top_count})printf \",\n\t\t{\n\t\t\t\\\"count\\\": \\\"%s\\\",\n\t\t\t\\\"address\\\": \\\"%s\\\"\n\t\t}\",\$1,\$2}" >>"${report_jsn}"
-                                               ;;
-                                       "top_domains")
-                                               "${adb_awkcmd}" '{if($5!="NX")print $4}' "${report_srt}" | "${adb_sortcmd}" ${adb_srtopts} | uniq -c |
-                                                       "${adb_sortcmd}" ${adb_srtopts} -nr |
-                                                       "${adb_awkcmd}" "{ORS=\" \";if(NR==1)printf \"\n\t\t{\n\t\t\t\\\"count\\\": \\\"%s\\\",\n\t\t\t\\\"address\\\": \\\"%s\\\"\n\t\t}\",\$1,\$2; else if(NR<=${top_count})printf \",\n\t\t{\n\t\t\t\\\"count\\\": \\\"%s\\\",\n\t\t\t\\\"address\\\": \\\"%s\\\"\n\t\t}\",\$1,\$2}" >>"${report_jsn}"
-                                               ;;
-                                       "top_blocked")
-                                               "${adb_awkcmd}" '{if($5=="NX")print $4}' "${report_srt}" |
-                                                       "${adb_sortcmd}" ${adb_srtopts} | uniq -c | "${adb_sortcmd}" ${adb_srtopts} -nr |
-                                                       "${adb_awkcmd}" "{ORS=\" \";if(NR==1)printf \"\n\t\t{\n\t\t\t\\\"count\\\": \\\"%s\\\",\n\t\t\t\\\"address\\\": \\\"%s\\\"\n\t\t}\",\$1,\$2; else if(NR<=${top_count})printf \",\n\t\t{\n\t\t\t\\\"count\\\": \\\"%s\\\",\n\t\t\t\\\"address\\\": \\\"%s\\\"\n\t\t}\",\$1,\$2}" >>"${report_jsn}"
-                                               ;;
+                                       top_clients)
+                                               "${adb_sortcmd}" ${adb_srtopts} -nr "${top_tmpclients}" |
+                                               "${adb_awkcmd}" -v top_count="${top_count}" '
+                                                       BEGIN { ORS=""; OFS="" }
+                                                       NR==1 {
+                                                               printf "\n\t\t{\n\t\t\t\"count\": \"%s\",\n\t\t\t\"address\": \"%s\"\n\t\t}", $1, $2
+                                                       }
+                                                       NR>1 && NR<=top_count {
+                                                               printf ",\n\t\t{\n\t\t\t\"count\": \"%s\",\n\t\t\t\"address\": \"%s\"\n\t\t}", $1, $2
+                                                       }
+                                               ' >>"${report_jsn}"
+                                       ;;
+                                       top_domains)
+                                               "${adb_sortcmd}" ${adb_srtopts} -nr "${top_tmpdomains}" |
+                                               "${adb_awkcmd}" -v top_count="${top_count}" '
+                                                       BEGIN { ORS=""; OFS="" }
+                                                       NR==1 {
+                                                               printf "\n\t\t{\n\t\t\t\"count\": \"%s\",\n\t\t\t\"address\": \"%s\"\n\t\t}", $1, $2
+                                                       }
+                                                       NR>1 && NR<=top_count {
+                                                               printf ",\n\t\t{\n\t\t\t\"count\": \"%s\",\n\t\t\t\"address\": \"%s\"\n\t\t}", $1, $2
+                                                       }
+                                               ' >>"${report_jsn}"
+                                       ;;
+                                       top_blocked)
+                                               "${adb_sortcmd}" ${adb_srtopts} -nr "${top_tmpblocked}" |
+                                               "${adb_awkcmd}" -v top_count="${top_count}" '
+                                                       BEGIN { ORS=""; OFS="" }
+                                                       NR==1 {
+                                                               printf "\n\t\t{\n\t\t\t\"count\": \"%s\",\n\t\t\t\"address\": \"%s\"\n\t\t}", $1, $2
+                                                       }
+                                                       NR>1 && NR<=top_count {
+                                                               printf ",\n\t\t{\n\t\t\t\"count\": \"%s\",\n\t\t\t\"address\": \"%s\"\n\t\t}", $1, $2
+                                                       }
+                                               ' >>"${report_jsn}"
+                                       ;;
                                esac
-                               printf "\n\t%s\n" "]," >>"${report_jsn}"
+                               printf "\n\t],\n" >>"${report_jsn}"
                        done
+                       rm -f "${top_tmpclients}" "${top_tmpdomains}" "${top_tmpblocked}"
+
+                       # build json request list
+                       #
                        search="${search//./\\.}"
                        search="${search//[+*~%\$&\"\' ]/}"
-                       "${adb_awkcmd}" "BEGIN{i=0;printf \"\t\\\"requests\\\": [\n\"}/(${search})/{i++;if(i==1)printf \"\n\t\t{\n\t\t\t\\\"date\\\": \\\"%s\\\",\n\t\t\t\\\"time\\\": \\\"%s\\\",\n\t\t\t\\\"client\\\": \\\"%s\\\",\n\t\t\t\\\"domain\\\": \\\"%s\\\",\n\t\t\t\\\"rc\\\": \\\"%s\\\"\n\t\t}\",\$1,\$2,\$3,\$4,\$5;else if(i<=${res_count})printf \",\n\t\t{\n\t\t\t\\\"date\\\": \\\"%s\\\",\n\t\t\t\\\"time\\\": \\\"%s\\\",\n\t\t\t\\\"client\\\": \\\"%s\\\",\n\t\t\t\\\"domain\\\": \\\"%s\\\",\n\t\t\t\\\"rc\\\": \\\"%s\\\"\n\t\t}\",\$1,\$2,\$3,\$4,\$5}END{printf \"\n\t]\n}\n\"}" "${adb_reportdir}/adb_report.srt" >>"${report_jsn}"
-                       : >"${report_srt}"
+                       "${adb_awkcmd}" "
+                               BEGIN {
+                                       i = 0
+                                       printf \"\t\\\"requests\\\": [\n\"
+                               }
+                               # Only process lines that match the search AND have exactly 6 fields
+                               (/(${search})/ && NF == 6) {
+                                       i++
+                                       if (i == 1) {
+                                               printf \"\n\t\t{\
+                                               \n\t\t\t\\\"date\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"time\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"client\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"type\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"domain\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"rc\\\": \\\"%s\\\"\
+                                               \n\t\t}\",
+                                               \$1, \$2, \$3, \$4, \$5, \$6
+                                       }
+                                       else if (i <= ${res_count}) {
+                                               printf \",\n\t\t{\
+                                               \n\t\t\t\\\"date\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"time\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"client\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"type\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"domain\\\": \\\"%s\\\",\
+                                               \n\t\t\t\\\"rc\\\": \\\"%s\\\"\
+                                               \n\t\t}\",
+                                               \$1, \$2, \$3, \$4, \$5, \$6
+                                       }
+                               }
+                               END {
+                                       printf \"\n\t]\n}\n\"
+                               }
+                       " "${adb_reportdir}/adb_report.srt" >> "${report_jsn}"
+                       rm -f "${report_srt}"
                fi
 
                # retrieve/prepare map data
@@ -1687,19 +1835,19 @@ f_report() {
                                done
                        elif json_get_type status "${top}" && [ "${top}" = "requests" ] && [ "${status}" = "array" ]; then
                                printf "%s\n%s\n%s\n" ":::" "::: Latest DNS Queries" ":::" >>"${report_txt}"
-                               printf "%-15s%-15s%-45s%-80s%s\n" "Date" "Time" "Client" "Domain" "Answer" >>"${report_txt}"
+                               printf "%-11s%-9s%-40s%-5s%-70s%s\n" "Date" "Time" "Client" "Type" "Domain" "Answer" >>"${report_txt}"
                                json_select "${top}"
                                index="1"
                                while json_get_type status "${index}" && [ "${status}" = "object" ]; do
                                        json_get_values item "${index}"
-                                       printf "%-15s%-15s%-45s%-80s%s\n" ${item} >>"${report_txt}"
+                                       printf "%-11s%-9s%-40s%-5s%-70s%s\n" ${item} >>"${report_txt}"
                                        index="$((index + 1))"
                                done
                        fi
                        json_select ".."
                done
                content="$("${adb_catcmd}" "${report_txt}" 2>/dev/null)"
-               : >"${report_txt}"
+               rm -f "${report_txt}"
        fi
 
        # report output
@@ -1719,7 +1867,7 @@ f_report() {
                        ;;
                "mail")
                        [ "${adb_mail}" = "1" ] && [ -x "${adb_mailservice}" ] && "${adb_mailservice}" "${content}" >/dev/null 2>&1
-                       : >"${report_txt}"
+                       rm -f "${report_txt}"
                        ;;
        esac
 }
git clone https://git.99rst.org/PROJECT