# `Linx.Netfilter.Expr`
[🔗](https://github.com/oshlabs/linx/blob/v0.2.0/lib/linx/netfilter/expr.ex#L1)

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

  * [`Documentation/networking/netlink_spec/nftables` — Expression
    types](https://docs.kernel.org/networking/netlink_spec/nftables.html)

# `addr_input`

```elixir
@type addr_input() ::
  {0..255, 0..255, 0..255, 0..255}
  | {0..65535, 0..65535, 0..65535, 0..65535, 0..65535, 0..65535, 0..65535,
     0..65535}
  | &lt;&lt;_::32&gt;&gt;
  | &lt;&lt;_::128&gt;&gt;
  | String.t()
  | Linx.IP.t()
```

Address-input forms accepted by `dnat_to/3` / `snat_to/3`.

# `t`

```elixir
@type t() :: %Linx.Netfilter.Expr{data: term(), name: atom() | String.t()}
```

# `bitwise`

```elixir
@spec bitwise(binary(), binary(), keyword()) :: t()
```

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)`).

# `cmp`

```elixir
@spec cmp(atom(), binary(), keyword()) :: t()
```

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`

```elixir
@spec counter(keyword()) :: t()
```

Counter expression. Per-rule packet + byte counter; the kernel
increments it on every match. Reads back via `pull/1..2`.

# `ct`

```elixir
@spec ct(
  atom(),
  keyword()
) :: t()
```

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>>)]

# `dnat_to`

```elixir
@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 via `Linx.IP.parse/1`).

`port` is a non-negative integer (encoded big-endian 16-bit), or
`nil` for address-only NAT.

Options:

  * `:flags` — passed through to `nat/2` (`:random`, `:persistent`, …).
  * `:reg_addr` / `:reg_port` — override the default register
    choice (`1` for addr, `2` for port).

# `immediate`

```elixir
@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`

```elixir
@spec log(keyword()) :: t()
```

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 to `5000` (the
    Linx convention for "I don't care which group").
  * `:prefix` — string label that shows up in `Event.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` (NF_LOG_*). Most callers want them set at the
    Log listener side via `:flags` there instead.

# `lookup`

```elixir
@spec lookup(
  String.t(),
  keyword()
) :: t()
```

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`

```elixir
@spec masquerade(keyword()) :: t() | [t()]
```

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).

# `meta`

```elixir
@spec meta(
  atom(),
  keyword()
) :: t()
```

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).

# `nat`

```elixir
@spec nat(
  atom(),
  keyword()
) :: t()
```

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` — `:dnat` or `:snat`.
  * `:family` — `:ip` | `:ip6`. Required even in an `:inet`
    table; the NAT expression needs to know the address size.

Optional:

  * `:reg_addr_min` — register holding the min (or only) target
    address. Defaults to `nil` (no address remap — only
    meaningful for SNAT in some configs).
  * `:reg_addr_max` — register holding the max address of a
    range. `nil` for single-address NAT.
  * `:reg_proto_min` / `:reg_proto_max` — port range registers.
  * `:flags` — `[:random, :fully_random, :persistent, :netmap]`.
    The encoder automatically sets `:map_ips` when address regs
    are present and `:proto_specified` when port regs are
    present.

# `new`

```elixir
@spec new(atom() | String.t(), term()) :: t()
```

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.

# `payload`

```elixir
@spec payload(
  atom(),
  keyword()
) :: t()
```

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).

# `payload`

```elixir
@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`

```elixir
@spec redirect(keyword()) :: t() | [t()]
```

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`

```elixir
@spec reject(
  atom(),
  keyword()
) :: t()
```

Reject expression. Produces an explicit rejection response then
drops the packet.

Types:

  * `:icmp_unreach` — ICMP destination-unreachable (default for
    `:ip` / `:ip6` / `:inet` families). `:icmp_code` opt 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).

# `set_literal`

```elixir
@spec set_literal([term()], atom(), keyword()) :: t()
```

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.). `:anonymous` is always added; `:constant`
    is added by default (anonymous sets are constant unless
    explicitly otherwise).

# `snat_to`

```elixir
@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.

# `verdict?`

```elixir
@spec verdict?(t()) :: boolean()
```

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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
