Wheelhouse docs

Put the engine in the packet path#

You will end up with Suricata verdicting packets rather than watching a copy of them: the firewall hands matched packets to a kernel queue, a userspace engine decides on each one, and a packet it rejects never reaches the other side.

The IDS is detection — VyOS runs Suricata in af-packet mode, where it watches and cannot drop. Inline prevention needs the engine in the path, which on Linux means NFQUEUE. All of it is configuration in the tree, and it was verified on the bench on 2026-08-29.

Before you start#

  • The operator role and a licence.
  • An existing accept rule in the firewall covering the traffic you want inspected. This matters — see the next section.
  • Container support on the router, and a route to the registry to pull the engine image.
  • An honest answer to "what happens if this drops something it should not". Read the fail-open section below before you commit anything.

The safe pattern: convert an accept rule#

Route an existing accept rule through the engine. Same match, same permitted traffic, now inspected. No new hole opens, because a terminal queue rule that then accepts is exactly the accept it replaced.

The IDS page offers a fresh interface-wide rule as well, and flags it — a queue rule is terminal, so a wrong scope changes what the firewall permits, in both directions.

Step 1 — Read the plan#

The agent renders the operations without applying them:

bash
R=https://<router>:8443
T=wh_...
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
  -d '{"chain":"forward","rule":"10","queue":0,"interface":""}' "$R/api/ids/ips/plan"

The IDS page does the same thing and shows you the result before anything is staged.

Step 2 — The engine#

It is a container, declared in the config tree, so it diffs, commits, rolls back and survives an image upgrade like everything else — and so this works even when the agent runs off-router.

set container name suricata-ips image docker.io/jasonish/suricata:latest
set container name suricata-ips allow-host-networks
set container name suricata-ips capability net-admin
set container name suricata-ips capability net-raw
set container name suricata-ips command '-q 0 --set nfq.mode=accept --set nfq.fail-open=true'
set container name suricata-ips volume log source /config/apps/suricata-ips/log
set container name suricata-ips volume log destination /var/log/suricata

Three details in there are load-bearing:

  • Host networking plus net-admin is what lets a container reach the host's netfilter queue.
  • command, not arguments. VyOS' quadlet backend emits arguments only when a command is set too; on that backend an engine declared with arguments alone starts with the image default and never attaches to the queue, while the status says it is in the path. command works on both backends.
  • nfq.fail-open=true is the engine's own fail-open, the second of two layers.

Step 3 — The firewall rule#

set firewall ipv4 forward filter rule 10 action queue
set firewall ipv4 forward filter rule 10 queue 0
set firewall ipv4 forward filter rule 10 queue-options bypass

If you are creating a new rule rather than converting one, the plan adds its scope too:

set firewall ipv4 forward filter rule 10 inbound-interface name eth0

The two fail-open layers#

Step 4 — Commit, and check the state#

The IDS page's IPS panel reports three separate things, and all three have to be true:

Reported asMeans
engine_declaredThe container is in the configuration
engine_runningA container named suricata-ips has a status beginning Up
in_pathAt least one firewall rule anywhere has action queue

It also lists each queue rule with its chain, its number and whether it has bypass. That last column is the one to look at: a queue rule without bypass and without a running engine is a black hole.

The status reader tolerates all three shapes VyOS uses to serialise a valueless node under queue-options — a bare string, an array and a map — so a rule that is in fact safe is not reported as a fail-open risk.

Check it worked#

Traffic still flows. Before anything else. From a client behind the rule:

bash
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com
ping -c 20 192.0.2.1

On the bench, an engine running with bypass removed showed 0% loss — which is only possible if the engine is genuinely verdicting packets rather than the kernel passing them by another path.

The engine is up.

bash
show container

Alerts are arriving, in the volume you mounted:

bash
sudo tail -f /config/apps/suricata-ips/log/fast.log

And the firewall counter on the queue rule is risingthe Firewall page, or show firewall statistics. A queue rule with a flat counter is a rule that is not matching, and the traffic is going through some other rule uninspected.

Undoing it#

The IDS page's Remove stages the reverse: delete the container, and turn each queue rule back into a plain accept.

delete container name suricata-ips
set firewall ipv4 forward filter rule 10 action accept
delete firewall ipv4 forward filter rule 10 queue
delete firewall ipv4 forward filter rule 10 queue-options

Note the order of effect: the rule goes back to being the accept it was, which is why converting an accept rule was the safe way in.

See also#


Checked against agent/ids.go · ui/src/pages/Ids.tsx · PLAN.md

Updated 2026-09-02 ips suricata nfqueue security