diff --git a/net/banip/Makefile b/net/banip/Makefile index 9a9003c7f1..dde9c03758 100644 --- a/net/banip/Makefile +++ b/net/banip/Makefile @@ -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 diff --git a/net/banip/README.md b/net/banip/README.md index 39cc02b986..9483fdcd95 100644 --- a/net/banip/README.md +++ b/net/banip/README.md @@ -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`). diff --git a/net/banip/files/banip-functions.sh b/net/banip/files/banip-functions.sh index 3327cd0f07..ddd306dbb7 100644 --- a/net/banip/files/banip-functions.sh +++ b/net/banip/files/banip-functions.sh @@ -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 diff --git a/net/banip/files/banip-service.sh b/net/banip/files/banip-service.sh index 1fc8d91c7d..e496ad6cba 100755 --- a/net/banip/files/banip-service.sh +++ b/net/banip/files/banip-service.sh @@ -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 diff --git a/net/banip/files/banip.init b/net/banip/files/banip.init index 3ae275a3fc..5764dbc36c 100755 --- a/net/banip/files/banip.init +++ b/net/banip/files/banip.init @@ -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")"