int (*checkout)(kr_layer_t *ctx, knot_pkt_t *packet, struct sockaddr *dst, int type);
/** Finalises the answer.
- * Last chance to affect what will get into the answer, including EDNS.*/
+ * Last chance to affect what will get into the answer, including EDNS.
+ * Not called if the packet is being dropped. */
int (*answer_finalize)(kr_layer_t *ctx);
/** The C module can store anything in here. */
return result;
}
-static void finalize_answer(knot_pkt_t *pkt, struct kr_request *req)
+static int finalize_answer(knot_pkt_t *pkt, struct kr_request *req)
{
/* Finalize header */
knot_pkt_t *answer = kr_request_ensure_answer(req);
- knot_wire_set_rcode(answer->wire, knot_wire_get_rcode(pkt->wire));
+ if (answer) {
+ knot_wire_set_rcode(answer->wire, knot_wire_get_rcode(pkt->wire));
+ req->state = KR_STATE_DONE;
+ }
+ return req->state;
}
static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral, const knot_dname_t **cname_ret)
array->at[last_idx] = entry;
entry->to_wire = true;
}
- finalize_answer(pkt, req);
- return KR_STATE_DONE;
+ return finalize_answer(pkt, req);
}
return kr_ok();
}
if (state != kr_ok()) {
return KR_STATE_FAIL;
}
- finalize_answer(pkt, req);
+ return finalize_answer(pkt, req);
} else {
/* Answer for sub-query; DS, IP for NS etc.
* It may contains NSEC \ NSEC3 records for
return KR_STATE_FAIL;
}
- finalize_answer(pkt, req);
- return KR_STATE_DONE;
+ return finalize_answer(pkt, req);
}
|| (knot_rrtype_is_metatype(qry->stype)
/* && qry->stype != KNOT_RRTYPE_ANY hmm ANY seems broken ATM */)) {
knot_pkt_t *ans = kr_request_ensure_answer(ctx->req);
+ if (!ans) return ctx->req->state;
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);
+ if (!kr_request_ensure_answer(req))
+ return req->state;
knot_wire_set_rcode(req->answer->wire, KNOT_RCODE_YXDOMAIN);
break;
case KNOT_RCODE_REFUSED:
}
rrarray_finalize:
- /* Finish construction of libknot-format RRsets. */
+ /* Finish construction of libknot-format RRsets.
+ * We do this even if dropping the answer, though it's probably useless. */
(void)0;
ranked_rr_array_t *selected[] = kr_request_selected(req);
for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) {
answer->opt_rr = knot_rrset_copy(request->ctx->downstream_opt_rr,
&answer->mm);
if (!answer->opt_rr)
- goto enomem;
+ goto enomem; // answer is on mempool, so "leak" is OK
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();
+ request->state = KR_STATE_FAIL; // TODO: really combine with another flag?
+ return request->answer = NULL;
}
KR_PURE static bool kr_inaddr_equal(const struct sockaddr *a, const struct sockaddr *b)
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);
-
- /* Defensive style, in case someone has forgotten.
- * Beware: non-empty answers do make sense even with SERVFAIL case, etc. */
- if (request->state != KR_STATE_DONE) {
- uint8_t *wire = request->answer->wire;
- switch (knot_wire_get_rcode(wire)) {
- case KNOT_RCODE_NOERROR:
- case KNOT_RCODE_NXDOMAIN:
- knot_wire_clear_ad(wire);
- knot_wire_clear_aa(wire);
- knot_wire_set_rcode(wire, KNOT_RCODE_SERVFAIL);
+ /* Finalize answer and construct whole wire-format (unless dropping). */
+ knot_pkt_t *answer = kr_request_ensure_answer(request);
+ if (answer) {
+ ITERATE_LAYERS(request, NULL, answer_finalize);
+ answer_finalize(request);
+
+ /* Defensive style, in case someone has forgotten.
+ * Beware: non-empty answers do make sense even with SERVFAIL case, etc. */
+ if (request->state != KR_STATE_DONE) {
+ uint8_t *wire = answer->wire;
+ switch (knot_wire_get_rcode(wire)) {
+ case KNOT_RCODE_NOERROR:
+ case KNOT_RCODE_NXDOMAIN:
+ knot_wire_clear_ad(wire);
+ knot_wire_clear_aa(wire);
+ knot_wire_set_rcode(wire, KNOT_RCODE_SERVFAIL);
+ }
}
}
#ifndef NOVERBOSELOG
struct kr_rplan *rplan = &request->rplan;
struct kr_query *last = kr_rplan_last(rplan);
- VERBOSE_MSG(last, "finished: %d, queries: %zu, mempool: %zu B\n",
+ VERBOSE_MSG(last, "finished in state: %d, queries: %zu, mempool: %zu B\n",
request->state, rplan->resolved.len, (size_t) mp_total_size(request->pool.ctx));
#endif
/**
* 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?
+ * It may return NULL, in which case it marks ->state with _FAIL and no answer will be sent.
* 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))
+KR_EXPORT
knot_pkt_t * kr_request_ensure_answer(struct kr_request *request);
/**
local ffi = require('ffi')
local function fake_A_record(state, req)
local answer = req:ensure_answer()
+ if answer == nil then return nil end
local qry = req:current()
if qry.stype ~= kres.type.A then
return state
return function(_, req)
local qry = req:current()
local answer = req:ensure_answer()
+ if answer == nil then return nil end
local data = rtable[qry.stype]
ffi.C.kr_pkt_make_auth_header(answer)
local function localhost(_, req)
local qry = req:current()
local answer = req:ensure_answer()
+ if answer == nil then return nil end
ffi.C.kr_pkt_make_auth_header(answer)
local is_exact = ffi.C.knot_dname_is_equal(qry.sname, dname_localhost)
local function localhost_reversed(_, req)
local qry = req:current()
local answer = req:ensure_answer()
+ if answer == nil then return nil end
-- classify qry.sname:
local is_exact -- exact dname for localhost
-- Let's be defensive and clear the answer, too.
local pkt = req:ensure_answer()
+ if pkt == nil then return nil end
pkt:clear_payload()
return pkt
end
return function (_, req)
-- Write authority information
local answer = answer_clear(req)
+ if answer == nil then return nil end
ffi.C.kr_pkt_make_auth_header(answer)
answer:rcode(kres.rcode.NXDOMAIN)
answer:begin(kres.section.AUTHORITY)
policy.DENY = policy.DENY_MSG() -- compatibility with < 2.0
function policy.DROP(_, req)
- answer_clear(req)
+ local answer = answer_clear(req)
+ if answer == nil then return nil end
return kres.FAIL
end
function policy.REFUSE(_, req)
local answer = answer_clear(req)
+ if answer == nil then return nil end
answer:rcode(kres.rcode.REFUSED)
answer:ad(false)
return kres.DONE
end
local answer = answer_clear(req)
+ if answer == nil then return nil end
answer:tc(1)
answer:ad(false)
return kres.DONE
-- refuse query
local answer = req:ensure_answer()
+ if answer == nil then return nil end
answer:rcode(kres.rcode.REFUSED)
answer:ad(false)
return kres.DONE
local function refuse(req)
policy.REFUSE(nil, req)
local pkt = req:ensure_answer()
+ if pkt == nil then return nil end
pkt:aa(false)
pkt:begin(kres.section.ADDITIONAL)
local msg = 'blocked by DNS rebinding protection'
pkt:put('\11explanation\7invalid\0', 10800, pkt:qclass(), kres.type.TXT,
string.char(#msg) .. msg)
+ return kres.DONE
end
-- act on DNS queries which were not answered from cache
Typical example: NS address resolution -> only this NS won't be used
but others may still be OK (or we SERVFAIL due to no NS being usable).
--]]
- if qry.parent == nil then refuse(req) end
+ if qry.parent == nil then
+ state = refuse(req)
+ end
if verbose() then
ffi.C.kr_log_q(qry, 'rebinding',
'blocking blacklisted IP in RR \'%s\' received from IP %s\n',
kres.rr2str(bad_rr),
tostring(kres.sockaddr_t(req.upstream.addr)))
end
- return kres.DONE
+ return state
end
return M
{
struct kr_request *req = ctx->req;
uint8_t rd = knot_wire_get_rd(req->qsource.packet->wire);
+ if (rd)
+ return ctx->state;
- if (!rd) {
- 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;
- }
-
+ knot_pkt_t *answer = kr_request_ensure_answer(req);
+ if (!answer)
+ return ctx->state;
+ knot_wire_set_rcode(answer->wire, KNOT_RCODE_REFUSED);
+ knot_wire_clear_ad(answer->wire);
+ ctx->state = KR_STATE_DONE;
return ctx->state;
}
counter = 0
local function counter_func (state, req)
local answer = req:ensure_answer()
+ if answer == nil then return nil end
local qry = req:current()
if answer:qclass() == kres.class.IN
and qry.stype == kres.type.DNSKEY