Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 32 additions & 16 deletions .agents/skills/netifyd/references/nethsecurity-wiring.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,29 +83,44 @@ lists. `procd_set_param file /etc/netifyd.conf` means procd restarts the service

`/usr/sbin/dpi` is the apply path, in order:

1. `/usr/sbin/dpi-config` — reads `/etc/config/dpi`, writes `/etc/netifyd/netify-proc-flow-actions.json`
2. `/usr/sbin/dpi-nft` — writes the `dpi_actions` / `dpi_dummy` chains into
1. `/usr/libexec/ns-dpi/dpi-migrate` — converts a `/etc/config/dpi` still on the pre-appgroup schema
(`dpi.migrate_schema`, a no-op when there is nothing to migrate). It also runs from the
`21_dpi_migrate` uci-default; running it here covers restored backups and configs synced by an HA
peer, where uci-defaults don't run
2. `/usr/sbin/dpi-config` — reads `/etc/config/dpi`, writes `/etc/netifyd/netify-proc-flow-actions.json`
3. `/usr/sbin/dpi-nft` — writes the `dpi_actions` / `dpi_dummy` chains into
`/usr/share/nftables.d/table-pre/`
3. `/etc/init.d/netifyd reload`
4. `/etc/init.d/netifyd reload`

Steps 1 and 2 run under `/var/lock/dpi.lock`, shared with the iface hotplug, so the migration and the
generation never run twice at once.

Current generator behaviour worth knowing before touching it:

- `valid_actions` is `block`, `bulk`, `best_effort`, `video`, `voice`; anything else, or a disabled rule,
is skipped.
- A rule's raw `criteria` takes precedence over generated source/application/protocol/category matching.
Double quotes in it are rewritten to single quotes. **No `;` is appended to raw criteria** — generated
criteria get one, raw ones don't. Same for entries in the global `exemptions` array.
- A `device` naming a VLAN device causes `vlan_id == N && ` to be prepended — including to raw criteria,
so a rule carrying both its own `criteria` and a `device` gets the term whether or not it already has one.
- A hidden `analyzed` action (`detection_guessed || detection_complete;`) is appended last, labelling every
analysed flow.
- `dpi.config.firewall_exemption` optionally pre-fills `exemptions` with every firewall interface IP;
`exemption` sections add more, and an exemption whose criteria is an object ID is expanded to its IPs.
- The first action is a hidden `analyzed` one, generated by `dpi-config` and never stored in UCI:
priority `0`, `halt_on_match: false`, criteria `detection_guessed || detection_complete;`. It labels every
analysed flow and lets the evaluation continue to the user rules.
- User rules are emitted with their UCI `priority` (dense, from `1`) and `halt_on_match: true`: the first
matching rule wins. Actions are `block` and `allow`; `allow` sets a label no nft rule acts on, so it only
shields its flows from the block rules below it. A rule with no `enabled` option is skipped.
- Rules without a raw `criteria` (the ones created by the API) get it from `dpi.build_rule_criteria`:
`source` ORed, ANDed with the expanded `appgroup` members; `ns_match_all` with no source becomes `*`.
- Rules with a raw `criteria` (hand-written, or frozen by the migration) have double quotes rewritten to
single quotes and `;` appended when missing: an unterminated expression is a syntax error.
- A rule that would emit an empty criteria is skipped, because an empty criteria takes the agent down.
- A rule still on the pre-appgroup schema (`device`, `application`, `protocol` or `category` set) is
skipped with a warning, never emitted: read as a current rule, its `source` alone would block every flow
of those hosts. The migration in step 1 converts it first.
- The global `exemptions` are always pre-filled with every firewall interface IP, so DPI rules can not
lock the administrator out; `/etc/hotplug.d/iface/90-dpi-exemption` re-runs `dpi-config` on `ifup` /
address `ifupdate` and does `netifyd reload` only if the JSON changed and the agent is running (so HA
backup nodes are left alone). They are the only entries in `exemptions`.
- `dpi.config.log_blocked` drives the nft log rule in `dpi-nft`, not any netify logging target.

Enforcement is nft reading conntrack labels, not netifyd acting directly: netifyd classifies and sets a
label via its `ctlabel` target, and `dpi_actions` (hook prerouting, `filter + 10`) rejects labelled
traffic (`netify-blocked`) or DSCP-marks it (the QoS labels — the DPI→qosify bridge).
label via its `ctlabel` target, and `dpi_actions` (hook prerouting, `filter + 10`) rejects the flows
labelled `netify-blocked`. The chain still DSCP-marks the QoS labels, which no DPI action sets any more:
they are the DPI→qosify bridge, left in place.

## Conntrack labels

Expand All @@ -121,6 +136,7 @@ across an upgrade keep their labels, so **never reassign an existing bit** — t
| 4 | `best_effort` |
| 5 | `video` |
| 6 | `voice` |
| 7 | `netify-allowed` |

`netify-analyzed` is consumed by no nft rule and read only by `ns.flows` for display — it is a diagnostic
that tells support whether netifyd actually analysed a flow. "Nothing reads it in code" is not a reason to
Expand Down
Loading
Loading