]> git.ipfire.org Git - ipfire-2.x.git/commitdiff
knot-resolver: Make multiple addresses work
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 8 Jul 2026 13:34:44 +0000 (13:34 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 9 Jul 2026 10:30:52 +0000 (10:30 +0000)
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 <michael.tremer@ipfire.org>
lfs/knot-resolver
src/patches/knot-resolver-6.4.0-1854.patch [new file with mode: 0644]

index 14ce5eb7c92fc292c6cbc5b23e42561265047676..3d49a3bc7d218bede3eb502897e436bc125ffcc6 100644 (file)
@@ -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 (file)
index 0000000..94267c2
--- /dev/null
@@ -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).