Commit 14de9110 authored by Marek Vavruša's avatar Marek Vavruša

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.
parent d316da92
Pipeline #36690 passed with stages
in 11 minutes and 22 seconds
......@@ -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 {
......
......@@ -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};
......
......@@ -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,
},
})
......
......@@ -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;
}
......
......@@ -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. */
......
......@@ -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;
};
......
-- 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
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
......@@ -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).
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment