#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
+#include <libknot/internal/sockaddr.h>
#include "lib/nsrep.h"
#include "lib/defines.h"
ns->name = name;
ns->score = score;
if (addr == NULL) {
+ ns->addr.ip.sa_family = AF_UNSPEC;
return;
}
static int eval_nsrep(const char *k, void *v, void *baton)
{
- unsigned score = KR_NS_VALID;
struct kr_nsrep *ns = baton;
+ unsigned score = ns->score;
pack_t *addr_set = v;
uint8_t *addr = NULL;
/* Name server is better candidate if it has address record. */
- if (addr_set->len > 0) {
- addr = pack_head(*addr_set);
- score += 1;
+ uint8_t *it = pack_head(*addr_set);
+ while (it != pack_tail(*addr_set)) {
+ void *val = pack_obj_val(it);
+ size_t len = pack_obj_len(it);
+ unsigned *cached = lru_get(ns->repcache, val, len);
+ unsigned addr_score = (cached) ? *cached : KR_NS_UNKNOWN / 2;
+ /** @todo Favorize IPv6 */
+ if (addr_score <= score) {
+ addr = it;
+ score = addr_score;
+ }
+ it = pack_obj_next(it);
+ }
+ /* No known address */
+ if (!addr) {
+ score = KR_NS_UNKNOWN;
}
/* Update best scoring nameserver. */
- if (ns->score < score) {
+ if (score < ns->score) {
update_nsrep(ns, (const knot_dname_t *)k, addr, score);
}
return kr_ok();
}
-int kr_nsrep_elect(struct kr_nsrep *ns, map_t *nsset)
+int kr_nsrep_elect(struct kr_nsrep *ns, map_t *nsset, kr_nsrep_lru_t *repcache)
{
+ ns->repcache = repcache;
ns->addr.ip.sa_family = AF_UNSPEC;
- ns->score = KR_NS_INVALID;
+ ns->score = KR_NS_MAX_SCORE + 1;
return map_walk(nsset, eval_nsrep, ns);
}
+
+int kr_nsrep_update(struct kr_nsrep *ns, unsigned score, kr_nsrep_lru_t *repcache)
+{
+ if (!ns || !repcache || 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);
+ if (!cur) {
+ return kr_error(ENOMEM);
+ }
+ /* Score limits */
+ if (score > KR_NS_MAX_SCORE) {
+ score = KR_NS_MAX_SCORE;
+ }
+ /* Set initial value or smooth over last two measurements */
+ if (*cur != 0) {
+ *cur = (*cur + score) / 2;
+ } else {
+ /* First measurement, reset */
+ *cur = score;
+ }
+ return kr_ok();
+}
#include <netinet/in.h>
#include <libknot/dname.h>
+#include <limits.h>
#include "lib/generic/map.h"
+#include "lib/generic/lru.h"
/**
- * Special values for nameserver score.
- * All positive values mean valid nameserver.
+ * Special values for nameserver score (RTT in miliseconds)
*/
enum kr_ns_score {
- KR_NS_INVALID = 0,
- KR_NS_VALID = 1
+ KR_NS_MAX_SCORE = 10 * 1000,
+ KR_NS_TIMEOUT = KR_NS_MAX_SCORE,
+ KR_NS_UNKNOWN = 10
};
+/**
+ * NS reputation/QoS tracking.
+ */
+typedef lru_hash(unsigned) kr_nsrep_lru_t;
+
/**
* Name server representation.
* Contains extra information about the name server, e.g. score
{
unsigned score; /**< Server score */
const knot_dname_t *name; /**< Server name */
+ kr_nsrep_lru_t *repcache; /**< Reputation cache pointer */
union {
struct sockaddr ip;
struct sockaddr_in ip4;
/**
* Elect best nameserver/address pair from the nsset.
- * @param ns updated NS representation
- * @param nsset NS set to choose from
- * @return score, see enum kr_ns_score
+ * @param ns updated NS representation
+ * @param nsset NS set to choose from
+ * @param repcache reputation storage
+ * @return score, see enum kr_ns_score
+ */
+int kr_nsrep_elect(struct kr_nsrep *ns, map_t *nsset, kr_nsrep_lru_t *repcache);
+
+/**
+ * Update NS quality 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
+ * @return 0 on success, error code on failure
*/
-int kr_nsrep_elect(struct kr_nsrep *ns, map_t *nsset);
+int kr_nsrep_update(struct kr_nsrep *ns, unsigned score, kr_nsrep_lru_t *repcache);
#define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(rplan), "resl", fmt)
+/** @internal Subtract time (best effort) */
+float time_diff(struct timeval *begin, struct timeval *end)
+{
+ return (end->tv_sec - begin->tv_sec) * 1000 +
+ (end->tv_usec - begin->tv_usec) / 1000.0;
+
+}
+
/** Invalidate current NS/addr pair. */
static int invalidate_ns(struct kr_rplan *rplan, struct kr_query *qry)
{
- uint8_t *addr = kr_nsrep_inaddr(qry->ns.addr);
- size_t addr_len = kr_nsrep_inaddr_len(qry->ns.addr);
- knot_rdata_t rdata[knot_rdata_array_size(addr_len)];
- knot_rdata_init(rdata, addr_len, addr, 0);
- return kr_zonecut_del(&qry->zone_cut, qry->ns.name, rdata);
+ if (qry->ns.addr.ip.sa_family != AF_UNSPEC) {
+ uint8_t *addr = kr_nsrep_inaddr(qry->ns.addr);
+ size_t addr_len = kr_nsrep_inaddr_len(qry->ns.addr);
+ knot_rdata_t rdata[knot_rdata_array_size(addr_len)];
+ knot_rdata_init(rdata, addr_len, addr, 0);
+ return kr_zonecut_del(&qry->zone_cut, qry->ns.name, rdata);
+ } else {
+ return kr_zonecut_del(&qry->zone_cut, qry->ns.name, NULL);
+ }
}
static int ns_resolve_addr(struct kr_query *qry, struct kr_request *param)
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);
if (invalidate_ns(rplan, qry) == 0) {
qry->flags &= ~QUERY_TCP;
}
+ /* Track RTT for iterative answers */
+ } 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);
}
/* Pop query if resolved. */
*/
if (qry->flags & QUERY_AWAIT_CUT) {
struct kr_cache_txn txn;
- if (kr_cache_txn_begin(&rplan->context->cache, &txn, NAMEDB_RDONLY) != 0) {
+ if (kr_cache_txn_begin(&request->ctx->cache, &txn, NAMEDB_RDONLY) != 0) {
kr_zonecut_set_sbelt(&qry->zone_cut);
} else {
kr_zonecut_find_cached(&qry->zone_cut, qry->sname, &txn, qry->timestamp.tv_sec);
ns_election:
/* Elect best nameserver candidate */
- kr_nsrep_elect(&qry->ns, &qry->zone_cut.nsset);
- if (qry->ns.score < KR_NS_VALID) {
+ kr_nsrep_elect(&qry->ns, &qry->zone_cut.nsset, request->ctx->nsrep);
+ 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);
} else {
if (qry->ns.addr.ip.sa_family == AF_UNSPEC) {
if (ns_resolve_addr(qry, request) != 0) {
+ qry->flags &= ~(QUERY_AWAIT_IPV6|QUERY_AWAIT_IPV4);
goto ns_election; /* Must try different NS */
}
knot_overlay_reset(&request->overlay);
struct sockaddr *addr = &qry->ns.addr.ip;
inet_ntop(addr->sa_family, kr_nsrep_inaddr(qry->ns.addr), ns_str, sizeof(ns_str));
knot_dname_to_str(zonecut_str, qry->zone_cut.name, sizeof(zonecut_str));
- DEBUG_MSG("=> querying: '%s' zone cut: '%s' m12n: '%s'\n", ns_str, zonecut_str, qname_str);
+ DEBUG_MSG("=> querying: '%s' score: %u zone cut: '%s' m12n: '%s'\n", ns_str, qry->ns.score, zonecut_str, qname_str);
#endif
/* Prepare additional query */
if (ret != 0) {
return KNOT_STATE_FAIL;
}
+ gettimeofday(&qry->timestamp, NULL);
*dst = &qry->ns.addr.ip;
*type = (qry->flags & QUERY_TCP) ? SOCK_STREAM : SOCK_DGRAM;
return state;
#include <libknot/packet/pkt.h>
#include "lib/generic/array.h"
+#include "lib/nsrep.h"
#include "lib/rplan.h"
#include "lib/module.h"
#include "lib/cache.h"
{
mm_ctx_t *pool;
struct kr_cache cache;
+ kr_nsrep_lru_t *nsrep;
module_array_t *modules;
uint32_t options;
};