void *root;
struct knot_mm *pool;
} map_t;
+struct kr_cache_scope {
+ uint8_t family;
+ uint8_t scope_len;
+ uint8_t address[16];
+};
+typedef struct kr_cache_scope kr_cache_scope_t;
struct kr_qflags {
_Bool NO_MINIMIZE : 1;
_Bool NO_THROTTLE : 1;
trace_log_f trace_log;
trace_callback_f trace_finish;
int vars_ref;
- int cache_scope_len_bits;
- const uint8_t *cache_scope;
+ kr_cache_scope_t cache_scope;
knot_mm_t pool;
};
enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32};
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);
int kr_cache_closest_apex(struct kr_cache *, const knot_dname_t *, _Bool, knot_dname_t **);
-int kr_cache_insert_rr(struct kr_cache *, const knot_rrset_t *, const knot_rrset_t *, uint8_t, uint32_t);
int kr_cache_remove(struct kr_cache *, const knot_dname_t *, uint16_t);
int kr_cache_remove_subtree(struct kr_cache *, const knot_dname_t *, _Bool, int);
-int kr_cache_insert_rr(struct kr_cache *, const knot_rrset_t *, const knot_rrset_t *, uint8_t, uint32_t, const uint8_t *, int);
+int kr_cache_insert_rr(struct kr_cache *, const knot_rrset_t *, const knot_rrset_t *, uint8_t, uint32_t, const kr_cache_scope_t *);
int kr_cache_sync(struct kr_cache *);
typedef struct {
uint8_t bitmap[32];
# generics
map_t
# libkres
+ struct kr_cache_scope
+ kr_cache_scope_t
struct kr_qflags
rr_array_t
struct ranked_rr_array_entry
local kr_cache_t = ffi.typeof('struct kr_cache')
ffi.metatype( kr_cache_t, {
__index = {
- insert = function (self, rr, rrsig, rank, timestamp, scope, scope_bits)
+ insert = function (self, rr, rrsig, rank, timestamp, scope)
assert(ffi.istype(kr_cache_t, self))
assert(ffi.istype(knot_rrset_t, rr), 'RR must be a rrset type')
assert(not rrsig or ffi.istype(knot_rrset_t, rrsig), 'RRSIG must be nil or of the rrset type')
timestamp = tonumber(now.tv_sec)
end
-- Insert record into cache
- local ret = C.kr_cache_insert_rr(self, rr, rrsig, tonumber(rank or 0), timestamp, scope, scope_bits or 0)
+ local ret = C.kr_cache_insert_rr(self, rr, rrsig, tonumber(rank or 0), timestamp, scope)
if ret ~= 0 then return nil, knot_error_t(ret) end
return true
end,
static ssize_t stash_rrset(struct kr_cache *cache, const struct kr_query *qry,
const knot_rrset_t *rr, const knot_rrset_t *rr_sigs, uint32_t timestamp,
uint8_t rank, trie_t *nsec_pmap, bool *has_optout,
- const uint8_t *scope, int scope_len_bits);
+ kr_cache_scope_t *scope);
/** Preliminary checks before stash_rrset(). Don't call if returns <= 0. */
static int stash_rrset_precond(const knot_rrset_t *rr, const struct kr_query *qry/*logs*/);
}
int kr_cache_insert_rr(struct kr_cache *cache, const knot_rrset_t *rr, const knot_rrset_t *rrsig,
- uint8_t rank, uint32_t timestamp, const uint8_t *scope, int scope_len_bits)
+ uint8_t rank, uint32_t timestamp, kr_cache_scope_t *scope)
{
int err = stash_rrset_precond(rr, NULL);
if (err <= 0) {
return kr_ok();
}
- ssize_t written = stash_rrset(cache, NULL, rr, rrsig, timestamp, rank, NULL, NULL, scope, scope_len_bits);
+ ssize_t written = stash_rrset(cache, NULL, rr, rrsig, timestamp, rank, NULL, NULL, scope);
/* Zone's NSEC* parames aren't updated, but that's probably OK
* for kr_cache_insert_rr() */
if (written >= 0) {
return ret;
}
-int cache_key_write_scope(struct key *k, size_t off, const uint8_t *scope, int scope_len_bits)
+/** Helper to convert IANA address family codes to address lengths. */
+static int iana_family_to_length(int iana_family)
{
- const int scope_len_bytes = (scope_len_bits + 7) / 8;
- if (!k || !scope || off + scope_len_bytes + 1 > KR_CACHE_KEY_MAXLEN) {
- return kr_error(EINVAL);
+ switch (iana_family) {
+ case KNOT_ADDR_FAMILY_IPV4:
+ return 4;
+ case KNOT_ADDR_FAMILY_IPV6:
+ return 16;
+ default:
+ return 0;
}
+}
- /* Write scope at current offset */
- memmove(k->buf + off, scope, scope_len_bytes);
+int cache_key_write_scope(struct key *k, size_t off, const kr_cache_scope_t *scope)
+{
+ if (!scope || scope->family == AF_UNSPEC) {
+ return 0;
+ }
- /* Write a terminal byte to distinguish 'no scope' from 'global scope' */
- k->buf[off + scope_len_bytes] = '\0';
+ int address_length = iana_family_to_length(scope->family);
+ if (!k || address_length <= 0 || off + address_length + 1 > KR_CACHE_KEY_MAXLEN) {
+ return kr_error(EINVAL);
+ }
- return scope_len_bytes + 1;
+ /* Write scope at current offset */
+ const int scope_len_bytes = (scope->scope_len + 7) / 8;
+ memmove(k->buf + off, scope->address, scope_len_bytes);
+ memset(k->buf + off + scope_len_bytes, 0, address_length - scope_len_bytes);
+ /* Mask bits in the last byte if the scope length isn't divisible by 8
+ * e.g. 11111100/5 -> 11111000, it will shift right and left with zero extension.
+ * This is necessary for correct LEQ search when the bitmask does not end on the whole byte boundary.
+ */
+ const size_t shift = (scope_len_bytes * 8) - scope->scope_len;
+ if (shift != 0) {
+ const size_t last_byte_off = off + scope_len_bytes - 1;
+ k->buf[last_byte_off] = (k->buf[last_byte_off] >> shift) << shift;
+ }
+
+ /* Always write bitlength byte to distinguish 'not scoped' from 'global scope' */
+ k->buf[off + address_length] = scope->scope_len;
+
+ return address_length + 1;
}
/** Like key_exact_type() but omits a couple checks not holding for pkt cache. */
-knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type, const uint8_t *scope, int scope_len_bits)
+knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type, const kr_cache_scope_t *scope)
{
assert(check_rrtype(type, NULL));
if (!is_scopable_type(type)) {
scope = NULL;
- scope_len_bits = 0;
}
switch (type) {
k->type = type;
off += sizeof(type);
- int ret = cache_key_write_scope(k, off, scope, scope_len_bits);
+ int ret = cache_key_write_scope(k, off, scope);
if (ret > 0) {
off += ret;
}
static ssize_t stash_rrset(struct kr_cache *cache, const struct kr_query *qry,
const knot_rrset_t *rr, const knot_rrset_t *rr_sigs, uint32_t timestamp,
uint8_t rank, trie_t *nsec_pmap, bool *has_optout,
- const uint8_t *scope, int scope_len)
+ kr_cache_scope_t *scope)
{
assert(stash_rrset_precond(rr, qry) > 0);
if (!cache) {
/* Construct the key under which RRs will be stored,
* and add corresponding nsec_pmap item (if necessary). */
- int used_scope_len = -1;
struct key k_storage, *k = &k_storage;
knot_db_val_t key;
switch (rr->type) {
assert(!ret);
return kr_error(ret);
}
- /* Scope the record if authoritative, and scopeable type */
- if ((!qry || !qry->parent) && kr_rank_test(rank, KR_RANK_AUTH) && is_scopable_type(rr->type)) {
- used_scope_len = scope_len;
- } else {
- /*
- * Exclude infrastructure service requests (e.g. A/AAAA for an NS set)
- * and exclude non-authoritative data (records from other sections)
- */
+ /*
+ * Scope the record if it's authoritative rank, and scopeable type.
+ * Exclude infrastructure service requests (e.g. A/AAAA for an NS set)
+ * and exclude non-authoritative data (records from other sections)
+ */
+ if ((qry && qry->parent) || !kr_rank_test(rank, KR_RANK_AUTH) || !is_scopable_type(rr->type)) {
scope = NULL;
- scope_len = 0;
}
-
- key = key_exact_type(k, rr->type, scope, scope_len);
+
+ key = key_exact_type(k, rr->type, scope);
}
/* Compute materialized sizes of the new data. */
*encl_str = kr_dname_text(encloser);
VERBOSE_MSG(qry, "=> stashed %s%s %s, rank 0%.2o, scoped: %d "
"%d B total, incl. %d RRSIGs\n",
- (wild_labels ? "*." : ""), encl_str, type_str, rank, used_scope_len,
+ (wild_labels ? "*." : ""), encl_str, type_str, rank,
+ scope ? (int)scope->scope_len : 0,
(int)val_new_entry.len, (rr_sigs ? rr_sigs->rrs.count : 0)
);
} }
struct kr_request *req = qry->request;
ssize_t written = stash_rrset(cache, qry, rr, rr_sigs, qry->timestamp.tv_sec,
- entry->rank, nsec_pmap, has_optout, req->cache_scope, req->cache_scope_len_bits);
+ entry->rank, nsec_pmap, has_optout, &req->cache_scope);
if (written < 0) {
kr_log_error("[%5hu][cach] stash failed, ret = %d\n", qry->id, ret);
return (int) written;
struct key k_storage, *k = &k_storage;
int ret = kr_dname_lf(k->buf, dname, false);
if (ret) return kr_error(ret);
- knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_NS, NULL, 0);
+ knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_NS, NULL);
knot_db_val_t val_orig = { NULL, 0 };
ret = cache_op(cache, read, &key, &val_orig, 1);
if (ret && ret != -ABS(ENOENT)) {
VERBOSE_MSG(qry, "=> EL read failed (ret: %d)\n", ret);
return kr_ok();
}
+
/* Prepare new entry_list_t so we can just write at el[0]. */
entry_list_t el;
int log_refresh_by = 0;
int ret = kr_dname_lf(k->buf, name, false);
if (ret) return kr_error(ret);
- knot_db_val_t key = key_exact_type(k, type, NULL, 0);
+ knot_db_val_t key = key_exact_type(k, type, NULL);
knot_db_val_t val = { NULL, 0 };
ret = cache_op(cache, read, &key, &val, 1);
if (!ret) ret = entry_h_seek(&val, type);
int ret = kr_dname_lf(k->buf, name, false);
if (ret) return kr_error(ret);
- knot_db_val_t key = key_exact_type(k, type, NULL, 0);
+ knot_db_val_t key = key_exact_type(k, type, NULL);
return cache_op(cache, remove, &key, 1);
}
if (ret) return kr_error(ret);
// use a mock type
- knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_A, NULL, 0);
+ knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_A, NULL);
/* CACHE_KEY_DEF */
key.len -= sizeof(uint16_t); /* the type */
if (!exact_name) {
uint64_t checkpoint_monotime; /**< Monotonic milliseconds on the last check-point. */
};
+/*
+ * Reuse ECS semantics for cache scoping - the scope is defined as network prefix.
+ * \see draft-ietf-dnsop-edns-client-subnet
+ */
+struct kr_cache_scope {
+ /*! \brief FAMILY */
+ uint8_t family;
+ /*! \brief SCOPE PREFIX-LENGTH */
+ uint8_t scope_len;
+ /*! \brief ADDRESS */
+ uint8_t address[KNOT_EDNS_CLIENT_SUBNET_ADDRESS_MAXLEN];
+};
+
+typedef struct kr_cache_scope kr_cache_scope_t;
+
/**
* Open/create cache with provided storage options.
* @param cache cache structure to be initialized
* @param rank rank of the data
* @param timestamp current time
* @param scope scope of the record
- * @param scope_len_bits scope of the record in bits
* @return 0 or an errcode
*/
KR_EXPORT
int kr_cache_insert_rr(struct kr_cache *cache, const knot_rrset_t *rr, const knot_rrset_t *rrsig,
- uint8_t rank, uint32_t timestamp, const uint8_t *scope, int scope_len_bits);
+ uint8_t rank, uint32_t timestamp, kr_cache_scope_t *scope);
/**
* Clear all items from the cache.
assert(owner == NULL);
return;
}
- key = key_exact_type_maypkt(k, pkt_type, NULL, 0);
+ key = key_exact_type_maypkt(k, pkt_type, NULL);
/* For now we stash the full packet byte-exactly as it came from upstream. */
const uint16_t pkt_size = pkt->size;
* .. 1 terminator \x00
*
* The E tag has additional information:
- * .. t type in text (e.g AAAA, t = 1 .. 9 (as of now))
+ * .. 2 type in wireformat (e.g AAAA = [0 28])
* .. s cache scope (e.g. [192 168 1], s = 0 .. 16)
*/
-int cache_key_write_scope(struct key *k, size_t off, const uint8_t *scope, int scope_len_bits);
+int cache_key_write_scope(struct key *k, size_t off, const kr_cache_scope_t *scope);
/** Finish constructing string key for for exact search.
* It's assumed that kr_dname_lf(k->buf, owner, *) had been ran.
*/
-knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type, const uint8_t *scope, int scope_len_bits);
+knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type, const kr_cache_scope_t *scope);
/** Like key_exact_type_maypkt but with extra checks if used for RRs only. */
-static inline knot_db_val_t key_exact_type(struct key *k, uint16_t type, const uint8_t *scope, int scope_len_bits)
+static inline knot_db_val_t key_exact_type(struct key *k, uint16_t type, const kr_cache_scope_t *scope)
{
switch (type) {
/* Sanity check: forbidden types represented in other way(s). */
assert(false);
return (knot_db_val_t){ NULL, 0 };
}
- return key_exact_type_maypkt(k, type, scope, scope_len_bits);
+ return key_exact_type_maypkt(k, type, scope);
}
/**
* Return cache scope as a hexstring.
*/
-static char *cache_scope_hex(const uint8_t *scope, int scope_len_bits)
+static char *cache_scope_hex(kr_cache_scope_t *scope)
{
- const int len = (scope_len_bits + 7) / 8;
+ if (!scope || scope->family == AF_UNSPEC) {
+ return NULL;
+ }
+
+ const int len = (scope->scope_len + 7) / 8;
char *hex_str = calloc(1, len * 2 + 1);
for (int i = 0; i < len; ++i) {
- snprintf(hex_str + (i * 2), 3, "%02x", scope[i]);
+ snprintf(hex_str + (i * 2), 3, "%02x", scope->address[i]);
}
+
return hex_str;
}
+static inline size_t cache_key_scope_off(struct key *k)
+{
+ /* Seek past the name [terminator, tag] + u16 type */
+ return (k->buf[0] + (2 * sizeof(uint8_t)) + sizeof(uint16_t));
+
+}
+
+/**
+ * Seek scope from the cache key.
+ * Note: see cache_key_write_scope documentation for key format reference.
+ */
+static int cache_key_read_scope(knot_db_val_t key, size_t off, const uint8_t **scope, uint8_t *scope_len)
+{
+ /* Check if there's at least bitlength byte */
+ if (key.len == 0 || off >= key.len) {
+ return kr_error(ENOENT);
+ }
+ /* Set pointer and retrieve bitlength */
+ const uint8_t *base = (const uint8_t *)key.data;
+ scope[0] = base + off;
+ scope_len[0] = base[key.len - 1];
+ return kr_ok();
+}
+
+/* Check that one scoped key covers another one (they're not necessarily equal) */
+static int cache_key_match_scope(knot_db_val_t wanted_key, knot_db_val_t found_key, size_t key_length, const kr_cache_scope_t *scope)
+{
+ /* Check that the key part (without the scope) matches to make sure the keys differ only in scope. */
+ if (found_key.len == wanted_key.len && memcmp(found_key.data, wanted_key.data, key_length) == 0) {
+ /* Parse the scope from cached key and check that it covers the requested scope */
+ uint8_t found_scope_len = 0;
+ const uint8_t *found_scope = NULL;
+ if (cache_key_read_scope(found_key, key_length, &found_scope, &found_scope_len) == 0 &&
+ kr_bitcmp((const char *)found_scope, (const char *)scope->address, found_scope_len) == 0) {
+ return kr_ok();
+ }
+ }
+ return kr_error(ENOENT);
+}
+
/** Almost whole .produce phase for the cache module.
* \note we don't transition to KR_STATE_FAIL even in case of "unexpected errors".
*/
/**** 1. find the name or the closest (available) zone, not considering wildcards
**** 1a. exact name+type match (can be negative answer in insecure zones) */
- knot_db_val_t key = key_exact_type_maypkt(k, qry->stype, req->cache_scope, req->cache_scope_len_bits);
+ kr_cache_scope_t *scope = &req->cache_scope;
+ knot_db_val_t key = key_exact_type_maypkt(k, qry->stype, scope);
knot_db_val_t val = { NULL, 0 };
ret = cache_op(cache, read, &key, &val, 1);
/* If the name is expected to be scope, but there's no scoped result in cache,
- * check global scope, as the name may not be scoped by server. */
- if (req->cache_scope != NULL && ret && ret == -abs(ENOENT)) {
- /* Retry using global scope */
- VERBOSE_MSG(qry, "=> searching global scope /0\n");
- key = key_exact_type_maypkt(k, qry->stype, req->cache_scope, 0);
- ret = cache_op(cache, read, &key, &val, 1);
+ * check closest scope, as the name may not be scoped by server. */
+ if (ret && ret == -abs(ENOENT) && scope->family != AF_UNSPEC && is_scopable_type(qry->stype)) {
+ knot_db_val_t wanted_key = key;
+ VERBOSE_MSG(qry, "=> searching closest scope for /%d\n", scope->scope_len);
+ int err = cache_op(cache, read_leq, &key, &val);
+ if (err >= 0) {
+ ret = cache_key_match_scope(wanted_key, key, cache_key_scope_off(k), scope);
+ }
}
if (!ret) {
/* found an entry: test conditions, materialize into pkt, etc. */
assert(false);
return ctx->state;
} else if (!ret) {
- WITH_VERBOSE(qry) {
- if (req->cache_scope && is_scopable_type(qry->stype)) {
- auto_free char *hex_str = cache_scope_hex(req->cache_scope, req->cache_scope_len_bits);
- VERBOSE_MSG(qry, "=> found exact match in scope %s/%d\n", hex_str, req->cache_scope_len_bits);
- }
- }
cache->stats.hit += 1;
return KR_STATE_DONE;
}
/* Assuming k->buf still starts with zone's prefix,
* look up the SOA in cache. */
k->buf[0] = k->zlf_len;
- key = key_exact_type(k, KNOT_RRTYPE_SOA, NULL, 0);
+ key = key_exact_type(k, KNOT_RRTYPE_SOA, NULL);
knot_db_val_t val = { NULL, 0 };
ret = cache_op(cache, read, &key, &val, 1);
const struct entry_h *eh;
}
WITH_VERBOSE(qry) {
auto_free char *scope_hex = NULL;
- if (req->cache_scope && is_scopable_type(type)) {
- scope_hex = cache_scope_hex(req->cache_scope, req->cache_scope_len_bits);
+ if (req->cache_scope.family != AF_UNSPEC && is_scopable_type(type)) {
+ scope_hex = cache_scope_hex(&req->cache_scope);
}
VERBOSE_MSG(qry, "=> satisfied by exact RR or CNAME: rank 0%.2o, new TTL %d, scope %s/%d\n",
- eh->rank, new_ttl, scope_hex ? scope_hex : "", scope_hex ? req->cache_scope_len_bits : 0);
+ eh->rank, new_ttl, scope_hex ? scope_hex : "", req->cache_scope.scope_len);
}
return kr_ok();
}
const uint16_t type, const uint8_t lowest_rank,
const struct kr_query *qry, struct kr_cache *cache)
{
- knot_db_val_t key = key_exact_type(k, type, NULL, 0);
+ knot_db_val_t key = key_exact_type(k, type, NULL);
/* Find the record. */
knot_db_val_t val = { NULL, 0 };
int ret = cache_op(cache, read, &key, &val, 1);
struct kr_query *qry, bool only_NS, bool is_DS, uint8_t rank_min)
{
/* get the current timestamp */
- const uint8_t *cache_scope = NULL;
- int cache_scope_len_bits = 0;
+ kr_cache_scope_t *cache_scope = NULL;
uint32_t timestamp;
if (qry) {
timestamp = qry->timestamp.tv_sec;
- cache_scope = qry->request->cache_scope;
- cache_scope_len_bits = qry->request->cache_scope_len_bits;
+ cache_scope = &qry->request->cache_scope;
} else {
struct timeval tv;
if (gettimeofday(&tv, NULL)) return kr_error(errno);
* The CNAME is going to get rewritten to NS key, but it will be scoped if possible.
*/
const uint16_t find_type = exact_match ? KNOT_RRTYPE_CNAME : KNOT_RRTYPE_NS;
- knot_db_val_t key = key_exact_type(k, find_type, cache_scope, cache_scope_len_bits);
+ knot_db_val_t key = key_exact_type(k, find_type, cache_scope);
knot_db_val_t val;
int ret = cache_op(cache, read, &key, &val, 1);
/* Try in global scope if scoped, but no immediate match found */
- if (exact_match && cache_scope != NULL && ret == -abs(ENOENT)) {
- key = key_exact_type_maypkt(k, KNOT_RRTYPE_NS, cache_scope, 0);
- ret = cache_op(cache, read, &key, &val, 1);
+ if (exact_match && ret == -abs(ENOENT) && cache_scope && cache_scope->family != AF_UNSPEC) {
+ knot_db_val_t wanted_key = key;
+ int err = cache_op(cache, read_leq, &key, &val);
+ if (err >= 0) {
+ ret = cache_key_match_scope(wanted_key, key, cache_key_scope_off(k), cache_scope);
+ }
}
if (ret == -abs(ENOENT)) goto next_label;
if (ret) {
trace_log_f trace_log; /**< Logging tracepoint */
trace_callback_f trace_finish; /**< Request finish tracepoint */
int vars_ref; /**< Reference to per-request variable table. LUA_NOREF if not set. */
- int cache_scope_len_bits; /**< Cache scope length (bits) */
- const uint8_t *cache_scope; /**< Cache scope for the request */
+ kr_cache_scope_t cache_scope; /**< Request cache scope */
knot_mm_t pool;
};