From 14de9110d4b675c7daf110fc510bd6f30e203443 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20Vavru=C5=A1a?= Date: Tue, 27 Mar 2018 22:43:53 -0700 Subject: [PATCH] daemon: allow per-request variables in Lua The handlers in Lua can now store per-request variables that are automatically GC'd when the request is finished. This is useful for stateful modules, such as DNS64 that uses internal option flags for state tracking. The layers can now get a variable table like so: ``` local vars = kres.request_t(r):vars() vars.hello = true ``` The variables are persisted between different layers for each request. --- daemon/engine.c | 2 +- daemon/lua/kres-gen.lua | 1 + daemon/lua/kres.lua | 25 +++++++++++++++++ daemon/worker.c | 20 ++++++++++++++ daemon/worker.h | 1 + lib/resolve.h | 1 + modules/dns64/dns64.lua | 19 +++++++------ modules/dns64/dns64.test.lua | 52 ++++++++++++++++++++++++++++++++++++ modules/hints/README.rst | 7 +++++ modules/hints/hints.c | 45 +++++++++++++++++++++++-------- 10 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 modules/dns64/dns64.test.lua diff --git a/daemon/engine.c b/daemon/engine.c index 7dc89d431..05c078dba 100644 --- a/daemon/engine.c +++ b/daemon/engine.c @@ -565,7 +565,7 @@ static int l_trampoline(lua_State *L) const char *args = NULL; auto_free char *cleanup_args = NULL; if (lua_gettop(L) > 0) { - if (lua_istable(L, 1)) { + if (lua_istable(L, 1) || lua_isboolean(L, 1)) { cleanup_args = l_pack_json(L, 1); args = cleanup_args; } else { diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua index 023bbc6cd..732e7ff69 100644 --- a/daemon/lua/kres-gen.lua +++ b/daemon/lua/kres-gen.lua @@ -178,6 +178,7 @@ struct kr_request { int has_tls; trace_log_f trace_log; trace_callback_f trace_finish; + int vars_ref; knot_mm_t pool; }; enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32}; diff --git a/daemon/lua/kres.lua b/daemon/lua/kres.lua index d781d84c8..f3f1b649b 100644 --- a/daemon/lua/kres.lua +++ b/daemon/lua/kres.lua @@ -729,6 +729,31 @@ ffi.metatype( kr_request_t, { assert(ffi.istype(kr_request_t, req)) return C.kr_rplan_pop(C.kr_resolve_plan(req), qry) end, + -- Return per-request variable table + -- The request can store anything in this Lua table and it will be freed + -- when the request is closed, it doesn't have to worry about contents. + vars = function (req) + assert(ffi.istype(kr_request_t, req)) + -- Return variable if it's already stored + local var = worker.vars[req.vars_ref] + if var then + return var + end + -- Either take a slot number from freelist + -- or find a first free slot (expand the table) + local ref = worker.vars[0] + if ref then + worker.vars[0] = worker.vars[ref] + else + ref = #worker.vars + 1 + end + -- Create new variables table + var = {} + worker.vars[ref] = var + -- Save reference in the request + req.vars_ref = ref + return var + end, }, }) diff --git a/daemon/worker.c b/daemon/worker.c index f61c9b588..fb4091d78 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -531,6 +531,7 @@ static struct request_ctx *request_create(struct worker_ctx *worker, struct kr_request *req = &ctx->req; req->pool = pool; + req->vars_ref = LUA_NOREF; /* Remember query source addr */ if (!addr || (addr->sa_family != AF_INET && addr->sa_family != AF_INET6)) { @@ -614,6 +615,20 @@ static int request_start(struct request_ctx *ctx, knot_pkt_t *query) static void request_free(struct request_ctx *ctx) { struct worker_ctx *worker = ctx->worker; + /* Dereference any Lua vars table if exists */ + if (ctx->req.vars_ref != LUA_NOREF) { + lua_State *L = worker->engine->L; + /* Get worker variables table */ + lua_rawgeti(L, LUA_REGISTRYINDEX, worker->vars_table_ref); + /* Get next free element (position 0) and store it under current reference (forming a list) */ + lua_rawgeti(L, -1, 0); + lua_rawseti(L, -2, ctx->req.vars_ref); + /* Set current reference as the next free element */ + lua_pushinteger(L, ctx->req.vars_ref); + lua_rawseti(L, -2, 0); + lua_pop(L, 1); + ctx->req.vars_ref = LUA_NOREF; + } /* Return mempool to ring or free it if it's full */ pool_release(worker, ctx->req.pool.ctx); /* @note The 'task' is invalidated from now on. */ @@ -2523,6 +2538,11 @@ struct worker_ctx *worker_create(struct engine *engine, knot_mm_t *pool, lua_setfield(engine->L, -2, "pid"); lua_pushnumber(engine->L, worker_count); lua_setfield(engine->L, -2, "count"); + /* Register table for worker per-request variables */ + lua_newtable(engine->L); + lua_setfield(engine->L, -2, "vars"); + lua_getfield(engine->L, -1, "vars"); + worker->vars_table_ref = luaL_ref(engine->L, LUA_REGISTRYINDEX); lua_pop(engine->L, 1); return worker; } diff --git a/daemon/worker.h b/daemon/worker.h index c65528b1d..0d1bcc208 100644 --- a/daemon/worker.h +++ b/daemon/worker.h @@ -122,6 +122,7 @@ struct worker_ctx { uv_loop_t *loop; int id; int count; + int vars_table_ref; unsigned tcp_pipeline_max; /** Addresses to bind for outgoing connections or AF_UNSPEC. */ diff --git a/lib/resolve.h b/lib/resolve.h index 011679ec6..95598e80b 100644 --- a/lib/resolve.h +++ b/lib/resolve.h @@ -215,6 +215,7 @@ struct kr_request { int has_tls; trace_log_f trace_log; /**< Logging tracepoint */ trace_callback_f trace_finish; /**< Request finish tracepoint */ + int vars_ref; /**< Reference to per-request variable table. LUA_NOREF if not set. */ knot_mm_t pool; }; diff --git a/modules/dns64/dns64.lua b/modules/dns64/dns64.lua index 6b527e956..1c65aff04 100644 --- a/modules/dns64/dns64.lua +++ b/modules/dns64/dns64.lua @@ -1,22 +1,24 @@ -- Module interface local ffi = require('ffi') -local mod = {} +local M = {} local addr_buf = ffi.new('char[16]') + -- Config -function mod.config (confstr) +function M.config (confstr) if confstr == nil then return end - mod.proxy = kres.str2ip(confstr) - if mod.proxy == nil then error('[dns64] "'..confstr..'" is not a valid address') end + M.proxy = kres.str2ip(confstr) + if M.proxy == nil then error('[dns64] "'..confstr..'" is not a valid address') end end + -- Layers -mod.layer = { +M.layer = { consume = function (state, req, pkt) if state == kres.FAIL then return state end pkt = kres.pkt_t(pkt) req = kres.request_t(req) local qry = req:current() -- Observe only authoritative answers - if mod.proxy == nil or not qry.flags.RESOLVED then + if M.proxy == nil or not qry.flags.RESOLVED then return state end -- Synthetic AAAA from marked A responses @@ -31,7 +33,7 @@ mod.layer = { rrs._owner = ffi.cast('knot_dname_t *', orig:owner()) -- explicit cast needed here for k = 1, orig.rrs.rr_count do local rdata = orig:rdata( k - 1 ) - ffi.copy(addr_buf, mod.proxy, 16) + ffi.copy(addr_buf, M.proxy, 16) ffi.copy(addr_buf + 12, rdata, 4) ffi.C.knot_rrset_add_rdata(rrs, ffi.string(addr_buf, 16), 16, orig:ttl(), req.pool) end @@ -60,4 +62,5 @@ mod.layer = { return state end } -return mod + +return M diff --git a/modules/dns64/dns64.test.lua b/modules/dns64/dns64.test.lua new file mode 100644 index 000000000..1798ccd0e --- /dev/null +++ b/modules/dns64/dns64.test.lua @@ -0,0 +1,52 @@ +local condition = require('cqueues.condition') + +-- setup resolver +modules = { 'hints', 'dns64' } +hints['dns64.example'] = '192.168.1.1' +hints.use_nodata(true) -- Respond NODATA to AAAA query +dns64.config('fe80::21b:77ff:0:0') + +-- helper to wait for query resolution +local function wait_resolve(qname, qtype) + local waiting, done, cond = false, false, condition.new() + local rcode, answers = kres.rcode.SERVFAIL, {} + resolve { + name = qname, + type = qtype, + finish = function (answer, _) + answer = kres.pkt_t(answer) + rcode = answer:rcode() + answers = answer:section(kres.section.ANSWER) + -- Signal as completed + if waiting then + cond:signal() + end + done = true + end, + } + -- Wait if it didn't finish immediately + if not done then + waiting = true + cond:wait() + end + return rcode, answers +end + +-- test builtin rules +local function test_builtin_rules() + local rcode, answers = wait_resolve('dns64.example', kres.type.AAAA) + same(rcode, kres.rcode.NOERROR, 'dns64.example returns NOERROR') + same(#answers, 1, 'dns64.example synthesised answer') + local expect = {'dns64.example.', '0', 'AAAA', 'fe80::21b:77ff:c0a8:101'} + if #answers > 0 then + local rr = {kres.rr2str(answers[1]):match('(%S+)%s+(%S+)%s+(%S+)%s+(%S+)')} + same(rr, expect, 'dns64.example synthesised correct AAAA record') + end +end + +-- plan tests +local tests = { + test_builtin_rules, +} + +return tests \ No newline at end of file diff --git a/modules/hints/README.rst b/modules/hints/README.rst index fef407157..c28f56838 100644 --- a/modules/hints/README.rst +++ b/modules/hints/README.rst @@ -107,3 +107,10 @@ Properties .. tip:: A good rule of thumb is to select only a few fastest root hints. The server learns RTT and NS quality over time, and thus tries all servers available. You can help it by preselecting the candidates. +.. function:: hints.use_nodata(toggle) + + :param bool toggle: true if enabling NODATA synthesis, false if disabling + :return: ``{ result: bool }`` + + If set to true, NODATA will be synthesised for matching hint name, but mismatching type (e.g. AAAA query when only A hint exists). + diff --git a/modules/hints/hints.c b/modules/hints/hints.c index 457729d84..315678460 100644 --- a/modules/hints/hints.c +++ b/modules/hints/hints.c @@ -41,6 +41,7 @@ struct hints_data { struct kr_zonecut hints; struct kr_zonecut reverse_hints; + bool use_nodata; }; /** Useful for returning from module properties. */ @@ -52,17 +53,22 @@ static char * bool2jsonstr(bool val) return result; } -static int put_answer(knot_pkt_t *pkt, knot_rrset_t *rr) +static int put_answer(knot_pkt_t *pkt, struct kr_query *qry, knot_rrset_t *rr, bool use_nodata) { int ret = 0; - if (!knot_rrset_empty(rr)) { + if (!knot_rrset_empty(rr) || use_nodata) { /* Update packet question */ if (!knot_dname_is_equal(knot_pkt_qname(pkt), rr->owner)) { kr_pkt_recycle(pkt); - knot_pkt_put_question(pkt, rr->owner, rr->rclass, rr->type); + knot_pkt_put_question(pkt, qry->sname, qry->sclass, qry->stype); + } + if (!knot_rrset_empty(rr)) { + /* Append to packet */ + ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, rr, KNOT_PF_FREE); + } else { + /* Return empty answer if name exists, but type doesn't match */ + knot_wire_set_aa(pkt->wire); } - /* Append to packet */ - ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, rr, KNOT_PF_FREE); } else { ret = kr_error(ENOENT); } @@ -73,7 +79,7 @@ static int put_answer(knot_pkt_t *pkt, knot_rrset_t *rr) return ret; } -static int satisfy_reverse(struct kr_zonecut *hints, knot_pkt_t *pkt, struct kr_query *qry) +static int satisfy_reverse(struct kr_zonecut *hints, knot_pkt_t *pkt, struct kr_query *qry, bool use_nodata) { /* Find a matching name */ pack_t *addr_set = kr_zonecut_find(hints, qry->sname); @@ -92,10 +98,10 @@ static int satisfy_reverse(struct kr_zonecut *hints, knot_pkt_t *pkt, struct kr_ knot_rrset_add_rdata(&rr, addr_val, len, 0, &pkt->mm); } - return put_answer(pkt, &rr); + return put_answer(pkt, qry, &rr, use_nodata); } -static int satisfy_forward(struct kr_zonecut *hints, knot_pkt_t *pkt, struct kr_query *qry) +static int satisfy_forward(struct kr_zonecut *hints, knot_pkt_t *pkt, struct kr_query *qry, bool use_nodata) { /* Find a matching name */ pack_t *addr_set = kr_zonecut_find(hints, qry->sname); @@ -121,7 +127,7 @@ static int satisfy_forward(struct kr_zonecut *hints, knot_pkt_t *pkt, struct kr_ addr = pack_obj_next(addr); } - return put_answer(pkt, &rr); + return put_answer(pkt, qry, &rr, use_nodata); } static int query(kr_layer_t *ctx, knot_pkt_t *pkt) @@ -141,11 +147,11 @@ static int query(kr_layer_t *ctx, knot_pkt_t *pkt) switch(qry->stype) { case KNOT_RRTYPE_A: case KNOT_RRTYPE_AAAA: /* Find forward record hints */ - if (satisfy_forward(&data->hints, pkt, qry) != 0) + if (satisfy_forward(&data->hints, pkt, qry, data->use_nodata) != 0) return ctx->state; break; case KNOT_RRTYPE_PTR: /* Find PTR record */ - if (satisfy_reverse(&data->reverse_hints, pkt, qry) != 0) + if (satisfy_reverse(&data->reverse_hints, pkt, qry, data->use_nodata) != 0) return ctx->state; break; default: @@ -570,6 +576,22 @@ static char* hint_root_file(void *env, struct kr_module *module, const char *arg return strdup(err_msg ? err_msg : ""); } +static char* hint_use_nodata(void *env, struct kr_module *module, const char *args) +{ + struct hints_data *data = module->data; + if (!args) { + return NULL; + } + + JsonNode *root_node = json_decode(args); + if (!root_node || root_node->tag != JSON_BOOL) { + return bool2jsonstr(false); + } + + data->use_nodata = root_node->bool_; + return bool2jsonstr(true); +} + /* * Module implementation. */ @@ -656,6 +678,7 @@ struct kr_prop *hints_props(void) { &hint_add_hosts, "add_hosts", "Load a file with hosts-like formatting and add contents into hints.", }, { &hint_root, "root", "Replace root hints set (empty value to return current list).", }, { &hint_root_file, "root_file", "Replace root hints set from a zonefile.", }, + { &hint_use_nodata, "use_nodata", "Synthesise NODATA if name matches, but type doesn't.", }, { NULL, NULL, NULL } }; return prop_list; -- 2.47.2