Linx.Netlink.Nfnl.Codec (Linx v0.1.0)

Copy Markdown View Source

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.

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

batch_begin(subsys, opts \\ [])

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

batch_end(subsys)

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

decode_nfgenmsg(arg1)

@spec decode_nfgenmsg(binary()) :: {0..255, 0..255, 0..65535, binary()}

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.

encode_nfgenmsg(family \\ :unspec, res_id \\ 0)

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

get_gen(socket)

@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

nfproto(atom)

@spec nfproto(atom()) :: 0..255

NFPROTO_* address-family constants for the nfgen_family header field.

nlmsg_type(subsys, msg_type)

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

split_type(type)

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

subsys_ctnetlink()

@spec subsys_ctnetlink() :: 1

NFNL_SUBSYS_CTNETLINK — the conntrack sub-subsystem id (1).

subsys_nftables()

@spec subsys_nftables() :: 10

NFNL_SUBSYS_NFTABLES — the nf_tables sub-subsystem id (10).

subsys_queue()

@spec subsys_queue() :: 3

NFNL_SUBSYS_QUEUE — the NFQUEUE sub-subsystem id (3).

subsys_ulog()

@spec subsys_ulog() :: 4

NFNL_SUBSYS_ULOG — the NFLOG sub-subsystem id (4).