From: Petr Špaček Date: Fri, 29 Mar 2019 15:35:31 +0000 (+0100) Subject: DoH experiment X-Git-Tag: v4.0.0~10^2~24 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=53fef489fa0ea8f33449a71550a8887c43b4ed7f;p=thirdparty%2Fknot-resolver.git DoH experiment First version which actually works with Firefox DoH in default configuration. Limitations: - does not support HTTP GET method - headers for HTTP cache are not generated - error handling is largely missing - no tests - ACLs will not work, modules do not see source IP address of the HTTP endpoint --- diff --git a/daemon/bindings/worker.c b/daemon/bindings/worker.c index a393a8651..e19f4bd01 100644 --- a/daemon/bindings/worker.c +++ b/daemon/bindings/worker.c @@ -18,6 +18,73 @@ #include "daemon/worker.h" + static void stackDump (lua_State *L) { + int i; + int top = lua_gettop(L); + for (i = 1; i <= top; i++) { /* repeat for each level */ + int t = lua_type(L, i); + printf("%d ", i); /* put a separator */ + switch (t) { + + case LUA_TSTRING: /* strings */ + printf("`%s'", lua_tostring(L, i)); + break; + + case LUA_TBOOLEAN: /* booleans */ + printf(lua_toboolean(L, i) ? "true" : "false"); + break; + + case LUA_TNUMBER: /* numbers */ + printf("%g", lua_tonumber(L, i)); + break; + + default: /* other values */ + printf("%s", lua_typename(L, t)); + break; + + } + printf(" "); /* put a separator */ + } + printf("\n"); /* end the listing */ + } + + +static int wrk_resolve_pkt(lua_State *L) +{ + stackDump(L); + struct worker_ctx *worker = wrk_luaget(L); + if (!worker) { + return 0; + } + + // FIXME: merge with wrk_resolve + const struct kr_qflags options = {}; + knot_pkt_t *pkt = *(knot_pkt_t **)lua_topointer(L, 1); + printf("%p\n", pkt); + if (!pkt) + lua_error_maybe(L, ENOMEM); + + printf("%s\n", kr_pkt_text(pkt)); + + /* Create task and start with a first question */ + + struct qr_task *task = worker_resolve_start(worker, pkt, options); + if (!task) { + lua_error_p(L, "couldn't create a resolution request"); + } + + /* Add initialisation callback */ + if (lua_isfunction(L, 2)) { + lua_pushvalue(L, 2); + lua_pushlightuserdata(L, worker_task_request(task)); + (void) execute_callback(L, 1); + } + + /* Start execution */ + int ret = worker_resolve_exec(task, pkt); + lua_pushboolean(L, ret == 0); + return 1; +} static int wrk_resolve(lua_State *L) { @@ -148,6 +215,7 @@ int kr_bindings_worker(lua_State *L) { static const luaL_Reg lib[] = { { "resolve_unwrapped", wrk_resolve }, + { "resolve_unwrapped_pkt", wrk_resolve_pkt }, { "stats", wrk_stats }, { NULL, NULL } }; diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua index 10c9e3dd5..9d8d970bb 100644 --- a/daemon/lua/kres-gen.lua +++ b/daemon/lua/kres-gen.lua @@ -440,4 +440,5 @@ int zs_parse_record(zs_scanner_t *); int zs_set_input_file(zs_scanner_t *, const char *); int zs_set_input_string(zs_scanner_t *, const char *, size_t); const char *zs_strerror(const int); +uint32_t packet_ttl(const knot_pkt_t *pkt, bool is_negative); ]] diff --git a/daemon/lua/sandbox.lua.in b/daemon/lua/sandbox.lua.in index 3d79b6615..481f01951 100644 --- a/daemon/lua/sandbox.lua.in +++ b/daemon/lua/sandbox.lua.in @@ -66,6 +66,28 @@ worker.resolve = function (qname, qtype, qclass, options, finish, init) return worker.resolve_unwrapped(qname, qtype, qclass, options, init_cb) end +worker.resolve_pkt = function (pkt, finish, init) + local init_cb, finish_cb = init, nil + if finish then + -- Create callback for finalization + finish_cb = ffi.cast('trace_callback_f', function (req) + req = kres.request_t(req) + finish(req.answer, req) + finish_cb:free() + end) + -- Wrap initialiser to install finish callback + init_cb = function (req) + req = kres.request_t(req) + if init then init(req) end + req.trace_finish = finish_cb + end + end + + -- Translate options and resolve + return worker.resolve_unwrapped_pkt(pkt, init_cb) +end + + resolve = worker.resolve -- Shorthand for aggregated per-worker information diff --git a/lib/cache/entry_pkt.c b/lib/cache/entry_pkt.c index 00e73d4ff..34562c454 100644 --- a/lib/cache/entry_pkt.c +++ b/lib/cache/entry_pkt.c @@ -26,7 +26,8 @@ /** Compute TTL for a packet. Generally it's minimum TTL, with extra conditions. */ -static uint32_t packet_ttl(const knot_pkt_t *pkt, bool is_negative) +KR_EXPORT +uint32_t packet_ttl(const knot_pkt_t *pkt, bool is_negative) { bool has_ttl = false; uint32_t ttl = UINT32_MAX; diff --git a/lib/cache/util.h b/lib/cache/util.h new file mode 100644 index 000000000..ecf11aa8a --- /dev/null +++ b/lib/cache/util.h @@ -0,0 +1,3 @@ +#include + +uint32_t packet_ttl(const knot_pkt_t *pkt, bool is_negative); diff --git a/modules/http/http.lua.in b/modules/http/http.lua.in index 86736be32..ed4866234 100644 --- a/modules/http/http.lua.in +++ b/modules/http/http.lua.in @@ -108,6 +108,12 @@ for k, v in pairs(http_trace.endpoints) do end M.trace = http_trace +local http_doh = require('kres_modules.http_doh') +for k, v in pairs(http_doh.endpoints) do + M.endpoints[k] = v +end +M.doh = http_doh + -- Export HTTP service page snippets M.snippets = {} diff --git a/modules/http/http_doh.lua b/modules/http/http_doh.lua new file mode 100644 index 000000000..705505721 --- /dev/null +++ b/modules/http/http_doh.lua @@ -0,0 +1,101 @@ +local ffi = require('ffi') +local condition = require('cqueues.condition') + +local function get_http_ttl(pkt) + -- minimum TTL from all RRs in ANSWER + if true then + local an_records = pkt:section(kres.section.ANSWER) + local is_negative = #an_records <= 0 + -- FIXME: does not work for positive answers + return ffi.C.packet_ttl(pkt, is_negative) + end + + -- garbage + if an_count > 0 then + local min_ttl = 4294967295 + for i = 1, an_count do + local rr = an_records[i] + min_ttl = math.min(rr.ttl, min_ttl) + end + return min_ttl + end + + -- no ANSWER records, try SOA + local auth_records = pkt:section(kres.section.AUTHORITY) + local auth_count = #auth_records + if auth_count > 0 then + for i = 1, an_count do + local rr = an_records[i] + if rr.type == kres.type.SOA then + knot_soa_minimum() + end + end + return 0 -- no SOA, uncacheable + end +end + +-- Trace execution of DNS queries +local function serve_doh(h, stream) + local path = h:get(':path') + local input = stream:get_body_as_string(10) -- FIXME: timeout + -- Output buffer + local output = '' + + -- 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 answers, authority = {}, {} + local cond = condition.new() + local waiting, done = false, false + local finish_cb = function (answer, req) + print(tostring(answer)) + + print('TTL: ', get_http_ttl(answer)) + + -- binary output + output = ffi.string(answer.wire, answer.size) + if waiting then + cond:signal() + end + done = true + end + + -- Resolve query + wire = ffi.cast("void *", input) + local pkt = ffi.C.knot_pkt_new(wire, #input, nil); + if not pkt then + output = 'shit happened in knot_pkt_new' + else + output = 'knot_pkt_new ok' + end + + local result = ffi.C.knot_pkt_parse(pkt, 0) + if result > 0 then + output = output .. '\nshit in knot_pkt_parse' + else + output = output .. '\nknot_pkt_parse ok' + end + print(pkt) + print(output) + worker.resolve_pkt(pkt, finish_cb) + -- worker.resolve("www.nic.cz", 666, KNOT_CLASS_IN, NULL, finish_cb) + + -- Wait for asynchronous query and free callbacks + if not done then + waiting = true + cond:wait() + end + + -- Return buffered data + if not done then + return 504, 'huh?' -- FIXME + end + return output, nil, 'application/dns-message' +end + +-- Export endpoints +return { + endpoints = { + ['/doh'] = {'text/plain', serve_doh}, + } +} diff --git a/modules/http/meson.build b/modules/http/meson.build index 8a4ac23c7..8817e82ba 100644 --- a/modules/http/meson.build +++ b/modules/http/meson.build @@ -11,6 +11,7 @@ lua_http = configure_file( lua_mod_src += [ lua_http, + files('http_doh.lua'), files('http_trace.lua'), files('prometheus.lua'), ]