summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVladimír Čunát <vladimir.cunat@nic.cz>2023-08-04 19:22:23 +0200
committerVladimír Čunát <vladimir.cunat@nic.cz>2023-08-17 15:58:24 +0200
commit321b39bdebbead9dfd339b40197d4c7e0bd27d61 (patch)
treed35228daf68553ed3f4535ede5a5e8661a5a19cf
parentMerge branch 'rm-poetry-lock' into '6.0' (diff)
downloadknot-resolver-321b39bdebbead9dfd339b40197d4c7e0bd27d61.tar.xz
knot-resolver-321b39bdebbead9dfd339b40197d4c7e0bd27d61.zip
hints: merge RRs instead of replacing them
We had this behavior in 5.x. Lua level: affects hints.set() and hints['name'] and hints.add_hosts() YAML level: /local-data/addresses and /local-data/addresses-files I considered various approaches when writing this. This one won because in /etc/hosts like files a name can be repeated with arbitrary lines in between, and users can reasonably expect it to collect all addresses.
-rw-r--r--lib/rules/api.c47
-rw-r--r--lib/rules/api.h9
-rw-r--r--modules/hints/hints.c21
3 files changed, 67 insertions, 10 deletions
diff --git a/lib/rules/api.c b/lib/rules/api.c
index 6abc0ac1..03fa1bc5 100644
--- a/lib/rules/api.c
+++ b/lib/rules/api.c
@@ -570,6 +570,53 @@ int kr_rule_local_data_del(const knot_rrset_t *rrs, kr_rule_tags_t tags)
knot_db_val_t key = local_data_key(rrs, key_data, RULESET_DEFAULT);
return ruledb_op(remove, &key, 1);
}
+int kr_rule_local_data_merge(const knot_rrset_t *rrs, const kr_rule_tags_t tags)
+{
+ ENSURE_the_rules;
+ // Construct the DB key.
+ uint8_t key_data[KEY_MAXLEN];
+ knot_db_val_t key = local_data_key(rrs, key_data, RULESET_DEFAULT);
+ knot_db_val_t val;
+ // Transaction: we assume that we're in a RW transaction already,
+ // so that here we already "have a lock" on the last version.
+ int ret = ruledb_op(read, &key, &val, 1);
+ if (abs(ret) == abs(ENOENT))
+ goto fallback;
+ if (ret)
+ return kr_error(ret);
+ // check tags
+ kr_rule_tags_t tags_old;
+ if (deserialize_fails_assert(&val, &tags_old) || tags_old != tags)
+ goto fallback;
+ // merge TTLs
+ uint32_t ttl;
+ if (deserialize_fails_assert(&val, &ttl))
+ goto fallback;
+ if (ttl > rrs->ttl)
+ ttl = rrs->ttl;
+ knot_rrset_t rrs_new;
+ knot_rrset_init(&rrs_new, rrs->owner, rrs->type, rrs->rclass, ttl);
+ // merge the rdatasets
+ knot_mm_t *mm = mm_ctx_mempool2(MM_DEFAULT_BLKSIZE); // frag. optimization
+ if (!mm)
+ return kr_error(ENOMEM);
+ ret = rdataset_materialize(&rrs_new.rrs, val.data, val.data + val.len, mm);
+ if (kr_fails_assert(ret >= 0)) { // just invalid call or rubbish data
+ mm_ctx_delete(mm);
+ return ret;
+ }
+ ret = knot_rdataset_merge(&rrs_new.rrs, &rrs->rrs, mm);
+ if (ret) { // ENOMEM or hitting 64 KiB limit
+ mm_ctx_delete(mm);
+ return kr_error(ret);
+ }
+ // everything is ready to insert the merged RRset
+ ret = local_data_ins(key, &rrs_new, NULL, tags);
+ mm_ctx_delete(mm);
+ return ret;
+fallback:
+ return local_data_ins(key, rrs, NULL, tags);
+}
/** Empty or NXDOMAIN or NODATA. Returning kr_error(EAGAIN) means the rule didn't match. */
static int answer_zla_empty(val_zla_type_t type, struct kr_query *qry, knot_pkt_t *pkt,
diff --git a/lib/rules/api.h b/lib/rules/api.h
index fa50807e..e4995582 100644
--- a/lib/rules/api.h
+++ b/lib/rules/api.h
@@ -70,6 +70,15 @@ int kr_view_select_action(const struct kr_request *req, knot_db_val_t *selected)
KR_EXPORT
int kr_rule_local_data_ins(const knot_rrset_t *rrs, const knot_rdataset_t *sig_rds,
kr_rule_tags_t tags);
+/** Merge RRs into a local data rule.
+ *
+ * - If tags don't match, overwrite the data and return kr_error(EEXIST).
+ * - RRSIGs get dropped, if any were attached.
+ * - We assume that this is called with a RW transaction open already,
+ * which is always true in normal usage (long RW txn covering whole config).
+ */
+KR_EXPORT
+int kr_rule_local_data_merge(const knot_rrset_t *rrs, kr_rule_tags_t tags);
/** Remove a local data rule.
*
diff --git a/modules/hints/hints.c b/modules/hints/hints.c
index ccbc9880..b5f8e3de 100644
--- a/modules/hints/hints.c
+++ b/modules/hints/hints.c
@@ -142,11 +142,12 @@ static int add_pair(const struct hints_data *data, const char *name, const char
} else {
ret = knot_rrset_add_rdata(&rrs, (const uint8_t *)&ia.ip4.sin_addr, 4, NULL);
}
- if (!ret) ret = kr_rule_local_data_ins(&rrs, NULL, KR_RULE_TAGS_ALL);
+ if (!ret) ret = kr_rule_local_data_merge(&rrs, KR_RULE_TAGS_ALL);
if (!ret && data->use_nodata) {
rrs.type = KNOT_RRTYPE_CNAME;
rrs.rrs.count = 0;
rrs.rrs.size = 0;
+ // no point in the _merge() variant here
ret = kr_rule_local_data_ins(&rrs, NULL, KR_RULE_TAGS_ALL);
}
@@ -167,7 +168,9 @@ static int add_reverse_pair(const struct hints_data *data, const char *name, con
return kr_error(EINVAL);
int ret = knot_rrset_add_rdata(&rrs, ptr_name, knot_dname_size(ptr_name), NULL);
if (!ret) {
- ret = kr_rule_local_data_ins(&rrs, NULL, KR_RULE_TAGS_ALL);
+ // We use _merge(). Using multiple PTR RRs is not recommended generally,
+ // but here it seems better than choosing any "arbitrarily".
+ ret = kr_rule_local_data_merge(&rrs, KR_RULE_TAGS_ALL);
knot_rdataset_clear(&rrs.rrs, NULL);
}
return ret;
@@ -481,15 +484,13 @@ int hints_init(struct kr_module *module)
module->layer = &layer;
static const struct kr_prop props[] = {
- /* FIXME(decide): .set() and .del() used to work on individual RRs;
- * now they overwrite or delete whole RRsets.
- * Also, .get() doesn't work at all.
+ /* TODO(decide): .del() used to work on individual RRs;
+ * now it deletes whole RRsets. Also, .get() doesn't work at all.
+ *
+ * We'll probably be deprecating access direct through these non-declarative
+ * commands (set, get, del) which are also usable dynamically.
*
- * It really depends what kind of config/API we'll be exposing to user.
- * - Manipulating whole RRsets generally makes more sense to me.
- * (But hints.set() currently can't even insert larger sets.)
- * - We'll probably be deprecating access through these non-declarative
- * commands (set, get, del) which are also usable dynamically.
+ * For .set() and .add_hosts() see the RW transaction note at kr_rule_local_data_merge()
*/
{ &hint_set, "set", "Set {name, address} hint.", },
{ &hint_del, "del", "Delete one {name, address} hint or all addresses for the name.", },