NFTables.Expr.Structs (NFTables v0.8.2)
View SourceLow-level helper functions for building nftables expression structures.
This module provides the foundational API for constructing the raw expression
data structures used by nftables. These are the building blocks used by the
higher-level NFTables.Expr module and its sub-modules.
NOTE: All functions return maps with atom keys (not string keys). The JSON encoding happens later in the Builder/Local pipeline.
Most users should use NFTables.Expr instead of calling these functions directly.
Expression Format
nftables uses expressions that consist of:
- Matches: Compare packet fields against values
- Statements: Perform actions (counter, log, limit, mark, etc.)
- Verdicts: Terminal decisions (accept, drop, reject, etc.)
Examples
# Simple IP match
Expr.payload_match("ip", "saddr", "192.168.1.1")
#=> %{match: %{
# left: %{payload: %{protocol: "ip", field: "saddr"}},
# right: "192.168.1.1",
# op: "=="
# }}
# Connection tracking match
Expr.ct_match("state", ["established", "related"])
#=> %{match: %{
# left: %{ct: %{key: "state"}},
# right: ["established", "related"],
# op: "in"
# }}
# Verdict
Expr.verdict("drop")
#=> %{drop: nil}Reference
Official nftables documentation: https://wiki.nftables.org/wiki-nftables/index.php/JSON_API
Summary
Functions
Build a bitwise AND match.
Build a counter statement.
Build a connection tracking match expression.
Build a connection tracking original direction match.
Build a CT set statement (set connection tracking value).
Build a DNAT (Destination NAT) statement.
Build a goto verdict (goto another chain, no return).
Build a jump verdict (jump to another chain).
Build a limit statement (rate limiting).
Build a log statement.
Build a masquerade statement.
Build a meta expression match.
Build a meta set statement (set packet mark, priority, etc.).
Build an OSF (OS Fingerprinting) match expression.
Build an OSF match expression with value comparison.
Build a payload match expression.
Build a payload match with prefix (CIDR notation).
Build a payload range match.
Build a raw payload expression for offset-based matching.
Build a raw payload match expression.
Build a reject verdict with optional type.
Build a set add operation.
Build a set membership match.
Build a set update operation (for meters).
Build a SNAT (Source NAT) statement.
Build a socket match expression.
Build a socket match expression with value comparison.
Build a verdict expression.
Functions
Build a bitwise AND match.
Used for TCP flags, fragmentation checks, etc.
Examples
# TCP flags: Check if SYN is set (mask includes SYN, ACK, RST, FIN)
bitwise_and_match(
%{payload: %{protocol: "tcp", field: "flags"}},
["syn", "ack", "rst", "fin"],
["syn"]
)
@spec counter() :: map()
Build a counter statement.
Counts packets and bytes.
Examples
counter()
#=> %{counter: nil}
Build a connection tracking match expression.
Parameters
key- CT key ("state", "status", "mark", "bytes", "packets", etc.)value- Value to matchop- Comparison operator (default: "in" for lists, "==" for single values)
Examples
# Match established/related connections
ct_match("state", ["established", "related"])
# Match connection mark
ct_match("mark", 42, "==")
# Match connection bytes
ct_match("bytes", 1000000, ">")
Build a connection tracking original direction match.
Examples
ct_original_match("saddr", "192.168.1.1")
#=> Match original source address
Build a CT set statement (set connection tracking value).
Examples
ct_set("mark", 42)
ct_set("helper", "ftp")
Build a DNAT (Destination NAT) statement.
Options
:port- Port or port range:family- Address family ("ip" or "ip6", default: "ip")
Examples
dnat("192.168.1.10")
dnat("192.168.1.10", port: 8080)
dnat("192.168.1.10", port: [8080, 8090])
dnat("2001:db8::10", family: "ip6")
Build a goto verdict (goto another chain, no return).
Examples
goto("custom_chain")
#=> %{goto: %{target: "custom_chain"}}
Build a jump verdict (jump to another chain).
Examples
jump("custom_chain")
#=> %{jump: %{target: "custom_chain"}}
Build a limit statement (rate limiting).
Options
:rate- Rate number (required):per- Time unit ("second", "minute", "hour", "day") (required):burst- Burst packets (optional):inv- Invert match (rate over) (optional)
Examples
limit(10, "minute")
limit(100, "second", burst: 200)
limit(5, "minute", burst: 10, inv: true) # Rate over 5/min
Build a log statement.
Options
:prefix- Log prefix string (required):level- Syslog level ("emerg", "alert", "crit", "err", "warn", "notice", "info", "debug"):flags- Log flags list (["tcp sequence", "tcp options", "ip options", "skuid", "ether", "all"])
Examples
log("SSH_ATTEMPT: ")
log("DROPPED: ", level: "warn")
log("AUDIT: ", level: "info", flags: ["all"])
Build a masquerade statement.
Examples
masquerade()
masquerade(port: [1024, 65535])
Build a meta expression match.
Meta expressions match packet metadata (not packet contents).
Parameters
key- Meta key ("mark", "iif", "oif", "length", "protocol", etc.)value- Value to matchop- Comparison operator (default: "==")
Examples
# Match packet mark
meta_match("mark", 100)
# Match input interface
meta_match("iifname", "eth0")
# Match packet length
meta_match("length", 1000, ">")
Build a meta set statement (set packet mark, priority, etc.).
Examples
meta_set("mark", 100)
meta_set("priority", 1)
Build an OSF (OS Fingerprinting) match expression.
OSF performs passive operating system detection by analyzing TCP SYN packet characteristics. Requires the pf.os fingerprint database to be loaded.
Parameters
key- What to match: "name" (OS name) or "version" (OS version)ttl- TTL matching mode (default: "loose"):- "loose" - Allow TTL variations
- "skip" - Ignore TTL completely
- "strict" - Require exact TTL match
Examples
# Match OS name
osf_match("name")
#=> %{osf: %{key: "name", ttl: "loose"}}
# Match OS version with strict TTL
osf_match("version", "strict")
#=> %{osf: %{key: "version", ttl: "strict"}}Requirements
The pf.os database must be loaded before using OSF:
nfnl_osf -f /usr/share/pf.os
Supported OS Names
Common values include: "Linux", "Windows", "MacOS", "FreeBSD", "OpenBSD"
Build an OSF match expression with value comparison.
Examples
# Match Linux systems
osf_match_value("name", "Linux")
#=> %{match: %{left: %{osf: %{key: "name", ttl: "loose"}}, right: "Linux", op: "=="}}
# Match specific OS version
osf_match_value("version", "3.x", "strict")
#=> %{match: %{left: %{osf: %{key: "version", ttl: "strict"}}, right: "3.x", op: "=="}}
Build a payload match expression.
Matches a protocol field against a value.
Parameters
protocol- Protocol name ("ip", "ip6", "tcp", "udp", "icmp", etc.)field- Field name ("saddr", "daddr", "sport", "dport", etc.)value- Value to match (string, integer, or list)op- Comparison operator (default: "==")
Examples
# IPv4 source address
payload_match("ip", "saddr", "192.168.1.1")
# TCP destination port
payload_match("tcp", "dport", 80)
# Port range
payload_match("tcp", "dport", %{range: [1024, 65535]})
# Not equal
payload_match("ip", "saddr", "10.0.0.0/8", "!=")
Build a payload match with prefix (CIDR notation).
Examples
payload_match_prefix("ip", "saddr", "192.168.1.0", 24)
#=> Matches 192.168.1.0/24
Build a payload range match.
Examples
payload_match_range("tcp", "dport", 1024, 65535)
#=> Matches ports 1024-65535
@spec payload_raw(atom(), non_neg_integer(), pos_integer()) :: map()
Build a raw payload expression for offset-based matching.
Raw payload matching allows matching arbitrary bytes at specific offsets, bypassing protocol-specific parsing. Essential for custom protocols or DPI.
Parameters
base- Base reference point::ll- Link layer (Ethernet header start):nh- Network header (IP header start):th- Transport header (TCP/UDP header start):ih- Inner header (for tunneled packets)
offset- Bit offset from base (not byte offset!)length- Number of bits to extract
Examples
# Extract 32 bits at network header offset 96 (source IP)
payload_raw(:nh, 96, 32)
#=> %{payload: %{base: "nh", offset: 96, len: 32}}
# Extract 16 bits at transport header offset 16 (dest port)
payload_raw(:th, 16, 16)
# Extract 8 bits at network header offset 0 (IP version + IHL)
payload_raw(:nh, 0, 8)Notes
- Offsets are in bits, not bytes (multiply byte offset by 8)
- Length is also in bits (e.g., 32 bits = 4 bytes)
- Network byte order (big endian) is assumed
@spec payload_raw_match(atom(), non_neg_integer(), pos_integer(), term(), String.t()) :: map()
Build a raw payload match expression.
Convenience function combining payload_raw/3 with a match.
Parameters
base- Base reference (:ll, :nh, :th, :ih)offset- Bit offset from baselength- Number of bitsvalue- Value to match againstop- Comparison operator (default: "==")
Examples
# Match source IP (32 bits at nh+96)
payload_raw_match(:nh, 96, 32, <<192, 168, 1, 1>>)
# Match destination port 53
payload_raw_match(:th, 16, 16, 53)
# Match DNS port with not-equal
payload_raw_match(:th, 16, 16, 53, "!=")
Build a reject verdict with optional type.
Examples
reject()
reject("tcp reset")
reject("icmpx port-unreachable")
Build a set add operation.
Similar to set_update but uses "add" operation instead of "update". "add" fails if element already exists, "update" updates existing or adds new.
Examples
set_add_operation(
%{payload: %{protocol: "ip", field: "saddr"}},
"tracked_ips",
[counter()]
)
Build a set membership match.
Checks if a value is in a named set.
Examples
# Check if source IP is in blocklist
set_match("ip", "saddr", "@blocklist")
# Check if destination port is in allowed_ports set
set_match("tcp", "dport", "@allowed_ports")
Build a set update operation (for meters).
Set update operations add elements to a set with associated statements (like limit). Used for per-key rate limiting (meters).
Parameters
elem- Element expression(s) to add to set (single value or list for composite keys)set_name- Name of the set (without @ prefix)statements- List of statement expressions to associate with the element
Examples
# Per-IP rate limiting
set_update(
%{payload: %{protocol: "ip", field: "saddr"}},
"ssh_ratelimit",
[limit(3, "minute", burst: 5)]
)
# Composite key (src + dst IP)
set_update(
[
%{payload: %{protocol: "ip", field: "saddr"}},
%{payload: %{protocol: "ip", field: "daddr"}}
],
"flow_limits",
[limit(100, "second")]
)
Build a SNAT (Source NAT) statement.
Options
:port- Port or port range:family- Address family ("ip" or "ip6", default: "ip")
Examples
snat("203.0.113.1")
snat("203.0.113.1", port: 1024)
snat("203.0.113.1", port: [1024, 65535])
snat("2001:db8::1", family: "ip6")
Build a socket match expression.
Socket matching allows matching packets based on socket attributes. Useful for transparent proxy setups and process-based filtering.
Parameters
key- Socket attribute to match:"transparent"- Match transparent sockets (for TPROXY)"mark"- Match socket mark"wildcard"- Match wildcard sockets
Examples
# Match packets with transparent socket
socket_match("transparent")
#=> %{socket: %{key: "transparent"}}
# Match socket mark
socket_match("mark")
#=> %{socket: %{key: "mark"}}
Build a socket match expression with value comparison.
Examples
# Match transparent socket (value = 1)
socket_match_value("transparent", 1)
#=> %{match: %{left: %{socket: %{key: "transparent"}}, right: 1, op: "=="}}
Build a verdict expression.
Supported Verdicts
- "accept" - Accept the packet
- "drop" - Drop the packet
- "continue" - Continue to next rule
- "return" - Return from chain
Examples
verdict("accept")
#=> %{accept: nil}
verdict("drop")
#=> %{drop: nil}