banip: update 1.9.0-2

* use RDAP startAddress/endAddress as a single interval element (one nft call per lookup)
* validate RDAP ranges, block nft command and log line injection
* always restart the procd instance (unique nonce via env)
* restart the log monitor if the log reader terminates (e.g. logd restart)
* f_rmpid clears the pidfile before killing child processes
* EXIT trap: route unexpected service exits through f_log "err"
* readme update

Signed-off-by: Dirk Brenken <dev@brenken.org>
(cherry picked from commit 4a69a0b767)
This commit is contained in:
Dirk Brenken committed 2026-09-25 23:12:40 +02:00
1 parent 4887ba99a7
commit 32dd807af8
5 files changed
+54 -30

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=banip
PKG_VERSION:=1.9.0
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_LICENSE:=GPL-3.0-or-later
PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org>
+1
View File
@@ -453,6 +453,7 @@ You find the `Log Terms` option in LuCI under the `Log Settings` tab. Feel free
**Allow-/Blocklist handling**
banIP supports local allow- and block-lists, MAC/IPv4/IPv6 addresses (incl. ranges in CIDR notation) or domain names. These files are located in /etc/banip/banip.allowlist and /etc/banip/banip.blocklist.
Unsuccessful login attempts or suspicious requests will be tracked and added to the local blocklist (see the `ban_autoblocklist` option). The blocklist behaviour can be further tweaked with the `ban_nftexpiry` option.
Elements added by the log monitor, including the RDAP ranges of an offending address, expire after `ban_nftexpiry`, but every new hit within an existing range resets its timeout. As the Sets use `auto-merge`, overlapping or adjacent ranges are also combined into a single larger element. A network under continuous attack can therefore stay in the blocklist far beyond the configured expiry and grow over time - this is intended behaviour, not a stale entry.
Depending on the options `ban_autoallowlist` and `ban_autoallowuplink` the uplink subnet or the uplink IP will be added automatically to local allowlist.
Entries in CIDR notation always refer to the whole network - set host bits are masked by nftables, e.g. `192.168.1.5/24` ends up as `192.168.1.0/24` in the Set and `2001:db8::e65f:1:2:3/64` as `2001:db8::/64`. To allow or block a single host omit the prefix length or use /32 resp. /128.
Furthermore, you can reference external Allowlist URLs with additional IPv4 and IPv6 feeds (see `ban_allowurl`).
+40 -27
View File
@@ -261,9 +261,8 @@ f_trim() {
f_rmpid() {
local ppid pid pids_next pids_all childs newchilds
# kill all descendant processes of the pid in pidfile
#
ppid="$("${ban_catcmd}" "${ban_pidfile}" 2>>"${ban_errorlog}")"
: >"${ban_pidfile}"
if [ -n "${ppid}" ]; then
pids_next="$("${ban_pgrepcmd}" -P "${ppid}" 2>>"${ban_errorlog}")"
pids_all=""
@@ -288,7 +287,6 @@ f_rmpid() {
kill -INT "${pid}" >/dev/null 2>&1
done
fi
: >"${ban_pidfile}"
}
# write log messages
@@ -2848,10 +2846,19 @@ f_mail() {
f_log "debug" "f_mail ::: notification: ${ban_mailnotification}, template: ${ban_mailtemplate}, profile: ${ban_mailprofile}, receiver: ${ban_mailreceiver}, rc: ${?}"
}
# handle unexpected service exits
#
f_exit() {
trap - EXIT
if [ "$("${ban_catcmd}" "${ban_pidfile}" 2>/dev/null)" = "${$}" ]; then
f_log "err" "banIP service terminated unexpectedly"
fi
}
# log monitor
#
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 nft_expiry ip proto rdap_log rdap_rc rdap_start rdap_end rdap_range rdap_regex 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 monitor_set
@@ -3131,34 +3138,40 @@ f_monitor() {
# process RDAP response if valid JSON with expected content, otherwise log error
#
if [ "${rdap_rc}" = "0" ] && [ -s "${ban_rdapfile}.${ip}" ]; then
[ "${proto}" = ".v4" ] && rdap_idx="$("${ban_jsoncmd}" -i "${ban_rdapfile}.${ip}" -qe '@.cidr0_cidrs[@.v4prefix].*' | "${ban_awkcmd}" '{ORS=" "; print}')"
[ "${proto}" = ".v6" ] && rdap_idx="$("${ban_jsoncmd}" -i "${ban_rdapfile}.${ip}" -qe '@.cidr0_cidrs[@.v6prefix].*' | "${ban_awkcmd}" '{ORS=" "; print}')"
rdap_start="$("${ban_jsoncmd}" -i "${ban_rdapfile}.${ip}" -qe '@.startAddress')"
rdap_end="$("${ban_jsoncmd}" -i "${ban_rdapfile}.${ip}" -qe '@.endAddress')"
rdap_info="$("${ban_jsoncmd}" -l1 -i "${ban_rdapfile}.${ip}" -qe '@.country' -qe '@.notices[@.title="Source"].description[1]' | "${ban_awkcmd}" 'BEGIN{RS="";FS="\n"}{c=($1!=""?$1:"-"); s=($2!=""?$2:"-"); printf "%s, %s", c, s}')"
[ -z "${rdap_info}" ] || [ "${rdap_info}" = "-, -" ] && rdap_info="$("${ban_jsoncmd}" -l1 -i "${ban_rdapfile}.${ip}" -qe '@.notices[0].links[0].value' | "${ban_awkcmd}" 'BEGIN{FS="[/.]"}{printf"%s, %s","n/a",toupper($4)}')"
# if RDAP response contains (multiple) valid CIDR info,
# attempt to add entire range to blocklist set with same expiry as individual IP
# if RDAP response contains a valid address range (RFC 9083 startAddress/endAddress),
# add the entire range as a single interval element to blocklist set with same expiry as individual IP
#
base=""
for idx in ${rdap_idx}; do
if [ -z "${base}" ]; then
base="${idx}"
continue
else
case "${base}" in
"" | "::"* | "127."* | "0."* | "fe80:"*)
base=""
continue
;;
esac
[ -z "${base}" ] && continue
cidr="${base}/${idx}"
if "${ban_nftcmd}" add element inet banIP "blocklist${proto}" { ${cidr} ${nft_expiry} } >/dev/null 2>&1; then
f_log "info" "add IP range '${cidr}' (source: ${rdap_info:-"n/a"} ::: expiry: ${ban_nftexpiry:-"-"}) to blocklist${proto} set"
fi
base=""
rdap_range=""
rdap_regex=""
case "${rdap_start}-${rdap_end}" in
"-"* | "::"* | "127."* | "0."* | "fe80:"* | *[!0-9A-Fa-f.:-]*) ;;
*)
if [ "${proto}" = ".v4" ]; then
rdap_regex='^[0-9]{1,3}(\.[0-9]{1,3}){3}-[0-9]{1,3}(\.[0-9]{1,3}){3}$'
elif [ "${proto}" = ".v6" ]; then
rdap_regex='^[0-9A-Fa-f:]*:[0-9A-Fa-f:]*-[0-9A-Fa-f:]*:[0-9A-Fa-f:]*$'
fi
done
if [ -n "${rdap_regex}" ]; then
printf '%s' "${rdap_start}-${rdap_end}" | "${ban_grepcmd}" -qE "${rdap_regex}" && rdap_range="${rdap_start}-${rdap_end}"
fi
;;
esac
if [ -n "${rdap_range}" ]; then
if "${ban_nftcmd}" add element inet banIP "blocklist${proto}" { ${rdap_range} ${nft_expiry} } >/dev/null 2>&1; then
f_log "info" "add IP range '${rdap_range}' (source: ${rdap_info:-"n/a"} ::: expiry: ${ban_nftexpiry:-"-"}) to blocklist${proto} set"
else
f_log "info" "failed to add IP range '${rdap_range}' to blocklist${proto} set with rc '${?}'"
fi
else
rdap_start="${rdap_start%%[!0-9A-Fa-f.:]*}"
rdap_end="${rdap_end%%[!0-9A-Fa-f.:]*}"
f_log "info" "no valid rdap range (start: ${rdap_start:-"-"}/end: ${rdap_end:-"-"}) for IP '${ip}'"
fi
else
f_log "info" "rdap request failed (rc: ${rdap_rc:-"-"}/log: ${rdap_log:-"-"}) for IP '${ip}'"
fi
+10 -2
View File
@@ -11,6 +11,7 @@ read -r ban_starttime _ <"/proc/uptime"
ban_starttime="${ban_starttime%%.*}"
ban_funlib="/usr/lib/banip-functions.sh"
[ -z "${ban_bver}" ] && . "${ban_funlib}"
trap 'f_exit' EXIT
# load config and set banIP environment
#
@@ -186,6 +187,13 @@ f_log "info" "finish banIP processing"
rm -rf "${ban_lock}"
) &
# start detached log service (infinite loop)
# start detached log service (infinite loop),
# restart the monitor if the log reader terminates (e.g. logd restart),
# stop if the pidfile has been cleared by f_rmpid
#
f_monitor
while :; do
f_monitor
sleep 5
[ "$("${ban_catcmd}" "${ban_pidfile}" 2>/dev/null)" = "${$}" ] || break
f_log "info" "log reader terminated, restart detached banIP log service"
done
+2
View File
@@ -66,8 +66,10 @@ start_service() {
fi
fi
f_rmpid
read -r ban_nonce _ <"/proc/uptime"
procd_open_instance "banip-service"
procd_set_param command "${ban_service}" "${action}"
procd_set_param env ban_nonce="${ban_nonce}"
procd_set_param pidfile "${ban_pidfile}"
procd_set_param nice "$(uci_get banip global ban_nicelimit "0")"
procd_set_param limits nofile="$(uci_get banip global ban_filelimit "1024")"