]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
daemon: allow per-request variables in Lua
authorMarek Vavruša <mvavrusa@cloudflare.com>
Wed, 28 Mar 2018 05:43:53 +0000 (22:43 -0700)
committerMarek Vavruša <mvavrusa@cloudflare.com>
Wed, 30 May 2018 18:59:40 +0000 (11:59 -0700)
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
daemon/lua/kres-gen.lua
daemon/lua/kres.lua
daemon/worker.c
daemon/worker.h
lib/resolve.h
modules/dns64/dns64.lua
modules/dns64/dns64.test.lua [new file with mode: 0644]
modules/hints/README.rst
modules/hints/hints.c

index 7dc89d431a307f9c576e77d05b9e229fb50fedea..05c078dba82da4851f51ea5ca0f00f7421f22be1 100644 (file)
@@ -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 {
index 023bbc6cda27a230514b9a028ced704f49868296..732e7ff69115c1de556d7449902952ad78e9b99b 100644 (file)
@@ -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};
index d781d84c8b1675361bdd1d93dc1986030faa8d8b..f3f1b649b6dbe835d891d2711fdc49f1be9c180b 100644 (file)
@@ -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,
        },
 })
 
index f61c9b588b06d633c31e42dbf3f31ce4c4b1fd23..fb4091d78a3b4dc519aff60fb9055a346d9c0547 100644 (file)
@@ -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;
 }
index c65528b1d98099a3bb8db6f2399056670dbdffb8..0d1bcc20825ff82ef65f2ab06c803f5e5170208e 100644 (file)
@@ -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. */
index 011679ec676f1491892b3026d18245c9bacf4dfd..95598e80b11fdc6a396f8604381a4802fb7c9fba 100644 (file)
@@ -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;
 };
 
index 6b527e956a420c275ac7a2c333c014701f713dff..1c65aff049344e8afcb9b4f11322f9e6920ef371 100644 (file)
@@ -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 (file)
index 0000000..1798ccd
--- /dev/null
@@ -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
index fef40715749d565772ef289c37968a7fb24eee7f..c28f5683873b8769edf25b98005fbb9d687e62a4 100644 (file)
@@ -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).
+
index 457729d84f174c0a934b81a96dc33f683dcbdf59..315678460471295b631672377bbf1b47192910aa 100644 (file)
@@ -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;