Linx.Netfilter.Diff (Linx v0.1.0)

Copy Markdown View Source

Structural diff between two %Linx.Netfilter.Ruleset{} values, producing a %Linx.Netfilter.Patch{} of the minimum mutations that turn one into the other.

Identity rules

  • Tables, chains, sets, maps: identity is name (within family for tables, within table for everything else).
  • Rules within a chain: identity is :tag when set; for untagged rules the diff falls back to positional index.
  • Set elements: identity is the element value itself (set semantics — no "modified", just add and remove).

In-place vs delete+recreate

An entity attribute change is rendered as an in-place op when the kernel supports it, otherwise as a delete+create pair:

  • Rules: any structural change → :replace_rule (NLM_F_REPLACE over the kernel-assigned handle from the current state). Requires the rule to have a :tag (or stable positional position with no neighbour changes).
  • Chains: most attribute changes (type, hook, priority) can't be replaced — emit :delete_chain + :create_chain. We conservatively delete+create on any difference.
  • Tables: flags / use_count differences → no-op (the diff treats tables as opaque containers; their lifecycle is managed via the :owner flag).
  • Sets / maps: declaration changes (key_type, data_type, flags) → delete+create. Element changes → element-level add/remove ops.

Untagged rules

When a chain's rule list differs and any of its rules is untagged, the diff cannot use :tag-based identity. It falls back to: if the lists are identical → no-op; otherwise emit :delete_rule for every current rule and :create_rule for every desired rule (full chain rebuild). The :reconcile push mode rejects this case at its entry point — see Linx.Netfilter.Diff.validate_for_reconcile/1.

Summary

Functions

Returns the %Patch{} that transforms from into to. Both inputs are %Ruleset{} values — the same shape pull/1 returns and push/2 consumes.

Validates that desired is reconcile-safe: every chain with more than one rule has tags on all of its rules.

Functions

diff(from, to)

Returns the %Patch{} that transforms from into to. Both inputs are %Ruleset{} values — the same shape pull/1 returns and push/2 consumes.

The patch is topologically sorted (deletes before creates of their dependencies). Patch.empty?/1 is true iff the rulesets are structurally equal modulo handle / kernel-assigned values.

validate_for_reconcile(ruleset)

@spec validate_for_reconcile(Linx.Netfilter.Ruleset.t()) ::
  :ok | {:error, {:tag_required, {atom(), String.t(), String.t()}}}

Validates that desired is reconcile-safe: every chain with more than one rule has tags on all of its rules.

Returns :ok or {:error, {:tag_required, {family, table, chain}}}.