+Knot Resolver 6.0.8 (2024-0m-dd)
+================================
+
+Improvements
+------------
+- TLS (DoT, DoH): respect crypto policy overrides in OS (!1526)
+- arch package: fix after they renamed a dependency (!1536)
+- manager: export metrics to JSON via management HTTP API (!1527)
+
+ * JSON is the new default metrics output format
+ * the ``prometheus-client`` Python package is now an optional dependency,
+ required only for Prometheus export to work
+- cache: prefetching records
+
+ * predict module: prefetching expiring records moved to prefetch module
+ * prefetch module: new module to prefetch expiring records
++- stats: add separate metrics for IPv6 and IPv4 (!1545)
+
+.. TODO: Change the link below to a versioned one when releasing.
+
+Incompatible changes
+--------------------
+- cache: the ``/cache/prediction`` configuration property has been reorganized
+ into ``/cache/prefetch/expiring`` and ``/cache/prefetch/prediction``, changing
+ the default behaviour as well. See the `relevant documentation section
+ <https://www.knot-resolver.cz/documentation/latest/config-cache-predict.html>`_
+ for more.
+
+
+Knot Resolver 6.0.7 (2024-03-27)
+================================
+
+Improvements
+------------
+- manager: clear the cache via management HTTP API (#876, !1491)
+- manager: added support for Python 3.12 and removed for 3.7 (!1502)
+- manager: use build-time install prefix to execute `kresd` instead of PATH (!1511)
+- docs: documentation is now separated into user and developer parts (!1514)
+- daemon: ignore UDP requests from ports < 1024 (!1507)
+- manager: increase startup timeout for processes (!1518, !1520)
+- local-data: increase default DB size to 2G on 64-bit platforms (!1518)
+
+Bugfixes
+--------
+- fix listening by interface name containing dashes (#900, !1500)
+- fix kresctl http request timeout (!1505)
+- fix RPZ if it contains apex NS record (!1516)
+- fix RPZ if SOA is repated, as usual in AXFR output (!1521)
+- avoid RPZ overriding the root SOA (!1521)
+- fix on 32-bit systems with 64-bit time_t (!1510)
+- fix paths to knot-dns libs if exec_prefix != prefix (!1503)
+- manager: add missing early check that neither a custom port nor TLS is set for
+ authoritative server forwarding (#902, !1505)
+
+
+Knot Resolver 6.0.6 (2024-02-13)
+================================
+
+Security
+--------
+- CVE-2023-50868: NSEC3 closest encloser proof can exhaust CPU
+
+ * validator: lower the NSEC3 iteration limit (150 -> 50)
+ * validator: similarly also limit excessive NSEC3 salt length
+ * cache: limit the amount of work on SHA1 in NSEC3 aggressive cache
+ * validator: limit the amount of work on SHA1 in NSEC3 proofs
+ * validator: refuse to validate answers with more than 8 NSEC3 records
+
+- CVE-2023-50387 "KeyTrap": DNSSEC verification complexity
+ could be exploited to exhaust CPU resources and stall DNS resolvers.
+ Solution boils down mainly to limiting crypto-validations per packet.
+
+ We would like to thank Elias Heftrig, Haya Schulmann, Niklas Vogel and Michael Waidner
+ from the German National Research Center for Applied Cybersecurity ATHENE
+ for bringing this vulnerability to our attention.
+
+Improvements
+------------
+- update addresses of B.root-servers.net (!1478)
+- tweak the default run_dir on non-Linux (!1481)
+
+Bugfixes
+--------
+- fix potential SERVFAIL deadlocks if net.ipv6 = false (#880)
+- fix validation of RRsets around 64 KiB size; needs libknot >= 3.4 (!1497)
+
+
+Knot Resolver 6.0.5 (2024-01-09)
+================================
+
+6.0.x are "early access" versions,
+not generally recommended for production use.
+
+6.0 contains biggest changes in the history of Knot Resolver releases.
+You will have to rewrite your configuration. See documentation, in particular:
+https://www.knot-resolver.cz/documentation/latest/upgrading-to-6.html
+
+
+
+
+5.x branch longterm support
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Knot Resolver 5.7.3 (2024-0m-dd)
+ ================================
+
+ Improvements
+ ------------
+ - stats: add separate metrics for IPv6 and IPv4 (!1544)
+
Knot Resolver 5.7.2 (2024-03-27)
================================
CONST_METRICS(X)
#undef X
};
- const char *key;
+
+ /// These metrics are read-only views, each simply summing a pair of const_metrics items.
+ struct sum_metric {
- .key = "request." #proto, \
++ const char *sub_key;
+ const size_t *val1, *val2;
+ };
+ static const struct sum_metric sum_metrics[] = {
+ // We're using this to aggregate v4 + v6 pairs.
+ #define DEF(proto) { \
++ .sub_key = #proto, \
+ .val1 = &const_metrics[metric_request_ ## proto ## 4].val, \
+ .val2 = &const_metrics[metric_request_ ## proto ## 6].val, \
+ }
+ DEF(udp),
+ DEF(tcp),
+ DEF(xdp),
+ DEF(dot),
+ DEF(doh),
+ #undef DEF
+ };
+ static const size_t sum_metrics_len = sizeof(sum_metrics) / sizeof(sum_metrics[0]);
++#define SUM_METRICS_SUP_KEY "request"
+
/** @endcond */
/** @internal LRU hash of most frequent names. */
return str_value;
}
}
- if (strcmp(smi->key, args) == 0) {
+ /* Check if it exists in aggregate metrics. */
+ for (int i = 0; i < sum_metrics_len; ++i) {
+ const struct sum_metric *smi = &sum_metrics[i];
++ if (strcmp(smi->sub_key, args) == 0) {
+ ret = asprintf(&str_value, "%zu", *smi->val1 + *smi->val2);
+ if (ret < 0)
+ return NULL;
+ return str_value;
+ }
+ }
/* Check in variable map */
trie_val_t *val = trie_get_try(data->trie, args, strlen(args));
if (!val)
size_t key_prefix_len; /**< Prefix length. Prefix is a wildcard if zero. */
};
++/** Ensures that the `root` node contains an object (and only an object - not a
++ * number, array, or anything else) with the specified `key`. If this cannot be
++ * ensured, fails on an assertion, or returns `NULL` if asserts are disabled. */
++static JsonNode *ensure_object(JsonNode *root, const char *key)
++{
++ JsonNode *node = json_find_member(root, key);
++ if (node) {
++ if (kr_fails_assert(node->tag == JSON_OBJECT))
++ return NULL;
++ } else {
++ node = json_mkobject();
++ if (kr_fails_assert(node))
++ return NULL;
++ json_append_member(root, key, node);
++ }
++ return node;
++}
++
/** Inserts the entry with a matching key into the JSON object. */
static int list_entry(const char *key, uint32_t key_len, trie_val_t *val, void *baton)
{
if (!key_matches_prefix(key, key_len, ctx->key_prefix, ctx->key_prefix_len))
return 0;
size_t number = (size_t)*val;
- auto_free char *key_nt = strndup(key, key_len);
- json_append_member(ctx->root, key_nt, json_mknumber((double)number));
+
+ uint32_t dot_index = 0;
+ for (uint32_t i = 0; i < key_len; i++) {
+ if (!key[i])
+ break;
+ if (key[i] == '.') {
+ dot_index = i;
+ }
+ }
+
+ if (dot_index) {
+ auto_free char *sup_key_nt = strndup(key, dot_index);
+ auto_free char *sub_key_nt = strndup(key + dot_index + 1, key_len - dot_index - 1);
- JsonNode *sup = json_find_member(ctx->root, sup_key_nt);
- if (!sup) {
- sup = json_mkobject();
- if (kr_fails_assert(sup))
- return 0;
- json_append_member(ctx->root, sup_key_nt, sup);
- }
- if (kr_fails_assert(sup))
++ JsonNode *sup = ensure_object(ctx->root, sup_key_nt);
++ if (!sup)
+ return 0;
+ json_append_member(sup, sub_key_nt, json_mknumber((double)number));
+ } else {
+ auto_free char *key_nt = strndup(key, key_len);
+ json_append_member(ctx->root, key_nt, json_mknumber((double)number));
+ }
return 0;
}
*/
static char* stats_list(void *env, struct kr_module *module, const char *args)
{
++ char *ret;
JsonNode *root = json_mkobject();
/* Walk const metrics map */
size_t args_len = args ? strlen(args) : 0;
for (unsigned i = 0; i < metric_const_end; ++i) {
struct const_metric_elm *elm = &const_metrics[i];
- if (!args || strncmp(elm->key, args, args_len) == 0) {
- json_append_member(root, elm->key, json_mknumber((double)elm->val));
+ if (!args || strcmp(elm->sup_key, args) == 0) {
- JsonNode *sup = json_find_member(root, elm->sup_key);
++ JsonNode *sup = ensure_object(root, elm->sup_key);
+ if (!sup) {
- sup = json_mkobject();
- if (kr_fails_assert(sup))
- break;
- json_append_member(root, elm->sup_key, sup);
++ ret = strdup("\"ERROR\"");
++ goto exit;
+ }
- if (kr_fails_assert(sup))
- break;
+ json_append_member(sup, elm->sub_key, json_mknumber((double)elm->val));
}
}
- if (!args || strncmp(elm->key, args, args_len) == 0) {
++
++ /* Walk sum metrics map */
++ JsonNode *sum_sup = ensure_object(root, SUM_METRICS_SUP_KEY);
++ if (!sum_sup) {
++ ret = strdup("\"ERROR\"");
++ goto exit;
++ }
+ for (int i = 0; i < sum_metrics_len; ++i) {
+ const struct sum_metric *elm = &sum_metrics[i];
- json_append_member(root, elm->key, json_mknumber(val));
++ if (!args || strncmp(elm->sub_key, args, args_len) == 0) {
+ size_t val = *elm->val1 + *elm->val2;
++ json_append_member(sum_sup, elm->sub_key, json_mknumber(val));
+ }
+ }
++
struct list_entry_context ctx = {
.root = root,
.key_prefix = args,
};
struct stat_data *data = module->data;
trie_apply_with_key(data->trie, list_entry, &ctx);
-- char *ret = json_encode(root);
++ ret = json_encode(root);
++exit:
json_delete(root);
return ret;
}