From: Michael Tremer Date: Wed, 8 Jul 2026 13:34:44 +0000 (+0000) Subject: knot-resolver: Make multiple addresses work X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5eda68d8bf5335ff5fa6cb9d6ee96ae10881587d;p=ipfire-2.x.git knot-resolver: Make multiple addresses work Backport of: https://gitlab.nic.cz/knot/knot-resolver/-/merge_requests/1854 Fixes: #14015 - Knot Resolver: returns only first A record (of multiple) for given name Signed-off-by: Michael Tremer --- diff --git a/lfs/knot-resolver b/lfs/knot-resolver index 14ce5eb7c..3d49a3bc7 100644 --- a/lfs/knot-resolver +++ b/lfs/knot-resolver @@ -70,6 +70,7 @@ $(subst %,%_BLAKE2,$(objects)) : $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) @$(PREBUILD) @rm -rf $(DIR_APP) && cd $(DIR_SRC) && tar axf $(DIR_DL)/$(DL_FILE) + cd $(DIR_APP) && patch -Np1 < $(DIR_SRC)/src/patches/knot-resolver-6.4.0-1854.patch cd $(DIR_APP) && meson setup \ --prefix=/usr \ -Duser=knot-resolver \ diff --git a/src/patches/knot-resolver-6.4.0-1854.patch b/src/patches/knot-resolver-6.4.0-1854.patch new file mode 100644 index 000000000..94267c2a4 --- /dev/null +++ b/src/patches/knot-resolver-6.4.0-1854.patch @@ -0,0 +1,251 @@ +diff --git a/lib/cache/cdb_api.h b/lib/cache/cdb_api.h +index 9e8a34d58..b589b564a 100644 +--- a/lib/cache/cdb_api.h ++++ b/lib/cache/cdb_api.h +@@ -65,11 +65,15 @@ struct kr_cdb_api { + * \return error code - accepting RW transactions can fail with LMDB. + */ + int (*commit)(kr_cdb_pt db, struct kr_cdb_stats *stat, bool accept_rw, bool reset_ro); ++ /** Run before a row of operations to ensure they happen in a single RW transaction, ++ * at least in case of successes. */ ++ int (*txn_open_rw)(kr_cdb_pt db, struct kr_cdb_stats *stat/*unused*/); + + /* Data access */ + + int (*read)(kr_cdb_pt db, struct kr_cdb_stats *stat, + const knot_db_val_t *key, knot_db_val_t *val, int maxcount); ++ /* TODO: the behavior of write() around transactions is a bit complex. */ + int (*write)(kr_cdb_pt db, struct kr_cdb_stats *stat, const knot_db_val_t *key, + knot_db_val_t *val, int maxcount); + +@@ -111,13 +115,19 @@ struct kr_cdb_api { + int (*check_health)(kr_cdb_pt db, struct kr_cdb_stats *stat); + + +- /** Start iterating; return the first *val with *key. ++ /** Start iterating: get the first *val with *key + return error code. + * +- * This only makes sense if !is_cache. +- * TODO: it only works inside RO transactions for now. ++ * - in cache: ensures a RO transaction (and commits the RW txn if any) ++ * - in ruledb: transaction is preserved if exists, otherwise RO txn gets opened + */ + int (*it_first)(kr_cdb_pt db, struct kr_cdb_stats *stat, + const knot_db_val_t *key, knot_db_val_t *val); +- /** Advance to the next *val with the same key. */ ++ /** Advance to the next *val with the same key. Return error code. */ + int (*it_next)(kr_cdb_pt db, struct kr_cdb_stats *stat, knot_db_val_t *val); ++ /** Delete the current *val + return error code. ++ * ++ * You can it_next() to continue. ++ * This assumed that you got it_first() in a RW txn. ++ */ ++ int (*it_del)(kr_cdb_pt db, struct kr_cdb_stats *stat); + }; +diff --git a/lib/cache/cdb_lmdb.c b/lib/cache/cdb_lmdb.c +index 0319b5cbb..6221c9492 100644 +--- a/lib/cache/cdb_lmdb.c ++++ b/lib/cache/cdb_lmdb.c +@@ -38,12 +38,13 @@ struct lmdb_env + * + * - only one of (ro,rw) may be active at once + * - non-NULL .ro may be active or reset +- * - non-NULL .rw is always active ++ * - ro_curs can survive an RW transaction and renewed later in an RO txn ++ * - non-NULL .rw is always active, non-NULL rw_curs is always active + */ + struct { + bool ro_active, ro_curs_active; + MDB_txn *ro, *rw; +- MDB_cursor *ro_curs; ++ MDB_cursor *ro_curs, *rw_curs; + } txn; + + bool is_cache; /**< cache vs. rules; from struct kr_cdb_opts::is_cache */ +@@ -236,6 +237,7 @@ static int cdb_commit(kr_cdb_pt db, struct kr_cdb_stats *stats, bool accept_rw, + mdb_txn_abort(env->txn.rw); + } + env->txn.rw = NULL; /* the transaction got freed even in case of errors */ ++ env->txn.rw_curs = NULL; /* auto-closed with txn if existed */ + } else if (reset_ro && env->txn.ro && env->txn.ro_active) { + mdb_txn_reset(env->txn.ro); + env->txn.ro_active = false; +@@ -244,21 +246,39 @@ static int cdb_commit(kr_cdb_pt db, struct kr_cdb_stats *stats, bool accept_rw, + return ret; + } + +-/** Obtain a read-only cursor (and a read-only transaction). +- * TODO: allow RW transaction (for ruledb iterator) */ ++static int cdb_txn_open_rw(kr_cdb_pt db, struct kr_cdb_stats *stat/*unused*/) ++{ ++ struct lmdb_env *env = db2env(db); ++ MDB_txn *txn = NULL; ++ return txn_get(env, &txn, false); ++} ++ ++/** Obtain a cursor (and a transaction). ++ * ++ * - for cache these are read only ++ * - for policy the RW transaction gets preserved if open */ + static int txn_curs_get(struct lmdb_env *env, MDB_cursor **curs, struct kr_cdb_stats *stats) + { + if (kr_fails_assert(env && curs)) + return kr_error(EINVAL); + if (env->txn.ro_curs_active) +- goto success; +- /* Only in a read-only txn; TODO: it's a bit messy/coupled +- * At least for rules we don't do the auto-commit feature. */ +- if (env->txn.rw) { +- if (!env->is_cache) return kr_error(EINPROGRESS); ++ goto success_ro; ++ /* Only in a read-only txn if for cache; TODO: it's a bit messy/coupled */ ++ if (env->txn.rw && env->is_cache) { + int ret = cdb_commit(env2db(env), stats, true, false); + if (ret) return ret; + } ++ if (env->txn.rw && !env->is_cache) { ++ if (!env->txn.rw_curs) { ++ MDB_txn *txn = env->txn.rw; ++ int ret = mdb_cursor_open(txn, env->dbi, &env->txn.rw_curs); ++ if (ret) return lmdb_error(env, ret); ++ } ++ if (kr_fails_assert(env->txn.rw_curs)) ++ return kr_error(EINVAL); ++ *curs = env->txn.rw_curs; ++ return kr_ok(); ++ } + MDB_txn *txn = NULL; + int ret = txn_get(env, &txn, true); + if (ret) return ret; +@@ -270,7 +290,7 @@ static int txn_curs_get(struct lmdb_env *env, MDB_cursor **curs, struct kr_cdb_s + } + if (ret) return lmdb_error(env, ret); + env->txn.ro_curs_active = true; +-success: ++success_ro: + kr_assert(env->txn.ro_curs_active && env->txn.ro && env->txn.ro_active + && !env->txn.rw); + *curs = env->txn.ro_curs; +@@ -301,6 +321,7 @@ static void txn_abort(struct lmdb_env *env) + if (env->txn.rw) { + mdb_txn_abort(env->txn.rw); + env->txn.rw = NULL; /* the transaction got freed even in case of errors */ ++ env->txn.rw_curs = NULL; /* auto-closed with txn if existed */ + } + } + +@@ -940,7 +961,7 @@ static int cdb_it_next(kr_cdb_pt db, struct kr_cdb_stats *stats, knot_db_val_t * + if (kr_fails_assert(db && val)) + return kr_error(EINVAL); + struct lmdb_env *env = db2env(db); +- if (kr_fails_assert(!env->is_cache && env->txn.ro_curs_active)) ++ if (kr_fails_assert(!env->is_cache && (env->txn.ro_curs_active || env->txn.rw_curs))) + return kr_error(EINVAL); + + MDB_cursor *curs = NULL; +@@ -952,19 +973,35 @@ static int cdb_it_next(kr_cdb_pt db, struct kr_cdb_stats *stats, knot_db_val_t * + *val = val_mdb2knot(val2_m); + return kr_ok(); + } ++static int cdb_it_del(kr_cdb_pt db, struct kr_cdb_stats *stats) ++{ ++ if (kr_fails_assert(db)) ++ return kr_error(EINVAL); ++ struct lmdb_env *env = db2env(db); ++ if (kr_fails_assert(!env->is_cache && env->txn.rw_curs)) ++ return kr_error(EINVAL); ++ ++ MDB_cursor *curs = NULL; ++ int ret = txn_curs_get(env, &curs, stats); ++ if (ret) return ret; ++ ret = mdb_cursor_del(curs, 0); ++ if (ret) return lmdb_error(env, ret); ++ return kr_ok(); ++} + + + const struct kr_cdb_api *kr_cdb_lmdb(void) + { + static const struct kr_cdb_api api = { + "lmdb", +- cdb_init, cdb_deinit, cdb_count, cdb_clear, cdb_commit, ++ cdb_init, cdb_deinit, cdb_count, cdb_clear, ++ cdb_commit, cdb_txn_open_rw, + cdb_readv, cdb_writev, cdb_remove, + cdb_match, + cdb_read_leq, cdb_read_less, + cdb_usage_percent, cdb_get_maxsize, + cdb_check_health, +- cdb_it_first, cdb_it_next, ++ cdb_it_first, cdb_it_next, cdb_it_del, + }; + return &api; + } +diff --git a/lib/rules/api.c b/lib/rules/api.c +index d9dbfcf1f..4c213d6f5 100644 +--- a/lib/rules/api.c ++++ b/lib/rules/api.c +@@ -663,21 +663,27 @@ int kr_rule_local_data_merge(const knot_rrset_t *rrs, const kr_rule_tags_t tags, + uint8_t key_data[KEY_MAXLEN]; + knot_db_val_t key = local_data_key(rrs, key_data, RULESET_DEFAULT); + knot_db_val_t val; ++ int ret = ruledb_op(txn_open_rw); ++ if (ret) ++ return kr_error(ret); + // Transaction: we assume that we're in a RW transaction already, + // so that here we already "have a lock" on the last version. +- // FIXME: iterate over multiple tags, once iterator supports RW TXN +- int ret = ruledb_op(read, &key, &val, 1); ++ // Multiple variants are possible, with different tags. ++ for (ret = ruledb_op(it_first, &key, &val); ret == 0; ret = ruledb_op(it_next, &val)) { ++ // we're looking for the same tag-set ++ kr_rule_tags_t tags_old; ++ if (deserialize_fails_assert(&val, &tags_old) || tags_old != tags) ++ continue; ++ kr_rule_opts_t opts_old; ++ if (deserialize_fails_assert(&val, &opts_old)) ++ continue; ++ break; ++ } + 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; +- kr_rule_opts_t opts_old; +- if (deserialize_fails_assert(&val, &opts_old)) +- goto fallback; ++ + // merge TTLs + uint32_t ttl; + if (deserialize_fails_assert(&val, &ttl)) +@@ -700,6 +706,10 @@ int kr_rule_local_data_merge(const knot_rrset_t *rrs, const kr_rule_tags_t tags, + mm_ctx_delete(mm); + return kr_error(ret); + } ++ // ATM ruledb does not overwrite, so we `remove` before `write`. ++ ret = ruledb_op(it_del); ++ if (ret) ++ return kr_error(ret); + // everything is ready to insert the merged RRset + ret = local_data_ins(key, &rrs_new, NULL, tags, opts); + mm_ctx_delete(mm); +diff --git a/lib/rules/api.h b/lib/rules/api.h +index feea98e09..c3af5901b 100644 +--- a/lib/rules/api.h ++++ b/lib/rules/api.h +@@ -144,10 +144,8 @@ const uint32_t KR_RULE_TTL_DEFAULT; + KR_EXPORT + int kr_rule_local_data_ins(const knot_rrset_t *rrs, const knot_rdataset_t *sig_rds, + kr_rule_tags_t tags, kr_rule_opts_t opts); +-/** Merge RRs into a local data rule. ++/** Merge RRs into a local data rule with the same set of tags. + * +- * - FIXME: with multiple tags variants for the same name-type pair, +- * you typically end up with a single RR per RRset + * - 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).