Linx.Netfilter.Chain (Linx v0.1.0)

Copy Markdown View Source

An nftables chain — a named container of rules within a table.

Chains come in two flavours:

  • Base chains carry a :type + :hook + :priority and (optionally) a :policy. The kernel attaches them to the hook point so packets actually flow through them.

  • Regular chains have none of those — they are jump/goto targets reachable from base chains via verdicts. Pure organisational tools.

Fields

  • :name — chain name (unique within the table).
  • :table — name of the owning table; nil for free-standing chains, populated when added to a table.
  • :type:filter | :nat | :route for base chains, nil for regular chains.

  • :hook:prerouting | :input | :forward | :output | :postrouting | :ingress | :egress for base chains, nil for regular chains.

  • :priority — integer | atom (named priority) | {atom, integer} (named-with-offset, e.g. {:filter, -10}). Required for base chains; nil for regular chains.

  • :policy:accept | :drop; default verdict when no rule matches. Only meaningful on base chains. nil means "no explicit policy" (kernel defaults to :accept).

  • :device — interface name (string). Required for :ingress and :egress hooks; nil otherwise.
  • :flags — list (advanced): :hw_offload enables hardware offload (kernel + driver permitting); other flags reserved for future use.
  • :handle — kernel-assigned handle; nil until pushed.
  • :rules — ordered list of %Linx.Netfilter.Rule{}.

Validation

new/2 validates the chain's intrinsic shape (base-vs-regular consistency, required fields). Family-specific validation (e.g. :nat type only valid in ip/ip6/inet families, :ingress hook only valid in certain families) lives in validate_for_family/2 and runs when the chain is added to a table (Linx.Netfilter.Ruleset.add_chain/4).

Construction

iex> Chain.new("input", type: :filter, hook: :input, priority: 0, policy: :accept)
{:ok, %Linx.Netfilter.Chain{name: "input", type: :filter, hook: :input, ...}}

iex> Chain.new("ingress_drop", type: :filter, hook: :ingress, priority: -500, device: "eth0")
{:ok, %Linx.Netfilter.Chain{hook: :ingress, device: "eth0", ...}}

iex> Chain.new("input_extras")
{:ok, %Linx.Netfilter.Chain{type: nil, hook: nil, ...}}  # regular chain

Errors come back as {:error, {:bad_chain, reason}}.

References

Summary

Functions

Returns a new chain with rule appended to its rules list.

Returns true iff chain is a base chain (has :type, :hook, and :priority set).

Builds a chain. Validates intrinsic shape; family-specific validation happens at validate_for_family/2.

Bang variant of new/2 — returns the chain or raises ArgumentError.

Validates the chain against the given family — checks family-specific rules that new/2 can't (since it doesn't know the family).

Types

chain_type()

@type chain_type() :: :filter | :nat | :route

hook()

@type hook() ::
  :prerouting | :input | :forward | :output | :postrouting | :ingress | :egress

policy()

@type policy() :: :accept | :drop

priority()

@type priority() :: integer() | atom() | {atom(), integer()}

t()

@type t() :: %Linx.Netfilter.Chain{
  device: String.t() | nil,
  flags: [atom()],
  handle: pos_integer() | nil,
  hook: hook() | nil,
  name: String.t(),
  policy: policy() | nil,
  priority: priority() | nil,
  rules: [Linx.Netfilter.Rule.t()],
  table: String.t() | nil,
  type: chain_type() | nil
}

Functions

add_rule(chain, rule)

@spec add_rule(t(), Linx.Netfilter.Rule.t()) ::
  {:ok, t()} | {:error, {:bad_chain, term()}}

Returns a new chain with rule appended to its rules list.

This is a pure data operation — no validation beyond ensuring rule is a %Rule{}. Higher-level validation (tag uniqueness within chain) lives in Linx.Netfilter.Ruleset.add_rule/4.

The added rule's :chain field is set to the chain's name (so free-standing rules pick up their context when inserted).

base?(chain)

@spec base?(t()) :: boolean()

Returns true iff chain is a base chain (has :type, :hook, and :priority set).

new(name, opts \\ [])

@spec new(
  String.t(),
  keyword()
) :: {:ok, t()} | {:error, {:bad_chain, term()}}

Builds a chain. Validates intrinsic shape; family-specific validation happens at validate_for_family/2.

new!(name, opts \\ [])

@spec new!(
  String.t(),
  keyword()
) :: t()

Bang variant of new/2 — returns the chain or raises ArgumentError.

validate_for_family(chain, family)

@spec validate_for_family(t(), atom()) :: :ok | {:error, {:bad_chain, term()}}

Validates the chain against the given family — checks family-specific rules that new/2 can't (since it doesn't know the family).

Rules enforced:

  • Family-allowed chain types (e.g. :nat invalid in arp, bridge, netdev; :route only in ip/ip6).
  • Family-allowed hooks (e.g. arp only has :input/:output; netdev only has :ingress/:egress).
  • :nat chains restricted to NAT-applicable hooks.
  • :route chains restricted to :output hook.