Packages
Elixir library for working with Linux nftables rules. Provides high-level APIs for building tables, chains, rules, maps and sets. Works with NFTables.Port for communicating with the kernel firewall.
Current section
Files
Jump to
Current section
Files
lib/nftables/expr/payload.ex
defmodule NFTables.Expr.Payload do
@moduledoc """
Raw payload matching functions for deep packet inspection.
This module provides functions to match arbitrary bytes at specific offsets within
packet headers, bypassing protocol-specific parsing. This is essential for custom
protocols, deep packet inspection (DPI), and advanced packet manipulation.
## Important: Bit-Level Operations
**All offsets and lengths are specified in BITS, not bytes!**
To convert: `byte_offset * 8 = bit_offset`
Example: Byte offset 12 = Bit offset 96 (12 × 8)
## Base References
- `: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)
## Common Use Cases
- Custom protocol matching
- Deep packet inspection
- Protocol extension headers
- Tunneled packet inspection
- Advanced DPI rules
## Import
import NFTables.Expr.Payload
For more information, see the [nftables payload expressions wiki](https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_headers).
"""
alias NFTables.Expr
@doc """
Match raw payload bytes at specific offset.
Allows matching arbitrary bytes at specific offsets within packet headers.
Remember: offsets and lengths are in **bits**, not bytes!
## Parameters
- `builder` - Expression builder
- `base` - Base reference point (`:ll`, `:nh`, `:th`, `:ih`)
- `offset` - **Bit** offset from base (not bytes!)
- `length` - Number of **bits** to match
- `value` - Value to match against (integer, binary, or string)
## Examples
# Match DNS port (53) using raw payload at transport header offset 16 bits
udp()
|> payload_raw(:th, 16, 16, 53)
|> accept()
# Match IPv4 source address 192.168.1.1 (network header, byte 12-15 = bits 96-127)
payload_raw(:nh, 96, 32, <<192, 168, 1, 1>>)
|> drop()
# Match IPv6 next header field (routing header = 43)
protocol(:ipv6)
|> payload_raw(:nh, 48, 8, 43)
|> drop()
# Match first 4 bytes of HTTP GET request (inner header)
tcp()
|> dport(80)
|> payload_raw(:ih, 0, 32, "GET ")
|> log("HTTP GET")
|> accept()
# Match TCP SYN flag (transport header byte 13, bit 1)
payload_raw(:th, 104, 8, 0x02)
|> log("SYN packet")
## Offset Calculation Examples
### TCP Header
- Source port: byte 0-1 = bits 0-15
- Dest port: byte 2-3 = bits 16-31
- Sequence: byte 4-7 = bits 32-63
- Flags: byte 13 = bits 104-111
### IPv4 Header
- Protocol: byte 9 = bits 72-79
- Source IP: byte 12-15 = bits 96-127
- Dest IP: byte 16-19 = bits 128-159
## Notes
- Network byte order (big endian) is assumed
- Values can be integers, binaries, or strings
- For string values, they're converted to bytes
"""
@spec payload_raw(Expr.t(), atom(), non_neg_integer(), pos_integer(), term()) :: Expr.t()
def payload_raw(builder \\ Expr.expr(), base, offset, length, value) do
expr = Expr.Structs.payload_raw_match(base, offset, length, value)
Expr.add_expr(builder, expr)
end
@doc """
Match raw payload with bitwise AND mask.
Allows checking specific bits within a field using bitwise operations. Useful for
flag checking, field masking, and partial field matching.
## How It Works
1. Extract bits from packet: `field = packet[offset:offset+length]`
2. Apply mask: `masked = field & mask`
3. Compare: `masked == value`
## Parameters
- `builder` - Expression builder
- `base` - Base reference (`:ll`, `:nh`, `:th`, `:ih`)
- `offset` - **Bit** offset from base
- `length` - Number of **bits**
- `mask` - Bitmask to apply (integer)
- `value` - Value to match after masking
## Examples
# Check TCP SYN flag (bit 1 in TCP flags byte at offset 104 bits)
tcp()
|> payload_raw_masked(:th, 104, 8, 0x02, 0x02)
|> accept()
# Check IP DF (Don't Fragment) flag (bit 14 in flags field)
payload_raw_masked(:nh, 48, 16, 0x4000, 0x4000)
|> drop()
# Check if specific bit is set in custom protocol
payload_raw_masked(:ih, 8, 8, 0x80, 0x80)
|> counter()
|> accept()
# Match TCP flags: SYN+ACK (check bits 0 and 1)
payload_raw_masked(:th, 104, 8, 0x12, 0x12)
|> log("SYN-ACK")
## TCP Flags Reference
TCP flags byte (offset 104 bits / byte 13):
- `0x01` - FIN
- `0x02` - SYN
- `0x04` - RST
- `0x08` - PSH
- `0x10` - ACK
- `0x20` - URG
## Use Cases
- TCP flag checking (SYN, ACK, FIN, RST)
- IP flag inspection (DF, MF)
- Custom protocol bit flags
- Selective field matching
- Partial value extraction
"""
@spec payload_raw_masked(
Expr.t(),
atom(),
non_neg_integer(),
pos_integer(),
integer(),
integer()
) :: Expr.t()
def payload_raw_masked(builder \\ Expr.expr(), base, offset, length, mask, value) do
payload_expr = Expr.Structs.payload_raw(base, offset, length)
expr = Expr.Structs.bitwise_and_match(payload_expr, mask, value)
Expr.add_expr(builder, expr)
end
@doc """
Extract raw payload value for use in other operations.
Returns a payload expression that can be used as a key or value in set operations,
packet mangling, or other advanced matching. This is a lower-level function that
doesn't directly add to the builder.
## Parameters
- `base` - Base reference (`:ll`, `:nh`, `:th`, `:ih`)
- `offset` - **Bit** offset from base
- `length` - Number of **bits** to extract
## Examples
# Extract source IP as raw payload for set matching
key = payload_raw_expr(:nh, 96, 32)
# Use in advanced matching
custom_match(key, "value")
## Use Cases
- Set lookup keys
- Dynamic value extraction
- Packet mangling sources
- Advanced rule compositions
"""
@spec payload_raw_expr(atom(), non_neg_integer(), pos_integer()) :: map()
def payload_raw_expr(base, offset, length) do
Expr.Structs.payload_raw(base, offset, length)
end
end