NFTables (NFTables v0.8.2)
View SourceElixir interface to Linux nftables via libnftables JSON API.
NFTables provides a high-level, idiomatic Elixir API for managing nftables rules,
using the official libnftables library with JSON for all communication.
Quick Start
Start the NFTables port process and build firewall rules:
# Start the port process
{:ok, pid} = NFTables.Port.start_link()
# Create a simple firewall
import NFTables.Expr
NFTables.add(table: "filter", family: :inet)
|> NFTables.add(chain: "INPUT", type: :filter, hook: :input, priority: 0, policy: :drop)
|> NFTables.add(rule: state([:established, :related]) |> accept())
|> NFTables.add(rule: tcp() |> dport(22) |> accept())
|> NFTables.submit(pid: pid)
# Clean up when done
NFTables.Port.stop(pid)Import Options
You have two ways to import expression building functions:
Option 1: Use Macro (Import Everything)
The simplest approach - automatically imports all expression modules:
use NFTablesThis imports NFTables.Expr and all sub-modules, giving you access to all
expression building functions. Best for interactive use or when you need
many different types of expressions.
Option 2: Selective Imports (Import What You Need)
For production code, you may prefer explicit imports:
import NFTables.Expr
import NFTables.Expr.{Port, TCP, Verdict}This gives you fine-grained control and makes dependencies explicit. Best for production code where you want to minimize namespace pollution.
Both approaches are equally valid - choose based on your preferences and use case.
Main API Functions
Building Rules
add/1-2- Add tables, chains, rules, sets, etc.delete/1-2- Delete objectsflush/1-2- Flush objects (remove contents)flush_ruleset/0-2- Flush entire rulesetinsert/1-2- Insert rules at specific positionsreplace/1-2- Replace rules at specific handlesrename/1-2- Rename chains
Submitting Changes
submit/1-2- Submit configuration to nftables
Helper Functions
to_json/1- Convert to JSON stringto_map/1- Convert to Elixir mapset_family/2- Set address family
Module Organization
Core APIs
NFTables- Main public API (this module)NFTables.Expr- Fluent API for building rule expressionsNFTables.Query- Query tables, chains, rules, and sets
Convenience APIs
NFTables.Policy- Pre-built security policies (accept_established, allow_ssh, etc.)NFTables.NAT- NAT operations (port forwarding, masquerading, etc.)
Execution & Port Management
NFTables.Port- Port process management (start_link, stop)NFTables.Local- Local execution requestor
Internal APIs
NFTables.Builder- Internal builder implementation (use NFTables API instead)NFTables.Decoder- Decode nftables responsesNFTables.Requestor- Behaviour for custom submission handlers
Pipeline Pattern
All functions return a builder struct that can be piped:
NFTables.add(table: "filter")
|> NFTables.add(chain: "INPUT", type: :filter, hook: :input)
|> NFTables.add(rule: tcp() |> dport(80) |> accept())
|> NFTables.add(rule: tcp() |> dport(443) |> accept())
|> NFTables.submit(pid: pid)Context Tracking
The builder automatically tracks context (table, chain) so you don't need to repeat it:
NFTables.add(table: "filter", chain: "INPUT", type: :filter, hook: :input)
|> NFTables.add(rule: tcp() |> dport(22) |> accept())
|> NFTables.add(rule: tcp() |> dport(80) |> accept())
# Both rules automatically use filter/INPUTExamples
Basic Firewall
import NFTables.Expr
NFTables.add(table: "filter")
|> NFTables.add(chain: "INPUT", type: :filter, hook: :input, policy: :drop)
|> NFTables.add(chain: "FORWARD", type: :filter, hook: :forward, policy: :drop)
|> NFTables.add(chain: "OUTPUT", type: :filter, hook: :output, policy: :accept)
|> NFTables.add(rule: iif("lo") |> accept())
|> NFTables.add(rule: state([:established, :related]) |> accept())
|> NFTables.add(rule: tcp() |> dport(22) |> accept())
|> NFTables.submit(pid: pid)IP Blocking with Sets
NFTables.add(table: "filter")
|> NFTables.add(set: "blocklist", type: :ipv4_addr)
|> NFTables.add(element: ["1.2.3.4", "5.6.7.8"], set: "blocklist")
|> NFTables.add(chain: "INPUT", type: :filter, hook: :input)
|> NFTables.add(rule: ip_saddr() |> set_lookup("@blocklist") |> drop())
|> NFTables.submit(pid: pid)NAT / Port Forwarding
NFTables.add(table: "nat", family: :ip)
|> NFTables.add(chain: "PREROUTING", type: :nat, hook: :prerouting)
|> NFTables.add(rule: tcp() |> dport(8080) |> dnat("192.168.1.100:80"))
|> NFTables.submit(pid: pid)JSON API
The underlying JSON format follows the official nftables JSON schema. See: https://wiki.nftables.org/wiki-nftables/index.php/JSON_API
For advanced use cases requiring direct builder access, see NFTables.Builder documentation.
Summary
Functions
Convenience macro to import all NFTables.Expr modules.
Contextual add operation (arity-1) - starts new builder.
Contextual add operation (arity-2) - continues existing builder.
delete/1 delete an object, starts a new builder.
delete/2 operation same as delete/1 but continues existing builder.
Contextual flush operation (arity-1) - starts new builder.
Contextual flush operation (arity-2) - continues existing builder.
Flush the entire ruleset (remove all tables, chains, and rules).
Flush the entire ruleset (arity-2) - continues existing builder.
Contextual insert operation (arity-1) - starts new builder.
Contextual insert operation (arity-2) - continues existing builder.
Contextual rename operation (arity-1) - starts new builder.
Contextual rename operation (arity-2) - continues existing builder.
Contextual replace operation (arity-1) - starts new builder.
Contextual replace operation (arity-2) - continues existing builder.
Submit the builder configuration using the configured requestor.
Submit the builder configuration with options or override requestor.
Types
@type nft_family() :: :inet | :ip | :ip6 | :arp | :bridge | :netdev
@type t() :: NFTables.Builder.t()
Functions
Convenience macro to import all NFTables.Expr modules.
When you use NFTables, all expression building modules are automatically imported,
making all expression functions available without explicit imports.
Example
defmodule MyFirewall do
use NFTables
def build_rules(pid) do
NFTables.add(table: "filter")
|> NFTables.add(chain: "INPUT", type: :filter, hook: :input)
|> NFTables.add(rule: tcp() |> dport(22) |> accept())
|> NFTables.submit(pid: pid)
end
endThis is equivalent to:
import NFTables.Expr
import NFTables.Expr.{IP, Port, TCP, Layer2, CT, Advanced, Actions, NAT, Verdict, Meter, Protocols}Alternative: Selective Imports
For production code, you may prefer explicit imports for clarity:
import NFTables.Expr
import NFTables.Expr.{Port, TCP, Verdict}Both approaches are equally valid.
Contextual add operation (arity-1) - starts new builder.
Detects what to add based on keyword options provided.
Examples
# Add table
NFTables.add(table: "filter", family: :inet)
# Add chain
NFTables.add(chain: "INPUT", type: :filter)
# Add table and chain together
NFTables.add(table: "filter", chain: "INPUT", family: :inet)
# Add multiple rules
import NFTables.Expr
NFTables.add(rules: [
rule() |> tcp() |> dport(22) |> accept(),
rule() |> tcp() |> dport(80) |> accept()
])
Contextual add operation (arity-2) - continues existing builder.
Examples
NFTables.add(table: "filter")
|> NFTables.add(chain: "INPUT")
|> NFTables.add(rule: [%{accept: nil}])
delete/1 delete an object, starts a new builder.
Examples
builder |> delete(table: "filter")
builder |> delete(chain: "input")
builder |> delete(rule: [...], handle: 123)
delete/2 operation same as delete/1 but continues existing builder.
Contextual flush operation (arity-1) - starts new builder.
Contextual flush operation (arity-2) - continues existing builder.
options: :scope - when set to :all will flush everything (limited by :family option if that is specified) :family - limits flush to particular nft family: :inet | :ip | :ip6 | :arp | :bridge | :netdev
@spec flush_ruleset(keyword()) :: NFTables.Builder.t()
Flush the entire ruleset (remove all tables, chains, and rules).
Options
:family- Optional family to flush (default: all families)
Examples
# Flush all tables/chains/rules for all families
NFTables.flush_ruleset()
|> NFTables.submit(pid: pid)
# Flush only inet family
NFTables.flush_ruleset(family: :inet)
|> NFTables.submit(pid: pid)
@spec flush_ruleset( NFTables.Builder.t(), keyword() ) :: NFTables.Builder.t()
Flush the entire ruleset (arity-2) - continues existing builder.
Contextual insert operation (arity-1) - starts new builder.
Inserts a rule at a specific position in a chain.
Examples
import NFTables.Expr
NFTables.insert(table: "filter", chain: "INPUT", rule: tcp() |> dport(22) |> accept(), index: 0)
Contextual insert operation (arity-2) - continues existing builder.
Contextual rename operation (arity-1) - starts new builder.
Renames a chain.
Examples
NFTables.rename(table: "filter", chain: "input", newname: "INPUT")
Contextual rename operation (arity-2) - continues existing builder.
Contextual replace operation (arity-1) - starts new builder.
Replaces a rule at a specific handle.
Examples
import NFTables.Expr
NFTables.replace(table: "filter", chain: "INPUT", rule: tcp() |> dport(80) |> accept(), handle: 123)
Contextual replace operation (arity-2) - continues existing builder.
@spec submit(NFTables.Builder.t()) :: :ok | {:ok, term()} | {:error, term()}
Submit the builder configuration using the configured requestor.
Uses the requestor module specified in the builder's requestor field
(defaults to NFTables.Local for local execution).
Examples
{:ok, pid} = NFTables.Port.start_link()
NFTables.add(table: "filter")
|> NFTables.add(chain: "INPUT")
|> NFTables.submit(pid: pid)
@spec submit( NFTables.Builder.t(), keyword() ) :: :ok | {:ok, term()} | {:error, term()}
Submit the builder configuration with options or override requestor.
Examples
NFTables.add(table: "filter")
|> NFTables.submit(pid: pid, timeout: 10_000)
# Override requestor
NFTables.add(table: "filter")
|> NFTables.submit(requestor: MyApp.RemoteRequestor, node: :remote)