FIXME: see FIXMEs in diff, document the API change, re-review.
- avoid an assert() error in stash_rrset() (!1072)
- fix emergency cache locking bug introduced in 5.1.3 (!1078)
+Incompatible changes
+--------------------
+- minor changes in module API; see upgrading guide:
+ https://knot-resolver.readthedocs.io/en/stable/upgrading.html
+
Knot Resolver 5.1.3 (2020-09-08)
================================
knot_pkt_t *knot_pkt_new(void *, uint16_t, knot_mm_t *);
void knot_pkt_free(knot_pkt_t *);
int knot_pkt_parse(knot_pkt_t *, unsigned int);
+knot_pkt_t *kr_request_ensure_answer(struct kr_request *);
struct kr_rplan *kr_resolve_plan(struct kr_request *);
knot_mm_t *kr_resolve_pool(struct kr_request *);
struct kr_query *kr_rplan_push(struct kr_rplan *, struct kr_query *, const knot_dname_t *, uint16_t, uint16_t);
## libkres API
${CDEFS} ${LIBKRES} functions <<-EOF
# Resolution request
+ kr_request_ensure_answer
kr_resolve_plan
kr_resolve_pool
# Resolution plan
req.vars_ref = ref
return var
end,
+ -- Ensure that answer exists and return it; can't fail.
+ ensure_answer = function (req)
+ assert(ffi.istype(kr_request_t, req))
+ return C.kr_request_ensure_answer(req)
+ end,
},
})
static int request_start(struct request_ctx *ctx, knot_pkt_t *query)
{
assert(query && ctx);
- size_t answer_max = KNOT_WIRE_MIN_PKTSIZE;
- struct kr_request *req = &ctx->req;
- /* source.session can be empty if request was generated by kresd itself */
- struct session *s = ctx->source.session;
- if (!s || session_get_handle(s)->type == UV_TCP) {
- answer_max = KNOT_WIRE_MAX_PKTSIZE;
- } else if (knot_pkt_has_edns(query)) { /* EDNS */
- answer_max = MAX(knot_edns_get_payload(query->opt_rr),
- KNOT_WIRE_MIN_PKTSIZE);
- }
+ struct kr_request *req = &ctx->req;
req->qsource.size = query->size;
if (knot_pkt_has_tsig(query)) {
req->qsource.size += query->tsig_wire.len;
}
- knot_pkt_t *answer = knot_pkt_new(NULL, answer_max, &req->pool);
- if (!answer) { /* Failed to allocate answer */
- return kr_error(ENOMEM);
- }
-
knot_pkt_t *pkt = knot_pkt_new(NULL, req->qsource.size, &req->pool);
if (!pkt) {
return kr_error(ENOMEM);
/* Start resolution */
struct worker_ctx *worker = ctx->worker;
struct engine *engine = worker->engine;
- kr_resolve_begin(req, &engine->resolver, answer);
+ kr_resolve_begin(req, &engine->resolver);
worker->stats.queries += 1;
/* Throttle outbound queries only when high pressure */
if (worker->stats.concurrent < QUERY_RATE_THRESHOLD) {
qry->zone_cut.key = z_import->key;
qry->zone_cut.trust_anchor = z_import->ta;
- if (knot_pkt_init_response(request->answer, query) != 0) {
+ if (knot_pkt_init_response(answer, query) != 0) {
goto cleanup;
}
`DNS Flag Day 2020 <https://dnsflagday.net/2020/>`_. Please double-check your firewall,
it has to allow DNS traffic on UDP and also TCP port 53.
+
+5.1 to 5.2
+==========
+
+Module changes
+--------------
+* Reply packet :c:type:`kr_request.answer`
+ `is not allocated <https://gitlab.nic.cz/knot/knot-resolver/-/merge_requests/985>`_
+ immediately when the request comes.
+ See the new :c:func:`kr_request_ensure_answer` function,
+ wrapped for lua as ``req:ensure_answer()``.
+
+
5.0 to 5.1
==========
static void finalize_answer(knot_pkt_t *pkt, struct kr_request *req)
{
/* Finalize header */
- knot_pkt_t *answer = req->answer;
+ knot_pkt_t *answer = kr_request_ensure_answer(req);
knot_wire_set_rcode(answer->wire, knot_wire_get_rcode(pkt->wire));
}
if (qry->sclass != KNOT_CLASS_IN
|| (knot_rrtype_is_metatype(qry->stype)
/* && qry->stype != KNOT_RRTYPE_ANY hmm ANY seems broken ATM */)) {
- knot_wire_set_rcode(ctx->req->answer->wire, KNOT_RCODE_NOTIMPL);
+ knot_pkt_t *ans = kr_request_ensure_answer(ctx->req);
+ knot_wire_set_rcode(ans->wire, KNOT_RCODE_NOTIMPL);
return KR_STATE_FAIL;
}
case KNOT_RCODE_NXDOMAIN:
break; /* OK */
case KNOT_RCODE_YXDOMAIN: /* Basically a successful answer; name just doesn't fit. */
+ kr_request_ensure_answer(req);
knot_wire_set_rcode(req->answer->wire, KNOT_RCODE_YXDOMAIN);
break;
case KNOT_RCODE_REFUSED:
return knot_pkt_reserve(pkt, wire_size);
}
-static int answer_prepare(struct kr_request *req, knot_pkt_t *query)
-{
- knot_pkt_t *answer = req->answer;
- if (knot_pkt_init_response(answer, query) != 0) {
- return kr_error(ENOMEM); /* Failed to initialize answer */
- }
- /* Handle EDNS in the query */
- if (knot_pkt_has_edns(query)) {
- answer->opt_rr = knot_rrset_copy(req->ctx->downstream_opt_rr, &answer->mm);
- if (answer->opt_rr == NULL){
- return kr_error(ENOMEM);
- }
- /* Set DO bit if set (DNSSEC requested). */
- if (knot_pkt_has_dnssec(query)) {
- knot_edns_set_do(answer->opt_rr);
- }
- }
- return kr_ok();
-}
-
/**
* @param all_secure optionally &&-combine security of written RRs into its value.
* (i.e. if you pass a pointer to false, it will always remain)
return kr_ok();
}
-int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx, knot_pkt_t *answer)
+int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx)
{
/* Initialize request */
request->ctx = ctx;
- request->answer = answer;
+ request->answer = NULL;
request->options = ctx->options;
request->state = KR_STATE_CONSUME;
request->current_query = NULL;
}
}
- /* Initialize answer packet */
- knot_pkt_t *answer = request->answer;
- knot_wire_set_qr(answer->wire);
- knot_wire_clear_aa(answer->wire);
- knot_wire_set_ra(answer->wire);
- knot_wire_set_rcode(answer->wire, KNOT_RCODE_NOERROR);
-
- assert(request->qsource.packet);
- if (knot_wire_get_cd(request->qsource.packet->wire)) {
- knot_wire_set_cd(answer->wire);
- } else if (qry->flags.DNSSEC_WANT) {
- knot_wire_set_ad(answer->wire);
- }
-
/* Expect answer, pop if satisfied immediately */
ITERATE_LAYERS(request, qry, begin);
if ((request->state & KR_STATE_DONE) != 0) {
return request->state;
}
+knot_pkt_t * kr_request_ensure_answer(struct kr_request *request)
+{
+ if (request->answer)
+ return request->answer;
+
+ const knot_pkt_t *qs_pkt = request->qsource.packet;
+ assert(qs_pkt);
+ // Find answer_max: limit on DNS wire length.
+ size_t answer_max;
+ const struct kr_request_qsource_flags *qs_flags = &request->qsource.flags;
+ assert((qs_flags->tls || qs_flags->http) ? qs_flags->tcp : true);
+ if (!request->qsource.addr || qs_flags->tcp) {
+ // not on UDP
+ answer_max = KNOT_WIRE_MAX_PKTSIZE;
+ } else if (knot_pkt_has_edns(qs_pkt)) {
+ // UDP with EDNS
+ answer_max = MIN(knot_edns_get_payload(qs_pkt->opt_rr),
+ knot_edns_get_payload(request->ctx->downstream_opt_rr));
+ answer_max = MAX(answer_max, KNOT_WIRE_MIN_PKTSIZE);
+ } else {
+ // UDP without EDNS
+ answer_max = KNOT_WIRE_MIN_PKTSIZE;
+ }
+
+ // Allocate the packet.
+ knot_pkt_t *answer = request->answer =
+ knot_pkt_new(NULL, answer_max, &request->pool);
+ if (!answer || knot_pkt_init_response(answer, qs_pkt) != 0) {
+ assert(!answer); // otherwise we messed something up
+ goto enomem;
+ }
+
+ uint8_t *wire = answer->wire; // much was done by knot_pkt_init_response()
+ knot_wire_set_ra(wire);
+ knot_wire_set_rcode(wire, KNOT_RCODE_NOERROR);
+ if (knot_wire_get_cd(qs_pkt->wire)) {
+ knot_wire_set_cd(wire);
+ } else if (request->current_query && request->current_query->flags.DNSSEC_WANT) { // FIXME: ugly
+ knot_wire_set_ad(wire);
+ }
+
+ // Prepare EDNS if required.
+ if (knot_pkt_has_edns(qs_pkt)) {
+ answer->opt_rr = knot_rrset_copy(request->ctx->downstream_opt_rr,
+ &answer->mm);
+ if (!answer->opt_rr)
+ goto enomem;
+ if (knot_pkt_has_dnssec(qs_pkt))
+ knot_edns_set_do(answer->opt_rr);
+ }
+
+ return request->answer;
+enomem:
+ // Consequences of `return NULL` would be way too complicated to handle.
+ kr_log_error("ERROR: irrecoverable out of memory condition in %s\n", __func__);
+ abort();
+}
+
KR_PURE static bool kr_inaddr_equal(const struct sockaddr *a, const struct sockaddr *b)
{
const int a_len = kr_inaddr_len(a);
/* Empty resolution plan, push packet as the new query */
if (packet && kr_rplan_empty(rplan)) {
- if (answer_prepare(request, packet) != 0) {
- return KR_STATE_FAIL;
- }
return resolve_query(request, packet);
}
int kr_resolve_finish(struct kr_request *request, int state)
{
request->state = state;
+ kr_request_ensure_answer(request);
/* Finalize answer and construct wire-buffer. */
ITERATE_LAYERS(request, NULL, answer_finalize);
answer_finalize(request);
* };
*
* // Setup and provide input query
- * int state = kr_resolve_begin(&req, ctx, final_answer);
+ * int state = kr_resolve_begin(&req, ctx);
* state = kr_resolve_consume(&req, query);
*
* // Generate answer
*/
struct kr_request {
struct kr_context *ctx;
- knot_pkt_t *answer;
+ knot_pkt_t *answer; /**< See kr_request_ensure_answer() */
struct kr_query *current_query; /**< Current evaluated query. */
struct {
/** Address that originated the request. NULL for internal origin. */
/**
* Begin name resolution.
*
- * @note Expects a request to have an initialized mempool, the "answer" packet will
- * be kept during the resolution and will contain the final answer at the end.
+ * @note Expects a request to have an initialized mempool.
*
* @param request request state with initialized mempool
* @param ctx resolution context
- * @param answer allocated packet for final answer
* @return CONSUME (expecting query)
*/
KR_EXPORT
-int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx, knot_pkt_t *answer);
+int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx);
+
+/**
+ * Ensure that request->answer is usable, and return it (for convenience).
+ *
+ * It can not fail; FIXME: is it worth in the API to abort() instead of return NULL?
+ * Only use this when it's guaranteed that there will be no delay before sending it.
+ * You don't need to call this in places where "resolver knows" that there will be no delay.
+ */
+KR_EXPORT __attribute__((returns_nonnull))
+knot_pkt_t * kr_request_ensure_answer(struct kr_request *request);
/**
* Consume input packet (may be either first query or answer to query originated from kr_resolve_produce())
return ctx->state;
}
- knot_pkt_t *answer = req->answer;
+ knot_pkt_t *answer = req->answer; // FIXME: see kr_request_ensure_answer()
if (ctx->state & (KR_STATE_DONE | KR_STATE_FAIL)) {
return ctx->state;
local ffi = require('ffi')
local function gen_huge_answer(_, req)
- local answer = req.answer
+ local answer = req:ensure_answer()
ffi.C.kr_pkt_make_auth_header(answer)
answer:rcode(kres.rcode.NOERROR)
local function gen_varying_ttls(_, req)
local qry = req:current()
- local answer = req.answer
+ local answer = req:ensure_answer()
ffi.C.kr_pkt_make_auth_header(answer)
answer:rcode(kres.rcode.NOERROR)
-- Custom action which generates fake A record
local ffi = require('ffi')
local function fake_A_record(state, req)
- local answer = req.answer
+ local answer = req:ensure_answer()
local qry = req:current()
if qry.stype ~= kres.type.A then
return state
function policy.ANSWER(rtable, nodata)
return function(_, req)
local qry = req:current()
- local answer = req.answer
+ local answer = req:ensure_answer()
local data = rtable[qry.stype]
ffi.C.kr_pkt_make_auth_header(answer)
-- Rule for localhost. zone; see RFC6303, sec. 3
local function localhost(_, req)
local qry = req:current()
- local answer = req.answer
+ local answer = req:ensure_answer()
ffi.C.kr_pkt_make_auth_header(answer)
local is_exact = ffi.C.knot_dname_is_equal(qry.sname, dname_localhost)
-- TODO: much of this would better be left to the hints module (or coordinated).
local function localhost_reversed(_, req)
local qry = req:current()
- local answer = req.answer
+ local answer = req:ensure_answer()
-- classify qry.sname:
local is_exact -- exact dname for localhost
req.add_selected.len = 0
-- Let's be defensive and clear the answer, too.
- local pkt = req.answer
+ local pkt = req:ensure_answer()
pkt:clear_payload()
return pkt
end
end
function policy.TC(state, req)
- -- Skip non-UDP queries
- if req.answer.max_size == 65535 then
+ -- Avoid non-UDP queries
+ if req.qsource.flags.tcp then
return state
end
slice_queries[index][name] = count + 1
-- refuse query
- local answer = req.answer
+ local answer = req:ensure_answer()
answer:rcode(kres.rcode.REFUSED)
answer:ad(false)
return kres.DONE
local function refuse(req)
policy.REFUSE(nil, req)
- local pkt = req.answer
+ local pkt = req:ensure_answer()
pkt:aa(false)
pkt:begin(kres.section.ADDITIONAL)
uint8_t rd = knot_wire_get_rd(req->qsource.packet->wire);
if (!rd) {
- knot_pkt_t *answer = req->answer;
+ knot_pkt_t *answer = kr_request_ensure_answer(req);
knot_wire_set_rcode(answer->wire, KNOT_RCODE_REFUSED);
knot_wire_clear_ad(answer->wire);
ctx->state = KR_STATE_DONE;
-- count . IN DNSKEY queries
counter = 0
local function counter_func (state, req)
- local answer = req.answer
+ local answer = req:ensure_answer()
local qry = req:current()
if answer:qclass() == kres.class.IN
and qry.stype == kres.type.DNSKEY
end
function check_status(state, query)
- local answer = query.request.answer
-- status was present for 10 days
if cb_counter > 0 then
return policy.DENY_MSG('check last answer')
local ffi = require('ffi')
local function gen_huge_answer(_, req)
- local answer = req.answer
+ local answer = req:ensure_answer()
ffi.C.kr_pkt_make_auth_header(answer)
answer:rcode(kres.rcode.NOERROR)
local function gen_varying_ttls(_, req)
local qry = req:current()
- local answer = req.answer
+ local answer = req:ensure_answer()
ffi.C.kr_pkt_make_auth_header(answer)
answer:rcode(kres.rcode.NOERROR)