Skip to main content

lib/linx/netfilter/event.ex

defmodule Linx.Netfilter.Event do
  @moduledoc """
  A single multicast event from `NFNLGRP_NFTABLES` — a
  notification the kernel broadcasts after every successful
  ruleset commit.

  Each committed transaction generates a sequence of events on
  the multicast group:

    1. One `NFT_MSG_NEWGEN` with the new generation id and the
       committing process's pid / name.
    2. One event per entity created, modified, or deleted in the
       commit (`NFT_MSG_NEWTABLE`, `NFT_MSG_DELRULE`, …).

  `Linx.Netfilter.Monitor` walks this stream, attaching the most
  recent `NEWGEN`'s `gen_id` / `proc_pid` / `proc_name` to every
  subsequent entity event, so each `%Event{}` carries the full
  context of "who changed this and when".

  ## Fields

    * `:gen_id` — the ruleset generation this event belongs to
      (matches the gen returned by `Linx.Netlink.Nfnl.Codec.get_gen/1`
      at the moment of the commit). Nil only for `:new_gen` events
      that precede any committed change in the stream — rare.
    * `:proc_pid` — pid of the process that committed; nil if the
      kernel didn't include it (some configs / older kernels).
    * `:proc_name` — comm string of the committer (e.g. `"nft"`,
      `"beam.smp"`).
    * `:op` — what happened. One of:
      * `:new_gen` — the commit itself (gen-bump). `entity` is
        `%{id, proc_pid, proc_name}` redundantly.
      * `:new_table` / `:del_table` — table created / destroyed.
      * `:new_chain` / `:del_chain` — chain created / destroyed.
      * `:new_rule` / `:del_rule` — rule appended / removed.
      * `:new_set` / `:del_set`.
      * `:new_set_element` / `:del_set_element`.
      * `:new_obj` / `:del_obj`.
      * `:new_flowtable` / `:del_flowtable`.
    * `:entity` — the decoded value. The shape depends on `:op`:
      * `:new_table` / `:del_table` → `%Linx.Netfilter.Table{}`.
      * `:new_chain` / `:del_chain` → `{family, %Linx.Netfilter.Chain{}}`.
      * `:new_rule` / `:del_rule` → `{family, table_name, chain_name,
        %Linx.Netfilter.Rule{}}`.
      * `:new_set` / `:del_set` → `{family, %Set{} | %Map{}}`.
      * `:new_set_element` / `:del_set_element` →
        `{family, table_name, set_name, [elements]}`.
      * `:new_gen` → `%{id, proc_pid, proc_name}`.
      * Other ops → opaque binary payload (decoder fallback).

  ## Inspect

      #Linx.Netfilter.Event<gen=1247 nft new_rule inet/myapp/input>
  """

  @enforce_keys [:op]
  defstruct [:op, :entity, :gen_id, :proc_pid, :proc_name]

  @type op ::
          :new_gen
          | :new_table
          | :del_table
          | :new_chain
          | :del_chain
          | :new_rule
          | :del_rule
          | :new_set
          | :del_set
          | :new_set_element
          | :del_set_element
          | :new_obj
          | :del_obj
          | :new_flowtable
          | :del_flowtable
          | {:unknown, non_neg_integer()}

  @type t :: %__MODULE__{
          op: op(),
          entity: term(),
          gen_id: non_neg_integer() | nil,
          proc_pid: non_neg_integer() | nil,
          proc_name: String.t() | nil
        }

  defimpl Inspect do
    def inspect(%Linx.Netfilter.Event{} = e, _opts) do
      who = if e.proc_name, do: "#{e.proc_name} ", else: ""
      gen = if e.gen_id, do: "gen=#{e.gen_id} ", else: ""
      where = render_where(e.op, e.entity)
      "#Linx.Netfilter.Event<#{gen}#{who}#{e.op}#{where}>"
    end

    defp render_where(:new_gen, _), do: ""

    defp render_where(op, _) when op in [:new_table, :del_table] do
      ""
    end

    defp render_where(op, {family, %{table: t, name: n}})
         when op in [:new_chain, :del_chain, :new_set, :del_set] do
      " #{family}/#{t}/#{n}"
    end

    defp render_where(op, {family, table, chain, _rule})
         when op in [:new_rule, :del_rule] do
      " #{family}/#{table}/#{chain}"
    end

    defp render_where(op, {family, table, set, _elems})
         when op in [:new_set_element, :del_set_element] do
      " #{family}/#{table}/#{set}"
    end

    defp render_where(_, _), do: ""
  end
end