Skip to content

Blacklist and Threat Intelligence

Antonios Voulvoulis edited this page Jul 11, 2026 · 6 revisions

Blacklist & Threat Intelligence

Type: Module (composite) Domain: IP reputation and policy enforcement (manual, feeds, geographic policy) Since: v1.80.x Config: multiple (see per-source configuration below) Daemon dependency: Source-dependent — manual blacklist enforcement is kernel-self-contained; feed and geoban updates require the daemon Last verified: v1.218.7


Purpose

See also: Glossary | Health Model | Architecture | Known Limitations

Blacklist & Threat Intelligence is a composite module covering three distinct IP enforcement sources that converge on shared kernel sets:

Source Origin Enforcement path
Manual Operator nftban ban commands + auto-detect (LoginMon, portscan) Dedicated set + dedicated counter
Feeds External threat intelligence (e.g., TOR exit nodes) Shared set + shared counter
GeoBan Country/region blocking policy via GeoIP database Shared set + shared counter

Each source has its own lifecycle, freshness model, and configuration. They share kernel enforcement surfaces but have different evidence models.


Kernel Objects

Sets

Set Type Shared by Purpose
blacklist_ipv4 / blacklist_ipv6 interval, timeout, auto-merge Feeds + GeoBan CIDR range enforcement
blacklist_manual_ipv4 / blacklist_manual_ipv6 hash, timeout Manual bans + LoginMon + Portscan Per-IP ban enforcement

Critical distinction: Feeds and GeoBan share blacklist_ipv4/ipv6. Manual bans use a separate blacklist_manual_ipv4/ipv6. This means:

  • Manual-blacklist-family enforcement is independently provable (separate counter). Attribution to operator bans, LoginMon, or portscan is not.
  • Feed vs GeoBan enforcement is NOT independently provable (shared counter)

Evidence hierarchy for this composite module

Priority Source What it proves
1 (strongest) Manual blacklist set + manual counter Manual-family enforcement (shared across sources)
2 Feed/GeoBan shared set presence Data availability (ranges loaded)
3 Feed/GeoBan shared counter > 0 Family-level enforcement (non-attributable)
4 (weakest) Sync logs / GeoIP file freshness Pipeline provenance (data pipeline state)

Counters

Counter Shared by What it proves
input_blacklist_drop Feeds + GeoBan Blacklist-family enforcement (not attributable to feeds OR geoban)
input_blacklist_manual_drop Manual + LoginMon + Portscan Manual-blacklist-family enforcement (not attributable to specific source)

Set types explained

Set type Characteristic Used by
Interval (auto-merge) Stores CIDR ranges, kernel merges overlapping ranges blacklist_ipv4/ipv6 (feeds + geoban)
Hash (timeout) Stores individual IPs with per-entry expiry blacklist_manual_ipv4/ipv6 (manual bans)

Interval sets are efficient for large IP range lists (thousands of CIDRs). Hash sets are efficient for individual IP lookups with timeout-based expiry.

File-backed bans and unified replace (as of v1.203.0)

blacklist.d/*.conf entries (operator hand-edited bans and persisted permanent/persistent-offender bans) are routed by entry shape — symmetric for IPv4 and IPv6:

blacklist.d entry Routes to Set type
Single IP (e.g. 203.0.113.88) blacklist_manual_ipv4 / blacklist_manual_ipv6 hash
CIDR / range (e.g. 203.0.113.0/24) blacklist_ipv4 / blacklist_ipv6 interval

Why single IPs go to the hash set (fail-open fix). Before v1.203.0, blacklist.d single-IP entries were merged into the feed/geoban-owned interval set, which is then atomically flushed-and-replaced on every feed/geoban load — so a file-backed ban stopped being enforced once feeds loaded (BUG-BLACKLIST-FILE-ENTRY-FAIL-OPEN-ON-FEED-RELOAD). As of v1.203.0 single IPs are added to the manual hash sets, which the feed/geoban replace never touches, so a blacklist.d single-IP ban survives a feed reload.

Unified replace (no source clobber). As of v1.203.0 the interval sets blacklist_ipv4/_ipv6 are populated by a single atomic flush-and-replace whose input is the union of feed CIDRs + geoban CIDRs + blacklist.d CIDR/range entries. Because all interval sources go through one replace, they coexist and no source overwrites another (earlier, sequential per-source replaces could let a later source wipe an earlier one's ranges). Enforcement counters are unchanged — no new per-source counters were added (see Shared Counter Attribution Rules).

Removal: nftban unban <ip> remains the canonical removal path — it clears the entry from blacklist.d/*.conf and from the live kernel set(s). Deleting a single-IP line from blacklist.d by hand and re-syncing does not by itself remove a live hash-set entry; use nftban unban.


Source: Manual Blacklist

How it works

Operator issues nftban ban <ip> or an auto-detect module (LoginMon, portscan) adds an IP. The daemon writes the IP to blacklist_manual_ipv4/ipv6 with a timeout. The kernel drops matching traffic at pipeline Phase 2 (BAN).

Configuration

Manual blacklist infrastructure is always present in the base schema. There is no config switch that removes the manual blacklist sets from the firewall design.

State model

Set exists Elements > 0 Drops > 0 State
YES YES YES ENFORCING
YES YES NO PRIMED (entries loaded, awaiting matching traffic)
YES NO IDLE
NO DEGRADED (base schema violation)

Evidence

input_blacklist_manual_drop is the enforcement counter. It proves manual-blacklist-family enforcement but cannot attribute drops to a specific source (operator ban vs LoginMon vs portscan).


Source: Threat Feeds

How it works

Feed configuration files in conf.d/feeds/ define external IP lists (e.g., TOR exit nodes, known botnets). The daemon periodically syncs feed data, converts IP lists to CIDR ranges, and loads them into blacklist_ipv4/ipv6.

Sync is logged (as of v1.203.0, feed CIDRs are queued into the unified interval replace — see "File-backed bans and unified replace"): [SYNC] Feeds IPv4: 1270 input CIDRs queued for unified replace, followed by [SYNC] Unified blacklist_ipv4 replace: N input CIDRs (feeds+geoban+blacklist.d).

Configuration

Key File Meaning
Feed config files conf.d/feeds/*.conf Each .conf file defines one feed source

Feeds are configured by file presence. No master enable/disable switch exists — if feed config files exist, feeds are configured. If the conf.d/feeds/ directory is empty or absent, feeds are DISABLED.

State model

Feeds configured Sync status State
YES Sync success, data loaded LOADED
YES Sync failed DEGRADED
YES Data stale (feed file mtime > TTL) STALE
NO DISABLED

These are feed pipeline states, not direct enforcement states. LOADED means data is available in the shared blacklist set. Actual enforcement depends on matching traffic and is only provable at the shared blacklist-family level.

LOADED is NOT ENFORCING. Feed sync loading ranges into the kernel set is a data pipeline operation. Whether those ranges match any traffic is determined by the kernel. The shared input_blacklist_drop counter proves blacklist-family enforcement but cannot attribute it to feeds specifically.

Feed data freshness

Feed files are stored in /var/lib/nftban/feeds/. File mtime indicates last successful sync. Stale feed data (mtime exceeding the feed's configured TTL) is flagged as STALE — the data may still be correct but is not verified against the current upstream source.


Source: GeoBan (Geographic Policy)

How it works

GeoBan blocks traffic from entire countries based on a GeoIP database. The daemon reads the DB-IP Country Lite database (or MaxMind GeoLite2), resolves configured country codes to IP ranges, and loads them into blacklist_ipv4/ipv6 alongside feed data. As of v1.203.0 geoban ranges are contributed to the single unified interval replace (with feeds and blacklist.d CIDRs), so geoban and feed ranges coexist without overwriting each other.

Configuration

Key File Default Meaning
GEOBAN_ENABLED conf.d/geoban/main.conf "true" Master enable/disable. Enabled by default, but ships with GEOBAN_DEFAULT_POLICY="allow" (block only listed countries). With no countries configured, GeoBan is enabled-but-IDLE and blocks nothing; add countries to activate blocking.

Country selection is configured in the geoban config files. geoban: 0/0 in sync output means geoban is enabled but no countries are configured for blocking. GeoBan enabled with zero configured countries is valid IDLE for policy scope, not a failure.

State model

Config Database State
ENABLED Present + fresh LOADED
ENABLED Missing or empty STALE (finding VAL-GEOBAN-001 emitted)
ENABLED Stale (> 45 days) STALE
DISABLED DISABLED

GeoIP database

Property Value
Default database DB-IP Country Lite (dbip-country-lite.mmdb)
Location /var/lib/nftban/geoip/dbip-country-lite.mmdb
Update frequency Monthly (auto-sync via nftban-core-geoip.timer)
Freshness threshold 45 days (older = STALE)

The GeoIP database is a data dependency for GeoBan, not an enforcement object. Its presence proves data availability, not enforcement.


Shared Counter Attribution Rules

This is the most important evidence constraint for the blacklist module.

What the validator CAN claim

Claim Counter Valid?
"Blacklist-family enforcement is happening" input_blacklist_drop > 0 YES
"Manual-blacklist-family enforcement is happening" input_blacklist_manual_drop > 0 YES

What the validator MUST NOT claim

Claim Why invalid
"Feed enforcement is happening" input_blacklist_drop is shared with GeoBan — cannot attribute
"GeoBan enforcement is happening" Same shared counter — cannot attribute
"LoginMon enforcement is happening" (from counter alone) input_blacklist_manual_drop is shared with operator bans + portscan

Per-source attribution requires daemon-side provenance: ban log CLASS field (BLC-1/BLC-2 from v1.80.x) or journal source= metadata.


CLI Commands

nftban ban <ip> [duration]   # Add manual ban
nftban unban <ip>            # Remove manual ban
nftban blacklist list        # List manual blacklist entries
nftban feeds list            # List configured feeds
nftban feeds update          # Update feeds (all or a specific feed)
nftban geoban status         # Show GeoBan state
nftban geoban update         # Resolve countries and load geoban ranges
nftban geoip status          # Show GeoIP database state

Verification (MANDATORY)

# Check manual blacklist set (structural)
nft list set ip nftban blacklist_manual_ipv4
# Elements > 0 = PRIMED or ENFORCING (depends on counter)
# Elements = 0 = IDLE

# Check manual blacklist counter (enforcement)
nft list counter ip nftban input_blacklist_manual_drop
# Counter > 0 = manual-blacklist-family ENFORCING
# Counter = 0 = NEUTRAL

# Check feed/geoban shared set
nft list set ip nftban blacklist_ipv4
# Shows interval set with CIDR ranges from feeds + geoban
# Element count = combined feeds + geoban ranges (not separable)

# Check feed/geoban shared counter
nft list counter ip nftban input_blacklist_drop
# Counter > 0 = blacklist-family ENFORCING (not attributable)
# Counter = 0 = NEUTRAL

# Check feed sync status (as of v1.203.0)
journalctl -u nftband | grep -E "SYNC.*(Feeds|Geoban|Unified)" | tail -5
# Expected: "[SYNC] Feeds IPv4: N input CIDRs queued for unified replace"
#           "[SYNC] Unified blacklist_ipv4 replace: N input CIDRs (feeds+geoban+blacklist.d)"

# Check GeoIP database
ls -la /var/lib/nftban/geoip/dbip-country-lite.mmdb
# File exists + size > 0 + mtime < 45 days = fresh
# Missing or empty = STALE (VAL-GEOBAN-001)

# Check module states via validator
nftban validate --json | jq '.modules.blacklist'
# Shows blacklist-related state as exposed by the current validator schema
# Confirm manual/feeds/geoban sub-state fields against live output

Failure Modes

DEGRADED: Manual blacklist set missing

Symptom: Validator reports blacklist manual state as degraded. Cause: Base schema violation — the manual blacklist set should always exist. Fix: nftban firewall rebuild

DEGRADED: Feed sync failure

Symptom: Feeds state reported as degraded. Journal shows sync errors. Cause: Network issue, upstream feed unavailable, or daemon not running. Fix: nftban feeds update (manual retry). Check network connectivity.

STALE: GeoIP database missing or outdated

Symptom: Finding VAL-GEOBAN-001. GeoBan state = STALE. Cause: Database file missing, empty, or older than 45 days. Fix: nftban geoban update or nftban geoip update

Zero counter after feed load

Symptom: input_blacklist_drop = 0 despite feeds loaded with 847 ranges. Meaning: NEUTRAL — no inbound traffic matched any feed range. This is valid on hosts that don't receive traffic from blacklisted sources. It does NOT mean feeds are broken.

Counter reset after rebuild

All counters reset to 0 after nftban firewall rebuild. Zero counters immediately after rebuild = NEUTRAL (expected). Feed data is reloaded into the set as part of the rebuild process.


Limitations

  • Shared set for feeds + geoban (+ blacklist.d CIDRs). blacklist_ipv4/ipv6 contains ranges from all interval sources. As of v1.203.0 they are written by a single unified replace, so sources coexist (no clobber), but the kernel set alone cannot determine whether a given entry came from feeds, geoban, or blacklist.d. Daemon sync logs provide provenance.
  • Shared counter for feeds + geoban. input_blacklist_drop cannot attribute enforcement to feeds or geoban individually.
  • Shared counter for manual sources. input_blacklist_manual_drop cannot attribute enforcement to LoginMon, portscan, or operator bans individually.
  • Feed data is not versioned. No mechanism to detect whether a feed upstream changed its content between syncs. Freshness is tracked by file mtime only.
  • Interval set auto-merge. The kernel merges overlapping CIDR ranges for efficiency. This means the element count in blacklist_ipv4 may differ from the number of input CIDRs reported by the sync log. This is correct kernel behavior, not a data loss issue.
  • GeoBan depends on external database. DB-IP Country Lite updates monthly. If the update timer fails or the upstream is unavailable, the database becomes stale. The database may still be usable (country-to-IP mapping changes slowly) but is not guaranteed accurate.

Clone this wiki locally