Commit 7fae33f4 authored by Grigorii Demidov's avatar Grigorii Demidov

Merge branch 'lua-add-per-request-variables' into 'master'

daemon: allow per-request variables in Lua

See merge request !533
parents d316da92 14de9110
Pipeline #36695 passed with stages
in 12 minutes and 15 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