Blocking with the Linux Agent
The community feed in the kernel and the addresses this host bans itself: the two kinds of set and their lifetimes, how a local ban begins and ends, the escalation with real numbers, the chain in the kernel, the two switches mode and ban.enabled, and how one ban or everything is lifted.
The community feed
This is the part the Network-Level Blocking page describes as a shell script. The agent does the same thing, with the parts that are easy to get wrong when you write it yourself already handled: one conditional request per list so an unchanged list costs nothing, a size check before anything goes live, an atomic swap so there is never a window with an empty set, and port-scoped rules so a web attacker cannot lock anyone out of SSH.
| List | Kernel set | Ports the rule matches |
|---|---|---|
ssh | rip-ssh, rip-ssh-v6 | from lists.ssh.ports |
mail | rip-mail, rip-mail-v6 | 25, 465, 587, 110, 995, 143, 993 |
web | rip-web, rip-web-v6 | 80, 443 |
ftp | rip-ftp, rip-ftp-v6 | 21 |
edge | rip-edge, rip-edge-v6 | every port, on purpose |
group | rip-group, rip-group-v6 | every port; see below |
How often the feed is fetched depends on your plan, and the server decides it, not the
agent. On a licensed server the interval is every 15 minutes with a minimum confidence of 75
from Professional upwards, and hourly with a minimum confidence of 90 on Contributor. Business and
Enterprise are as Professional. The agent asks which values apply to this host and follows the
answer, so an upgrade takes effect without anyone editing a file. The answer is cached for up to
24 hours, and only an open api_key, license or reputation
condition makes it ask on every pass, so a plan change reaches the host within a day. There is no
configuration key for the interval, and a key you invent for it stops the agent with exit 2.
Two more rules sit on top of that. A list that was fetched less than 15 minutes ago is not fetched
again, because that is how long the server caches one, so running sync by hand is always
allowed and never harmful. And every fetch is conditional: an unchanged list answers 304 and costs
one request and no transfer, which is why a short interval is cheap for both sides.
The group list
A key in a group gets a sixth list, fetched as json with the community feed. Every address
is dropped on every port in the sets rip-group and rip-group-v6, whatever
service this host exposes. A key in no group changes nothing: the set stays empty and
status prints group=none.
The group's whitelist, if it carries one, is read in the same request and written to
/var/lib/reportedip-agent/group-whitelist, in the format of whitelist.conf
with each note as a comment. Do not edit that file by hand, the next sync writes it again. It is
applied in the same sync run in all three places a whitelist works: the kernel whitelist set, the
filter of every list, and the reporting gate of the watcher, one more layer next to this host's own
whitelist.conf and its auto whitelist. A key that leaves its group loses the file with
the next sync.
whitelist list and status show the group's entries on lines marked
group:, with their notes. status also names the group and its size next
to the account line and shows the entry count of the group list under the lists like any other. See
Groups for what a group is, the tariff limits, the whitelist and the webhook on the
portal side.
Blocking
The agent writes two very different things into the kernel, and an admin coming from fail2ban usually conflates them for the first week. They have different sources, different lifetimes and different switches, so it is worth separating them once and properly.
Two kinds of set, two lifetimes
| Community blacklist | Local finds | |
|---|---|---|
| Sets | rip-ssh, rip-mail, rip-web, rip-ftp, rip-edge, rip-group, and a -v6 set for each | rip-local and rip-local-v6 |
| Where the addresses come from | Every reporter in the community, filtered by your confidence | Only this host's own logs |
| How an entry arrives | The whole set is replaced on every feed pass | One address at a time, when a threshold is reached |
| How an entry leaves | With the next swap, when the feed no longer lists it | The kernel expires it when its own timeout runs out |
| Per-entry timeout | none | yes, that is the whole design |
| Switch | mode | ban.enabled, and mode on top |
| Needs a licence | yes, this is the paid part | no |
How a local ban begins and how it ends
A ban begins when a detector's counter for one address crosses the threshold of its event source. What happens then, in order:
- The whitelist is checked first, before anything else is even asked. A whitelisted address leaves
no trace at all, so you never find your own management range in
reportedip-agent ban listwondering whether it was blocked. The check reports which layer matched: a built-in reserved range, one of this host's own addresses, or your file. - The ban store,
/var/lib/reportedip-agent/bans.json, is asked for the ban time. It records the address, when the ban expires, how many bans fell inside the memory window, when the last one started, the source and the categories. The file is written before the kernel entry, so a crash right after the kernel write cannot lose the record that expires it. - The address goes into
rip-localwith that time as a per-entry timeout. The kernel expires it. There is no cleanup timer, no cron job and no unban queue, which is the reason a crashed or removed agent leaves no permanent block behind. - A line is written to the log with the address, the ban time, the source, how many bans that address has inside the window, the lag between the log line and the kernel entry, and the two commands to undo it.
A running ban is never shortened. When a new hit arrives for an address that is already banned,
nothing happens: it is not an escalation either, because escalation counts bans and not hits, exactly
like the recidive jail it replaces. An address that is already blocked cannot be blocked again. That
rule is also what keeps the transition period honest, when the agent sees one attack twice, once as a
raw line from auth.log and once as a ban line from fail2ban.log.
A replayed log line does not produce a ban either. A hit whose timestamp is not newer than the last ban of that address is a replay after a hard stop, not a new offence, and the reporting path has the same guard in its dedup file.
The state survives more than a restart. With ban.enabled: true, every sync run compares
bans.json against what the kernel actually holds and puts back what is missing, with
the time the ban has left:
- After a reboot that is all of them, because neither an ipset timeout nor the set
itself survives a reboot. This is why the sync service is enabled for
multi-user.targetand runs after every firewall service. - During normal operation it is whatever took the set away: a
firewall-cmd --reload, acsf -r, anipset flush. Entries the kernel still holds are left alone, so the gap is a part of one sync interval and not the rest of the ban. The log says explicitly when entries were missing under active bans, because that means something edited the set under the agent. - An expired record never comes back. Yesterday's attacker is no reason to cut someone off today.
- The whitelist is re-checked on every restore. It may have grown while the host was down, and an address you whitelisted yesterday must not be blocked again by a record from the day before.
Escalation, with real numbers
The first ban lasts time_minutes. Every repeat inside the memory window multiplies it by
escalate, and max_time_minutes caps it. With the defaults
(30 minutes, multiplier 4, cap one week) an address that keeps coming back goes:
| Ban | Calculation | Duration |
|---|---|---|
| 1st | time_minutes | 30 minutes |
| 2nd | 30 × 4 | 2 hours |
| 3rd | 120 × 4 | 8 hours |
| 4th | 480 × 4 | 32 hours |
| 5th | 1920 × 4 | 128 hours, five days and eight hours |
| 6th and after | would be 512 hours, capped | 7 days, the value of max_time_minutes |
The chain in the kernel
With the ipset backend the agent owns exactly one chain, rip-blacklist, and one jump
into it. A sync rebuilds the chain whenever its fingerprint (the config and the chain as the kernel
reports it) differs, so its content is always what the config says; otherwise it leaves the chain
alone, so the packet counters keep counting. The jump is inserted at position 1 of
INPUT only if it is not already there.
# What a sync builds, in this order:
iptables -N rip-blacklist
iptables -F rip-blacklist
iptables -A rip-blacklist -m set --match-set rip-whitelist src -j RETURN
iptables -A rip-blacklist -m set --match-set rip-local src -j DROP
iptables -A rip-blacklist -p tcp --dport 22 \
-m set --match-set rip-ssh src -j DROP
iptables -A rip-blacklist -p tcp -m multiport --dports 25,465,587,110,995,143,993 \
-m set --match-set rip-mail src -j DROP
iptables -A rip-blacklist -p tcp -m multiport --dports 80,443 \
-m set --match-set rip-web src -j DROP
iptables -A rip-blacklist -m set --match-set rip-edge src -j DROP
iptables -A rip-blacklist -m set --match-set rip-group src -j DROP
# Only with a scan source in the config: a SYN to a port this host
# does not listen on is logged, at most ten a minute. Listening ports
# and the passive range of a running FTP server return first, then any
# port a socket listens on at this very moment.
iptables -N rip-blacklist-scan
iptables -A rip-blacklist-scan -p tcp -m multiport --dports 21,22,25,80,443,40110:40210 -j RETURN
iptables -A rip-blacklist-scan -p tcp -m socket --nowildcard -j RETURN
iptables -A rip-blacklist-scan -m limit --limit 10/min --limit-burst 20 \
-j LOG --log-prefix "rip-scan: "
iptables -A rip-blacklist -p tcp --syn -j rip-blacklist-scan
iptables -I INPUT 1 -j rip-blacklist
The whitelist is the first rule and returns, it is not the last rule. A
RETURN in front of everything else means a whitelisted address leaves the chain before
any set is consulted, so it outranks every ban including one that is already in
rip-local. That is what makes whitelist add a working way out of a lockout,
with no sync and no restart. A whitelist placed at the end would be a list of exceptions that arrive
too late, and on a host where you have locked yourself out, "too late" is the entire problem.
The local ban rule carries no port match, because a host that attacked your mail
server has no business on port 22 either. The rules for ssh, mail,
web and ftp are port-scoped, so an address on the
web list is dropped on 80 and 443 and can still reach SSH. That is deliberate: a shared address, a
carrier NAT or a compromised proxy on the web list must not lock an administrator out of the
machine. Only edge and group match every port: for edge that is what the
category behind it means, for group it is the point of sharing a ban.
With the nftables backend the shape is the same in native form: a table inet reportedip,
a chain hooked into input at priority -10 with policy accept, the two whitelist
return rules first, then rip-local, then the feed lists with their port
matches. The whitelist sets are interval sets so they hold CIDR ranges; the local sets carry the
timeout flag, which both tools require on the set before any element may carry a timeout at all.
In mode: log the same rules are built, and the verdict is a rate-limited log line
(six per minute, prefixed rip-<list>:) instead of DROP. Everything
else is identical, which is why the log mode is a real rehearsal and not a simulation.
mode and ban.enabled are two switches
This is the single most common misunderstanding, so it gets its own paragraph.
mode decides what the rules do. ban.enabled decides whether your
own finds ever become an entry at all. Both are on by default (mode: drop,
ban.enabled: true). With ban.enabled: false, mode: drop gets
you the community list enforced and nothing of your own.
mode | ban.enabled | Community list | Your own finds |
|---|---|---|---|
log | false | Matched and logged, nothing dropped | Reported to the community, not blocked locally |
log | true | Matched and logged | Recorded in rip-local and visible in ban list, logged instead of dropped |
drop | false | Dropped | Reported only. This is a perfectly sensible steady state. |
drop | true | Dropped | Dropped as well, with escalation. The full configuration. |
off | either | Nothing in the packet path: the jump is removed; with ipset the chain is left with its rules, with nftables the chain is deleted | Recorded in the store, no effect |
off is the emergency switch and it is honest about it. It removes what hooks the chain
into the packet path, up to five times to catch duplicate jumps, and fails loudly if a jump survives,
because saying "off" while still dropping traffic is worse than an error. With nftables the chain
itself is deleted. The sets keep their entries in both backends, so switching back needs no full
download.
A manual ban ignores both switches on purpose: reportedip-agent ban add works with
ban.enabled: false, because that is a deliberate act by an operator, the same way
fail2ban-client set banip was. It still respects the whitelist, and it still refuses
when the host has no firewall backend at all.
Switching from log to drop
# 1. What would have been dropped? The rules already match in
# log mode, so ask the kernel log.
journalctl -k --since "24 hours ago" | grep -c "rip-"
journalctl -k --since "24 hours ago" | grep -o "rip-[a-z]*:" | sort | uniq -c
# 2. Is anything of yours in there? Check every source address
# against your whitelist before you switch.
reportedip-agent whitelist list
# 3. Switch, and rebuild the rules.
sed -i "s/^mode: log/mode: drop/" /etc/reportedip-agent/config.yaml
reportedip-agent sync
reportedip-agent status
# 4. Only now the second switch, if you want it.
# ban.enabled: true, then restart the daemon.
systemctl restart reportedip-agent.service
reportedip-agent ban list
Looking at what is really in the kernel
reportedip-agent status is the summary, and it reads both the state files and the live
kernel so the two can be compared. Where they disagree, the kernel is the truth and status says so:
a record without a kernel entry is a ban that is not in effect.
# The agent's own view
reportedip-agent status
reportedip-agent ban list
# ipset backend, straight from the kernel
ipset list -t rip-ssh # header and entry count only
ipset list rip-local # entries, each with its timeout
iptables -S rip-blacklist
iptables -L INPUT -n --line-numbers | head
# nftables backend
nft list table inet reportedip
nft list set inet reportedip rip-local
In ban list the column kernel is what the kernel still has on the clock and
record is what bans.json expects. A dash under kernel next to a
running record means the ban is not in effect and the next sync will restore it. A row whose record
says none exists in the kernel only, which means somebody used ipset or
nft by hand, or the store was lost: it will still expire, but no reboot brings it back.
Lifting one ban, and removing everything
reportedip-agent whitelist add <address> takes effect
immediately and outranks every ban, including one already in the set. It writes the file and the
live kernel set in one step, and it needs no sync and no restart.
# Lift one ban and forget its history. The next detection starts
# from a first offence rather than escalating on top of a verdict
# you disagreed with.
reportedip-agent unban 203.0.113.45
# Never ban and never report this address or range, from now on.
reportedip-agent whitelist add 203.0.113.0/24 "customer office"
# Ban by hand. Works even with ban.enabled: false. A time given
# here is the time, not the first step of an escalation.
reportedip-agent ban add 203.0.113.45
reportedip-agent ban add 203.0.113.45 1440
# Stop blocking without uninstalling: no jump, the sets keep their entries.
sed -i "s/^mode: .*/mode: off/" /etc/reportedip-agent/config.yaml
reportedip-agent sync
# Remove the agent from the packet path completely (ipset backend).
iptables -D INPUT -j rip-blacklist; iptables -F rip-blacklist; iptables -X rip-blacklist
ip6tables -D INPUT -j rip-blacklist; ip6tables -F rip-blacklist; ip6tables -X rip-blacklist
for s in $(ipset list -n | grep "^rip-"); do ipset destroy "$s"; done
# nftables backend
nft delete table inet reportedip
unban and whitelist add both take the sync lock, so the restore step of a
running sync cannot put back what you are lifting. Removing an entry from the whitelist file is the
one operation that is not instant: it takes effect with the next sync rebuild, and removing an
exception never needs to be immediate.
rip- set.
iptables-restore discards the whole file when it hits an unknown set, so one stale
line in /etc/iptables/rules.v4 can take your entire firewall down at the next boot.
The agent rebuilds its chain on every boot by itself and needs nothing persisted.
status warns when it finds rip- in rules.v4,
rules.v6, /etc/sysconfig/iptables, ip6tables or
/etc/nftables.conf. It does not look into /etc/nftables.d.
Last updated: · Maintained by the ReportedIP team