A single netfilter expression — one node in a rule's expression list.
Expressions are the kernel's per-rule building blocks: a payload
extraction, a comparison, a lookup, a verdict load. Each carries
a :name (the kernel's expression-type string, e.g. "payload",
"cmp", "immediate", "lookup") and :data (kind-specific
arguments — a keyword list, a map, a verdict, …).
Constructors
immediate/1— load a verdict (or a constant) into the kernel's register 0.new/2— generic constructor for arbitrary(name, data)pairs. Useful for callers building expressions Linx doesn't yet have a dedicated helper for.
There are also kind-specific helpers (cmp/3, payload/3,
meta/2, bitwise/3, ct/2, lookup/2, reject/2,
counter/1, NAT helpers). They are extra entry points over the
same struct shape, not extra fields.
Inspect
iex> Expr.immediate(Verdict.accept())
#Linx.Netfilter.Expr<immediate accept>
iex> Expr.new(:counter, %{packets: 0, bytes: 0})
#Linx.Netfilter.Expr<counter>References
Summary
Functions
Bitwise AND-with-mask expression. Used most commonly to mask
CIDR-shaped addresses before a cmp comparison.
Comparison expression. Compares the value in sreg against
value using op.
Counter expression. Per-rule packet + byte counter; the kernel
increments it on every match. Reads back via pull/1..2.
Connection-tracking load expression. Reads key (:state,
:mark, etc.) from conntrack state into dreg.
Builds a DNAT statement — destination NAT to addr (and
optionally port).
An immediate expression — load a verdict into register 0.
Log expression — emits a per-packet event to the NFLOG
subsystem (NFNL_SUBSYS_ULOG) on the named group. Combine with
Linx.Netfilter.log_listen/2 for in-BEAM packet observability.
Set-lookup expression. Looks up sreg's value in the named set;
emits a verdict on hit (for plain sets) or loads associated data
into dreg (for maps and vmaps).
Masquerade expression — SNAT to the outgoing interface's primary address. The kernel resolves the address at packet-traversal time, which makes this the right choice for setups where the outbound IP isn't known at rule-write time (DHCP-assigned WAN, PPP links).
Metadata-load expression. Reads key from the packet's metadata
into dreg.
Generic constructor: an expression named name carrying data.
Named-shortcut variant of payload/4 for common header fields.
Payload-extraction expression. Reads len bytes at offset into
base-anchored header data and stores in dreg.
Redirect expression — DNAT to the local machine, optionally changing the destination port. The kernel uses the input interface's address as the new destination, which makes this the right choice for transparent proxies / port-shifting on a single host.
Reject expression. Produces an explicit rejection response then drops the packet.
Anonymous-set lookup — tcp dport { 22, 80, 443 } accept.
Builds an SNAT statement — source NAT to addr (and optionally
port). Same shape as dnat_to/3.
Returns true if expr is an immediate expression carrying a
%Verdict{} — i.e. a terminal expression in a rule.
Types
Functions
Bitwise AND-with-mask expression. Used most commonly to mask
CIDR-shaped addresses before a cmp comparison.
mask and xor must be the same length; len is that length
in bytes (defaults to byte_size(mask)).
Comparison expression. Compares the value in sreg against
value using op.
op is one of :eq, :neq, :lt, :lte, :gt, :gte.
value is a raw binary of the right length (1/2/4/16 bytes
depending on what was loaded into sreg).
Most callers don't construct cmp/3 directly — the high-level
~NFT sigil and pipeline helpers compose payload + cmp
into the natural "tcp dport 22" shape.
iex> Expr.cmp(:eq, <<22::big-16>>)
#Linx.Netfilter.Expr<cmp>
Counter expression. Per-rule packet + byte counter; the kernel
increments it on every match. Reads back via pull/1..2.
Connection-tracking load expression. Reads key (:state,
:mark, etc.) from conntrack state into dreg.
CT-state matching uses the bitmask integers — wrap the state
atom(s) with
Linx.Netfilter.Wire.ct_state_bits/1 to produce the comparison
value:
[Expr.ct(:state), Expr.cmp(:neq, <<Wire.ct_state_bits(:invalid)::big-32>>)]
@spec dnat_to(addr_input(), pos_integer() | nil, keyword()) :: [t()]
Builds a DNAT statement — destination NAT to addr (and
optionally port).
Returns a list of %Expr{}: an immediate-load of the
address into a register, an optional immediate-load of the port,
then the nat expression itself. Rule.build/2 flattens nested
lists, so this composes naturally:
Rule.build([
Expr.payload(:tcp_dport),
Expr.cmp(:eq, <<8080::big-16>>),
Expr.dnat_to({10, 0, 0, 5}, 80)
])addr may be:
- an IPv4 4-tuple —
{10, 0, 0, 5}. - an IPv6 8-tuple —
{0xfc00, 0, 0, 0, 0, 0, 0, 1}. - a raw binary — 4 bytes for IPv4, 16 for IPv6.
- a
%Linx.IP{}struct. - a string —
"10.0.0.5"/"fc00::1"(parsed viaLinx.IP.parse/1).
port is a non-negative integer (encoded big-endian 16-bit), or
nil for address-only NAT.
Options:
:flags— passed through tonat/2(:random,:persistent, …).:reg_addr/:reg_port— override the default register choice (1for addr,2for port).
@spec immediate(Linx.Netfilter.Verdict.input()) :: t()
An immediate expression — load a verdict into register 0.
The data is the verdict struct itself. Accepts a %Verdict{} or
any input form Verdict.new!/1 understands.
This constructor also accepts a constant (a binary or integer)
for loading a value into a register prior to a cmp — the kernel
uses the same expression for both forms.
iex> Expr.immediate(Verdict.accept())
#Linx.Netfilter.Expr<immediate accept>
iex> Expr.immediate(:drop)
#Linx.Netfilter.Expr<immediate drop>
Log expression — emits a per-packet event to the NFLOG
subsystem (NFNL_SUBSYS_ULOG) on the named group. Combine with
Linx.Netfilter.log_listen/2 for in-BEAM packet observability.
Options (all optional):
:group— NFLOG group (1..65535). Defaults to5000(the Linx convention for "I don't care which group").:prefix— string label that shows up inEvent.prefix; useful for tagging rules ("blocked", "audit", …). Max 127 bytes.:snaplen— bytes of packet payload to copy (overrides the consumer's copy_mode/snaplen if set on the rule). 0 = no packet data.:qthreshold— kernel-side queue threshold; packets are batched into multipart netlink messages up to this count.:flags—:tcp_seq/:tcp_opt/:ip_opt/:uid/:macdecode(NFLOG*). Most callers want them set at the Log listener side via:flagsthere instead.
Set-lookup expression. Looks up sreg's value in the named set;
emits a verdict on hit (for plain sets) or loads associated data
into dreg (for maps and vmaps).
Options:
:sreg— register to look up from (default 1).:dreg— register to store the map's data value in (for maps / vmaps). Omitted for plain sets.:flags—[:inv]to invert match (NFT_LOOKUP_F_INV).
Masquerade expression — SNAT to the outgoing interface's primary address. The kernel resolves the address at packet-traversal time, which makes this the right choice for setups where the outbound IP isn't known at rule-write time (DHCP-assigned WAN, PPP links).
Only valid in postrouting chains.
Options:
:flags—[:random, :fully_random, :persistent].:port_min/:port_max— masquerade with a specific port-range remap (rare; kernel default reuses the original source port).
Metadata-load expression. Reads key from the packet's metadata
into dreg.
Keys: :len, :protocol, :mark, :iif, :oif, :iifname,
:oifname, :nfproto, :l4proto, etc. (see
Linx.Netfilter.Wire.meta_key_int/1 for the full list).
Low-level NAT expression. Most callers want dnat_to/2 or
snat_to/2 — those construct the accompanying immediate-load
expressions automatically. Use nat/2 directly when you need
fine control over register choices, ranges, or flags.
Required:
:type—:dnator:snat.:family—:ip|:ip6. Required even in an:inettable; the NAT expression needs to know the address size.
Optional:
:reg_addr_min— register holding the min (or only) target address. Defaults tonil(no address remap — only meaningful for SNAT in some configs).:reg_addr_max— register holding the max address of a range.nilfor single-address NAT.:reg_proto_min/:reg_proto_max— port range registers.:flags—[:random, :fully_random, :persistent, :netmap]. The encoder automatically sets:map_ipswhen address regs are present and:proto_specifiedwhen port regs are present.
Generic constructor: an expression named name carrying data.
Most callers want one of the kind-specific helpers (immediate/1,
cmp/3, payload/3, etc.). Use new/2 when you're constructing
an expression Linx doesn't yet have a dedicated helper for.
Named-shortcut variant of payload/4 for common header fields.
Supported aliases:
:ip_saddr/:ip_daddr— IPv4 source / dest address (network base, offsets 12 / 16, length 4).:ip6_saddr/:ip6_daddr— IPv6 source / dest address (network base, offsets 8 / 24, length 16).:ip_protocol— IPv4 protocol byte (network base, offset 9, length 1).:tcp_sport/:tcp_dport— TCP source / dest port (transport base, offsets 0 / 2, length 2). Same wire form as:udp_sport/:udp_dport.:udp_sport/:udp_dport.:icmp_type/:icmp_code— (transport base, offsets 0 / 1, length 1).
@spec payload(atom(), non_neg_integer(), pos_integer(), keyword()) :: t()
Payload-extraction expression. Reads len bytes at offset into
base-anchored header data and stores in dreg.
base is :link / :network / :transport / :inner. Use
the named shortcuts (payload(:tcp_dport), etc.) for the common
cases.
iex> Expr.payload(:transport, 2, 2)
#Linx.Netfilter.Expr<payload>
iex> Expr.payload(:tcp_dport) # equivalent to (:transport, 2, 2)
#Linx.Netfilter.Expr<payload>
Redirect expression — DNAT to the local machine, optionally changing the destination port. The kernel uses the input interface's address as the new destination, which makes this the right choice for transparent proxies / port-shifting on a single host.
Only valid in prerouting / output chains.
Options:
:port— redirect to this port (16-bit). Omit to keep the original port.:flags—[:random, :fully_random].
Reject expression. Produces an explicit rejection response then drops the packet.
Types:
:icmp_unreach— ICMP destination-unreachable (default for:ip/:ip6/:inetfamilies).:icmp_codeopt sets the ICMP code (defaults to 3 — port-unreachable).:tcp_reset— TCP RST (only valid for TCP packets).:icmpx_unreach— family-agnostic ICMP-unreach (kernel picks ICMP vs ICMPv6 based on packet family).
Anonymous-set lookup — tcp dport { 22, 80, 443 } accept.
Returns a sentinel %Expr{name: :__anon_set} that the encoder
expands at to_batch/2 time into an auto-generated
NFT_SET_F_ANONYMOUS | NFT_SET_F_CONSTANT set plus a regular
lookup expression referencing it. The anonymous-set lifecycle
is tied to the rule — it lives and dies with it.
values is the same shape as a Linx.Netfilter.Set's elements
list. key_type is the same set of atoms Linx.Netfilter.Set.new!/2 accepts.
Rule.build([
Expr.payload(:tcp_dport),
Expr.set_literal([22, 80, 443], :inet_service),
Verdict.accept()
])Options:
:flags— passed to the auto-generated set (:interval,:constant, etc.).:anonymousis always added;:constantis added by default (anonymous sets are constant unless explicitly otherwise).
@spec snat_to(addr_input(), pos_integer() | nil, keyword()) :: [t()]
Builds an SNAT statement — source NAT to addr (and optionally
port). Same shape as dnat_to/3.
SNAT is meaningful in postrouting chains; the kernel rejects SNAT in prerouting / output.
Returns true if expr is an immediate expression carrying a
%Verdict{} — i.e. a terminal expression in a rule.
Also recognises wrapped immediate-verdict in the
%{dreg: 0, value: %Verdict{}} shape that the codec uses.