comparison mod_firewall/trace.lib.lua @ 6538:de1f17ed4dac

mod_firewall: Add support for tracing rule evaluation in real-time
author Matthew Wild <mwild1@gmail.com>
date Mon, 11 May 2026 13:37:19 +0100
parents
children
comparison
equal deleted inserted replaced
6537:9bc83fba89ac 6538:de1f17ed4dac
1 local async = require "prosody.util.async";
2
3 local tracers = {};
4
5 local trace_mt = {
6 __close = function (t)
7 for session, options in pairs(tracers) do
8 if not options.tag or options.tag == (t.tag or "untagged") then
9 session.print(("\n## Evaluating [%s::%s] (%s)"):format(t.filename, t.chain, t.tag or "untagged"));
10 local stanza_log = options.stanzas;
11 if stanza_log == "full" then
12 session.print(t.stanza);
13 elseif not stanza_log or stanza_log == "header" then
14 session.print(t.stanza:top_tag());
15 end
16
17 for rule_n, rule_trace in ipairs(t.rules) do
18 local source = t.source[rule_n].source;
19 for cond_n, matched in ipairs(rule_trace) do
20 session.print(
21 ("%s %s"):format(
22 matched and "✔" or "✖",
23 source.conditions[cond_n] or ("Rule %d condition %d"):format(rule_n, cond_n)
24 )
25 );
26 end
27 if rule_trace.matched then
28 for _, action in ipairs(source.actions) do
29 session.print("➜ "..action);
30 end
31 end
32 end
33 end
34 end
35 end;
36 };
37
38 local function init(filename, chain_name, stanza, tag, source_rules)
39 local rule_trace = {};
40 local trace = setmetatable({
41 filename = filename;
42 chain = chain_name;
43 rules = rule_trace;
44 stanza = stanza;
45 tag = tag;
46 source = source_rules;
47 }, trace_mt);
48 return trace, function (rule_n, cond_n, result)
49 local rule = rule_trace[rule_n];
50 if not rule then
51 rule = {};
52 rule_trace[rule_n] = rule;
53 end
54
55 if cond_n then
56 rule[cond_n] = result;
57 return result;
58 else
59 -- Rule finished
60 rule.matched = result;
61 end
62 end;
63 end
64
65
66 module:add_item("shell-command", {
67 section = "firewall";
68 section_desc = "mod_firewall commands";
69 name = "trace";
70 desc = "Trace executions";
71 args = {
72 { name = "host", type = "string" };
73 };
74 host_selector = "host";
75 flags = {
76 short_params = { t = "tag" };
77 value_params = { tag = true, stanzas = true };
78 };
79 handler = function(self, host, opts) --luacheck: ignore 212/self 212/host
80 self.session.print("# Tracing mod_firewall on "..host);
81 tracers[self.session] = opts or {};
82
83 while self.session.is_connected() do
84 async.sleep(3);
85 end
86
87 tracers[self.session] = nil;
88 return true;
89 end;
90 });
91
92
93 return { init = init };