{
/* Open resolution context */
engine->resolver.modules = &engine->modules;
- /* Open NS reputation cache */
- engine->resolver.nsrep = malloc(lru_size(kr_nsrep_lru_t, DEFAULT_NSREP_SIZE));
- if (engine->resolver.nsrep) {
- lru_init(engine->resolver.nsrep, DEFAULT_NSREP_SIZE);
+ /* Open NS rtt + reputation cache */
+ engine->resolver.cache_rtt = malloc(lru_size(kr_nsrep_lru_t, LRU_RTT_SIZE));
+ if (engine->resolver.cache_rtt) {
+ lru_init(engine->resolver.cache_rtt, LRU_RTT_SIZE);
+ }
+ engine->resolver.cache_rep = malloc(lru_size(kr_nsrep_lru_t, LRU_REP_SIZE));
+ if (engine->resolver.cache_rep) {
+ lru_init(engine->resolver.cache_rep, LRU_REP_SIZE);
}
/* Load basic modules */
network_deinit(&engine->net);
kr_cache_close(&engine->resolver.cache);
- lru_deinit(engine->resolver.nsrep);
+ lru_deinit(engine->resolver.cache_rtt);
+ lru_deinit(engine->resolver.cache_rep);
/* Unload modules. */
for (size_t i = 0; i < engine->modules.len; ++i) {
#pragma once
/* Magic defaults */
-#ifndef DEFAULT_NSREP_SIZE
-#define DEFAULT_NSREP_SIZE 4096 /**< Default NS reputation cache size */
+#ifndef LRU_RTT_SIZE
+#define LRU_RTT_SIZE 4096 /**< NS RTT cache size */
+#endif
+#ifndef LRU_REP_SIZE
+#define LRU_REP_SIZE (LRU_RTT_SIZE / 2) /**< NS reputation cache size */
#endif
/*
/* Create a server engine. */
mm_ctx_t pool;
- mm_ctx_mempool(&pool, 4096);
+ mm_ctx_mempool(&pool, MM_DEFAULT_BLKSIZE);
struct engine engine;
ret = engine_init(&engine, &pool);
if (ret != 0) {
#include "lib/nsrep.h"
#include "lib/rplan.h"
+#include "lib/resolve.h"
#include "lib/defines.h"
#include "lib/generic/pack.h"
static int eval_nsrep(const char *k, void *v, void *baton)
{
- struct kr_nsrep *ns = baton;
+ struct kr_query *qry = baton;
+ struct kr_nsrep *ns = &qry->ns;
+ struct kr_context *ctx = ns->ctx;
unsigned score = KR_NS_MAX_SCORE;
+ unsigned reputation = 0;
uint8_t *addr = NULL;
+ /* Fetch NS reputation */
+ if (ctx->cache_rep) {
+ unsigned *cached = lru_get(ctx->cache_rep, k, knot_dname_size((const uint8_t *)k));
+ if (cached) {
+ reputation = *cached;
+ }
+ }
+
/* Favour nameservers with unknown addresses to probe them,
* otherwise discover the current best address for the NS. */
pack_t *addr_set = (pack_t *)v;
if (addr_set->len == 0) {
score = KR_NS_UNKNOWN;
+ /* If the server is unknown but has rep record, treat it as timeouted */
+ if ((reputation & KR_NS_NOIP4) && (reputation & KR_NS_NOIP6)) {
+ score = KR_NS_TIMEOUT;
+ reputation = 0; /* Start with clean slate */
+ }
} else {
- score = eval_addr_set(addr_set, ns->repcache, score, &addr);
+ score = eval_addr_set(addr_set, ctx->cache_rtt, score, &addr);
}
/* Probabilistic bee foraging strategy (naive).
* The fastest NS is preferred by workers until it is depleted (timeouts or degrades),
* at the same time long distance scouts probe other sources (low probability).
* Servers on TIMEOUT (depleted) can be probed by the dice roll only */
- if (score < ns->score && (ns->flags & QUERY_NO_THROTTLE || score < KR_NS_TIMEOUT)) {
+ if (score < ns->score && (qry->flags & QUERY_NO_THROTTLE || score < KR_NS_TIMEOUT)) {
update_nsrep(ns, (const knot_dname_t *)k, addr, score);
+ ns->reputation = reputation;
} else {
/* With 5% chance, probe server with a probability given by its RTT / MAX_RTT */
unsigned roll = rand() % KR_NS_MAX_SCORE;
if ((roll % 100 < 5) && (roll >= score)) {
update_nsrep(ns, (const knot_dname_t *)k, addr, score);
+ ns->reputation = reputation;
return 1; /* Stop evaluation */
}
}
return kr_ok();
}
-int kr_nsrep_elect(struct kr_nsrep *ns, map_t *nsset, kr_nsrep_lru_t *repcache)
+int kr_nsrep_elect(struct kr_query *qry, struct kr_context *ctx)
{
- ns->repcache = repcache;
+ if (!qry || !ctx) {
+ return kr_error(EINVAL);
+ }
+
+ struct kr_nsrep *ns = &qry->ns;
+ ns->ctx = ctx;
ns->addr.ip.sa_family = AF_UNSPEC;
+ ns->reputation = 0;
ns->score = KR_NS_MAX_SCORE + 1;
- return map_walk(nsset, eval_nsrep, ns);
+ return map_walk(&qry->zone_cut.nsset, eval_nsrep, qry);
}
-int kr_nsrep_update(struct kr_nsrep *ns, unsigned score, kr_nsrep_lru_t *repcache)
+int kr_nsrep_update_rtt(struct kr_nsrep *ns, unsigned score, kr_nsrep_lru_t *cache)
{
- if (!ns || !repcache || ns->addr.ip.sa_family == AF_UNSPEC) {
+ if (!ns || !cache || ns->addr.ip.sa_family == AF_UNSPEC) {
return kr_error(EINVAL);
}
char *addr = kr_nsrep_inaddr(ns->addr);
size_t addr_len = kr_nsrep_inaddr_len(ns->addr);
- unsigned *cur = lru_set(repcache, addr, addr_len);
+ unsigned *cur = lru_set(cache, addr, addr_len);
if (!cur) {
return kr_error(ENOMEM);
}
}
return kr_ok();
}
+
+int kr_nsrep_update_rep(struct kr_nsrep *ns, unsigned reputation, kr_nsrep_lru_t *cache)
+{
+ if (!ns || !cache ) {
+ return kr_error(EINVAL);
+ }
+
+ /* Store in the struct */
+ ns->reputation = reputation;
+ /* Store reputation in the LRU cache */
+ unsigned *cur = lru_set(cache, (const char *)ns->name, knot_dname_size(ns->name));
+ if (!cur) {
+ return kr_error(ENOMEM);
+ }
+ *cur = reputation;
+ return kr_ok();
+}
\ No newline at end of file
#include "lib/generic/map.h"
#include "lib/generic/lru.h"
+struct kr_query;
+
/**
- * Special values for nameserver score (RTT in miliseconds)
+ * NS RTT score (special values).
+ * @note RTT is measured in milliseconds.
*/
enum kr_ns_score {
KR_NS_MAX_SCORE = KR_CONN_RTT_MAX,
KR_NS_UNKNOWN = 10
};
+/**
+ * NS QoS flags.
+ */
+enum kr_ns_rep {
+ KR_NS_NOIP4 = 1 << 0, /**< NS has no IPv4 */
+ KR_NS_NOIP6 = 1 << 1, /**< NS has no IPv6 */
+ KR_NS_NOEDNS = 1 << 2 /**< NS has no EDNS support */
+};
+
/**
* NS reputation/QoS tracking.
*/
*/
struct kr_nsrep
{
- unsigned score; /**< Server score */
- unsigned flags; /**< Server flags */
- const knot_dname_t *name; /**< Server name */
- kr_nsrep_lru_t *repcache; /**< Reputation cache pointer */
+ unsigned score; /**< NS score */
+ unsigned reputation; /**< NS reputation */
+ const knot_dname_t *name; /**< NS name */
+ struct kr_context *ctx; /**< Resolution context */
union {
struct sockaddr ip;
struct sockaddr_in ip4;
struct sockaddr_in6 ip6;
- } addr; /**< Server address */
+ } addr; /**< NS address */
};
/** @internal Address bytes for given family. */
/**
* Elect best nameserver/address pair from the nsset.
- * @param ns updated NS representation
- * @param nsset NS set to choose from
- * @param repcache reputation storage
- * @return score, see enum kr_ns_score
+ * @param qry updated query
+ * @param ctx resolution context
+ * @return 0 or an error code
*/
-int kr_nsrep_elect(struct kr_nsrep *ns, map_t *nsset, kr_nsrep_lru_t *repcache);
+int kr_nsrep_elect(struct kr_query *qry, struct kr_context *ctx);
/**
- * Update NS quality information.
+ * Update NS address RTT information.
*
* @brief Reputation is smoothed over last N measurements.
*
* @param ns updated NS representation
* @param score new score (i.e. RTT), see enum kr_ns_score
- * @param reputation reputation storage
+ * @param cache LRU cache
+ * @return 0 on success, error code on failure
+ */
+int kr_nsrep_update_rtt(struct kr_nsrep *ns, unsigned score, kr_nsrep_lru_t *cache);
+
+/**
+ * Update NS name quality information.
+ *
+ * @brief Reputation is smoothed over last N measurements.
+ *
+ * @param ns updated NS representation
+ * @param reputation combined reputation flags, see enum kr_ns_rep
+ * @param cache LRU cache
* @return 0 on success, error code on failure
*/
-int kr_nsrep_update(struct kr_nsrep *ns, unsigned score, kr_nsrep_lru_t *repcache);
+int kr_nsrep_update_rep(struct kr_nsrep *ns, unsigned reputation, kr_nsrep_lru_t *cache);
start_from = parent->zone_cut.name;
}
/* Find closest zone cut from cache */
- kr_zonecut_find_cached(&qry->zone_cut, start_from, &txn, qry->timestamp.tv_sec);
+ kr_zonecut_find_cached(req->ctx, &qry->zone_cut, start_from, &txn, qry->timestamp.tv_sec);
kr_cache_txn_abort(&txn);
}
}
static int ns_resolve_addr(struct kr_query *qry, struct kr_request *param)
{
struct kr_rplan *rplan = ¶m->rplan;
+ struct kr_context *ctx = param->ctx;
+
/* Start NS queries from root, to avoid certain cases
* where a NS drops out of cache and the rest is unavailable,
} else if (!(qry->flags & QUERY_AWAIT_IPV4)) {
next_type = KNOT_RRTYPE_A;
qry->flags |= QUERY_AWAIT_IPV4;
+ /* Hmm, no useable IPv6 then. */
+ kr_nsrep_update_rep(&qry->ns, qry->ns.reputation | KR_NS_NOIP6, ctx->cache_rep);
}
/* Bail out if the query is already pending or dependency loop. */
if (!next_type || kr_rplan_satisfies(qry->parent, qry->ns.name, KNOT_CLASS_IN, next_type)) {
- DEBUG_MSG("=> dependency loop, bailing out\n");
+ /* No IPv4 nor IPv6, flag server as unuseable. */
+ DEBUG_MSG("=> unresolvable NS address, bailing out\n");
+ kr_nsrep_update_rep(&qry->ns, qry->ns.reputation | (KR_NS_NOIP4|KR_NS_NOIP6), ctx->cache_rep);
invalidate_ns(rplan, qry);
return kr_error(EHOSTUNREACH);
}
int kr_resolve_consume(struct kr_request *request, knot_pkt_t *packet)
{
struct kr_rplan *rplan = &request->rplan;
+ struct kr_context *ctx = request->ctx;
struct kr_query *qry = kr_rplan_current(rplan);
/* Empty resolution plan, push packet as the new query */
DEBUG_MSG("=> ns unreachable, retrying over TCP\n");
qry->flags |= QUERY_TCP;
return KNOT_STATE_CONSUME; /* Try again */
- } else {
- kr_nsrep_update(&qry->ns, KR_NS_TIMEOUT, qry->ns.repcache);
}
} else {
state = knot_overlay_consume(&request->overlay, packet);
/* Resolution failed, invalidate current NS and reset to UDP. */
if (state == KNOT_STATE_FAIL) {
DEBUG_MSG("=> resolution failed, invalidating\n");
+ kr_nsrep_update_rtt(&qry->ns, KR_NS_TIMEOUT, ctx->cache_rtt);
if (invalidate_ns(rplan, qry) == 0) {
qry->flags &= ~QUERY_TCP;
}
} else if (!(qry->flags & QUERY_CACHED)) {
struct timeval now;
gettimeofday(&now, NULL);
- kr_nsrep_update(&qry->ns, time_diff(&qry->timestamp, &now), qry->ns.repcache);
+ kr_nsrep_update_rtt(&qry->ns, time_diff(&qry->timestamp, &now), ctx->cache_rtt);
}
/* Pop query if resolved. */
ns_election:
/* Elect best nameserver candidate */
assert(++ns_election_iter < KR_ITER_LIMIT);
- /* Set slow NS throttling mode */
- qry->ns.flags = 0;
- if (qry->flags & QUERY_NO_THROTTLE) {
- qry->ns.flags = QUERY_NO_THROTTLE;
- }
- kr_nsrep_elect(&qry->ns, &qry->zone_cut.nsset, request->ctx->nsrep);
+ kr_nsrep_elect(qry, request->ctx);
if (qry->ns.score > KR_NS_MAX_SCORE) {
DEBUG_MSG("=> no valid NS left\n");
knot_overlay_reset(&request->overlay);
kr_rplan_pop(rplan, qry);
return KNOT_STATE_PRODUCE;
} else {
+ /* Update query flags based on the NS reputation */
+ if (qry->ns.reputation & KR_NS_NOIP6) {
+ qry->flags |= QUERY_AWAIT_IPV6;
+ }
+ if (qry->ns.reputation & KR_NS_NOIP4) {
+ qry->flags |= QUERY_AWAIT_IPV4;
+ }
+ /* Resolve address records */
if (qry->ns.addr.ip.sa_family == AF_UNSPEC) {
if (ns_resolve_addr(qry, request) != 0) {
qry->flags &= ~(QUERY_AWAIT_IPV6|QUERY_AWAIT_IPV4);
{
mm_ctx_t *pool;
struct kr_cache cache;
- kr_nsrep_lru_t *nsrep;
+ kr_nsrep_lru_t *cache_rtt;
+ kr_nsrep_lru_t *cache_rep;
module_array_t *modules;
uint32_t options;
};
}
/** Fetch best NS for zone cut. */
-static int fetch_ns(struct kr_zonecut *cut, const knot_dname_t *name, struct kr_cache_txn *txn, uint32_t timestamp)
+static int fetch_ns(struct kr_context *ctx, struct kr_zonecut *cut, const knot_dname_t *name, struct kr_cache_txn *txn, uint32_t timestamp)
{
uint32_t drift = timestamp;
knot_rrset_t cached_rr;
for (unsigned i = 0; i < cached_rr.rrs.rr_count; ++i) {
const knot_dname_t *ns_name = knot_ns_name(&cached_rr.rrs, i);
kr_zonecut_add(cut, ns_name, NULL);
- fetch_addr(cut, ns_name, KNOT_RRTYPE_A, txn, timestamp);
- fetch_addr(cut, ns_name, KNOT_RRTYPE_AAAA, txn, timestamp);
+ /* Fetch NS reputation and decide whether to prefetch A/AAAA records. */
+ unsigned *cached = lru_get(ctx->cache_rep, (const char *)ns_name, knot_dname_size(ns_name));
+ unsigned reputation = (cached) ? *cached : 0;
+ if (!(reputation & KR_NS_NOIP4)) {
+ fetch_addr(cut, ns_name, KNOT_RRTYPE_A, txn, timestamp);
+ }
+ if (!(reputation & KR_NS_NOIP6)) {
+ fetch_addr(cut, ns_name, KNOT_RRTYPE_AAAA, txn, timestamp);
+ }
}
/* Always keep SBELT as a backup for root */
return kr_ok();
}
-int kr_zonecut_find_cached(struct kr_zonecut *cut, const knot_dname_t *name, struct kr_cache_txn *txn, uint32_t timestamp)
+int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut, const knot_dname_t *name,
+ struct kr_cache_txn *txn, uint32_t timestamp)
{
if (cut == NULL) {
return kr_error(EINVAL);
/* Start at QNAME parent. */
name = knot_wire_next_label(name, NULL);
while (txn) {
- if (fetch_ns(cut, name, txn, timestamp) == 0) {
+ if (fetch_ns(ctx, cut, name, txn, timestamp) == 0) {
update_cut_name(cut, name);
return kr_ok();
}
#include "lib/cache.h"
struct kr_rplan;
+struct kr_context;
/**
* Current zone cut representation.
/**
* Populate zone cut address set from cache.
- *
+ *
+ * @param ctx resolution context (to fetch data from LRU caches)
* @param cut zone cut to be populated
* @param name QNAME to start finding zone cut for
* @param txn cache transaction (read)
* @param timestamp transaction timestamp
* @return 0 or error code
*/
-int kr_zonecut_find_cached(struct kr_zonecut *cut, const knot_dname_t *name, struct kr_cache_txn *txn, uint32_t timestamp);
+int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut, const knot_dname_t *name,
+ struct kr_cache_txn *txn, uint32_t timestamp);
return NULL;
}
- /* Create RTT tracking */
- global_context.nsrep = malloc(lru_size(kr_nsrep_lru_t, 1000));
- assert(global_context.nsrep);
- lru_init(global_context.nsrep, 1000);
+ /* Create RTT + reputation tracking */
+ global_context.cache_rtt = malloc(lru_size(kr_nsrep_lru_t, 1024));
+ assert(global_context.cache_rtt);
+ lru_init(global_context.cache_rtt, 1024);
+ global_context.cache_rep = malloc(lru_size(kr_nsrep_lru_t, 512));
+ assert(global_context.cache_rep);
+ lru_init(global_context.cache_rep, 512);
global_context.options = QUERY_NO_THROTTLE;
/* No configuration parsing support yet. */
}
array_clear(global_modules);
kr_cache_close(&global_context.cache);
- lru_deinit(global_context.nsrep);
- free(global_context.nsrep);
+ lru_deinit(global_context.cache_rtt);
+ lru_deinit(global_context.cache_rep);
+ free(global_context.cache_rtt);
+ free(global_context.cache_rep);
test_tmpdir_remove(global_tmpdir);
global_tmpdir = NULL;
assert_null((void *)kr_zonecut_find(NULL, NULL));
assert_null((void *)kr_zonecut_find(&cut, NULL));
assert_int_not_equal(kr_zonecut_set_sbelt(NULL), 0);
- assert_int_not_equal(kr_zonecut_find_cached(NULL, NULL, NULL, 0), 0);
+ assert_int_not_equal(kr_zonecut_find_cached(NULL, NULL, NULL, NULL, 0), 0);
}
int main(void)