Commit 149d5e1c authored by Marek Vavruša's avatar Marek Vavruša Committed by Marek Vavruša

lib/nsrep: NS reputation cache in addition to RTT cache

this LRU-like cache tracks lame nameservers, unresolvable
nameservers (to not waste resources in trying to resolve them),
and possibly other features (extension support, …)
parent 2690745f
......@@ -158,10 +158,14 @@ static int init_resolver(struct engine *engine)
{
/* 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 */
......@@ -244,7 +248,8 @@ void engine_deinit(struct engine *engine)
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) {
......
......@@ -17,8 +17,11 @@
#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
/*
......
......@@ -129,7 +129,7 @@ int main(int argc, char **argv)
/* 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) {
......
......@@ -22,6 +22,7 @@
#include "lib/nsrep.h"
#include "lib/rplan.h"
#include "lib/resolve.h"
#include "lib/defines.h"
#include "lib/generic/pack.h"
......@@ -81,30 +82,48 @@ static unsigned eval_addr_set(pack_t *addr_set, kr_nsrep_lru_t *rttcache, unsign
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 */
}
}
......@@ -112,23 +131,29 @@ static int eval_nsrep(const char *k, void *v, void *baton)
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);
}
......@@ -148,3 +173,20 @@ int kr_nsrep_update(struct kr_nsrep *ns, unsigned score, kr_nsrep_lru_t *repcach
}
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
......@@ -24,8 +24,11 @@
#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,
......@@ -33,6 +36,15 @@ enum kr_ns_score {
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.
*/
......@@ -45,15 +57,15 @@ typedef lru_hash(unsigned) kr_nsrep_lru_t;
*/
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. */
......@@ -65,21 +77,32 @@ struct kr_nsrep
/**
* 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);
......@@ -64,7 +64,7 @@ static void ns_fetch_cut(struct kr_query *qry, struct kr_request *req)
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);
}
}
......@@ -72,6 +72,8 @@ static void ns_fetch_cut(struct kr_query *qry, struct kr_request *req)
static int ns_resolve_addr(struct kr_query *qry, struct kr_request *param)
{
struct kr_rplan *rplan = &param->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,
......@@ -85,10 +87,14 @@ static int ns_resolve_addr(struct kr_query *qry, struct kr_request *param)
} 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);
}
......@@ -343,6 +349,7 @@ int kr_resolve_query(struct kr_request *request, const knot_dname_t *qname, uint
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 */
......@@ -366,8 +373,6 @@ int kr_resolve_consume(struct kr_request *request, knot_pkt_t *packet)
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);
......@@ -376,6 +381,7 @@ int kr_resolve_consume(struct kr_request *request, knot_pkt_t *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;
}
......@@ -383,7 +389,7 @@ int kr_resolve_consume(struct kr_request *request, knot_pkt_t *packet)
} 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. */
......@@ -451,18 +457,21 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
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);
......
......@@ -101,7 +101,8 @@ struct kr_context
{
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;
};
......
......@@ -211,7 +211,7 @@ static void fetch_addr(struct kr_zonecut *cut, const knot_dname_t *ns, uint16_t
}
/** 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;
......@@ -226,8 +226,15 @@ static int fetch_ns(struct kr_zonecut *cut, const knot_dname_t *name, struct kr_
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 */
......@@ -238,7 +245,8 @@ static int fetch_ns(struct kr_zonecut *cut, const knot_dname_t *name, struct kr_
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);
......@@ -247,7 +255,7 @@ int kr_zonecut_find_cached(struct kr_zonecut *cut, const knot_dname_t *name, str
/* 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();
}
......
......@@ -21,6 +21,7 @@
#include "lib/cache.h"
struct kr_rplan;
struct kr_context;
/**
* Current zone cut representation.
......@@ -98,11 +99,13 @@ int kr_zonecut_set_sbelt(struct kr_zonecut *cut);
/**
* 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);
......@@ -77,10 +77,13 @@ static PyObject* init(PyObject* self, PyObject* args)
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. */
......@@ -102,8 +105,10 @@ static PyObject* deinit(PyObject* self, PyObject* args)
}
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;
......
......@@ -30,7 +30,7 @@ static void test_zonecut_params(void **state)
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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment