From: Dirk Brenken Date: Sat, 19 Sep 2026 17:50:03 +0000 (+0200) Subject: banip: release 1.9.0 X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=72e4f90d756b0cf9da01e7b955ad48279df0578c;p=openwrt-packages.git banip: release 1.9.0 - add the trigger interfaces to the runtime status - support url templates for the country and asn feeds; the readme contains an example of how to use an alternative MaxMind source as country feed - fix the fallback handling of the custom feed file - disable the curl url globbing - drop the unsupported 'w' nft expiry unit - LuCI: show the trigger interfaces, label the board model - LuCI: drop the unsupported 'w' expiry unit - LuCI: fix the custom feed file handling - readme update Signed-off-by: Dirk Brenken --- diff --git a/net/banip/Makefile b/net/banip/Makefile index 68ca3410c..9a9003c7f 100644 --- a/net/banip/Makefile +++ b/net/banip/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=banip -PKG_VERSION:=1.8.13 -PKG_RELEASE:=2 +PKG_VERSION:=1.9.0 +PKG_RELEASE:=1 PKG_LICENSE:=GPL-3.0-or-later PKG_MAINTAINER:=Dirk Brenken diff --git a/net/banip/README.md b/net/banip/README.md index 537f2e06b..39cc02b98 100644 --- a/net/banip/README.md +++ b/net/banip/README.md @@ -228,7 +228,7 @@ The `report` sub-command accepts an output mode: `text` (default, human-readable | ban_nftloglevel | option | warn | nft loglevel, values: emerg, alert, crit, err, warn, notice, info, debug | | ban_nftpriority | option | -100 | nft priority for the banIP table (the prerouting table is fixed to priority -150) | | ban_nftpolicy | option | memory | nft policy for banIP-related Sets, values: memory, performance | -| ban_nftexpiry | option | - | expiry time (ms|s|m|h|d|w) for auto added blocklist members (also controls the monitor cache refresh interval) | +| ban_nftexpiry | option | - | expiry time (ms|s|m|h|d) for auto added blocklist members (also controls the monitor cache refresh interval) | | ban_nftretry | option | 3 | number of Set load attempts in case of an error | | ban_nftcount | option | 0 | enable nft counter for every Set element | | ban_bcp38 | option | 0 | block packets with spoofed source IP addresses in all supported chains | @@ -306,17 +306,17 @@ The `report` sub-command accepts an output mode: `text` (default, human-readable ~# /etc/init.d/banip status ::: banIP runtime information + status : active (nft: ✔, monitor: ✔) - + frontend_ver : 1.8.9-r1 - + backend_ver : 1.8.9-r1 - + element_count : 138 148 (chains: 7, sets: 13, rules: 50) - + active_feeds : allowlist.v4MAC, allowlist.v6MAC, allowlist.v4, allowlist.v6, dns.v4, blocklist.v4MAC, blocklist.v6MAC, doh.v6, blocklist.v4, doh.v4, blocklist.v6, dns.v6, hagezi.v4 - + active_devices : wan: pppoe-wan / wan-if: wan, wan_6 / vlan-allow: - / vlan-block: - - + active_uplink : 5.73.162.23, 2a13:4800:204:319e:b26d:238b:d7fe:8213 - + nft_info : ver: 1.1.6-r1, priority: -100, policy: performance, loglevel: warn, expiry: 2h, limit (icmp/syn/udp): 25/10/100 + + frontend_ver : 1.9.0-r1 + + backend_ver : 1.9.0-r1 + + element_count : 111 582 (chains: 7, sets: 21, rules: 50) + + active_feeds : allowlist.v4MAC, allowlist.v6MAC, allowlist.v4, allowlist.v6, cinsscore.v4, debl.v4, country.v6, debl.v6, doh.v4, doh.v6, spamhaus.v4, ipthreat.v4, country.v4, spamhaus.v6, threat.v4, turris.v4, turris.v6, blocklist.v4MAC, blocklist.v6MAC, blocklist.v4, blocklist.v6 + + active_devices : trigger: wan / wan: pppoe-wan / wan-if: wan, wan_6 / vlan-allow: - / vlan-block: - + + active_uplink : 46.167.33.169, 2a04:5700:304:ea66:e1fa:9a7f:ff78:ec41 + + nft_info : ver: 1.1.6-r4, priority: -100, policy: performance, loglevel: warn, expiry: 2h, limit (icmp/syn/udp): 25/10/100, loglimit (rate/burst): 10/5 + run_info : base: /mnt/data/banIP, backup: /mnt/data/banIP/backup, report: /mnt/data/banIP/report, error: /mnt/data/banIP/error - + run_flags : auto: ✔, proto (4/6): ✔/✔, bcp38: ✔, log (pre/in/out): ✘/✘/✔, count: ✔, dedup: ✔, split: ✘, custom feed: ✘, allowed only: ✘ - + last_run : mode: restart, 2026-01-12 06:16:19, duration: 0m 36s, memory: 1446.84 MB available - + system_info : cores: 4, log: logread, fetch: curl, Bananapi BPI-R3, mediatek/filogic, OpenWrt SNAPSHOT (r32542-bf46d119a2) + + run_flags : auto: ✔, proto (4/6): ✔/✔, bcp38: ✔, log (pre/in/out): ✘/✘/✘, count: ✔, dedup: ✔, split: ✘, custom feed: ✘, allowed only: ✘, debug: ✔ + + last_run : mode: restart, date / time: 2026-09-19 15:27:29, duration: 0m 28s, memory: 1122.99 MB available + + system_info : cores: 4, log: logread, fetch: curl, Bananapi BPI-R3, mediatek/filogic, OpenWrt SNAPSHOT (r36370-5369699684) ``` **banIP search information** @@ -454,6 +454,7 @@ You find the `Log Terms` option in LuCI under the `Log Settings` tab. Feel free 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. 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`). Both local lists also accept domain names as input to allow IP filtering based on these names. The corresponding IPs (IPv4 & IPv6) will be extracted and added to the Sets. @@ -569,7 +570,7 @@ Please note: for security reasons use this cgi interface only internally and onl By default banIP uses the following pre-configured download options: ``` - * curl: --connect-timeout 20 --retry-delay 10 --retry 4 --retry-max-time 80 --retry-all-errors --fail --silent --show-error --location -o + * curl: --connect-timeout 20 --retry-delay 10 --retry 4 --retry-max-time 80 --retry-all-errors --fail --silent --globoff --show-error --location -o * wget: --no-cache --no-cookies --timeout=20 --waitretry=10 --tries=5 --retry-connrefused -O * uclient-fetch: --timeout=20 -O ``` @@ -631,13 +632,13 @@ A feed is a single JSON object, keyed by a unique feed name (no spaces, no speci The object supports the following fields: | Field | Required | Description | -| :------ | :------: | :--------------------------------------------------------------------------------------------------------------------- | +| :------ | :------: | :----------------------------------------------------------------------------------------------------------------------- | | url_4 | yes\* | download URL of the IPv4 list. \*at least one of `url_4`/`url_6` must be present | -| url_6 | yes\* | download URL of the IPv6 list. May point to the **same** URL as `url_4` if the source mixes IPv4 and IPv6 in one file | -| rule | yes | the parsing ruleset, max. 4 space separated parameters (see below) | -| chain | yes | the default chain/direction: `in`, `out` or `inout` (see below) | -| descr | yes | a short human-readable description shown in LuCI and the feed table | -| flag | no | optional, space separated list of extra options: archive format and/or protocol/port limitations (see below) | +| url_6 | yes\* | download URL of the IPv6 list. May point to the **same** URL as `url_4` if the source mixes IPv4 and IPv6 in one file | +| rule | yes | the parsing ruleset, max. 4 space separated parameters (see below) | +| chain | yes | the default chain/direction: `in`, `out` or `inout` (see below) | +| descr | yes | a short human-readable description shown in LuCI and the feed table | +| flag | no | optional, space separated list of extra options: archive format and/or protocol/port limitations (see below) | **The `url_4` / `url_6` fields** Each address family is fetched and processed independently. Three cases: @@ -645,6 +646,22 @@ Each address family is fetched and processed independently. Three cases: * separate IPv4 and IPv6 files: set both to their respective URLs (e.g. `doh`, `spamhaus`) * a single dual-stack file that mixes v4 and v6 entries: point both `url_4` and `url_6` at that same URL. banIP fetches it only once and process it for each family and the per-family regex extracts the matching addresses; the non-matching lines are simply ignored. (e.g. `threatview`, which ships v4 and v6 in one file). +The `country` and `asn` feeds are special: banIP fetches one file per selected country code / ASN, so their URLs are templates. The placeholder is replaced by the configured value as-is, so country codes have to be lowercase, just like in banip.countries. The placeholder `{country}` is replaced by the lowercase ISO country code, `{asn}` by the bare AS number, e.g. `https://www.ipdeny.com/ipblocks/data/aggregated/{country}-aggregated.zone` or `https://asn.ipinfo.app/api/text/list/AS{asn}`. A URL without placeholder is treated as a base path and gets the legacy suffix appended (`-aggregated.zone` resp. `AS`), so older custom feed files keep working. The placeholder can sit anywhere in the URL, which allows to switch the country source to a differently structured one. Example for a GeoLite2-based alternative, which ships IPv4 and IPv6 in one file per country: + +```json + "country":{ + "url_4": "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/{country}.txt", + "url_6": "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/{country}.txt", + "rule": "feed 1", + "chain": "in", + "descr": "country blocks (GeoLite2)" + }, +``` + +Pointing `url_4` and `url_6` at the same file is the dual-stack case described above, so each country file is downloaded only once and processed for both address families. This source includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com - its use is subject to the MaxMind GeoLite End User License Agreement. + +Keep in mind that alternative sources come with their own license terms, which you have to check yourself, and that they differ in semantics (geolocation vs. RIR allocation), granularity (set sizes and memory footprint may grow considerably) and code coverage (a missing country file only logs a download failure for that code). + **The `rule` field** The rule consists of max. 4 individual, space separated parameters: 1. **type**: `feed` or `suricata` (required) diff --git a/net/banip/files/banip-functions.sh b/net/banip/files/banip-functions.sh index 6f139bd03..3327cd0f0 100644 --- a/net/banip/files/banip-functions.sh +++ b/net/banip/files/banip-functions.sh @@ -610,10 +610,10 @@ f_getdl() { case "${ban_fetchcmd##*/}" in "curl") [ "${ban_fetchinsecure}" = "1" ] && insecure="--insecure" - ban_fetchparm="${ban_fetchparm:-"${insecure} --connect-timeout 20 --retry-delay 10 --retry $((ban_fetchretry - 1)) --retry-max-time $(((ban_fetchretry - 1) * 20)) --retry-all-errors --fail --silent --show-error --location -o"}" - ban_rdapparm="--connect-timeout 5 --silent --location -o" - ban_etagparm="--connect-timeout 5 --silent --location --head" - ban_geoparm="--connect-timeout 5 --silent --location --data" + ban_fetchparm="${ban_fetchparm:-"${insecure} --connect-timeout 20 --retry-delay 10 --retry $((ban_fetchretry - 1)) --retry-max-time $(((ban_fetchretry - 1) * 20)) --retry-all-errors --fail --silent --globoff --show-error --location -o"}" + ban_rdapparm="--connect-timeout 5 --silent --globoff --location -o" + ban_etagparm="--connect-timeout 5 --silent --globoff --location --head" + ban_geoparm="--connect-timeout 5 --silent --globoff --location --data" ;; "wget") [ "${ban_fetchinsecure}" = "1" ] && insecure="--no-check-certificate" @@ -893,15 +893,20 @@ f_refresh() { # get feed information # f_getfeed() { + local feedlist quiet="${1}" + json_init if [ -s "${ban_customfeedfile}" ]; then if json_load_file "${ban_customfeedfile}" >/dev/null 2>&1; then - return - else - f_log "info" "can't load banIP custom feed file" + json_get_keys feedlist + if [ -n "${feedlist}" ]; then + [ -z "${quiet}" ] && f_log "info" "banIP custom feed file loaded successfully" + return + fi fi fi if [ -s "${ban_feedfile}" ] && json_load_file "${ban_feedfile}" >/dev/null 2>&1; then + [ -z "${quiet}" ] && f_log "info" "banIP default feed file loaded successfully" return else f_log "err" "can't load banIP feed file" @@ -979,6 +984,24 @@ f_skipfeed() { return 0 } +# build a country/asn feed url from a template +# +f_feedurl() { + local url="${1}" key="${2}" value="${3}" + + case "${url}" in + *"{${key}}"*) + printf '%s%s%s' "${url%%"{${key}}"*}" "${value}" "${url#*"{${key}}"}" + ;; + *) + case "${key}" in + "country") printf '%s%s-aggregated.zone' "${url}" "${value}" ;; + "asn") printf '%sAS%s' "${url}" "${value}" ;; + esac + ;; + esac +} + # handle etag http header # f_etag() { @@ -1431,13 +1454,13 @@ f_down() { if [ "${ban_countrysplit}" = "1" ]; then country="${feed%.*}" country="${country#*.}" - f_etag "${feed}" "${feed_url}${country}-aggregated.zone" ".${country}" + f_etag "${feed}" "$(f_feedurl "${feed_url}" "country" "${country}")" ".${country}" etag_rc="${?}" else etag_rc="0" etag_cnt="$(printf '%s' "${ban_country}" | "${ban_wccmd}" -w)" for country in ${ban_country}; do - if ! f_etag "${feed}" "${feed_url}${country}-aggregated.zone" ".${country}" "${etag_cnt}"; then + if ! f_etag "${feed}" "$(f_feedurl "${feed_url}" "country" "${country}")" ".${country}" "${etag_cnt}"; then etag_rc="$((etag_rc + 1))" fi done @@ -1447,13 +1470,13 @@ f_down() { if [ "${ban_asnsplit}" = "1" ]; then asn="${feed%.*}" asn="${asn#*.}" - f_etag "${feed}" "${feed_url}AS${asn}" ".${asn}" + f_etag "${feed}" "$(f_feedurl "${feed_url}" "asn" "${asn}")" ".${asn}" etag_rc="${?}" else etag_rc="0" etag_cnt="$(printf '%s' "${ban_asn}" | "${ban_wccmd}" -w)" for asn in ${ban_asn}; do - if ! f_etag "${feed}" "${feed_url}AS${asn}" ".${asn}" "${etag_cnt}"; then + if ! f_etag "${feed}" "$(f_feedurl "${feed_url}" "asn" "${asn}")" ".${asn}" "${etag_cnt}"; then etag_rc="$((etag_rc + 1))" fi done @@ -1654,7 +1677,7 @@ f_down() { if [ "${feed%%.*}" = "country" ]; then if [ "${ban_countrysplit}" = "0" ]; then for country in ${ban_country}; do - if "${ban_fetchcmd}" ${ban_fetchparm} "${tmp_raw}" "${feed_url}${country}-aggregated.zone" 2>>"${ban_errorlog}"; then + if "${ban_fetchcmd}" ${ban_fetchparm} "${tmp_raw}" "$(f_feedurl "${feed_url}" "country" "${country}")" 2>>"${ban_errorlog}"; then if [ -s "${tmp_raw}" ]; then "${ban_catcmd}" "${tmp_raw}" 2>>"${ban_errorlog}" >>"${tmp_load}" feed_rc="${?}" @@ -1667,7 +1690,7 @@ f_down() { else country="${feed%.*}" country="${country#*.}" - if "${ban_fetchcmd}" ${ban_fetchparm} "${tmp_load}" "${feed_url}${country}-aggregated.zone" 2>>"${ban_errorlog}"; then + if "${ban_fetchcmd}" ${ban_fetchparm} "${tmp_load}" "$(f_feedurl "${feed_url}" "country" "${country}")" 2>>"${ban_errorlog}"; then feed_rc="${?}" else feed_rc="4" @@ -1679,7 +1702,7 @@ f_down() { elif [ "${feed%%.*}" = "asn" ]; then if [ "${ban_asnsplit}" = "0" ]; then for asn in ${ban_asn}; do - if "${ban_fetchcmd}" ${ban_fetchparm} "${tmp_raw}" "${feed_url}AS${asn}" 2>>"${ban_errorlog}"; then + if "${ban_fetchcmd}" ${ban_fetchparm} "${tmp_raw}" "$(f_feedurl "${feed_url}" "asn" "${asn}")" 2>>"${ban_errorlog}"; then if [ -s "${tmp_raw}" ]; then "${ban_catcmd}" "${tmp_raw}" 2>>"${ban_errorlog}" >>"${tmp_load}" feed_rc="${?}" @@ -1692,7 +1715,7 @@ f_down() { else asn="${feed%.*}" asn="${asn#*.}" - if "${ban_fetchcmd}" ${ban_fetchparm} "${tmp_load}" "${feed_url}AS${asn}" 2>>"${ban_errorlog}"; then + if "${ban_fetchcmd}" ${ban_fetchparm} "${tmp_load}" "$(f_feedurl "${feed_url}" "asn" "${asn}")" 2>>"${ban_errorlog}"; then feed_rc="${?}" else feed_rc="4" @@ -1896,7 +1919,7 @@ f_restore() { f_rmset() { 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 + f_getfeed "quiet" json_get_keys feedlist tmp_del="${ban_tmpfile}.final.delete" table_json="$("${ban_nftcmd}" -tj list table inet banIP 2>>"${ban_errorlog}")" @@ -2066,6 +2089,11 @@ f_genstatus() { json_add_string "${object}" "${object}" done json_close_array + json_add_array "trigger_interfaces" + for object in ${ban_trigger:-"-"}; do + json_add_string "${object}" "${object}" + done + json_close_array json_add_array "wan_devices" for object in ${ban_dev:-"-"}; do json_add_string "${object}" "${object}" @@ -2113,8 +2141,10 @@ f_getstatus() { json_get_values values "${key}" >/dev/null 2>&1 value="${values// /, }" elif [ "${key}" = "wan_devices" ]; then + json_get_values values "trigger_interfaces" >/dev/null 2>&1 + value="trigger: ${values// /, } / " json_get_values values "${key}" >/dev/null 2>&1 - value="wan: ${values// /, } / " + value="${value}wan: ${values// /, } / " json_get_values values "wan_interfaces" >/dev/null 2>&1 value="${value}wan-if: ${values// /, } / " json_get_values values "vlan_allow" >/dev/null 2>&1 @@ -2128,7 +2158,7 @@ f_getstatus() { [ "${value}" = "active" ] && value="${value} ($(f_actual))" fi fi - if [ "${key}" != "wan_interfaces" ] && [ "${key}" != "vlan_allow" ] && [ "${key}" != "vlan_block" ]; then + if [ "${key}" != "trigger_interfaces" ] && [ "${key}" != "wan_interfaces" ] && [ "${key}" != "vlan_allow" ] && [ "${key}" != "vlan_block" ]; then printf ' + %-17s : %s\n' "${key}" "${value:-"-"}" fi done @@ -2855,18 +2885,17 @@ f_monitor() { # determine nft timeout expression and cache interval # - if printf '%s' "${ban_nftexpiry}" | grep -qE '^([1-9][0-9]*(ms|s|m|h|d|w))+$'; then + if printf '%s' "${ban_nftexpiry}" | grep -qE '^([1-9][0-9]*(ms|s|m|h|d))+$'; then nft_expiry="timeout ${ban_nftexpiry}" cache_interval="$(printf '%s' "${ban_nftexpiry}" | "${ban_awkcmd}" '{ s = 0 str = $0 - while (match(str, /([0-9]+)(ms|s|m|h|d|w)/, a)) { + while (match(str, /([0-9]+)(ms|s|m|h|d)/, a)) { if (a[2] == "ms") s += a[1] / 1000 else if (a[2] == "s") s += a[1] else if (a[2] == "m") s += a[1] * 60 else if (a[2] == "h") s += a[1] * 3600 else if (a[2] == "d") s += a[1] * 86400 - else if (a[2] == "w") s += a[1] * 604800 str = substr(str, RSTART + RLENGTH) } interval = int(s / 2) diff --git a/net/banip/files/banip-service.sh b/net/banip/files/banip-service.sh index df74e45de..1fc8d91c7 100755 --- a/net/banip/files/banip-service.sh +++ b/net/banip/files/banip-service.sh @@ -98,17 +98,31 @@ for feed in allowlist ${ban_monitorfeed} ${ban_feed} blocklist; do if [ "${ban_protov4}" = "1" ] && [ -n "${feed_url_4}" ] && [ -n "${feed_rule}" ]; then feed_ipv="4" if [ "${feed}" = "country" ] && [ "${ban_countrysplit}" = "1" ]; then - for country in ${ban_country}; do - (f_down "${feed}.${country}" "${feed_ipv}" "${feed_url_4}" "${feed_rule}" "${feed_chain:-"in"}" "${feed_flag}") & - [ "${cnt}" -ge "${ban_cores}" ] && wait -n - cnt="$((cnt + 1))" - done + if [ "${feed_url_4}" = "${feed_url_6}" ]; then + feed_url_6="local" + for country in ${ban_country}; do + f_down "${feed}.${country}" "${feed_ipv}" "${feed_url_4}" "${feed_rule}" "${feed_chain:-"in"}" "${feed_flag}" + done + else + for country in ${ban_country}; do + (f_down "${feed}.${country}" "${feed_ipv}" "${feed_url_4}" "${feed_rule}" "${feed_chain:-"in"}" "${feed_flag}") & + [ "${cnt}" -ge "${ban_cores}" ] && wait -n + cnt="$((cnt + 1))" + done + fi elif [ "${feed}" = "asn" ] && [ "${ban_asnsplit}" = "1" ]; then - for asn in ${ban_asn}; do - (f_down "${feed}.${asn}" "${feed_ipv}" "${feed_url_4}" "${feed_rule}" "${feed_chain:-"in"}" "${feed_flag}") & - [ "${cnt}" -ge "${ban_cores}" ] && wait -n - cnt="$((cnt + 1))" - done + if [ "${feed_url_4}" = "${feed_url_6}" ]; then + feed_url_6="local" + for asn in ${ban_asn}; do + f_down "${feed}.${asn}" "${feed_ipv}" "${feed_url_4}" "${feed_rule}" "${feed_chain:-"in"}" "${feed_flag}" + done + else + for asn in ${ban_asn}; do + (f_down "${feed}.${asn}" "${feed_ipv}" "${feed_url_4}" "${feed_rule}" "${feed_chain:-"in"}" "${feed_flag}") & + [ "${cnt}" -ge "${ban_cores}" ] && wait -n + cnt="$((cnt + 1))" + done + fi else if [ "${feed_url_4}" = "${feed_url_6}" ]; then feed_url_6="local" diff --git a/net/banip/files/banip.feeds b/net/banip/files/banip.feeds index eb324e7a4..717d46080 100644 --- a/net/banip/files/banip.feeds +++ b/net/banip/files/banip.feeds @@ -1,7 +1,7 @@ { "asn":{ - "url_4": "https://asn.ipinfo.app/api/text/list/", - "url_6": "https://asn.ipinfo.app/api/text/list/", + "url_4": "https://asn.ipinfo.app/api/text/list/AS{asn}", + "url_6": "https://asn.ipinfo.app/api/text/list/AS{asn}", "rule": "feed 1", "chain": "in", "descr": "ASN IP segments" @@ -45,8 +45,8 @@ "descr": "suspicious attacker IPs" }, "country":{ - "url_4": "https://www.ipdeny.com/ipblocks/data/aggregated/", - "url_6": "https://www.ipdeny.com/ipv6/ipaddresses/aggregated/", + "url_4": "https://www.ipdeny.com/ipblocks/data/aggregated/{country}-aggregated.zone", + "url_6": "https://www.ipdeny.com/ipv6/ipaddresses/aggregated/{country}-aggregated.zone", "rule": "feed 1", "chain": "in", "descr": "country blocks"