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 {
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};
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,
},
})
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)) {
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. */
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;
}
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. */
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
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
return state
end
}
-return mod
+
+return M
--- /dev/null
+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
.. 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).
+
struct hints_data {
struct kr_zonecut hints;
struct kr_zonecut reverse_hints;
+ bool use_nodata;
};
/** Useful for returning from module properties. */
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);
}
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);
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);
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)
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:
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.
*/
{ &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;