Linx.NFT (Linx v0.1.0)

Copy Markdown View Source

The public entry point for the ~NFT sigil and the file-mode parser. Plumbs source → Linx.NFT.TokenizerLinx.NFT.ParserLinx.NFT.Compiler%Linx.Netfilter.Ruleset{}, plus a canonical emit going the other way (format/1).

Three peer authoring surfaces produce the same %Ruleset{} via the same validator-setter functions (Linx.Netfilter.Ruleset. add_table!/3, add_chain!/4, add_rule!/4, etc.):

  • Pipeline DSL — direct Ruleset.new() |> add_table!(…) calls. Best for programmatic construction.
  • ~NFT sigil — inline nft syntax, parsed at compile time. Best for hand-authoring a ruleset alongside Elixir code (Nerves boot scripts, container compositions).
  • Linx.NFT.parse_file/1 — same parser/compiler, file input. Best for importing an existing nftables.conf.

Round-trip:

iex> import Linx.NFT
iex> rs = ~NFT"""
...> table inet myapp {
...>   chain input {
...>     type filter hook input priority 0
...>     policy drop
...>     tcp dport 22 accept
...>   }
...> }
...> """
iex> emitted = Linx.NFT.format(rs)
iex> {:ok, rs2} = Linx.NFT.parse(emitted)
iex> rs == rs2
true

Compile-time errors

Parse or compile errors inside a ~NFT sigil raise Linx.NFT.ParseError at compile time, with the Elixir-compiler-style caret rendering keyed off the surrounding .ex file's line numbers (the tokenizer's :line option lines up with __CALLER__.line):

** (Linx.NFT.ParseError) lib/myapp/firewall.ex:42:14: ...
|
| tcp dport ? accept
|           ^

Scope

The grammar slice currently supported matches the Linx.NFT.Compiler capabilities (see that module's @moduledoc). It targets the common ~85% subset; the long tail (synproxy, secmark, osf, fib, jhash, advanced ct fields, dup/fwd, ipsec contexts) is not yet implemented (see docs/netfilter/DESIGN.md).

Interpolation

~NFT is an uppercase sigil, so Elixir's parser leaves #{...} alone and the macro receives the literal binary — the same pattern Phoenix HEEx uses for ~H. Our own Linx.NFT.Tokenizer recognises #{...} as an interpolation marker (its :interpolation? mode is enabled by the sigil always), captures the raw Elixir source between the braces, and emits an :elixir_expr token at that position.

When any :elixir_expr tokens are present, the macro switches to Linx.NFT.RuntimeCompiler, which emits Elixir code that builds the Ruleset at runtime. At each interpolation position, the emitted code calls into Linx.NFT.Runtime with the field kind the surrounding nft syntax expects ({:int, _}, :ipv4, :ipv6, :ifname) — that's where the runtime type check happens. Pass an integer where a port is expected and you get a <<port::big-16>> bytestring; pass a binary where it shouldn't be and you get a runtime ArgumentError naming the kind.

Supported interpolation positions today:

  • Match RHS — tcp dport #{port}, ip saddr #{addr}, meta iifname #{name}.

Interpolations in keyword positions (table name, chain name, family, hook, …) raise a ParseError — they'd require per-validator wiring that hasn't landed yet.

Sigil bodies with NO interpolations stay on the compile-time static path — the %Ruleset{} is computed at macro-expansion time and emitted as a literal value.

Summary

Functions

Emits a %Ruleset{} as canonical nft syntax.

Parses a binary holding nft syntax into a %Ruleset{}.

Reads a .nft file and parses it into a %Ruleset{}.

The ~NFT sigil. Parses inline nft syntax at compile time and returns a %Linx.Netfilter.Ruleset{}. Bodies with #{...} interpolations are compiled to runtime-evaluating code; bodies without interpolations are compiled to a literal value.

Functions

format(rs)

@spec format(Linx.Netfilter.Ruleset.t()) :: String.t()

Emits a %Ruleset{} as canonical nft syntax.

The output is syntactically valid nftables.conf-compatible source that parses back to an equivalent %Ruleset{} (modulo comments, blank lines, and the original ordering of unrelated items — trivia preservation is a v2 enhancement). See Linx.NFT.Formatter for the per-construct emit policy.

parse(source, opts \\ [])

@spec parse(
  String.t(),
  keyword()
) :: {:ok, Linx.Netfilter.Ruleset.t()} | {:error, Linx.NFT.ParseError.t()}

Parses a binary holding nft syntax into a %Ruleset{}.

Options

  • :file — source filename for error messages (default "nofile").

Returns {:ok, Ruleset.t()} | {:error, ParseError.t()}.

parse_file(path)

@spec parse_file(Path.t()) ::
  {:ok, Linx.Netfilter.Ruleset.t()}
  | {:error, Linx.NFT.ParseError.t() | File.posix()}

Reads a .nft file and parses it into a %Ruleset{}.

Returns {:ok, Ruleset.t()} | {:error, ParseError.t() | File.posix()}.

sigil_NFT(arg, modifiers)

(macro)

The ~NFT sigil. Parses inline nft syntax at compile time and returns a %Linx.Netfilter.Ruleset{}. Bodies with #{...} interpolations are compiled to runtime-evaluating code; bodies without interpolations are compiled to a literal value.

Raises Linx.NFT.ParseError at compile time on syntax or compile errors.

Examples

iex> import Linx.NFT
iex> rs = ~NFT"table inet x { }"
iex> rs.tables |> map_size()
1

Modifierless.