Linx.Netfilter.Encoder (Linx v0.1.0)

Copy Markdown View Source

Converts %Linx.Netfilter.*{} value structs into the %Linx.Netlink.Message{} shapes that ride inside a NFNL_MSG_BATCH_BEGIN / NFNL_MSG_BATCH_END envelope.

Each public function returns a single %Message{} (or a list, for entities that map to multiple wire messages). to_batch/2 walks a full %Ruleset{} and produces the ordered message list for a :replace-mode push. Linx.Netlink.Nfnl.batch/2 wraps the envelope and drives the transaction.

Wire format quirks

  • Every nftables NLA_U32 / NLA_U64 is big-endian on the wire — opposite to rtnetlink. Linx.Netfilter.Wire.u32_be/1 / u64_be/1 do the conversion.
  • The nfgenmsg.family byte carries the family this message applies to (NFPROTO_INET, etc.); res_id is 0 for most ops, the batch target's subsys id only for BATCH_BEGIN/END.
  • Attribute IDs are netfilter-namespaced (NFTATABLE, NFTACHAIN, etc.) — the same numeric ID can mean different things in different attribute sets.

References

  • nft_table.c — kernel-side parser; canonical authority on attribute order and required fields.

Summary

Functions

Builds a NEWCHAIN message for chain within family.

Builds a DELCHAIN message — removes a chain by name.

Builds a DELRULE message — removes a single rule by its kernel-assigned handle.

Builds a DELSET message — removes a named set by (family, table, name). Returns :enoent from the kernel if missing.

Builds a DELSETELEM message that removes the given elements from a set by raw element values.

Builds a DELTABLE message for the given table.

Builds a DESTROYTABLE message — silently a no-op if the table doesn't exist. Used by :replace-mode push to clear pre-existing state before re-creating it.

Encodes a %Linx.Netfilter.Patch{} into the ordered list of %Message{}s to send inside a single BATCH transaction.

Builds a GETTABLE request — for fetching one table by (family, name) from the kernel.

Builds a GETTABLE dump request — returns every table in the netns. Filtering by family is optional (:unspec returns all families).

Builds a NEWRULE message for rule inside the (family, table, chain) scope.

Builds a NEWSET message. Accepts either a %Linx.Netfilter.Set{} (plain set) or a %Linx.Netfilter.Map{} (typed map, including vmaps — data_type: :verdict).

Builds a NEWSETELEM message — adds one or more elements to a named set.

Builds a single-table NEWTABLE message. The result is a %Message{} ready to drop into a batch.

Builds the ordered message list for a :replace-mode push of ruleset.

Functions

chain(chain, family, opts \\ [])

Builds a NEWCHAIN message for chain within family.

Required at construction:

  • NFTA_CHAIN_TABLE (string) — taken from chain.table.
  • NFTA_CHAIN_NAME (string).

Base-chain attributes (set together when chain is a base chain):

  • NFTA_CHAIN_HOOK (nested) — NFTA_HOOK_HOOKNUM (u32 BE), NFTA_HOOK_PRIORITY (s32 BE), and NFTA_HOOK_DEV (string) for :ingress/:egress.
  • NFTA_CHAIN_TYPE (string: "filter" | "nat" | "route").

  • NFTA_CHAIN_POLICY (u32 BE, NF_ACCEPT=1 / NF_DROP=0) — only when chain.policy is set.
  • NFTA_CHAIN_FLAGS (u32 BE) — when chain.flags is non-empty.

Regular chains skip the HOOK / TYPE / POLICY attributes entirely.

delete_chain(family, table_name, chain_name)

Builds a DELCHAIN message — removes a chain by name.

delete_rule(family, table_name, chain_name, handle)

Builds a DELRULE message — removes a single rule by its kernel-assigned handle.

delete_set(family, table_name, set_name)

Builds a DELSET message — removes a named set by (family, table, name). Returns :enoent from the kernel if missing.

delete_set_elements(family, table_name, set_name, elements, key_type, data_type)

@spec delete_set_elements(
  Linx.Netfilter.Table.family(),
  String.t(),
  String.t(),
  [term()],
  atom(),
  atom() | nil
) :: Linx.Netlink.Message.t()

Builds a DELSETELEM message that removes the given elements from a set by raw element values.

deltable(family, name)

Builds a DELTABLE message for the given table.

Returns :enoent from the kernel if the table doesn't exist — use destroytable/2 for silent-if-missing semantics (6.3+).

destroytable(family, name)

Builds a DESTROYTABLE message — silently a no-op if the table doesn't exist. Used by :replace-mode push to clear pre-existing state before re-creating it.

Requires kernel ≥ 6.3 (where DESTROY* messages were added).

from_patch(patch)

@spec from_patch(Linx.Netfilter.Patch.t()) :: [Linx.Netlink.Message.t()]

Encodes a %Linx.Netfilter.Patch{} into the ordered list of %Message{}s to send inside a single BATCH transaction.

Sets need their NFTA_SET_ID generated per-batch; this function assigns ids in patch order so element-add ops can later reference them by name (the kernel resolves by name regardless, but libnftnl convention is to also send the id).

gettable(family, name)

Builds a GETTABLE request — for fetching one table by (family, name) from the kernel.

No NLM_F_DUMP — that's gettable_dump/1 (for listing all tables in the netns).

gettable_dump(family \\ :unspec)

@spec gettable_dump(atom()) :: Linx.Netlink.Message.t()

Builds a GETTABLE dump request — returns every table in the netns. Filtering by family is optional (:unspec returns all families).

rule(rule, family, table_name, chain_name, opts \\ [])

Builds a NEWRULE message for rule inside the (family, table, chain) scope.

Attribute payload:

  • NFTA_RULE_TABLE (string).
  • NFTA_RULE_CHAIN (string).
  • NFTA_RULE_EXPRESSIONS (nested list of NFTA_LIST_ELEMs) — each list element wraps NFTA_EXPR_NAME (string) plus NFTA_EXPR_DATA (nested, per-expression attribute set).

Comment / tag round-trip via NFTA_RULE_USERDATA.

set(set_or_map, family, opts \\ [])

Builds a NEWSET message. Accepts either a %Linx.Netfilter.Set{} (plain set) or a %Linx.Netfilter.Map{} (typed map, including vmaps — data_type: :verdict).

The encoder auto-derives the NFT_SET_F_MAP flag for maps and NFT_SET_F_EVAL for :dynamic sets; callers shouldn't include them in :flags directly (but set_flags_int/1 accepts them for round-trip purposes).

set_elements(set_or_map, family, opts \\ [])

Builds a NEWSETELEM message — adds one or more elements to a named set.

Elements is a list of either:

  • raw key terms (for plain sets) — [{10,0,0,1}, "1.2.3.4", ...].
  • {key, data} tuples (for maps and vmaps) — [{22, %Verdict{...}}, ...].

The encoder normalises each into the wire form using key_type / data_type from the parent set.

table(table, opts \\ [])

Builds a single-table NEWTABLE message. The result is a %Message{} ready to drop into a batch.

Default flags: NLM_F_CREATE — creates the table if missing, updates flags otherwise. Pass excl: true for create-or-fail (NLM_F_EXCL).

Attribute payload:

  • NFTA_TABLE_NAME (string)
  • NFTA_TABLE_FLAGS (u32 BE) — :dormant | :owner | :persist flags OR'd via Wire.table_flags_int/1. Omitted when zero (libnftnl convention).

  • NFTA_TABLE_USERDATA (binary) — omitted when nil.

to_batch(ruleset, opts \\ [])

Builds the ordered message list for a :replace-mode push of ruleset.

For each table in ruleset, the batch contains:

  1. DESTROYTABLE — silently destroys any existing table by the same (family, name), ensuring fresh state.
  2. NEWTABLE — recreate with the desired flags.
  3. NEWCHAIN for each chain in the table (in map iteration order; the kernel doesn't care about chain order).
  4. NEWRULE for each rule in each chain, in rule-list order — rule ordering matters at runtime, the kernel preserves the batch order via NLM_F_APPEND.

Objects and flowtables are not yet emitted. The batch shape supports them by interleaving in step 3.

This list is intended to drop straight into Linx.Netlink.Nfnl.batch/2; it does not include the BATCH_BEGIN / BATCH_END envelope (that's batch/2's job).