From 4101f5e124aac351bd2c5d04b5b535328dd5b565 Mon Sep 17 00:00:00 2001 From: Frantisek Tobias Date: Thu, 1 Aug 2024 13:25:45 +0200 Subject: doc/dev: removed outdated requirements from Knot Resolver library --- lib/README.rst | 5 ----- 1 file changed, 5 deletions(-) (limited to 'lib') diff --git a/lib/README.rst b/lib/README.rst index b631fe7b..f2463d4a 100644 --- a/lib/README.rst +++ b/lib/README.rst @@ -4,11 +4,6 @@ Knot Resolver library ********************* -Requirements -============ - -* libknot_ 2.0 (Knot DNS high-performance DNS library.) - For users ========= -- cgit v1.2.3 From 42f28872c7b1991194f0fcdeae8d333e4004ec2c Mon Sep 17 00:00:00 2001 From: menakite <29005531+menakite@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:46:42 +0200 Subject: lib: fix typo in generated doc for log group "zoncut". Setting log_groups({ 'zonecut' }) just leads to a warning and no "zone cut" logging shown. --- lib/log.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/log.h b/lib/log.h index a3887e57..d3bc9145 100644 --- a/lib/log.h +++ b/lib/log.h @@ -109,7 +109,7 @@ enum kr_log_group { #define LOG_GRP_VALIDATOR_TAG "valdtr" /**< ``valdtr``: operations related to validate layer */ #define LOG_GRP_RESOLVER_TAG "resolv" /**< ``resolv``: operations related to resolving */ #define LOG_GRP_SELECTION_TAG "select" /**< ``select``: operations related to server selection */ -#define LOG_GRP_ZCUT_TAG "zoncut" /**< ``zonecut``: operations related to zone cut */ +#define LOG_GRP_ZCUT_TAG "zoncut" /**< ``zoncut``: operations related to zone cut */ #define LOG_GRP_COOKIES_TAG "cookie" /**< ``cookie``: operations related to cookies */ #define LOG_GRP_STATISTICS_TAG "statis" /**< ``statis``: operations related to statistics */ #define LOG_GRP_REBIND_TAG "rebind" /**< ``rebind``: operations related to rebinding */ -- cgit v1.2.3 From 07c1353d0b60cf40c10cbb939b8767a03b24aa0c Mon Sep 17 00:00:00 2001 From: menakite <29005531+menakite@users.noreply.github.com> Date: Sun, 11 Aug 2024 04:07:32 +0200 Subject: iterator: fix handling of ANY queries and != IN classes. It was supposed to answer NOTIMP, but was returning FAIL instead of DONE and so the answer was SERVFAIL. Also added EDE code "Not Supported" for clarity. --- lib/layer/iterate.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c index 69fe344c..c6f9ec97 100644 --- a/lib/layer/iterate.c +++ b/lib/layer/iterate.c @@ -922,14 +922,14 @@ static int begin(kr_layer_t *ctx) } struct kr_query *qry = ctx->req->current_query; - /* Avoid any other classes, and avoid any meta-types ~~except for ANY~~. */ - if (qry->sclass != KNOT_CLASS_IN - || (knot_rrtype_is_metatype(qry->stype) - /* && qry->stype != KNOT_RRTYPE_ANY hmm ANY seems broken ATM */)) { + /* Avoid any other classes, and avoid any meta-types. */ + if (qry->sclass != KNOT_CLASS_IN || knot_rrtype_is_metatype(qry->stype)) { knot_pkt_t *ans = kr_request_ensure_answer(ctx->req); - if (!ans) return ctx->req->state; + if (!ans) + return ctx->req->state; knot_wire_set_rcode(ans->wire, KNOT_RCODE_NOTIMPL); - return KR_STATE_FAIL; + kr_request_set_extended_error(ctx->req, KNOT_EDNS_EDE_NOTSUP, "57CK"); + return KR_STATE_DONE; } return reset(ctx); -- cgit v1.2.3 From 1b58c7ec24fdf76d28c6a6d0a056618f8dcb2566 Mon Sep 17 00:00:00 2001 From: Vladimír Čunát Date: Mon, 12 Aug 2024 10:57:11 +0200 Subject: NEWS+comment for the parent commit --- NEWS | 3 +++ lib/layer/iterate.c | 1 + 2 files changed, 4 insertions(+) (limited to 'lib') diff --git a/NEWS b/NEWS index e8ba5230..92ebe306 100644 --- a/NEWS +++ b/NEWS @@ -10,11 +10,14 @@ Improvements The secret is created automatically if the user does not configure their own secret in the configuration. This means that the workers will be able to resume each other's TLS sessions, regardless of whether the user has configured it to do so. +- answer NOTIMPL for meta-types and non-IN RR classes (!1589) + Bugfixes -------- - daemon/proxyv2: fix informing the engine about TCP/TLS from the actual client (!1578) + Knot Resolver 6.0.8 (2024-07-23) ================================ diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c index c6f9ec97..3cc641cd 100644 --- a/lib/layer/iterate.c +++ b/lib/layer/iterate.c @@ -927,6 +927,7 @@ static int begin(kr_layer_t *ctx) knot_pkt_t *ans = kr_request_ensure_answer(ctx->req); if (!ans) return ctx->req->state; + /* This RCODE is explicitly suggested for meta QTYPEs in RFC 8906 sec.7 */ knot_wire_set_rcode(ans->wire, KNOT_RCODE_NOTIMPL); kr_request_set_extended_error(ctx->req, KNOT_EDNS_EDE_NOTSUP, "57CK"); return KR_STATE_DONE; -- cgit v1.2.3 From 4fe0099eaad6aaa531df9df38957292165e11d7d Mon Sep 17 00:00:00 2001 From: menakite <29005531+menakite@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:14:09 +0200 Subject: {daemon,lib}: sync EDE codes supported by libknot. Adds the following extended error codes: * 25 (Signature Expired before Valid): KNOT_EDNS_EDE_EXPIRED_INV * 26 (Too Early): KNOT_EDNS_EDE_TOO_EARLY * 27 (Unsupported NSEC3 Iterations Value): KNOT_EDNS_EDE_NSEC3_ITERS * 28 (Unable to conform to policy): KNOT_EDNS_EDE_NONCONF_POLICY * 29 (Synthesized): KNOT_EDNS_EDE_SYNTHESIZED --- daemon/lua/kres.lua | 5 +++++ lib/layer/validate.c | 2 +- lib/resolve.c | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/daemon/lua/kres.lua b/daemon/lua/kres.lua index 44434b4d..473d0828 100644 --- a/daemon/lua/kres.lua +++ b/daemon/lua/kres.lua @@ -231,6 +231,11 @@ local const_extended_error = { NREACH_AUTH = 22, NETWORK = 23, INV_DATA = 24, + EXPIRED_INV = 25, + TOO_EARLY = 26, + NSEC3_ITERS = 27, + NONCONF_POLICY = 28, + SYNTHESIZED = 29, } -- Constant tables diff --git a/lib/layer/validate.c b/lib/layer/validate.c index af20b2e4..75d68eb3 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -1137,7 +1137,7 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt) count += (knot_pkt_rr(sec, i)->type == KNOT_RRTYPE_NSEC3); if (count > 8) { VERBOSE_MSG(qry, "<= too many NSEC3 records in AUTHORITY (%d)\n", count); - kr_request_set_extended_error(req, 27/*KNOT_EDNS_EDE_NSEC3_ITERS*/, + kr_request_set_extended_error(req, KNOT_EDNS_EDE_NSEC3_ITERS, /* It's not about iteration values per se, but close enough. */ "DYRH: too many NSEC3 records"); qry->flags.DNSSEC_BOGUS = true; diff --git a/lib/resolve.c b/lib/resolve.c index 4730f105..4b4827f2 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -972,12 +972,15 @@ knot_mm_t *kr_resolve_pool(struct kr_request *request) static int ede_priority(int info_code) { switch(info_code) { + case KNOT_EDNS_EDE_TOO_EARLY: + return 910; case KNOT_EDNS_EDE_DNSKEY_BIT: case KNOT_EDNS_EDE_DNSKEY_MISS: case KNOT_EDNS_EDE_SIG_EXPIRED: case KNOT_EDNS_EDE_SIG_NOTYET: case KNOT_EDNS_EDE_RRSIG_MISS: case KNOT_EDNS_EDE_NSEC_MISS: + case KNOT_EDNS_EDE_EXPIRED_INV: return 900; /* Specific DNSSEC failures */ case KNOT_EDNS_EDE_BOGUS: return 800; /* Generic DNSSEC failure */ @@ -990,6 +993,7 @@ static int ede_priority(int info_code) return 600; /* Policy related */ case KNOT_EDNS_EDE_DNSKEY_ALG: case KNOT_EDNS_EDE_DS_DIGEST: + case KNOT_EDNS_EDE_NSEC3_ITERS: return 500; /* Non-critical DNSSEC issues */ case KNOT_EDNS_EDE_STALE: case KNOT_EDNS_EDE_STALE_NXD: @@ -1002,10 +1006,12 @@ static int ede_priority(int info_code) case KNOT_EDNS_EDE_NREACH_AUTH: case KNOT_EDNS_EDE_NETWORK: case KNOT_EDNS_EDE_INV_DATA: + case KNOT_EDNS_EDE_SYNTHESIZED: return 200; /* Assorted codes */ case KNOT_EDNS_EDE_OTHER: return 100; /* Most generic catch-all error */ case KNOT_EDNS_EDE_NONE: + case KNOT_EDNS_EDE_NONCONF_POLICY: /* Defined by an expired Internet Draft */ return 0; /* No error - allow overriding */ default: kr_assert(false); /* Unknown info_code */ -- cgit v1.2.3 From fac462e163a2614e24d2c604a9b120b949796a72 Mon Sep 17 00:00:00 2001 From: menakite <29005531+menakite@users.noreply.github.com> Date: Sat, 10 Aug 2024 01:19:40 +0200 Subject: validator: avoid clearing EDE if query didn't actually fail --- lib/layer/validate.c | 83 +++++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 40 deletions(-) (limited to 'lib') diff --git a/lib/layer/validate.c b/lib/layer/validate.c index 75d68eb3..45522fa2 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -1321,53 +1321,45 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt) return KR_STATE_DONE; } -/** Hide RRsets which did not validate from clients. */ -static int hide_bogus(kr_layer_t *ctx) { - if (knot_wire_get_cd(ctx->req->qsource.packet->wire)) { - return ctx->state; - } - /* We don't want to send bogus answers to clients, not even in SERVFAIL - * answers, but we cannot drop whole sections. If a CNAME chain - * SERVFAILs somewhere, the steps that were OK should be put into - * answer. - * - * There is one specific issue: currently we follow CNAME *before* - * we validate it, because... iterator comes before validator. - * Therefore some rrsets might be added into req->*_selected before - * we detected failure in validator. - * TODO: better approach, probably during work on parallel queries. - */ - const ranked_rr_array_t *sel[] = kr_request_selected(ctx->req); - for (knot_section_t sect = KNOT_ANSWER; sect <= KNOT_ADDITIONAL; ++sect) { - for (size_t i = 0; i < sel[sect]->len; ++i) { - ranked_rr_array_entry_t *e = sel[sect]->at[i]; - e->to_wire = e->to_wire - && !kr_rank_test(e->rank, KR_RANK_INDET) - && !kr_rank_test(e->rank, KR_RANK_BOGUS) - && !kr_rank_test(e->rank, KR_RANK_MISMATCH) - && !kr_rank_test(e->rank, KR_RANK_MISSING); +/** + * Hide RRsets which did not validate from clients and clear Extended + * Error if a query failed validation, but later managed to succeed. + */ +static int validate_finalize(kr_layer_t *ctx) { + if (!knot_wire_get_cd(ctx->req->qsource.packet->wire)) { + /* We don't want to send bogus answers to clients, not even in SERVFAIL + * answers, but we cannot drop whole sections. If a CNAME chain + * SERVFAILs somewhere, the steps that were OK should be put into + * answer. + * + * There is one specific issue: currently we follow CNAME *before* + * we validate it, because... iterator comes before validator. + * Therefore some rrsets might be added into req->*_selected before + * we detected failure in validator. + * TODO: better approach, probably during work on parallel queries. + */ + const ranked_rr_array_t *sel[] = kr_request_selected(ctx->req); + for (knot_section_t sect = KNOT_ANSWER; sect <= KNOT_ADDITIONAL; ++sect) { + for (size_t i = 0; i < sel[sect]->len; ++i) { + ranked_rr_array_entry_t *e = sel[sect]->at[i]; + e->to_wire = e->to_wire + && !kr_rank_test(e->rank, KR_RANK_INDET) + && !kr_rank_test(e->rank, KR_RANK_BOGUS) + && !kr_rank_test(e->rank, KR_RANK_MISMATCH) + && !kr_rank_test(e->rank, KR_RANK_MISSING); + } } } - return ctx->state; -} -static int validate_wrapper(kr_layer_t *ctx, knot_pkt_t *pkt) { - // Wrapper for now. - int ret = validate(ctx, pkt); - struct kr_request *req = ctx->req; - struct kr_query *qry = req->current_query; - if (ret & KR_STATE_FAIL && qry->flags.DNSSEC_BOGUS) - qry->server_selection.error(qry, req->upstream.transport, KR_SELECTION_DNSSEC_ERROR); - if (ret & KR_STATE_DONE && !qry->flags.DNSSEC_BOGUS) { - /* Don't report extended DNS errors related to validation - * when it managed to succeed (e.g. by trying different auth). */ - switch (req->extended_error.info_code) { + /* Clear DNSSEC-related Extended Error in case the request managed to succeed somehow. */ + if (ctx->state == KR_STATE_DONE) { + switch (ctx->req->extended_error.info_code) { case KNOT_EDNS_EDE_BOGUS: case KNOT_EDNS_EDE_NSEC_MISS: case KNOT_EDNS_EDE_RRSIG_MISS: case KNOT_EDNS_EDE_SIG_EXPIRED: case KNOT_EDNS_EDE_SIG_NOTYET: - kr_request_set_extended_error(req, KNOT_EDNS_EDE_NONE, NULL); + kr_request_set_extended_error(ctx->req, KNOT_EDNS_EDE_NONE, NULL); break; case KNOT_EDNS_EDE_DNSKEY_MISS: case KNOT_EDNS_EDE_DNSKEY_BIT: @@ -1376,6 +1368,17 @@ static int validate_wrapper(kr_layer_t *ctx, knot_pkt_t *pkt) { default: break; /* Remaining codes don't indicate hard DNSSEC failure. */ } } + + return ctx->state; +} + +static int validate_wrapper(kr_layer_t *ctx, knot_pkt_t *pkt) { + // Wrapper for now. + int ret = validate(ctx, pkt); + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + if (ret & KR_STATE_FAIL && qry->flags.DNSSEC_BOGUS) + qry->server_selection.error(qry, req->upstream.transport, KR_SELECTION_DNSSEC_ERROR); return ret; } @@ -1385,7 +1388,7 @@ int validate_init(struct kr_module *self) { static const kr_layer_api_t layer = { .consume = &validate_wrapper, - .answer_finalize = &hide_bogus, + .answer_finalize = &validate_finalize, }; self->layer = &layer; return kr_ok(); -- cgit v1.2.3 From b5b117bf66480aa5f73221a81a0fb841c2d90638 Mon Sep 17 00:00:00 2001 From: Vladimír Čunát Date: Mon, 19 Aug 2024 15:42:11 +0200 Subject: validator nit: move validate_wrapper() to a better place --- lib/layer/validate.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/layer/validate.c b/lib/layer/validate.c index 45522fa2..395640cc 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -1320,6 +1320,15 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt) VERBOSE_MSG(qry, "<= answer valid, OK\n"); return KR_STATE_DONE; } +static int validate_wrapper(kr_layer_t *ctx, knot_pkt_t *pkt) { + // Wrapper for now. + int ret = validate(ctx, pkt); + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + if (ret & KR_STATE_FAIL && qry->flags.DNSSEC_BOGUS) + qry->server_selection.error(qry, req->upstream.transport, KR_SELECTION_DNSSEC_ERROR); + return ret; +} /** * Hide RRsets which did not validate from clients and clear Extended @@ -1372,16 +1381,6 @@ static int validate_finalize(kr_layer_t *ctx) { return ctx->state; } -static int validate_wrapper(kr_layer_t *ctx, knot_pkt_t *pkt) { - // Wrapper for now. - int ret = validate(ctx, pkt); - struct kr_request *req = ctx->req; - struct kr_query *qry = req->current_query; - if (ret & KR_STATE_FAIL && qry->flags.DNSSEC_BOGUS) - qry->server_selection.error(qry, req->upstream.transport, KR_SELECTION_DNSSEC_ERROR); - return ret; -} - /** Module implementation. */ int validate_init(struct kr_module *self) -- cgit v1.2.3 From b905135b48b64e034c1ea83cc9e759654b3aa2b3 Mon Sep 17 00:00:00 2001 From: Frantisek Tobias Date: Mon, 19 Aug 2024 17:16:38 +0200 Subject: modules/stats: add answer.stale --- NEWS | 1 + daemon/lua/kres-gen-33.lua | 1 + lib/resolve.h | 1 + manager/knot_resolver_manager/statistics.py | 6 ++++++ modules/serve_stale/serve_stale.lua | 7 +++++-- modules/stats/README.rst | 2 ++ modules/stats/stats.c | 8 +++++++- 7 files changed, 23 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/NEWS b/NEWS index a9540326..11c4bc45 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ Improvements - answer NOTIMPL for meta-types and non-IN RR classes (!1589) - views: improve interaction with old-style policies (!1576) +- stats: add stale answer counter 'answer.stale' (!1591) Bugfixes -------- diff --git a/daemon/lua/kres-gen-33.lua b/daemon/lua/kres-gen-33.lua index 6be16bc4..40a03453 100644 --- a/daemon/lua/kres-gen-33.lua +++ b/daemon/lua/kres-gen-33.lua @@ -248,6 +248,7 @@ struct kr_request { ranked_rr_array_t add_selected; _Bool answ_validated; _Bool auth_validated; + _Bool stale_accounted; uint8_t rank; struct kr_rplan rplan; trace_log_f trace_log; diff --git a/lib/resolve.h b/lib/resolve.h index 443fef29..cbc20877 100644 --- a/lib/resolve.h +++ b/lib/resolve.h @@ -260,6 +260,7 @@ struct kr_request { ranked_rr_array_t add_selected; bool answ_validated; /**< internal to validator; beware of caching, etc. */ bool auth_validated; /**< see answ_validated ^^ ; TODO */ + bool stale_accounted; /** Overall rank for the request. * diff --git a/manager/knot_resolver_manager/statistics.py b/manager/knot_resolver_manager/statistics.py index 4a0eb783..ae9d9811 100644 --- a/manager/knot_resolver_manager/statistics.py +++ b/manager/knot_resolver_manager/statistics.py @@ -119,6 +119,12 @@ if _prometheus_support: label=("instance_id", sid), value=metrics["answer"]["cached"], ) + yield _counter( + "resolver_answer_stale", + "number of queries that utilized stale data", + label=("instance_id", sid), + value=metrics["answer"]["stale"], + ) yield _counter( "resolver_answer_rcode_noerror", "number of NOERROR answers", diff --git a/modules/serve_stale/serve_stale.lua b/modules/serve_stale/serve_stale.lua index faf07fbe..c1528e80 100644 --- a/modules/serve_stale/serve_stale.lua +++ b/modules/serve_stale/serve_stale.lua @@ -8,9 +8,10 @@ local ffi = require('ffi') M.timeout = 3*sec M.callback = ffi.cast("kr_stale_cb", - function (ttl) --, name, type, qry) + function (ttl, _, _, qry) --log_debug(ffi.C.SRVSTALE, ' => called back with TTL: ' .. tostring(ttl)) if ttl + 3600 * 24 > 0 then -- at most one day stale + qry.request.stale_accounted = true return 1 else return -1 @@ -27,7 +28,9 @@ M.layer = { local now = ffi.C.kr_now() local deadline = qry.creation_time_mono + M.timeout if now > deadline or qry.flags.NO_NS_FOUND then - log_debug(ffi.C.LOG_GRP_SRVSTALE, ' => no reachable NS, using stale data') + log_qry(qry, ffi.C.LOG_GRP_SRVSTALE, + ' => no reachable NS, using stale data "%s"', + kres.dname2str(qry:name())) qry.stale_cb = M.callback -- TODO: probably start the same request that doesn't stale-serve, -- but first we need some detection of non-interactive / internal requests. diff --git a/modules/stats/README.rst b/modules/stats/README.rst index 1def925c..e9258274 100644 --- a/modules/stats/README.rst +++ b/modules/stats/README.rst @@ -55,6 +55,8 @@ Built-in counters keep track of number of queries and answers matching specific +-----------------+----------------------------------+ | answer.cached | queries answered from cache | +-----------------+----------------------------------+ +| answer.stale | queries that utilized stale data | ++-----------------+----------------------------------+ +-----------------+----------------------------------+ | **Answers categorized by RCODE** | diff --git a/modules/stats/stats.c b/modules/stats/stats.c index deed9c94..596847d7 100644 --- a/modules/stats/stats.c +++ b/modules/stats/stats.c @@ -37,12 +37,17 @@ #define UPSTREAMS_COUNT 512 /* Size of recent upstreams */ #endif -/** @cond internal Fixed-size map of predefined metrics. */ +/** @cond internal Fixed-size map of predefined metrics. + * + * When changing the list, don't forget _parse_resolver_metrics() + * in ../../manager/knot_resolver_manager/statistics.py + */ #define CONST_METRICS(X) \ X(answer,total) X(answer,noerror) X(answer,nodata) X(answer,nxdomain) X(answer,servfail) \ X(answer,cached) X(answer,1ms) X(answer,10ms) X(answer,50ms) X(answer,100ms) \ X(answer,250ms) X(answer,500ms) X(answer,1000ms) X(answer,1500ms) X(answer,slow) \ X(answer,sum_ms) \ + X(answer,stale) \ X(answer,aa) X(answer,tc) X(answer,rd) X(answer,ra) X(answer, ad) X(answer,cd) \ X(answer,edns0) X(answer,do) \ X(query,edns) X(query,dnssec) \ @@ -303,6 +308,7 @@ static int collect(kr_layer_t *ctx) DEPRECATED use new names metric_answer_edns0 and metric_answer_do */ + stat_const_add(data, metric_answer_stale, param->stale_accounted); stat_const_add(data, metric_query_edns, knot_pkt_has_edns(param->answer)); stat_const_add(data, metric_query_dnssec, knot_pkt_has_dnssec(param->answer)); -- cgit v1.2.3 From 39f4b5af72f3aca0174476235f43915477f20adb Mon Sep 17 00:00:00 2001 From: menakite <29005531+menakite@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:36:54 +0200 Subject: cache: move setting EDE "Stale Answer" to the the serve_stale module. It is not guaranteed yet that the request will finish in state DONE. This prevents other EDE codes from being applied to the request and in case the request ends in FAIL state it produces a SERVFAIL answer with EDE "Stale Answer", which is a bit weird. Move setting EDEs in answer_finalize in the serve_stale module, where the proper EDE in case of NXDOMAIN is set too. --- lib/cache/api.c | 4 +--- modules/serve_stale/serve_stale.lua | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/cache/api.c b/lib/cache/api.c index 0cd18534..046dae20 100644 --- a/lib/cache/api.c +++ b/lib/cache/api.c @@ -237,9 +237,7 @@ int32_t get_new_ttl(const struct entry_h *entry, const struct kr_query *qry, int res_stale = qry->stale_cb(res, owner, type, qry); if (res_stale >= 0) { VERBOSE_MSG(qry, "responding with stale answer\n"); - /* LATER: Perhaps we could use a more specific Stale - * NXDOMAIN Answer code for applicable responses. */ - kr_request_set_extended_error(qry->request, KNOT_EDNS_EDE_STALE, "6Q6X"); + qry->request->stale_accounted = true; return res_stale; } } diff --git a/modules/serve_stale/serve_stale.lua b/modules/serve_stale/serve_stale.lua index c1528e80..7aa2ee78 100644 --- a/modules/serve_stale/serve_stale.lua +++ b/modules/serve_stale/serve_stale.lua @@ -11,7 +11,6 @@ M.callback = ffi.cast("kr_stale_cb", function (ttl, _, _, qry) --log_debug(ffi.C.SRVSTALE, ' => called back with TTL: ' .. tostring(ttl)) if ttl + 3600 * 24 > 0 then -- at most one day stale - qry.request.stale_accounted = true return 1 else return -1 @@ -39,6 +38,23 @@ M.layer = { return state end, + + answer_finalize = function (state, req) + local qry = req:resolved() + if state ~= kres.DONE or qry == nil then + return state + end + + if req.stale_accounted and qry.stale_cb ~= nil then + if req.answer:rcode() == kres.rcode.NOERROR then + req:set_extended_error(kres.extended_error.STALE, 'WFAC') + elseif req.answer:rcode() == kres.rcode.NXDOMAIN then + req:set_extended_error(kres.extended_error.STALE_NXD, 'QSF6') + end + end + + return state + end, } return M -- cgit v1.2.3 From 9215892b5722e2ab42d93ea9f466c6fc2fd97414 Mon Sep 17 00:00:00 2001 From: Vladimír Čunát Date: Sun, 1 Sep 2024 11:01:22 +0200 Subject: lib/utils: generalize kr_strcatdup() for mempools It's trivial really, and I'd like to use it now. --- lib/utils.c | 4 ++-- lib/utils.h | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/utils.c b/lib/utils.c index d04f5467..882aeb3e 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -107,7 +107,7 @@ static inline int u16tostr(uint8_t *dst, uint16_t num) return 5; } -char* kr_strcatdup(unsigned n, ...) +char* kr_strcatdup_pool(knot_mm_t *pool, unsigned n, ...) { if (n < 1) { return NULL; @@ -132,7 +132,7 @@ char* kr_strcatdup(unsigned n, ...) char *result = NULL; if (total_len > 0) { if (unlikely(total_len == SIZE_MAX)) return NULL; - result = malloc(total_len + 1); + result = mm_alloc(pool, total_len + 1); } if (result) { char *stream = result; diff --git a/lib/utils.h b/lib/utils.h index e03b473d..e8122c99 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -170,9 +170,11 @@ typedef struct kr_http_header_array_entry { /** Array of HTTP headers for DoH. */ typedef array_t(kr_http_header_array_entry_t) kr_http_header_array_t; -/** Concatenate N strings. */ +/** Concatenate N strings and put the result into a mempool. */ KR_EXPORT -char* kr_strcatdup(unsigned n, ...); +char* kr_strcatdup_pool(knot_mm_t *pool, unsigned n, ...); +/** Concatenate N strings. */ +#define kr_strcatdup(n, ...) kr_strcatdup_pool(NULL, n, ## __VA_ARGS__) /** Construct absolute file path, without resolving symlinks. * \return malloc-ed string or NULL (+errno in that case) */ -- cgit v1.2.3 From 48f57424c33496fd354300300af0a183f0f50e87 Mon Sep 17 00:00:00 2001 From: menakite <29005531+menakite@users.noreply.github.com> Date: Sun, 11 Aug 2024 05:44:21 +0200 Subject: resolver,validator: provide more EDE codes. dnssec: * Provide a way to retrieve whether a DNSKEY has the Zone Key bit set, and add bindings for Lua modules (kr_dnssec_key_zonekey_flag), like kr_dnssec_key_sep_flag. * In kr_ds_algo_support() provide a way to retrieve what is wrong with the keys. * Check if a RRSIG RR has the signature expired already before inception time. validator: * Set EDE "Unsupported NSEC3 Iterations Value" when downgrading. * Set EDE "Signature Expired before Valid" when checking RRSIGs. * Set EDE "No Zone Key Bit Set" when a DNSKEY with the Zone Key Bit set to 0 is discarded. * Instead of the generic "Other Error" with extra text "unsupported digest/key", set appropriate EDEs "Unsupported DNSKEY Algorithm" and "Unsupported DS Digest Type". resolver: * Set EDE "No Reachable Authority" when it is decided that all authoritative servers are unreachable or misbehaving. Some parts adjusted by vcunat, in particular construction of EDE messages. --- daemon/lua/kres-gen-33.lua | 1 + daemon/lua/kres-gen.sh | 1 + lib/dnssec.c | 26 +++++++++++++++++-------- lib/dnssec.h | 32 ++++++++++++++++++++++++------- lib/layer/validate.c | 47 +++++++++++++++++++++++++++++++++++----------- lib/resolve-produce.c | 12 ++++++++++++ lib/resolve.c | 11 +++++++++++ 7 files changed, 104 insertions(+), 26 deletions(-) (limited to 'lib') diff --git a/daemon/lua/kres-gen-33.lua b/daemon/lua/kres-gen-33.lua index 1a085741..ecbb6330 100644 --- a/daemon/lua/kres-gen-33.lua +++ b/daemon/lua/kres-gen-33.lua @@ -490,6 +490,7 @@ int kr_ta_add(trie_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t int kr_ta_del(trie_t *, const knot_dname_t *); void kr_ta_clear(trie_t *); _Bool kr_dnssec_key_sep_flag(const uint8_t *); +_Bool kr_dnssec_key_zonekey_flag(const uint8_t *); _Bool kr_dnssec_key_revoked(const uint8_t *); int kr_dnssec_key_tag(uint16_t, const uint8_t *, size_t); int kr_dnssec_key_match(const uint8_t *, size_t, const uint8_t *, size_t); diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh index 8d0b91c0..8fe72035 100755 --- a/daemon/lua/kres-gen.sh +++ b/daemon/lua/kres-gen.sh @@ -285,6 +285,7 @@ ${CDEFS} ${LIBKRES} functions <<-EOF kr_ta_clear # DNSSEC kr_dnssec_key_sep_flag + kr_dnssec_key_zonekey_flag kr_dnssec_key_revoked kr_dnssec_key_tag kr_dnssec_key_match diff --git a/lib/dnssec.c b/lib/dnssec.c index 77cec796..169ce2bf 100644 --- a/lib/dnssec.c +++ b/lib/dnssec.c @@ -63,6 +63,10 @@ static int validate_rrsig_rr(int *flags, int cov_labels, if (kr_fails_assert(flags && rrsigs && vctx && vctx->zone_name)) { return kr_error(EINVAL); } + if (knot_rrsig_sig_expiration(rrsigs) < knot_rrsig_sig_inception(rrsigs)) { + vctx->rrs_counters.expired_before_inception++; + return kr_error(EINVAL); + } /* bullet 5 */ if (knot_rrsig_sig_expiration(rrsigs) < vctx->timestamp) { vctx->rrs_counters.expired++; @@ -435,26 +439,32 @@ finish: return vctx->result; } -bool kr_ds_algo_support(const knot_rrset_t *ta) +int kr_ds_algo_support(const knot_rrset_t *ta) { if (kr_fails_assert(ta && ta->type == KNOT_RRTYPE_DS && ta->rclass == KNOT_CLASS_IN)) - return false; + return kr_error(EINVAL); /* Check if at least one DS has a usable algorithm pair. */ + int ret = kr_error(ENOENT); knot_rdata_t *rdata_i = ta->rrs.rdata; for (uint16_t i = 0; i < ta->rrs.count; ++i, rdata_i = knot_rdataset_next(rdata_i)) { - if (dnssec_algorithm_digest_support(knot_ds_digest_type(rdata_i)) - && dnssec_algorithm_key_support(knot_ds_alg(rdata_i))) { - return true; - } + if (dnssec_algorithm_digest_support(knot_ds_digest_type(rdata_i))) { + if (dnssec_algorithm_key_support(knot_ds_alg(rdata_i))) + return kr_ok(); + else + ret = DNSSEC_INVALID_KEY_ALGORITHM; + } else + ret = DNSSEC_INVALID_DIGEST_ALGORITHM; } - return false; + return ret; } -// Now we instantiate these two as non-inline externally linkable code here (for lua). +// Now we instantiate these three as non-inline externally linkable code here (for lua). KR_EXPORT extern inline KR_PURE bool kr_dnssec_key_sep_flag(const uint8_t *dnskey_rdata); KR_EXPORT extern inline KR_PURE +bool kr_dnssec_key_zonekey_flag(const uint8_t *dnskey_rdata); +KR_EXPORT extern inline KR_PURE bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata); int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rdataset_t *sigs, diff --git a/lib/dnssec.h b/lib/dnssec.h index 52465042..b9f854d0 100644 --- a/lib/dnssec.h +++ b/lib/dnssec.h @@ -56,8 +56,9 @@ struct kr_rrset_validation_ctx { const struct kr_query *log_qry; /*!< The query; just for logging purposes. */ struct { unsigned int matching_name_type; /*!< Name + type matches */ - unsigned int expired; - unsigned int notyet; + unsigned int expired; /*!< Number of expired signatures */ + unsigned int notyet; /*!< Number of signatures not yet valid (inception > now) */ + unsigned int expired_before_inception; /*!< Number of signatures already expired before inception time */ unsigned int signer_invalid; /*!< Signer is not zone apex */ unsigned int labels_invalid; /*!< Number of labels in RRSIG */ unsigned int key_invalid; /*!< Algorithm/keytag/key owner */ @@ -78,10 +79,17 @@ typedef struct kr_rrset_validation_ctx kr_rrset_validation_ctx_t; int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx, knot_rrset_t *covered); /** - * Return true iff the RRset contains at least one usable DS. See RFC6840 5.2. + * Check whether the RRset contains at least one usable DS. + * + * See RFC6840 5.2. + * @param ta Pointer to TA RRSet. + * @return kr_ok() if at least one DS is supported + * DNSSEC_INVALID_KEY_ALGORITHM if all DSes are not supported, because of their key algorithm + * DNSSEC_INVALID_DIGEST_ALGORITHM if all DSes are not supported, because of their digest algorithm + * @note Given that entries are iterated until a supported DS is found, the error refers to the last one. */ KR_EXPORT KR_PURE -bool kr_ds_algo_support(const knot_rrset_t *ta); +int kr_ds_algo_support(const knot_rrset_t *ta); /** * Check whether the DNSKEY rrset matches the supplied trust anchor RRSet. @@ -97,13 +105,20 @@ int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rdataset_t *s // flags: https://www.iana.org/assignments/dnskey-flags/dnskey-flags.xhtml // https://datatracker.ietf.org/doc/html/rfc4034#section-2.1 -/** Return true if the DNSKEY has the SEP flag (normally ignored). */ +/** Return true if the DNSKEY has the SEP flag/bit set (normally ignored). */ KR_EXPORT inline KR_PURE bool kr_dnssec_key_sep_flag(const uint8_t *dnskey_rdata) { return dnskey_rdata[1] & 0x01; } +/** Return true if the DNSKEY has the Zone Key flag/bit set. */ +KR_EXPORT inline KR_PURE +bool kr_dnssec_key_zonekey_flag(const uint8_t *dnskey_rdata) +{ + return dnskey_rdata[0] & 0x01; +} + /** Return true if the DNSKEY is revoked. */ KR_EXPORT inline KR_PURE bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata) @@ -111,11 +126,14 @@ bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata) return dnskey_rdata[1] & 0x80; } -/** Return true if the DNSKEY could be used to validate zone records. */ +/** + * Return true if the DNSKEY could be used to validate zone records, meaning + * it correctly has the Zone Key flag/bit set to 1 and it is not revoked. + */ static inline KR_PURE bool kr_dnssec_key_usable(const uint8_t *dnskey_rdata) { - return (dnskey_rdata[0] & 0x01) && !kr_dnssec_key_revoked(dnskey_rdata); + return kr_dnssec_key_zonekey_flag(dnskey_rdata) && !kr_dnssec_key_revoked(dnskey_rdata); } /** Return DNSKEY tag. diff --git a/lib/layer/validate.c b/lib/layer/validate.c index 395640cc..d6840a52 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -137,6 +137,7 @@ do_downgrade: // we do this deep inside calls because of having signer name avai VERBOSE_MSG(qry, "<= DNSSEC downgraded due to expensive NSEC3: %d iterations, %d salt length\n", (int)knot_nsec3_iters(rd), (int)knot_nsec3_salt_len(rd)); + kr_request_set_extended_error(qry->request, KNOT_EDNS_EDE_NSEC3_ITERS, "AUO2"); qry->flags.DNSSEC_WANT = false; qry->flags.DNSSEC_INSECURE = true; rank_records(qry, true, KR_RANK_INSECURE, vctx->zone_name); @@ -242,7 +243,9 @@ static int validate_section(kr_rrset_validation_ctx_t *vctx, struct kr_query *qr } else { kr_rank_set(&entry->rank, KR_RANK_BOGUS); vctx->err_cnt += 1; - if (vctx->rrs_counters.expired > 0) + if (vctx->rrs_counters.expired_before_inception > 0) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_EXPIRED_INV, "XXAP"); + else if (vctx->rrs_counters.expired > 0) kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_EXPIRED, "YFJ2"); else if (vctx->rrs_counters.notyet > 0) kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_NOTYET, "UBBS"); @@ -368,7 +371,12 @@ static int validate_keyset(struct kr_request *req, knot_pkt_t *answer, bool has_ } } if (sig_index < 0) { - kr_request_set_extended_error(req, KNOT_EDNS_EDE_RRSIG_MISS, "EZDC"); + if (!kr_dnssec_key_zonekey_flag(qry->zone_cut.key->rrs.rdata->data)) { + kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_BIT, "YQEH"); + } else { + kr_request_set_extended_error(req, KNOT_EDNS_EDE_RRSIG_MISS, + "EZDC: no valid RRSIGs for DNSKEY"); + } return kr_error(ENOENT); } const knot_rdataset_t *sig_rds = &req->answ_selected.at[sig_index]->rr->rrs; @@ -395,15 +403,20 @@ static int validate_keyset(struct kr_request *req, knot_pkt_t *answer, bool has_ ret == 0 ? KR_RANK_SECURE : KR_RANK_BOGUS); if (ret != 0) { - log_bogus_rrsig(&vctx, qry->zone_cut.key, "bogus key"); - knot_rrset_free(qry->zone_cut.key, qry->zone_cut.pool); - qry->zone_cut.key = NULL; - if (vctx.rrs_counters.expired > 0) + if (!kr_dnssec_key_zonekey_flag(qry->zone_cut.key->rrs.rdata->data)) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_BIT, "CYNG"); + else if (vctx.rrs_counters.expired_before_inception > 0) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_EXPIRED_INV, "4UBF"); + else if (vctx.rrs_counters.expired > 0) kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_EXPIRED, "6GJV"); else if (vctx.rrs_counters.notyet > 0) kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_NOTYET, "4DJQ"); else kr_request_set_extended_error(req, KNOT_EDNS_EDE_BOGUS, "EXRU"); + + log_bogus_rrsig(&vctx, qry->zone_cut.key, "bogus key"); + knot_rrset_free(qry->zone_cut.key, qry->zone_cut.pool); + qry->zone_cut.key = NULL; return ret; } @@ -1147,10 +1160,19 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt) if (knot_wire_get_aa(pkt->wire) && qtype == KNOT_RRTYPE_DNSKEY) { const knot_rrset_t *ds = qry->zone_cut.trust_anchor; - if (ds && !kr_ds_algo_support(ds)) { - VERBOSE_MSG(qry, ">< all DS entries use unsupported algorithm pairs, going insecure\n"); - /* ^ the message is a bit imprecise to avoid being too verbose */ - kr_request_set_extended_error(req, KNOT_EDNS_EDE_OTHER, "LSLC: unsupported digest/key"); + ret = ds ? kr_ds_algo_support(ds) : kr_ok(); + if (ret != kr_ok()) { + char *reason = "???"; + if (ret == DNSSEC_INVALID_KEY_ALGORITHM) { + reason = "key"; + kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_ALG, "PBAO"); + } else if (ret == DNSSEC_INVALID_DIGEST_ALGORITHM) { + reason = "digest"; + kr_request_set_extended_error(req, KNOT_EDNS_EDE_DS_DIGEST, "DDDV"); + } + VERBOSE_MSG(qry, + ">< all DS entries are unsupported (last error: %s algorithm), going insecure\n", + reason); qry->flags.DNSSEC_WANT = false; qry->flags.DNSSEC_INSECURE = true; rank_records(qry, true, KR_RANK_INSECURE, qry->zone_cut.name); @@ -1367,11 +1389,14 @@ static int validate_finalize(kr_layer_t *ctx) { case KNOT_EDNS_EDE_NSEC_MISS: case KNOT_EDNS_EDE_RRSIG_MISS: case KNOT_EDNS_EDE_SIG_EXPIRED: + case KNOT_EDNS_EDE_EXPIRED_INV: case KNOT_EDNS_EDE_SIG_NOTYET: + case KNOT_EDNS_EDE_DNSKEY_BIT: + case KNOT_EDNS_EDE_DNSKEY_ALG: + case KNOT_EDNS_EDE_DS_DIGEST: kr_request_set_extended_error(ctx->req, KNOT_EDNS_EDE_NONE, NULL); break; case KNOT_EDNS_EDE_DNSKEY_MISS: - case KNOT_EDNS_EDE_DNSKEY_BIT: kr_assert(false); /* These EDE codes aren't used. */ break; default: break; /* Remaining codes don't indicate hard DNSSEC failure. */ diff --git a/lib/resolve-produce.c b/lib/resolve-produce.c index 563a2ca2..a3a2401e 100644 --- a/lib/resolve-produce.c +++ b/lib/resolve-produce.c @@ -697,6 +697,18 @@ int kr_resolve_produce(struct kr_request *request, struct kr_transport **transpo if (qry->flags.NO_NS_FOUND) { ITERATE_LAYERS(request, qry, reset); kr_rplan_pop(rplan, qry); + + /* Construct EDE message. We need it on mempool. */ + char cut_buf[KR_DNAME_STR_MAXLEN]; + char *msg = knot_dname_to_str(cut_buf, qry->zone_cut.name, sizeof(cut_buf)); + if (!kr_fails_assert(msg)) { + if (*qry->zone_cut.name != '\0') /* Strip trailing dot. */ + cut_buf[strlen(cut_buf) - 1] = '\0'; + msg = kr_strcatdup_pool(&request->pool, 2, + "P3CD: delegation ", cut_buf); + } + kr_request_set_extended_error(request, KNOT_EDNS_EDE_NREACH_AUTH, msg); + return KR_STATE_FAIL; } else { /* FIXME: This is probably quite inefficient: diff --git a/lib/resolve.c b/lib/resolve.c index 4b4827f2..bc00471b 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -738,6 +738,17 @@ int kr_resolve_consume(struct kr_request *request, struct kr_transport **transpo qry->flags.NO_NS_FOUND = true; return KR_STATE_PRODUCE; } + + /* Construct EDE message. We need it on mempool. */ + char cut_buf[KR_DNAME_STR_MAXLEN]; + char *msg = knot_dname_to_str(cut_buf, qry->zone_cut.name, sizeof(cut_buf)); + if (!kr_fails_assert(msg)) { + if (*qry->zone_cut.name != '\0') /* Strip trailing dot. */ + cut_buf[strlen(cut_buf) - 1] = '\0'; + msg = kr_strcatdup_pool(&request->pool, 2, + "OLX2: delegation ", cut_buf); + } + kr_request_set_extended_error(request, KNOT_EDNS_EDE_NREACH_AUTH, msg); return KR_STATE_FAIL; } } else { -- cgit v1.2.3 From fd7066d0033a45ecf58108e64a8c1865e5aaff3a Mon Sep 17 00:00:00 2001 From: menakite <29005531+menakite@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:52:45 +0200 Subject: validator: set EDE code if SEP does not match or DNSKEY is revoked. If the tag and algorithm of DS and DNSKEY do not correspond, or in case the DNSKEY is revoked, set EDE code "DNSKEY Missing". If both match, but the algorithm is not supported, set EDE code "Unsupported DNSKEY Algorithm". In case RRSIGs for DNSKEY exist, but can't be validated due to a key error, set EDE code "RRSIGs Missing". --- lib/layer/validate.c | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/layer/validate.c b/lib/layer/validate.c index d6840a52..1a871b44 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "lib/dnssec/nsec.h" #include "lib/dnssec/nsec3.h" @@ -403,8 +404,37 @@ static int validate_keyset(struct kr_request *req, knot_pkt_t *answer, bool has_ ret == 0 ? KR_RANK_SECURE : KR_RANK_BOGUS); if (ret != 0) { - if (!kr_dnssec_key_zonekey_flag(qry->zone_cut.key->rrs.rdata->data)) + const knot_rdataset_t *ds = &qry->zone_cut.trust_anchor->rrs; + int sep_keytag = kr_dnssec_key_tag(KNOT_RRTYPE_DS, ds->rdata->data, ds->rdata->len); + int dnskey_keytag = -1; + bool have_zone_key_bit = true, dnskey_algo_supported = true; + knot_rdata_t *rdata_sep = NULL, *rdata_i = qry->zone_cut.key->rrs.rdata; + for (uint8_t i = 0; i < qry->zone_cut.key->rrs.count; + ++i, rdata_i = knot_rdataset_next(rdata_i)) { + if (dnskey_keytag != sep_keytag) { + dnskey_keytag = kr_dnssec_key_tag(KNOT_RRTYPE_DNSKEY, rdata_i->data, rdata_i->len); + rdata_sep = rdata_i; + } + + if (!kr_dnssec_key_zonekey_flag(rdata_i->data)) + have_zone_key_bit = false; + + if (!dnssec_algorithm_key_support(knot_dnskey_alg(rdata_i))) + dnskey_algo_supported = false; + } + bool sep_matches_tag_algo = rdata_sep && sep_keytag == dnskey_keytag && + knot_ds_alg(ds->rdata) == knot_dnskey_alg(rdata_sep); + + if (!have_zone_key_bit) kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_BIT, "CYNG"); + else if (!sep_matches_tag_algo) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_MISS, "NMJZ: no matching SEP"); + else if (kr_dnssec_key_revoked(rdata_sep->data)) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_MISS, "DGVI: DNSKEY matching SEP has the Revoke bit set"); + else if (!dnskey_algo_supported) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_DNSKEY_ALG, "H6OO"); + else if (vctx.rrs_counters.matching_name_type == 0 && vctx.rrs_counters.key_invalid > 0) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_RRSIG_MISS, "7N4Z: no valid RRSIGs for DNSKEY"); else if (vctx.rrs_counters.expired_before_inception > 0) kr_request_set_extended_error(req, KNOT_EDNS_EDE_EXPIRED_INV, "4UBF"); else if (vctx.rrs_counters.expired > 0) @@ -1394,10 +1424,8 @@ static int validate_finalize(kr_layer_t *ctx) { case KNOT_EDNS_EDE_DNSKEY_BIT: case KNOT_EDNS_EDE_DNSKEY_ALG: case KNOT_EDNS_EDE_DS_DIGEST: - kr_request_set_extended_error(ctx->req, KNOT_EDNS_EDE_NONE, NULL); - break; case KNOT_EDNS_EDE_DNSKEY_MISS: - kr_assert(false); /* These EDE codes aren't used. */ + kr_request_set_extended_error(ctx->req, KNOT_EDNS_EDE_NONE, NULL); break; default: break; /* Remaining codes don't indicate hard DNSSEC failure. */ } -- cgit v1.2.3 From 9e0b9d121655ca2571f8cc4249b5037528897993 Mon Sep 17 00:00:00 2001 From: menakite <29005531+menakite@users.noreply.github.com> Date: Thu, 15 Aug 2024 22:01:23 +0200 Subject: cache: set EDE when synthesizing answer from aggressive cache --- lib/cache/peek.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib') diff --git a/lib/cache/peek.c b/lib/cache/peek.c index d12031fc..46a4868c 100644 --- a/lib/cache/peek.c +++ b/lib/cache/peek.c @@ -214,6 +214,7 @@ int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt) /* Try the NSEC* parameters in order, until success. * Let's not mix different parameters for NSEC* RRs in a single proof. */ + bool is_synthesized = false; for (int i = 0; ;) { int32_t log_new_ttl = -123456789; /* visually recognizable value */ ret = nsec_p_ttl(el[i], qry->timestamp.tv_sec, &log_new_ttl); @@ -234,6 +235,7 @@ int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt) /**** 2. and 3. inside */ ret = peek_encloser(k, &ans, sname_labels, lowest_rank, qry, cache); + is_synthesized = (ret == 0); nsec_p_cleanup(&ans.nsec_p); if (!ret) break; if (ret < 0) return ctx->state; @@ -316,6 +318,10 @@ int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt) qf->CACHED = true; qf->NO_MINIMIZE = true; + if (is_synthesized && qry == req->rplan.initial) { + kr_request_set_extended_error(req, KNOT_EDNS_EDE_SYNTHESIZED, + "2NEP: synthesized from aggressive cache"); + } return KR_STATE_DONE; } -- cgit v1.2.3 From 1ca37a0e312bd61b78f5334c8086967449a178a4 Mon Sep 17 00:00:00 2001 From: menakite <29005531+menakite@users.noreply.github.com> Date: Sun, 1 Sep 2024 20:56:44 +0200 Subject: validator: fix after fac462e163a2614e24d2c604a9b120b949796a72. See: https://gitlab.nic.cz/knot/knot-resolver/-/merge_requests/1590#note_304380 --- lib/layer/validate.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/layer/validate.c b/lib/layer/validate.c index 1a871b44..321b0a25 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -1415,6 +1415,13 @@ static int validate_finalize(kr_layer_t *ctx) { /* Clear DNSSEC-related Extended Error in case the request managed to succeed somehow. */ if (ctx->state == KR_STATE_DONE) { switch (ctx->req->extended_error.info_code) { + case KNOT_EDNS_EDE_DNSKEY_ALG: + case KNOT_EDNS_EDE_DS_DIGEST: + case KNOT_EDNS_EDE_NSEC3_ITERS: ; + /* These EDEs are meant to result into _INSECURE success. */ + const struct kr_query *qry = kr_rplan_resolved(&ctx->req->rplan); + if (qry->flags.DNSSEC_INSECURE) + break; case KNOT_EDNS_EDE_BOGUS: case KNOT_EDNS_EDE_NSEC_MISS: case KNOT_EDNS_EDE_RRSIG_MISS: @@ -1422,8 +1429,6 @@ static int validate_finalize(kr_layer_t *ctx) { case KNOT_EDNS_EDE_EXPIRED_INV: case KNOT_EDNS_EDE_SIG_NOTYET: case KNOT_EDNS_EDE_DNSKEY_BIT: - case KNOT_EDNS_EDE_DNSKEY_ALG: - case KNOT_EDNS_EDE_DS_DIGEST: case KNOT_EDNS_EDE_DNSKEY_MISS: kr_request_set_extended_error(ctx->req, KNOT_EDNS_EDE_NONE, NULL); break; -- cgit v1.2.3 From 20f67c0925fd6b050efcf565714720679f1fcd33 Mon Sep 17 00:00:00 2001 From: Vladimír Čunát Date: Thu, 12 Sep 2024 14:54:16 +0200 Subject: lib/generic/array: extend the return type of array_push*() In case of no error, the index is returned, but `int` isn't a great choice for returning array indices. (though I doubt that we can get anywhere near 2^31 elements in practice) Detected by Coverity. --- lib/generic/array.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/generic/array.h b/lib/generic/array.h index 9bea546b..eb1f7bc2 100644 --- a/lib/generic/array.h +++ b/lib/generic/array.h @@ -122,7 +122,7 @@ static inline void array_std_free(void *baton, void *p) * @return element index on success, <0 on failure */ #define array_push_mm(array, val, reserve, baton) \ - (int)((array).len < (array).cap ? ((array).at[(array).len] = (val), (array).len++) \ + (ssize_t)((array).len < (array).cap ? ((array).at[(array).len] = (val), (array).len++) \ : (array_reserve_mm(array, ((array).cap + 1), reserve, baton) < 0 ? -1 \ : ((array).at[(array).len] = (val), (array).len++))) -- cgit v1.2.3 From e28089896d1ada6e48ac27fd05b1fefc381f738e Mon Sep 17 00:00:00 2001 From: Vladimír Čunát Date: Wed, 25 Sep 2024 10:27:16 +0200 Subject: local-data: generate CNAMEs from DNAMEs As with some other aspects, these DNAMEs do not work exactly as in a real zone, e.g. they don't cause occlusion. --- NEWS | 1 + daemon/lua/kres-gen-33.lua | 2 +- doc/user/config-local-data.rst | 7 +++ lib/rules/api.c | 133 +++++++++++++++++++++++++++++++++++++---- lib/rules/api.h | 3 + lib/rules/zonefile.c | 6 +- 6 files changed, 136 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/NEWS b/NEWS index 759d6f70..8642a9cb 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,7 @@ Improvements - views: improve interaction with old-style policies (!1576) - stats: add stale answer counter 'answer.stale' (!1591) - extended_errors: answer with EDE in more cases (!1585, !1588, !1590, !1592) +- local-data: make DNAMEs work, i.e. generate CNAMEs (!1609) Bugfixes -------- diff --git a/daemon/lua/kres-gen-33.lua b/daemon/lua/kres-gen-33.lua index ecbb6330..c99a1dda 100644 --- a/daemon/lua/kres-gen-33.lua +++ b/daemon/lua/kres-gen-33.lua @@ -350,7 +350,7 @@ struct kr_query_data_src { kr_rule_fwd_flags_t flags; knot_db_val_t targets_ptr; }; -enum kr_rule_sub_t {KR_RULE_SUB_EMPTY = 1, KR_RULE_SUB_NXDOMAIN, KR_RULE_SUB_NODATA, KR_RULE_SUB_REDIRECT}; +enum kr_rule_sub_t {KR_RULE_SUB_EMPTY = 1, KR_RULE_SUB_NXDOMAIN, KR_RULE_SUB_NODATA, KR_RULE_SUB_REDIRECT, KR_RULE_SUB_DNAME}; enum kr_proto {KR_PROTO_INTERNAL, KR_PROTO_UDP53, KR_PROTO_TCP53, KR_PROTO_DOT, KR_PROTO_DOH, KR_PROTO_DOQ, KR_PROTO_COUNT}; typedef unsigned char kr_proto_set; kr_layer_t kr_layer_t_static; diff --git a/doc/user/config-local-data.rst b/doc/user/config-local-data.rst index 24293105..c11d8b36 100644 --- a/doc/user/config-local-data.rst +++ b/doc/user/config-local-data.rst @@ -77,6 +77,13 @@ It provides various input formats described in following subsections. 34.example.com AAAA 2001:db8::3 34.example.com AAAA 2001:db8::4 + .. warning:: + + While you can insert all kinds of records and rules into ``local-data:``, + they won't work exactly as in real zones on authoritative servers. + For example, wildcards won't get expanded and DNAMEs won't cause occlusion. + + Response Policy Zones (RPZ) --------------------------- diff --git a/lib/rules/api.c b/lib/rules/api.c index 5ecbe29e..53ebbf7e 100644 --- a/lib/rules/api.c +++ b/lib/rules/api.c @@ -46,8 +46,12 @@ static int answer_exact_match(struct kr_query *qry, knot_pkt_t *pkt, uint16_t ty const uint8_t *data, const uint8_t *data_bound); static int answer_zla_empty(val_zla_type_t type, struct kr_query *qry, knot_pkt_t *pkt, knot_db_val_t zla_lf, uint32_t ttl); +static int answer_zla_dname(val_zla_type_t type, struct kr_query *qry, knot_pkt_t *pkt, + knot_db_val_t zla_lf, uint32_t ttl, knot_db_val_t *val); static int answer_zla_redirect(struct kr_query *qry, knot_pkt_t *pkt, const char *ruleset_name, knot_db_val_t zla_lf, uint32_t ttl); +static int rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type, + const knot_dname_t *target, uint32_t ttl, kr_rule_tags_t tags); // LATER: doing tag_names_default() and kr_rule_tag_add() inside a RW transaction would be better. static int tag_names_default(void) @@ -418,25 +422,30 @@ int rule_local_data_answer(struct kr_query *qry, knot_pkt_t *pkt) uint32_t ttl = KR_RULE_TTL_DEFAULT; if (val.len >= sizeof(ttl)) // allow omitting -> can't kr_assert deserialize_fails_assert(&val, &ttl); - if (kr_fails_assert(val.len == 0)) { - kr_log_error(RULES, "ERROR: unused bytes: %zu\n", val.len); - return kr_error(EILSEQ); - } + // Finally execute the rule. switch (ztype) { case KR_RULE_SUB_EMPTY: case KR_RULE_SUB_NXDOMAIN: case KR_RULE_SUB_NODATA: ret = answer_zla_empty(ztype, qry, pkt, zla_lf, ttl); - if (ret == kr_error(EAGAIN)) - goto shorten; - return ret ? ret : RET_ANSWERED; + break; case KR_RULE_SUB_REDIRECT: ret = answer_zla_redirect(qry, pkt, ruleset_name, zla_lf, ttl); - return ret ? kr_error(ret) : RET_ANSWERED; + break; + case KR_RULE_SUB_DNAME: + ret = answer_zla_dname(ztype, qry, pkt, zla_lf, ttl, &val); + break; default: return kr_error(EILSEQ); } + if (kr_fails_assert(val.len == 0)) { + kr_log_error(RULES, "ERROR: unused bytes: %zu\n", val.len); + return kr_error(EILSEQ); + } + if (ret == kr_error(EAGAIN)) + goto shorten; + return ret ? kr_error(ret) : RET_ANSWERED; } while (true); } @@ -570,7 +579,17 @@ int local_data_ins(knot_db_val_t key, const knot_rrset_t *rrs, int ret = ruledb_op(write, &key, &val, 1); // TODO: overwriting on ==tags? // ENOSPC seems to be the only expectable error. kr_assert(ret == 0 || ret == kr_error(ENOSPC)); - return ret; + + if (ret || rrs->type != KNOT_RRTYPE_DNAME) + return ret; + // Now we do special handling for DNAMEs + // - we inserted as usual, so that it works with QTYPE == DNAME + // - now we insert a ZLA to handle generating CNAMEs + // - yes, some edge cases won't work as in real DNS zones (e.g. occlusion) + if (kr_fails_assert(rrs->rrs.count)) + return kr_error(EINVAL); + return rule_local_subtree(rrs->owner, KR_RULE_SUB_DNAME, + knot_dname_target(rrs->rrs.rdata), rrs->ttl, tags); } int kr_rule_local_data_del(const knot_rrset_t *rrs, kr_rule_tags_t tags) { @@ -697,6 +716,78 @@ static int answer_zla_empty(val_zla_type_t type, struct kr_query *qry, knot_pkt_ return kr_ok(); } +static int answer_zla_dname(val_zla_type_t type, struct kr_query *qry, knot_pkt_t *pkt, + const knot_db_val_t zla_lf, uint32_t ttl, knot_db_val_t *val) +{ + if (kr_fails_assert(type == KR_RULE_SUB_DNAME)) + return kr_error(EINVAL); + + const knot_dname_t *dname_target = val->data; + // Theoretically this check could overread the val->len, but that's OK, + // as the policy DB contents wouldn't be directly written by a malicious party. + // Moreover, an overread shouldn't cause worse than a clean segfault. + if (kr_fails_assert(knot_dname_size(dname_target) == val->len)) + return kr_error(EILSEQ); + { // update *val; avoiding void* arithmetics complicates this + char *tmp = val->data; + tmp += val->len; + val->data = tmp; + + val->len = 0; + } + + knot_dname_t apex_name[KNOT_DNAME_MAXLEN]; + int ret = knot_dname_lf2wire(apex_name, zla_lf.len, zla_lf.data); + CHECK_RET(ret); + + const bool hit_apex = knot_dname_is_equal(qry->sname, apex_name); + if (hit_apex && type == KR_RULE_SUB_DNAME) + return kr_error(EAGAIN); // LATER: maybe a type that matches apex + + // Start constructing the (pseudo-)packet. + ret = pkt_renew(pkt, qry->sname, qry->stype); + CHECK_RET(ret); + struct answer_rrset arrset; + memset(&arrset, 0, sizeof(arrset)); + + arrset.set.rr = knot_rrset_new(qry->sname, KNOT_RRTYPE_CNAME, + KNOT_CLASS_IN, ttl, &pkt->mm); + if (kr_fails_assert(arrset.set.rr)) + return kr_error(ENOMEM); + const knot_dname_t *cname_target = knot_dname_replace_suffix(qry->sname, + knot_dname_labels(apex_name, NULL), dname_target, &pkt->mm); + const int rdata_len = knot_dname_size(cname_target); + const bool cname_fits = rdata_len <= KNOT_DNAME_MAXLEN; + if (cname_fits) { + ret = knot_rrset_add_rdata(arrset.set.rr, cname_target, + knot_dname_size(cname_target), &pkt->mm); + CHECK_RET(ret); + } + + arrset.set.rank = KR_RANK_SECURE | KR_RANK_AUTH; // local data has high trust + arrset.set.expiring = false; + + if (cname_fits) { + knot_wire_set_rcode(pkt->wire, KNOT_RCODE_NOERROR); + ret = knot_pkt_begin(pkt, KNOT_ANSWER); + CHECK_RET(ret); + + // Put links to the RR into the pkt. + ret = pkt_append(pkt, &arrset); + CHECK_RET(ret); + } else { + knot_wire_set_rcode(pkt->wire, KNOT_RCODE_YXDOMAIN); + } + + // Finishing touches. + qry->flags.EXPIRING = false; + qry->flags.CACHED = true; + qry->flags.NO_MINIMIZE = true; + + VERBOSE_MSG(qry, "=> satisfied by local data (DNAME)\n"); + return kr_ok(); +} + static int answer_zla_redirect(struct kr_query *qry, knot_pkt_t *pkt, const char *ruleset_name, const knot_db_val_t zla_lf, uint32_t ttl) { @@ -760,6 +851,11 @@ nodata: // Want NODATA answer (or NOERROR if it hits apex SOA). return kr_ok(); } +int kr_rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type, + uint32_t ttl, kr_rule_tags_t tags) +{ + return rule_local_subtree(apex, type, NULL, ttl, tags); +} knot_db_val_t zla_key(const knot_dname_t *apex, uint8_t key_data[KEY_MAXLEN]) { kr_require(the_rules); @@ -775,11 +871,16 @@ knot_db_val_t zla_key(const knot_dname_t *apex, uint8_t key_data[KEY_MAXLEN]) key.len = key_data + KEY_DNAME_END_OFFSET - (uint8_t *)key.data; return key; } -int kr_rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type, - uint32_t ttl, kr_rule_tags_t tags) +static int rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type, + const knot_dname_t *target, uint32_t ttl, kr_rule_tags_t tags) { // type-check + const bool has_target = (type == KR_RULE_SUB_DNAME); switch (type) { + case KR_RULE_SUB_DNAME: + if (kr_fails_assert(!!target == has_target)) + return kr_error(EINVAL); + break; case KR_RULE_SUB_EMPTY: case KR_RULE_SUB_NXDOMAIN: case KR_RULE_SUB_NODATA: @@ -797,8 +898,10 @@ int kr_rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type, knot_db_val_t key = zla_key(apex, key_data); // Prepare the data into a temporary buffer. - const bool has_ttl = ttl != KR_RULE_TTL_DEFAULT; - const int val_len = sizeof(tags) + sizeof(ztype) + (has_ttl ? sizeof(ttl) : 0); + const int target_len = has_target ? knot_dname_size(target) : 0; + const bool has_ttl = ttl != KR_RULE_TTL_DEFAULT || has_target; + const int val_len = sizeof(tags) + sizeof(ztype) + (has_ttl ? sizeof(ttl) : 0) + + target_len; uint8_t buf[val_len], *data = buf; memcpy(data, &tags, sizeof(tags)); data += sizeof(tags); @@ -808,6 +911,10 @@ int kr_rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type, memcpy(data, &ttl, sizeof(ttl)); data += sizeof(ttl); } + if (has_target) { + memcpy(data, target, target_len); + data += target_len; + } kr_require(data == buf + val_len); knot_db_val_t val = { .data = buf, .len = val_len }; diff --git a/lib/rules/api.h b/lib/rules/api.h index f1737a19..c7d1dd29 100644 --- a/lib/rules/api.h +++ b/lib/rules/api.h @@ -156,11 +156,14 @@ enum kr_rule_sub_t { KR_RULE_SUB_NODATA, /// Redirect: anything beneath has the same data as apex (except NS+SOA). KR_RULE_SUB_REDIRECT, + /// Act similar to DNAME: rebase everything underneath by generated CNAMEs. + KR_RULE_SUB_DNAME, }; /** Insert a simple sub-tree rule. * * - into the default rule-set * - SOA and NS for generated answers aren't overridable. + * - type: you can't use _DNAME via this function; insert it by kr_rule_local_data_ins() */ KR_EXPORT int kr_rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type, diff --git a/lib/rules/zonefile.c b/lib/rules/zonefile.c index d308f375..773ca937 100644 --- a/lib/rules/zonefile.c +++ b/lib/rules/zonefile.c @@ -47,8 +47,10 @@ static void rr_scan2trie(zs_scanner_t *s) rr->ttl = s->r_ttl; // we could also warn here } else { rr = *rr_p = mm_alloc(s_data->pool, sizeof(*rr)); - knot_rrset_init(rr, NULL, s->r_type, KNOT_CLASS_IN, s->r_ttl); - // we don't ^^ need owner so save allocation + knot_dname_t *owner = NULL; // we only utilize owner for DNAMEs + if (s->r_type == KNOT_RRTYPE_DNAME) // Nit: copy could be done a bit faster + owner = knot_dname_copy(s->r_owner, s_data->pool); + knot_rrset_init(rr, owner, s->r_type, KNOT_CLASS_IN, s->r_ttl); } int ret = knot_rrset_add_rdata(rr, s->r_data, s->r_data_length, s_data->pool); kr_assert(!ret); -- cgit v1.2.3