summaryrefslogtreecommitdiffstats
path: root/modules/http/http_trace.lua
diff options
context:
space:
mode:
Diffstat (limited to 'modules/http/http_trace.lua')
-rw-r--r--modules/http/http_trace.lua77
1 files changed, 77 insertions, 0 deletions
diff --git a/modules/http/http_trace.lua b/modules/http/http_trace.lua
new file mode 100644
index 0000000..123cd52
--- /dev/null
+++ b/modules/http/http_trace.lua
@@ -0,0 +1,77 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+local condition = require('cqueues.condition')
+
+-- Trace execution of DNS queries
+local function serve_trace(h, _)
+ local path = h:get(':path')
+ local qname, qtype_str = path:match('/trace/([^/]+)/?([^/]*)')
+ if not qname then
+ return 400, 'expected /trace/<query name>/<query type>'
+ end
+
+ -- Parse query type (or default to A)
+ if not qtype_str or #qtype_str == 0 then
+ qtype_str = 'A'
+ end
+
+ local qtype = kres.type[qtype_str]
+ if not qtype then
+ return 400, string.format('unexpected query type: %s', qtype_str)
+ end
+
+ -- Create logging handler callback
+ local buffer = {}
+ local buffer_log_cb = ffi.cast('trace_log_f', function (_, msg)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ table.insert(buffer, ffi.string(msg))
+ end)
+
+ -- Wait for the result of the query
+ -- Note: We can't do non-blocking write to stream directly from resolve callbacks
+ -- because they don't run inside cqueue.
+ local cond = condition.new()
+ local waiting, done = false, false
+ local finish_cb = ffi.cast('trace_callback_f', function (req)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ table.insert(buffer, req:selected_tostring())
+ if waiting then
+ cond:signal()
+ end
+ done = true
+ end)
+
+ -- Resolve query and buffer logs into table
+ resolve {
+ name = qname,
+ type = qtype,
+ options = {'TRACE'},
+ init = function (req)
+ req:trace_chain_callbacks(buffer_log_cb, finish_cb)
+ end
+ }
+
+ -- Wait for asynchronous query and free callbacks
+ if not done then
+ waiting = true
+ cond:wait()
+ end
+
+ buffer_log_cb:free()
+ finish_cb:free()
+
+ -- Build the result
+ local result = table.concat(buffer, '')
+ -- Return buffered data
+ if not done then
+ return 504, result
+ end
+ return result
+end
+
+-- Export endpoints
+return {
+ endpoints = {
+ ['/trace'] = {'text/plain', serve_trace},
+ }
+}