Lunatik

netlink.channelModule

Generic netlink channel: a generic netlink family with one multicast group whose multicast/unicast are safe from softirq (netfilter hooks, XDP), for kernel-to-userspace delivery.

The message body is built in Lua (e.g. with netlink.message) and sent as-is; request/response netlink is otherwise done in Lua over the socket module. Only this softirq-capable send path needs a dedicated kernel object. A channel is created and used from a process or softirq runtime only, and new refuses a hardirq one: with an nlmon tap up, a send hands a copy of the message to dev_queue_xmit, which re-enables bottom halves, and the kernel warns when that happens in a hardirq. multicast and unicast refuse a call with IRQs disabled, where a softirq runtime’s callback can run too, as hid’s raw_event does behind a USB host controller that completes in its interrupt handler.

Usage

-- lunatik run -c softirq <script>: multicasts a notice for each IPv4 packet the host receives
local channel   = require("netlink.channel")
local message   = require("netlink.message")
local netfilter = require("netfilter")
local nf        = require("linux.nf")

local CMD <const>    = 1
local NOTICE <const> = 1

local family = channel.new("mychannel")
local notice = message.attrs{[NOTICE] = "packet"}

local function notify(skb)
  family:multicast(CMD, notice)
  return nf.action.ACCEPT
end

netfilter.register{
  hook     = notify,
  pf       = nf.proto.IPV4,
  hooknum  = nf.inet.PRE_ROUTING,
  priority = nf.ip.pri.FILTER,
}
netlink.channel:multicast (cmd[, payload]) Multicasts a message to every subscriber of the channel’s group.
netlink.channel:stop () Unregisters the channel’s family.
netlink.channel:unicast (portid, cmd[, payload]) Unicasts a message to a single userspace subscriber by port id.
new (name) Creates a generic netlink channel.
A generic netlink channel. Returned by netlink.channel.new(). Backed by a generic netlink family with one multicast group; multicast and unicast are safe from softirq (netfilter hooks, XDP) and deliver to userspace subscribers of the family.
netlink.channel:multicast (cmd[, payload])
Multicasts a message to every subscriber of the channel’s group. Safe to call from softirq.

Parameters

  • cmd integer Generic netlink command, from 0 to 255.
  • payload string Message body (e.g. from netlink.message). (optional)

Returns

    boolean whether it reached at least one subscriber.

Raises

“not allowed with IRQs disabled”; “out of bounds” if cmd is past 8 bits or negative, “closed object” if the channel has been stopped, or on an allocation error.
netlink.channel:stop ()
Unregisters the channel’s family. Userspace no longer resolves the family by name, its subscribers leave the group, and multicast and unicast raise. Calling it again does nothing, and a to-be-closed variable holding the channel stops it the same way.

Returns

    nil

Raises

“not allowed once the runtime is armed” from an interrupt-context runtime past its body; “not allowed under RTNL” from a netdevice callback, in whatever runtime or coroutine its task runs, since the unregistration takes a lock a request holds while it waits on RTNL.

Usage

family:stop()
netlink.channel:unicast (portid, cmd[, payload])
Unicasts a message to a single userspace subscriber by port id. Safe to call from softirq.

Parameters

  • portid integer Destination netlink port id, of a socket in the initial network namespace, from 1 to 0xffffffff.
  • cmd integer Generic netlink command, from 0 to 255.
  • payload string Message body (e.g. from netlink.message). (optional)

Returns

    boolean whether it was queued to the port id (false if the port id is gone or its receive buffer is full).

Raises

“not allowed with IRQs disabled”; “out of bounds” if portid is 0, negative or past 32 bits, or cmd is past 8 bits or negative, “closed object” if the channel has been stopped, or on an allocation error.
new (name)
Creates a generic netlink channel. Registers a generic netlink family name with a single multicast group, named lunatik; userspace resolves the family by name (e.g. via netlink.genl, or genl ctrl get name <name>) to learn that group’s id, and joins it with NETLINK_ADD_MEMBERSHIP. Like a netfilter hook, it must be created at script load (process context); its multicast/unicast may then be called from softirq.

Parameters

  • name string Generic netlink family name (up to GENL_NAMSIZ-1 bytes).

Returns

    netlink.channel the channel, which new keeps for its runtime: dropping it stops nothing, and the family stays registered until stop or the end of the runtime.

Raises

“not allowed once the runtime is armed” from an interrupt-context runtime past its body; “not allowed in a percpu runtime”; “not allowed in a hardirq runtime”; “not allowed under RTNL” from a netdevice callback, in whatever runtime or coroutine its task runs, since the registration takes a lock a request holds while it waits on RTNL; if the name is empty or too long, or if family registration fails.