Intrusion detection (IDS/IPS)¶
Suricata (in Fedora, GPL-2.0-only, with suricata-update for the rules)
watches the traffic through the WANs against the Emerging Threats Open
rules. In detect mode it alerts; in prevent mode it also drops what the
dropping rules match.
Config¶
# stronghold.yaml
ids:
mode: prevent # detect (default) or prevent
networks: [wan] # the WANs watched; default all
sources: [abuse.ch/feodotracker, abuse.ch/sslbl-blacklist] # besides et/open, always on
drop: [critical, major] # prevent: rules that drop, by ET's signature severity (default critical)
drop-rules: [2100498] # prevent: more rules that drop
disabled-rules: [2013028] # false alarms: switched off
cpus: 2-3 # Suricata on these CPUs alone; default any
The free sources offered are suricata-update's (suricata-update
list-sources --free): abuse.ch's (SSL blacklist, JA3, Feodo tracker,
URLhaus), Etnetera's attacker addresses, OISF traffic ID, tgreen's
hunting rules, Stamus lateral movement and nmap scans. Rule 2200121
("SURICATA Ethertype unknown", which VRRP and other non-IP frames on a
WAN set off) is always off.
How it runs¶
- Config:
/etc/suricata/stronghold.yamlincludes Fedora'ssuricata.yamland puts Stronghold's over it: the variables (HOME_NETis the inside networks, tunnels, private ranges andfc00::/7, and the IPv6 /64s on the inside interfaces when it's applied, such as the delegated prefix's; a key given replaces all of Fedora's, so every variable is there), the rule file,detect.profile: low(less memory), alerts and drops toeve.json, and the control socket. A unit drop-in (suricata.service.d/stronghold.conf) runs Suricata with it. - Detect: af-packet on the WANs' interfaces (a cluster per interface).
- Prevent:
stronghold_ids(nftables) queues the traffic forwarded in or out through the WANs to Suricata (queue num 0 bypass), after the firewall allowed it (priority filter + 10), so Suricata sees inside addresses (before outbound NAT);bypassandfail-openlet traffic pass when Suricata isn't running or can't keep up. The box's own traffic isn't queued. - Rules:
suricata-update(sources enabled and disabled to match the config;/etc/suricata/disable.confanddrop.conffrom it) when the IDS is turned on and then every day; Suricata reloads them (systemctl reload, USR2). "Update the rules now" (POST /v1/ids/update,stronghold ids update) does it at once. - Alerts: strongholdd follows
eve.json(also across rotation); the last 500 alerts are kept for the Firewall tab's card,GET /v1/idsandstronghold ids, and each goes to the alerts (docs/alerts.md, kindids): severity 1 (high) and blocked ones as warnings, others as info, each signature at most once per 15 minutes. From an alert the card can switch the rule off (a false alarm) or, in prevent mode, make it drop. - CPUs (
cpus, a list such as2-3or1,3): the unit Suricata runs in, on the host or its container's (stronghold-ids.service), is kept to them (AllowedCPUs, a drop-instronghold-cpus.conf), and Suricata runs its workers there (runmode: workers,set-cpu-affinity,mode: exclusive): one per CPU and WAN in detect mode (af-packet'sthreads), and in prevent mode one per queue, the traffic spread over a queue per CPU by flow (queue flags bypass,fanout to 0-N,-q 0 -q 1 …). Without it, af-packet starts as many workers as the box has CPUs and prevent mode has one queue. The threads keep their priority: raising it needsCAP_SYS_NICE, which the container hasn't (a worker failed to start). A CPU the box hasn't is refused when the change is applied. The other functions aren't kept off those CPUs. - In a pair only the active node runs Suricata (started and stopped with the role; the new active node fetches the rules first if it has none). The standby uses no memory for it.
-
Memory: about 500 MB with ET Open's ~53,000 rules on the lab's 2 GB VMs.
-
Connections it didn't see start (those a pair's node takes over from its peer, and all of them after Suricata restarts) are picked up and inspected (
stream.midstream,midstream-policy: ignore). Suricata 8's default in prevent mode drops them: in the lab a download carried over a handover died about 15 s later, dropped in Suricata's queue.
In the lab¶
- Detect: a request through the gateway carrying
uid=0(root)set off rule 2100498 ("GPL ATTACK_RESPONSE id check returned root"); the alert went out by email, ntfy and webhook. - Prevent, with that rule in
drop-rules: the same request was dropped (it never reached the server) while a plain one passed; with Suricata stopped, traffic still passed (fail-open). - A handover moved Suricata to the other node.
cpus: 0-1on gw1 (2 CPUs, prevent mode, in its container, 2026-10-20): two queues fanned out,W-NFQ#0on CPU 0 andW-NFQ#1on CPU 1, the rest of Suricata on 0-1 (AllowedCPUs), both queues bound; a plain request passed and theuid=0(root)one was dropped. Taken out again, the drop-in went and one queue came back.- On four CPUs (2026-10-22; a package-mode box,
ipsec-a: 4 vCPUs, 3 GiB, Suricata 8 from Fedora with ET Open's 53,234 rules, prevent mode,iperf3from the host through the box's NAT to a VM beyond the WAN, 12 s runs,-P 32, up / down in Gbit/s):
cpus |
up | down |
|---|---|---|
| none (one queue) | 0.84 | 0.86 |
0-1 (two queues, the other two CPUs the kernel's) |
4.4, 4.4, 3.0 | 1.3, 0.8, 0.8 |
0-3 (four queues) |
0.8, 0.8, 0.6 | 0.6, 0.7, 0.6 |
With -P 4 the same: about 1 Gbit/s for none and 0-1, 0.6–0.7 for
0-3. More CPUs for Suricata did not give more throughput: all four
made it slower than two, five times slower upstream. Whole-box CPU was
about 58% idle during a 0-3 run, so Suricata wasn't short of CPU
(why four workers on four CPUs are slower than two is not known; the
kernel forwards and queues on the same CPUs). Keep CPUs for the kernel: cpus should be a part of the box's,
half or less. The fan-out is by flow: four streams used two of the four
queues (/proc/net/netfilter/nfnetlink_queue), so few big flows
don't spread. These are VMs on one host with the load generator on the
same host, and the figures vary a lot between runs; they say what to
try, not what a box will do.
- The lab's "internet" is private addresses (inside HOME_NET), so rules
about external hosts don't fire there.