]> git.99rst.org Git - openwrt-packages.git/commitdiff
banip: release 1.8.12-1
authorDirk Brenken <redacted>
Fri, 21 Aug 2026 19:46:55 +0000 (21:46 +0200)
committerDirk Brenken <redacted>
Fri, 21 Aug 2026 19:47:17 +0000 (21:47 +0200)
* new option ban_monitorallowed: in allowlist-only mode
  the log monitor only honours the local allowlist, so attackers
  from an explicitly allowed segment can be banned (fixes openwrt/luci#8950)
* adds the allowlist.local.v4/.v6 Sets, built from /etc/banip/banip.allowlist only,
  referenced by no rule
* allowlist-only mode: skip only those external feeds whose direction is
  fully covered by the allowlist, instead of skipping all or none — split Sets included
* f_lookup keeps the monitor Set in sync with resolved domains
* f_restore: no more bogus per-proto restore attempts for the allowlist
* f_report: batched and deduplicated geo lookups, correct Set names in the report
* readme update

Signed-off-by: Dirk Brenken <redacted>
net/banip/Makefile
net/banip/files/README.md
net/banip/files/banip-functions.sh
net/banip/files/banip-service.sh

index ea7f46089384b66f26f21f19b0dec6102b3f3723..1cac2cebd27debeb6ba09171181b833012c20867 100644 (file)
@@ -5,8 +5,8 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=banip
-PKG_VERSION:=1.8.11
-PKG_RELEASE:=2
+PKG_VERSION:=1.8.12
+PKG_RELEASE:=1
 PKG_LICENSE:=GPL-3.0-or-later
 PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org>
 
index 0cbc43d802e9995a6298207bc13c5e1ab84cb94c..da217e937cce7e938ed6a97ea4f0aa1bee7449b2 100644 (file)
@@ -111,6 +111,7 @@ For a typical setup these few steps are enough to get banIP up and running — s
 * Automatically selects one of the following download utilities with ssl support: curl, uclient-fetch or full wget
 * Provides HTTP ETag support to download only resources that have been updated on the server side, to speed up banIP reloads and to save bandwidth
 * Supports an `allowlist only` mode, this option restricts the internet access only to specific, explicitly allowed IP segments
+* Optionally screens the allowed IP segments in `allowlist only` mode as well, i.e. the log monitor may ban attackers from an explicitly allowed segment while the local allowlist entries stay exempt (see `ban_monitorallowed`)
 * Supports external allowlist URLs to reference additional IPv4/IPv6 feeds
 * Optionally always allow certain protocols/destination ports in the inbound chain
 * Deduplicate IPs across all Sets (single IPs only, no intervals)
@@ -205,6 +206,7 @@ The `report` sub-command accepts an output mode: `text` (default, human-readable
 | ban_autoblocksubnet     | option | 0                             | add entire subnets to the blocklist Sets based on a rate-limited, non-blocking RDAP lookup for the suspicious IP  |
 | ban_autoallowuplink     | option | subnet                        | limit the uplink autoallow function to: `subnet`, `ip` or `disable` it at all                                     |
 | ban_allowlistonly       | option | 0                             | restrict the internet access only to specific, explicitly allowed IP segments                                     |
+| ban_monitorallowed      | option | 0                             | restrict the log monitor to the entries of the local allowlist (allowlist-only mode)                              |
 | ban_allowflag           | option | -                             | always allow certain protocols(tcp or udp) plus destination ports or port ranges, e.g.: `tcp 80 443-444`          |
 | ban_allowurl            | list   | -                             | external allowlist feed URLs, one or more references to simple remote IP lists                                    |
 | ban_basedir             | option | /tmp                          | base working directory while banIP processing                                                                     |
@@ -458,6 +460,10 @@ Both local lists also accept domain names as input to allow IP filtering based o
 **Allowlist-only mode**  
 banIP supports an "allowlist only" mode. This option restricts Internet access only to certain, explicitly permitted IP segments - and blocks access to the rest of the Internet. All IPs that are _not_ listed in the allowlist or in the external allowlist URLs are blocked. In this mode it might be useful to limit the allowlist feed to the inbound chain, to still allow outbound communication to the rest of the world.
 
+In this mode the allowlist Sets define the permitted address scope, they do not act as an accept precedence - the blocklist Set is still evaluated and still applies. By default the log monitor nevertheless treats every allowlisted IP as exempt, therefore attackers coming from a permitted segment (e.g. a country-wide external allowlist feed) are never banned. Enable the `ban_monitorallowed` option to restrict the log monitor to the entries of the local allowlist. banIP then processes an additional local feed which provides the `allowlist.local.v4`/`allowlist.local.v6` Sets, built from /etc/banip/banip.allowlist only - including the automatically added uplink. These Sets are not referenced by any rule, they are solely used by the log monitor: local entries stay exempt, IPs which are merely covered by an external allowlist feed can be banned. This allows a "permit my own country, but still screen it" setup. The option has no effect unless `ban_allowlistonly` is enabled, and the additional Sets are removed automatically as soon as it is switched off.
+
+External feeds are skipped in this mode as far as they are redundant: the allowlist emits an inverted and therefore terminal drop rule in every chain it covers, so a feed rule in exactly these chains can never match. With the default allowlist direction all external feeds are skipped. If the allowlist is limited to a single chain - e.g. to the inbound chain - only the feeds of that chain are skipped, feeds which also cover the opposite chain are still processed.
+
 **MAC/IP-binding**  
 banIP supports concatenation of local MAC addresses/ranges with IPv4/IPv6 addresses, e.g. to enforce dhcp assignments or to free connected clients from outbound blocking.
 The following notations in the local allow- and block-list are supported:
@@ -531,6 +537,8 @@ To make this work, banIP uses the following external components:
 * [CARTO basemap styles](https://github.com/CartoDB/basemap-styles) based on [OpenMapTiles](https://openmaptiles.org/schema)
 * The free and quite fast [IP Geolocation API](https://ip-api.com/) to resolve the required IP/geolocation information
 
+Please note: the free ip-api.com batch endpoint is rate limited to 15 requests per minute per source IP. Requests beyond that limit are throttled with HTTP 429, and constantly exceeding the limit gets the IP banned for an hour — in both cases the map stays empty and banIP logs an info message. To stay below the limit banIP collects the top listed elements of all Sets, deduplicates them and resolves them in as few batch requests as possible (100 IPs each, the maximum the endpoint accepts). A setup with up to ~1500 mapped elements therefore needs no more than 15 requests per report run. If you run a large number of Sets and regenerate the report frequently, lower `ban_map` to `0` or reduce the number of active feeds.
+
 **CGI interface to receive remote logging events**  
 banIP ships a basic cgi interface in `/www/cgi-bin/banip` to receive remote logging events (disabled by default). The cgi interface evaluates logging events via GET or POST request (see examples below). To enable the cgi interface set the following options:
 
index a14fa1f371c7178a8c9cfe85b713038bbc19bd24..80d9a4e70efb35723156306533f8a8b1843616ec 100644 (file)
@@ -69,6 +69,7 @@ ban_logoutbound="0"
 ban_allowurl=""
 ban_allowflag=""
 ban_allowlistonly="0"
+ban_monitorallowed="0"
 ban_autoallowlist="1"
 ban_autoallowuplink="subnet"
 ban_autoblocklist="1"
@@ -801,6 +802,69 @@ f_getelements() {
        [ -s "${file}" ] && printf '%s' "elements={ $("${ban_catcmd}" "${file}" 2>>"${ban_errorlog}") };"
 }
 
+# resolve the chain direction of a feed,
+# an explicitly configured direction always wins over the feed default
+#
+f_direction() {
+       local feed_name="${1}" feed_chain="${2}"
+
+       if [ "${feed_chain}" = "none" ]; then
+               printf '%s' "none"
+               return 0
+       fi
+       case " ${ban_feedin} " in
+       *" ${feed_name} "*)
+               printf '%s' "inbound"
+               return 0
+               ;;
+       esac
+       case " ${ban_feedout} " in
+       *" ${feed_name} "*)
+               printf '%s' "outbound"
+               return 0
+               ;;
+       esac
+       case " ${ban_feedinout} " in
+       *" ${feed_name} "*)
+               printf '%s' "inbound outbound"
+               return 0
+               ;;
+       esac
+       case "${feed_chain}" in
+       "in")
+               printf '%s' "inbound"
+               ;;
+       "out")
+               printf '%s' "outbound"
+               ;;
+       "inout")
+               printf '%s' "inbound outbound"
+               ;;
+       *)
+               printf '%s' "inbound"
+               ;;
+       esac
+}
+
+# check whether an external feed is redundant in allowlist-only mode
+#
+f_skipfeed() {
+       local direction allow_direction feed_direction="${1}"
+
+       [ "${ban_allowlistonly}" != "1" ] && return 1
+       allow_direction="$(f_direction "allowlist" "inout")"
+       for direction in ${feed_direction}; do
+               case " ${allow_direction} " in
+               *" ${direction} "*) ;;
+
+               *)
+                       return 1
+                       ;;
+               esac
+       done
+       return 0
+}
+
 # handle etag http header
 #
 f_etag() {
@@ -1150,53 +1214,34 @@ f_down() {
        # set feed direction
        #
        feed_name="${feed%%.*}"
-       if case " ${ban_feedin} " in
-               *" ${feed_name} "*)
-                       true
-                       ;;
-               *)
-                       false
-                       ;;
-               esac; then
+       feed_direction="$(f_direction "${feed_name}" "${feed_chain}")"
+       case "${feed_direction}" in
+       "inbound")
                feed_policy="in"
-               feed_direction="inbound"
-       elif case " ${ban_feedout} " in
-               *" ${feed_name} "*)
-                       true
-                       ;;
-               *)
-                       false
-                       ;;
-               esac; then
+               ;;
+       "outbound")
                feed_policy="out"
-               feed_direction="outbound"
-       elif case " ${ban_feedinout} " in
-               *" ${feed_name} "*)
-                       true
-                       ;;
-               *)
-                       false
-                       ;;
-               esac; then
+               ;;
+       "inbound outbound")
                feed_policy="inout"
-               feed_direction="inbound outbound"
-       else
-               feed_policy="${feed_chain}"
-               case "${feed_chain}" in
-               "in")
-                       feed_direction="inbound"
-                       ;;
-               "out")
-                       feed_direction="outbound"
-                       ;;
-               "inout")
-                       feed_direction="inbound outbound"
-                       ;;
-               *)
-                       feed_direction="inbound"
-                       ;;
-               esac
-       fi
+               ;;
+       *)
+               feed_policy="${feed_direction}"
+               ;;
+       esac
+
+       # skip external feeds which are already covered by the allowlist in allowlist-only mode
+       #
+       case "${feed_name}" in
+       "allowlist" | "blocklist") ;;
+
+       *)
+               if f_skipfeed "${feed_direction}"; then
+                       f_log "info" "skip feed '${feed}' in allowlistonly mode"
+                       return 0
+               fi
+               ;;
+       esac
 
        # prepare feed flags
        #
@@ -1264,7 +1309,7 @@ f_down() {
 
        # restore local backups
        #
-       if [ "${feed%%.*}" != "blocklist" ]; then
+       if [ "${feed%%.*}" != "blocklist" ] && [ "${feed%.*}" != "allowlist.local" ]; then
                if [ -n "${ban_etagparm}" ] && [ "${ban_action}" = "reload" ] && [ "${feed_url}" != "local" ] && [ "${feed%%.*}" != "allowlist" ]; then
                        etag_rc="0"
                        case "${feed%%.*}" in
@@ -1307,20 +1352,31 @@ f_down() {
                        esac
                fi
                if [ "${etag_rc}" = "0" ] || [ "${ban_action}" != "reload" ] || [ "${feed_url}" = "local" ]; then
-                       if [ "${feed%%.*}" = "allowlist" ] && [ ! -f "${tmp_allow}" ]; then
-                               f_restore "allowlist" "-" "${tmp_allow}" "${etag_rc}"
-                               restore_rc="${?}"
+                       if [ "${feed%.*}" = "allowlist" ]; then
+
+                               if [ ! -f "${tmp_allow}" ]; then
+                                       f_restore "allowlist" "-" "${tmp_allow}" "${etag_rc}"
+                                       restore_rc="${?}"
+                                       feed_rc="${restore_rc}"
+                               fi
                        else
                                f_restore "${feed}" "${feed_url}" "${tmp_load}" "${etag_rc}"
                                restore_rc="${?}"
+                               feed_rc="${restore_rc}"
                        fi
-                       feed_rc="${restore_rc}"
                fi
        fi
 
+       # prepare the monitor-only allowlist, local entries only
+       #
+       if [ "${feed%.*}" = "allowlist.local" ] && [ ! -f "${tmp_allow}" ]; then
+               "${ban_catcmd}" "${ban_allowlist}" 2>>"${ban_errorlog}" >"${tmp_allow}"
+               feed_rc="${?}"
+       fi
+
        # prepare local/remote allowlist
        #
-       if [ "${feed%%.*}" = "allowlist" ] && [ ! -f "${tmp_allow}" ]; then
+       if [ "${feed%.*}" = "allowlist" ] && [ ! -f "${tmp_allow}" ]; then
                "${ban_catcmd}" "${ban_allowlist}" 2>>"${ban_errorlog}" >"${tmp_allow}"
                feed_rc="${?}"
                for feed_url in ${ban_allowurl}; do
@@ -1330,7 +1386,7 @@ f_down() {
                                        feed_rc="${?}"
                                fi
                        else
-                               f_log "info" "download for feed '${feed%%.*}' failed"
+                               f_log "info" "download for feed '${feed%.*}' failed"
                                feed_rc="4"
                                break
                        fi
@@ -1724,7 +1780,7 @@ f_restore() {
 # remove staled Sets
 #
 f_rmset() {
-       local feedlist tmp_del table_json feed country asn table_sets handles handle expr del_set feed_rc
+       local feedlist tmp_del table_json feed country asn table_sets handles handle expr del_set chain feed_chain feed_covered feed_rc
 
        f_getfeed
        json_get_keys feedlist
@@ -1734,10 +1790,30 @@ f_rmset() {
        {
                printf '%s\n\n' "#!${ban_nftcmd} -f"
                for feed in ${table_sets}; do
+                       if [ "${feed%.*}" = "allowlist.local" ]; then
+                               if [ "${ban_allowlistonly}" = "1" ] && [ "${ban_monitorallowed}" = "1" ]; then
+                                       continue
+                               fi
+                       fi
+                       feed_covered="0"
+                       if [ "${ban_allowlistonly}" = "1" ]; then
+                               case "${feed%%.*}" in
+                               "allowlist" | "blocklist") ;;
+
+                               *)
+                                       feed_chain=""
+                                       if json_select "${feed%%.*}" >/dev/null 2>&1; then
+                                               json_get_var feed_chain "chain" >/dev/null 2>&1
+                                               json_select ".." >/dev/null 2>&1
+                                       fi
+                                       f_skipfeed "$(f_direction "${feed%%.*}" "${feed_chain:-"in"}")" && feed_covered="1"
+                                       ;;
+                               esac
+                       fi
 
                        # keep: active country split sets
                        #
-                       if [ "${feed%%.*}" = "country" ] && [ "${ban_countrysplit}" = "1" ]; then
+                       if [ "${feed_covered}" = "0" ] && [ "${feed%%.*}" = "country" ] && [ "${ban_countrysplit}" = "1" ]; then
                                country="${feed%.*}"
                                country="${country#*.}"
                                case " ${ban_feed} " in
@@ -1751,7 +1827,7 @@ f_rmset() {
 
                        # keep: active asn split sets
                        #
-                       if [ "${feed%%.*}" = "asn" ] && [ "${ban_asnsplit}" = "1" ]; then
+                       if [ "${feed_covered}" = "0" ] && [ "${feed%%.*}" = "asn" ] && [ "${ban_asnsplit}" = "1" ]; then
                                asn="${feed%.*}"
                                asn="${asn#*.}"
                                case " ${ban_feed} " in
@@ -1771,12 +1847,9 @@ f_rmset() {
                                *" ${feed%.*} "*)
                                        if [ "${feed%.*}" != "country" ] || [ "${ban_countrysplit}" != "1" ]; then
                                                if [ "${feed%.*}" != "asn" ] || [ "${ban_asnsplit}" != "1" ]; then
-                                                       if [ "${feed%.*}" = "allowlist" ] || [ "${feed%.*}" = "blocklist" ] || [ "${ban_allowlistonly}" != "1" ]; then
+                                                       if [ "${feed_covered}" = "0" ]; then
                                                                continue
                                                        fi
-                                                       case " ${ban_feedin} ${ban_feedout} " in
-                                                       *" allowlist "*) continue ;;
-                                                       esac
                                                fi
                                        fi
                                        ;;
@@ -1954,7 +2027,7 @@ f_getstatus() {
 #
 f_lookup() {
        local cnt list domain lookup ip dom ts proto elementsv4 elementsv6 start_time end_time duration cnt_domain="0" cnt_ip="0" feed="${1}"
-       local record_file tmp_dir target_file auto_flag
+       local record_file tmp_dir target_file auto_flag set_list set_name
 
        # measure runtime of lookup function for performance insights
        #
@@ -2023,16 +2096,22 @@ f_lookup() {
 
        # add resolved IPs to nftables Sets
        #
-       if [ -n "${elementsv4}" ]; then
-               if ! "${ban_nftcmd}" add element inet banIP "${feed}.v4" { ${elementsv4} } 2>>"${ban_errorlog}"; then
-                       f_log "info" "can't add lookup file to nfset '${feed}.v4'"
-               fi
+       set_list="${feed}"
+       if [ "${feed}" = "allowlist" ] && [ "${ban_allowlistonly}" = "1" ] && [ "${ban_monitorallowed}" = "1" ]; then
+               set_list="${set_list} allowlist.local"
        fi
-       if [ -n "${elementsv6}" ]; then
-               if ! "${ban_nftcmd}" add element inet banIP "${feed}.v6" { ${elementsv6} } 2>>"${ban_errorlog}"; then
-                       f_log "info" "can't add lookup file to nfset '${feed}.v6'"
+       for set_name in ${set_list}; do
+               if [ -n "${elementsv4}" ]; then
+                       if ! "${ban_nftcmd}" add element inet banIP "${set_name}.v4" { ${elementsv4} } 2>>"${ban_errorlog}"; then
+                               f_log "info" "can't add lookup file to nfset '${set_name}.v4'"
+                       fi
                fi
-       fi
+               if [ -n "${elementsv6}" ]; then
+                       if ! "${ban_nftcmd}" add element inet banIP "${set_name}.v6" { ${elementsv6} } 2>>"${ban_errorlog}"; then
+                               f_log "info" "can't add lookup file to nfset '${set_name}.v6'"
+                       fi
+               fi
+       done
 
        # measure end time and log performance insights
        #
@@ -2047,8 +2126,8 @@ f_lookup() {
 #
 f_report() {
        local report_jsn report_txt tmp_val table_json item sep table_sets set_cnt set_inbound set_outbound set_cntinbound set_cntoutbound set_proto set_dport set_details
-       local cnt ip expr detail jsnval timestamp autoadd_allow autoadd_block sum_sets sum_setinbound sum_setoutbound sum_cntelements sum_cntinbound sum_cntoutbound quantity
-       local chunk jsn table_jsn set_jsn map_jsn chain set_elements uplink_ip uplink_list sum_setelements sum_synflood sum_udpflood sum_icmpflood sum_ctinvalid sum_tcpinvalid sum_setports sum_bcp38 output="${1}"
+       local cnt ip expr detail jsnval timestamp autoadd_allow autoadd_block sum_sets sum_setinbound sum_setoutbound sum_cntelements sum_cntinbound sum_cntoutbound
+       local jsn table_jsn set_jsn map_jsn map_lookup chunk_no chain set_elements uplink_ip sum_setelements sum_synflood sum_udpflood sum_icmpflood sum_ctinvalid sum_tcpinvalid sum_setports sum_bcp38 output="${1}"
 
        f_conf
        f_mkdir "${ban_reportdir}"
@@ -2254,81 +2333,76 @@ f_report() {
                # retrieve/prepare map data
                #
                if [ "${ban_nftcount}" = "1" ] && [ "${ban_map}" = "1" ] && [ -s "${report_jsn}" ]; then
-                       cnt="1"
                        f_getdl
-                       printf '%s' ",[{}" >>"${map_jsn}"
+                       map_lookup="${ban_rundir}/report.map.lookup"
+                       : >"${map_lookup}"
+
+                       # collect the uplink IPs
+                       #
                        json_init
                        if json_load_file "${ban_rtfile}" >/dev/null 2>&1; then
                                json_get_values jsnval "active_uplink" >/dev/null 2>&1
                                for uplink_ip in ${jsnval}; do
                                        uplink_ip="${uplink_ip%%/*}"
                                        if [ -n "${uplink_ip}" ] && [ "${uplink_ip}" != "-" ]; then
-                                               uplink_list="${uplink_list}${uplink_list:+, }\"${uplink_ip}\""
+                                               printf '%s\t%s\n' "${uplink_ip}" "homeIP" >>"${map_lookup}"
                                        fi
                                done
                        fi
-                       if [ -n "${uplink_list}" ]; then
-                               "${ban_fetchcmd}" ${ban_geoparm} "[ ${uplink_list} ]" "${ban_geourl}" 2>>"${ban_errorlog}" |
-                                       "${ban_jsoncmd}" -qe '@[*&&@.status="success"]' |
-                                       "${ban_awkcmd}" -v feed="homeIP" '{printf ",{\"%s\": %s}\n",feed,$0}' >"${map_jsn}.home"
-                               if [ -s "${map_jsn}.home" ]; then
-                                       "${ban_catcmd}" "${map_jsn}.home" >>"${map_jsn}"
-                               fi
-                               "${ban_rmcmd}" -f "${map_jsn}.home"
+
+                       # collect the top listed IPs of all relevant Sets
+                       #
+                       json_init
+                       if json_load_file "${report_jsn}" >/dev/null 2>&1; then
+                               json_select "sets" >/dev/null 2>&1
+                               json_get_keys table_sets >/dev/null 2>&1
+                               for item in ${table_sets}; do
+                                       [ "${item%%_*}" = "allowlist" ] && continue
+                                       json_select "${item}"
+                                       jsnval=""
+                                       json_get_values jsnval "set_elements" >/dev/null 2>&1
+                                       for ip in ${jsnval}; do
+                                               printf '%s\t%s\n' "${ip}" "${item//_/.}" >>"${map_lookup}"
+                                       done
+                                       json_select ".."
+                               done
                        fi
-                       if [ -s "${map_jsn}" ]; then
-                               json_init
-                               if json_load_file "${report_jsn}" >/dev/null 2>&1; then
-                                       json_select "sets" >/dev/null 2>&1
-                                       json_get_keys table_sets >/dev/null 2>&1
-                                       if [ -n "${table_sets}" ]; then
-                                               for item in ${table_sets}; do
-                                                       [ "${item%%_*}" = "allowlist" ] && continue
-                                                       json_select "${item}"
-                                                       json_get_keys set_details
-                                                       for detail in ${set_details}; do
-                                                               if [ "${detail}" = "set_elements" ]; then
-                                                                       json_get_values jsnval "${detail}" >/dev/null 2>&1
-                                                                       jsnval="\"${jsnval// /\", \"}\""
-                                                               fi
-                                                       done
-                                                       if [ "${jsnval}" != '""' ]; then
-                                                               (
-                                                                       quantity="0"
-                                                                       chunk=""
-                                                                       for ip in ${jsnval}; do
-                                                                               chunk="${chunk} ${ip}"
-                                                                               quantity="$((quantity + 1))"
-                                                                               if [ "${quantity}" -eq "100" ]; then
-                                                                                       "${ban_fetchcmd}" ${ban_geoparm} "[ ${chunk} ]" "${ban_geourl}" 2>>"${ban_errorlog}" |
-                                                                                               "${ban_jsoncmd}" -qe '@[*&&@.status="success"]' | "${ban_awkcmd}" -v feed="${item//_v/.v}" '{printf ",{\"%s\": %s}\n",feed,$0}' >"${map_jsn}.${item}"
-                                                                                       chunk=""
-                                                                                       quantity="0"
-                                                                               fi
-                                                                       done
-                                                                       if [ "${quantity}" -gt "0" ]; then
-                                                                               "${ban_fetchcmd}" ${ban_geoparm} "[ ${chunk} ]" "${ban_geourl}" 2>>"${ban_errorlog}" |
-                                                                                       "${ban_jsoncmd}" -qe '@[*&&@.status="success"]' | "${ban_awkcmd}" -v feed="${item//_v/.v}" '{printf ",{\"%s\": %s}\n",feed,$0}' >>"${map_jsn}.${item}"
-                                                                       fi
-                                                               ) &
-                                                               [ "${cnt}" -gt "${ban_cores}" ] && wait -n
-                                                               cnt="$((cnt + 1))"
-                                                       fi
-                                                       json_select ".."
-                                               done
-                                               wait
 
-                                               # assemble map data from per-set fragments
-                                               #
-                                               for item in ${table_sets}; do
-                                                       if [ -s "${map_jsn}.${item}" ]; then
-                                                               "${ban_catcmd}" "${map_jsn}.${item}" >>"${map_jsn}"
-                                                       fi
-                                                       "${ban_rmcmd}" -f "${map_jsn}.${item}"
-                                               done
+                       # split the deduplicated IPs into batch requests of 100 IPs each,
+                       # the maximum the geo service accepts
+                       #
+                       if [ -s "${map_lookup}" ]; then
+                               "${ban_awkcmd}" -F '\t' -v file="${map_jsn}" -v size="100" \
+                                       '!seen[$1]++{no=int(cnt++/size)+1;printf "%s\"%s\"",(chunk[no]++?", ":""),$1 >(file ".req." no)}END{for(i=1;i<=no;i++)close(file ".req." i);printf "%s\n",no+0 >(file ".num")}' "${map_lookup}"
+                               chunk_no="$("${ban_catcmd}" "${map_jsn}.num" 2>>"${ban_errorlog}")"
+                               cnt="1"
+                               while [ "${cnt}" -le "${chunk_no:-0}" ]; do
+                                       (
+                                               "${ban_fetchcmd}" ${ban_geoparm} "[ $("${ban_catcmd}" "${map_jsn}.req.${cnt}") ]" "${ban_geourl}" 2>>"${ban_errorlog}" |
+                                                       "${ban_jsoncmd}" -qe '@[*&&@.status="success"]' |
+                                                       "${ban_awkcmd}" -F '\t' 'NR==FNR{if(!($1 in feed))feed[$1]=$2;next}
+                                                               match($0,/"query"[ \t]*:[ \t]*"[^"]+"/){query=substr($0,RSTART,RLENGTH);sub(/^"query"[ \t]*:[ \t]*"/,"",query);sub(/"$/,"",query);if(query in feed)printf ",{\"%s\": %s}\n",feed[query],$0}' \
+                                                               "${map_lookup}" - >"${map_jsn}.rsp.${cnt}"
+                                       ) &
+                                       [ "${cnt}" -gt "${ban_cores}" ] && wait -n
+                                       cnt="$((cnt + 1))"
+                               done
+                               wait
+
+                               # assemble map data from the batch fragments
+                               #
+                               cnt="1"
+                               while [ "${cnt}" -le "${chunk_no:-0}" ]; do
+                                       if [ -s "${map_jsn}.rsp.${cnt}" ]; then
+                                               [ -s "${map_jsn}" ] || printf '%s' ",[{}" >>"${map_jsn}"
+                                               "${ban_catcmd}" "${map_jsn}.rsp.${cnt}" >>"${map_jsn}"
                                        fi
-                               fi
+                                       cnt="$((cnt + 1))"
+                               done
+                               [ -s "${map_jsn}" ] || f_log "info" "no geo data received, the rate limit of '${ban_geourl}' has probably been exceeded"
+                               f_log "debug" "f_report  ::: geo requests: ${chunk_no:-0}, map data: $([ -s "${map_jsn}" ] && printf '%s' "yes" || printf '%s' "no")"
                        fi
+                       "${ban_rmcmd}" -f "${map_lookup}" "${map_jsn}".req.* "${map_jsn}".rsp.* "${map_jsn}.num"
                fi
 
                # text output preparation
@@ -2373,7 +2447,7 @@ f_report() {
                                                printf '%-25s%-15s%-24s%-24s%-24s%-24s\n' "    Set" "| Count   " "| Inbound (packets)" "| Outbound (packets)" "| Port/Protocol      " "| Elements (max. 50) "
                                                printf '%s\n' "    ---------------------+--------------+-----------------------+-----------------------+-----------------------+------------------------"
                                                for item in ${table_sets}; do
-                                                       printf '    %-21s' "${item//_v/.v}"
+                                                       printf '    %-21s' "${item//_/.}"
                                                        json_select "${item}"
                                                        json_get_keys set_details
                                                        for detail in ${set_details}; do
@@ -2421,11 +2495,11 @@ f_report() {
                [ -s "${report_txt}" ] && "${ban_catcmd}" "${report_txt}"
                ;;
        "json")
-               if [ "${ban_nftcount}" = "1" ] && [ "${ban_map}" = "1" ]; then
-                       jsn="$("${ban_catcmd}" ${report_jsn} ${map_jsn} 2>>"${ban_errorlog}")"
+               if [ "${ban_nftcount}" = "1" ] && [ "${ban_map}" = "1" ] && [ -s "${map_jsn}" ]; then
+                       jsn="$("${ban_catcmd}" "${report_jsn}" "${map_jsn}" 2>>"${ban_errorlog}")"
                        [ -n "${jsn}" ] && printf '[%s]]\n' "${jsn}"
                else
-                       jsn="$("${ban_catcmd}" ${report_jsn} 2>>"${ban_errorlog}")"
+                       jsn="$("${ban_catcmd}" "${report_jsn}" 2>>"${ban_errorlog}")"
                        [ -n "${jsn}" ] && printf '[%s]\n' "${jsn}"
                fi
                ;;
@@ -2616,7 +2690,7 @@ f_mail() {
 f_monitor() {
        local nft_expiry ip proto idx base cidr rdap_log rdap_rc rdap_idx rdap_info log_type allow_v4 allow_v6 block_v4 block_v6
        local file cache_ts date_stamp time_now time_elapsed cache_interval rdap_interval rdap_tsfile rdap_lock rdap_jobs
-       local rdap_ts block_cache block_cache_limit block_cache_cnt
+       local rdap_ts block_cache block_cache_limit block_cache_cnt monitor_set
 
        # intervals for periodic cache refresh and RDAP queries
        #
@@ -2625,6 +2699,14 @@ f_monitor() {
        rdap_tsfile="${ban_rundir}/banIP_rdap_ts"
        printf '%s' "0" >"${rdap_tsfile}"
 
+       # determine the allowlist Set used by the monitor
+       #
+       if [ "${ban_allowlistonly}" = "1" ] && [ "${ban_monitorallowed}" = "1" ]; then
+               monitor_set="allowlist.local"
+       else
+               monitor_set="allowlist"
+       fi
+
        # determine log reader type
        #
        if [ -f "${ban_logreadfile}" ] && [ -x "${ban_logreadcmd}" ] && [ "${ban_logreadcmd##*/}" = "tail" ]; then
@@ -2670,8 +2752,8 @@ f_monitor() {
 
                # retrieve/cache current allowlist/blocklist content
                #
-               allow_v4="$(nft_cache allowlist.v4)"
-               allow_v6="$(nft_cache allowlist.v6)"
+               allow_v4="$(nft_cache "${monitor_set}.v4")"
+               allow_v6="$(nft_cache "${monitor_set}.v6")"
                block_v4="$(nft_cache blocklist.v4)"
                block_v6="$(nft_cache blocklist.v6)"
 
@@ -2813,7 +2895,7 @@ f_monitor() {
 
                                # CIDR-aware allowlist lookup (only at block-time, not every IP)
                                #
-                               if "${ban_nftcmd}" get element inet banIP "allowlist${proto}" { ${ip} } >/dev/null 2>&1; then
+                               if "${ban_nftcmd}" get element inet banIP "${monitor_set}${proto}" { ${ip} } >/dev/null 2>&1; then
                                        block_cache_cnt="$((block_cache_cnt + 1))"
                                        if [ "${block_cache_cnt}" -ge "${block_cache_limit}" ]; then
                                                block_cache=""
@@ -2821,7 +2903,7 @@ f_monitor() {
                                                f_log "debug" "f_monitor ::: refreshed local monitor cache at ${date_stamp}"
                                        fi
                                        block_cache="${block_cache} ${ip} "
-                                       f_log "debug" "f_monitor ::: skip IP '${ip}', found via allowlist CIDR lookup"
+                                       f_log "debug" "f_monitor ::: skip IP '${ip}', found via ${monitor_set}${proto} CIDR lookup"
                                        continue
                                fi
 
index 2cab11c56f5fb0072ddab8f5a92ff9f46141c9fb..c53d2a45ca3f2cf18be28fd86bf508dc8232f0de 100755 (executable)
@@ -46,39 +46,31 @@ f_log "info" "start banIP download processes"
 f_getfeed
 [ "${ban_deduplicate}" = "1" ] && printf '\n' >"${ban_tmpfile}.deduplicate"
 
+# handle allowlistonly mode
+#
+if [ "${ban_allowlistonly}" = "1" ] && [ "${ban_monitorallowed}" = "1" ]; then
+       ban_monitorfeed="allowlist.local"
+fi
+
 # handle downloads
 #
 cnt="1"
-for feed in allowlist ${ban_feed} blocklist; do
+for feed in allowlist ${ban_monitorfeed} ${ban_feed} blocklist; do
 
        # local feeds (sequential processing)
        #
-       if [ "${feed}" = "allowlist" ] || [ "${feed}" = "blocklist" ]; then
+       if [ "${feed%%.*}" = "allowlist" ] || [ "${feed}" = "blocklist" ]; then
                for proto in 4MAC 6MAC 4 6; do
-                       f_down "${feed}" "${proto}" "-" "-" "inout"
+                       chain="inout"
+                       if [ "${feed}" = "allowlist.local" ]; then
+                               case "${proto}" in *MAC) continue ;; esac
+                               chain="none"
+                       fi
+                       f_down "${feed}" "${proto}" "-" "-" "${chain}"
                done
                continue
        fi
 
-       # skip external feeds in allowlistonly mode
-       #
-       if [ "${ban_allowlistonly}" = "1" ]; then
-               case " ${ban_feedin} " in
-               *" allowlist "*) ;;
-
-               *)
-                       case " ${ban_feedout} " in
-                       *" allowlist "*) ;;
-
-                       *)
-                               f_log "info" "skip feed '${feed}' in allowlistonly mode"
-                               continue
-                               ;;
-                       esac
-                       ;;
-               esac
-       fi
-
        # external feeds (parallel processing on multicore hardware)
        #
        if ! json_select "${feed}" >/dev/null 2>&1; then
git clone https://git.99rst.org/PROJECT