Wire-format helpers for nfnetlink (NETLINK_NETFILTER, protocol 12).
Three pieces of nfnetlink machinery live here:
1. The nfgenmsg header
Every nfnetlink message body starts with a fixed 4-byte header
(include/uapi/linux/netfilter/nfnetlink.h):
struct nfgenmsg {
__u8 nfgen_family; /* AF_* address family */
__u8 version; /* NFNETLINK_V0 = 0 */
__be16 res_id; /* subsystem-specific, BIG-ENDIAN on the wire */
};Linx.Netlink.Codec defaults to native byte order, but res_id is
documented as __be16 and is interpreted that way by the kernel —
notably as the target subsystem id on NFNL_MSG_BATCH_BEGIN /
_END and as the group number for NFNL_SUBSYS_ULOG config
messages. encode_nfgenmsg/3 and decode_nfgenmsg/1 handle the
byte-order asymmetry directly so the generic codec stays
byte-order-uniform.
2. Subsys-id multiplexing on nlmsghdr.type
Inside nfnetlink, the netlink message type is
(subsys_id << 8) | msg_type — the high byte selects which
sub-subsystem the message targets, the low byte selects the operation
within that subsystem. nlmsg_type/2 and split_type/1 are the
conversion helpers; subsys_* constants are the well-known subsys
ids.
3. Batched transactions
Every nf_tables ruleset mutation must sit between an
NFNL_MSG_BATCH_BEGIN and an NFNL_MSG_BATCH_END (the global
envelope types 0x10 / 0x11, defined in nfnetlink.h). The kernel
applies the inner messages atomically or rejects the batch whole.
batch_begin/1..2 and batch_end/1 produce the envelope messages —
the caller stitches them around the inner request stream.
Why this is its own module, not part of Linx.Netlink.Codec
The general codec is byte-order-uniform (native) and per-family
agnostic. nfnetlink's __be16 res_id and high-byte subsys
multiplexing are netfilter-specific quirks; keeping them here keeps
the shared codec clean.
Summary
Functions
Constructs a NFNL_MSG_BATCH_BEGIN envelope message targeting the
named sub-subsystem.
Constructs a NFNL_MSG_BATCH_END envelope message closing the batch.
Decodes the leading 4 bytes of a binary as a nfgenmsg header.
Encodes a 4-byte nfgenmsg header.
Sends NFT_MSG_GETGEN and returns the kernel's reply.
NFPROTO_* address-family constants for the nfgen_family header
field.
Packs a nfnetlink (subsys_id, msg_type) pair into the 16-bit
nlmsghdr.type the wire format uses.
Splits a nlmsghdr.type from nfnetlink back into {subsys_id, msg_type}.
NFNL_SUBSYS_CTNETLINK — the conntrack sub-subsystem id (1).
NFNL_SUBSYS_NFTABLES — the nf_tables sub-subsystem id (10).
NFNL_SUBSYS_QUEUE — the NFQUEUE sub-subsystem id (3).
NFNL_SUBSYS_ULOG — the NFLOG sub-subsystem id (4).
Functions
@spec batch_begin( :nftables | :ctnetlink | :queue | :ulog | 0..255, keyword() ) :: Linx.Netlink.Message.t()
Constructs a NFNL_MSG_BATCH_BEGIN envelope message targeting the
named sub-subsystem.
Returns a %Message{} with type = NFNL_MSG_BATCH_BEGIN (0x10),
payload = the 4-byte nfgenmsg carrying the target subsys id as
res_id. Caller fills in seq before encoding.
When genid is provided, appends NFNL_BATCH_GENID (attribute id
1, u32 BE) to the payload — the kernel rejects the batch with
-ERESTART at commit time if the netns ruleset generation has
advanced since genid was read. Used by :reconcile-mode pushes
for optimistic concurrency.
Examples
# Begin a batch targeting nf_tables:
iex> msg = Linx.Netlink.Nfnl.Codec.batch_begin(:nftables)
iex> msg.type
16 # NFNL_MSG_BATCH_BEGIN
iex> msg.payload
<<0, 0, 0, 10>> # nfgenmsg with res_id = NFNL_SUBSYS_NFTABLES (10)
@spec batch_end(:nftables | :ctnetlink | :queue | :ulog | 0..255) :: Linx.Netlink.Message.t()
Constructs a NFNL_MSG_BATCH_END envelope message closing the batch.
Returns a %Message{} with type = NFNL_MSG_BATCH_END (0x11). The
res_id mirrors the begin message but the kernel only acts on
end-of-batch signalling; the value is informational.
Decodes the leading 4 bytes of a binary as a nfgenmsg header.
Returns {family, version, res_id, rest}. family is returned as
the raw integer (caller maps to atom if useful); res_id is decoded
from big-endian.
Raises ArgumentError if the binary is shorter than 4 bytes.
@spec encode_nfgenmsg(atom() | 0..255, 0..65535) :: <<_::32>>
Encodes a 4-byte nfgenmsg header.
family is one of the NFPROTO_* constants (see nfproto/1);
res_id is sub-subsystem-specific and is __be16 on the wire.
Defaults: family: :unspec, res_id: 0.
Examples
iex> Linx.Netlink.Nfnl.Codec.encode_nfgenmsg()
<<0, 0, 0, 0>>
# BATCH_BEGIN targeting NFTABLES (res_id = 10, big-endian):
iex> Linx.Netlink.Nfnl.Codec.encode_nfgenmsg(:unspec, 10)
<<0, 0, 0, 10>>
# NFLOG (ULOG) config for group 5000 (res_id big-endian):
iex> Linx.Netlink.Nfnl.Codec.encode_nfgenmsg(:unspec, 5000)
<<0, 0, 19, 136>>
@spec get_gen(Linx.Netlink.Socket.t()) :: {:ok, %{ id: non_neg_integer(), proc_pid: non_neg_integer() | nil, proc_name: String.t() | nil }} | {:error, term()}
Sends NFT_MSG_GETGEN and returns the kernel's reply.
The reply is a single NFT_MSG_NEWGEN message carrying NFTA_GEN_ID
(32-bit monotonic generation counter) and — when the kernel was
built with the relevant config — NFTA_GEN_PROC_PID /
NFTA_GEN_PROC_NAME attributes attributing the most recent commit.
Examples
iex> {:ok, sock} = Linx.Netlink.Nfnl.open()
iex> {:ok, gen} = Linx.Netlink.Nfnl.Codec.get_gen(sock)
iex> is_integer(gen.id) and gen.id >= 0
true
@spec nfproto(atom()) :: 0..255
NFPROTO_* address-family constants for the nfgen_family header
field.
@spec nlmsg_type(0..255, 0..255) :: 0..65535
Packs a nfnetlink (subsys_id, msg_type) pair into the 16-bit
nlmsghdr.type the wire format uses.
Inside NETLINK_NETFILTER, the high byte is the sub-subsystem id
and the low byte is the operation. nlmsg_type(10, 0x10) (NFTABLES,
NEWGEN) produces 0x0a10.
@spec split_type(0..65535) :: {0..255, 0..255}
Splits a nlmsghdr.type from nfnetlink back into {subsys_id, msg_type}.
Inverse of nlmsg_type/2.
@spec subsys_ctnetlink() :: 1
NFNL_SUBSYS_CTNETLINK — the conntrack sub-subsystem id (1).
@spec subsys_nftables() :: 10
NFNL_SUBSYS_NFTABLES — the nf_tables sub-subsystem id (10).
@spec subsys_queue() :: 3
NFNL_SUBSYS_QUEUE — the NFQUEUE sub-subsystem id (3).
@spec subsys_ulog() :: 4
NFNL_SUBSYS_ULOG — the NFLOG sub-subsystem id (4).