diff --git a/daemon/engine.c b/daemon/engine.c index 33699df99904fa142d69b70f8515d9ad23e2095b..c8dfb40d36f0e184072cd33d531e30f7360f0234 100644 --- a/daemon/engine.c +++ b/daemon/engine.c @@ -203,6 +203,9 @@ static int init_resolver(struct engine *engine) { /* Open resolution context */ engine->resolver.modules = &engine->modules; + /* Set default root hints */ + kr_zonecut_init(&engine->resolver.root_hints, (const uint8_t *)"", engine->pool); + kr_zonecut_set_sbelt(&engine->resolver, &engine->resolver.root_hints); /* Open NS rtt + reputation cache */ engine->resolver.cache_rtt = malloc(lru_size(kr_nsrep_lru_t, LRU_RTT_SIZE)); if (engine->resolver.cache_rtt) { @@ -292,6 +295,7 @@ void engine_deinit(struct engine *engine) } network_deinit(&engine->net); + kr_zonecut_deinit(&engine->resolver.root_hints); kr_cache_close(&engine->resolver.cache); lru_deinit(engine->resolver.cache_rtt); lru_deinit(engine->resolver.cache_rep); diff --git a/lib/resolve.c b/lib/resolve.c index 0bc3fbd995017192ab8882deee7d73ce540e9ec2..74e9cf4e7c47a72c643900ac39b8612fd7e7e677 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -54,7 +54,7 @@ static int ns_fetch_cut(struct kr_query *qry, struct kr_request *req) struct kr_cache_txn txn; int ret = 0; if (kr_cache_txn_begin(&req->ctx->cache, &txn, NAMEDB_RDONLY) != 0) { - ret = kr_zonecut_set_sbelt(&qry->zone_cut); + ret = kr_zonecut_set_sbelt(req->ctx, &qry->zone_cut); } else { /* If at/subdomain of parent zone cut, start from 'one up' to avoid loops */ struct kr_query *parent = qry->parent; diff --git a/lib/resolve.h b/lib/resolve.h index 3411f3fa050893e89cfeda67757c7a793b10d1c8..8a976229d1e38e0c69475ee821b35fae1b28af40 100644 --- a/lib/resolve.h +++ b/lib/resolve.h @@ -104,6 +104,7 @@ typedef array_t(struct kr_module *) module_array_t; struct kr_context { mm_ctx_t *pool; + struct kr_zonecut root_hints; struct kr_cache cache; kr_nsrep_lru_t *cache_rtt; kr_nsrep_lru_t *cache_rep; diff --git a/lib/zonecut.c b/lib/zonecut.c index 810dc96a305238dbe54ce39eb96ac51cd8e08959..5ea9f0f2cbd20237e2a8604208d654cee09a62aa 100644 --- a/lib/zonecut.c +++ b/lib/zonecut.c @@ -64,7 +64,7 @@ static void update_cut_name(struct kr_zonecut *cut, const knot_dname_t *name) int kr_zonecut_init(struct kr_zonecut *cut, const knot_dname_t *name, mm_ctx_t *pool) { - if (cut == NULL || name == NULL) { + if (!cut || !name) { return kr_error(EINVAL); } @@ -87,7 +87,7 @@ static int free_addr_set(const char *k, void *v, void *baton) void kr_zonecut_deinit(struct kr_zonecut *cut) { - if (cut == NULL) { + if (!cut) { return; } mm_free(cut->pool, cut->name); @@ -97,16 +97,51 @@ void kr_zonecut_deinit(struct kr_zonecut *cut) void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name) { - if (cut == NULL || name == NULL) { + if (!cut || !name) { return; } kr_zonecut_deinit(cut); kr_zonecut_init(cut, name, cut->pool); } +static int copy_addr_set(const char *k, void *v, void *baton) +{ + pack_t *addr_set = v; + struct kr_zonecut *dst = baton; + /* Clone addr_set pack */ + pack_t *new_set = mm_alloc(dst->pool, sizeof(*new_set)); + if (!new_set) { + return kr_error(ENOMEM); + } + new_set->at = mm_alloc(dst->pool, addr_set->len); + if (!new_set->at) { + mm_free(dst->pool, new_set); + return kr_error(ENOMEM); + } + memcpy(new_set->at, addr_set->at, addr_set->len); + new_set->len = addr_set->len; + new_set->cap = new_set->len; + /* Reinsert */ + if (map_set(&dst->nsset, k, new_set) != 0) { + pack_clear_mm(*new_set, mm_free, dst->pool); + mm_free(dst->pool, new_set); + return kr_error(ENOMEM); + } + return kr_ok(); +} + +int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src) +{ + if (!dst || !src) { + return kr_error(EINVAL); + } + /* We're not touching src nsset, I promise */ + return map_walk((map_t *)&src->nsset, copy_addr_set, dst); +} + int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rdata_t *rdata) { - if (cut == NULL || ns == NULL) { + if (!cut || !ns) { return kr_error(EINVAL); } /* Fetch/insert nameserver. */ @@ -138,7 +173,7 @@ int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rd int kr_zonecut_del(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rdata_t *rdata) { - if (cut == NULL || ns == NULL) { + if (!cut || !ns) { return kr_error(EINVAL); } @@ -163,7 +198,7 @@ int kr_zonecut_del(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rd pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns) { - if (cut == NULL || ns == NULL) { + if (!cut || !ns) { return NULL; } @@ -172,13 +207,23 @@ pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns) return map_get(nsset, key); } -int kr_zonecut_set_sbelt(struct kr_zonecut *cut) +int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut) { - if (cut == NULL) { + if (!ctx || !cut) { return kr_error(EINVAL); } update_cut_name(cut, U8("")); + + /* Copy root hints from resolution context. */ + if (ctx->root_hints.nsset.root) { + int ret = kr_zonecut_copy(cut, &ctx->root_hints); + if (ret == 0) { + return ret; + } + } + + /* Copy compiled-in root hints */ for (unsigned i = 0; i < HINT_COUNT; ++i) { const struct hint_info *hint = &SBELT[i]; knot_rdata_t rdata[knot_rdata_array_size(HINT_ADDRLEN)]; @@ -239,7 +284,7 @@ static int fetch_ns(struct kr_context *ctx, struct kr_zonecut *cut, const knot_d /* Always keep SBELT as a backup for root */ if (name[0] == '\0') { - kr_zonecut_set_sbelt(cut); + kr_zonecut_set_sbelt(ctx, cut); } return kr_ok(); @@ -269,5 +314,5 @@ int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut, const } /* Name server not found, start with SBELT. */ - return kr_zonecut_set_sbelt(cut); + return kr_zonecut_set_sbelt(ctx, cut); } diff --git a/lib/zonecut.h b/lib/zonecut.h index 990220aba43e714456307ff13e18817488300575..c9be6b327666526c4bc130598179908b23583428 100644 --- a/lib/zonecut.h +++ b/lib/zonecut.h @@ -55,6 +55,14 @@ void kr_zonecut_deinit(struct kr_zonecut *cut); */ void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name); +/** + * Copy zone cut, including all data. + * @param dst destination zone cut + * @param src source zone cut + * @return 0 or an error code + */ +int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src); + /** * Add address record to the zone cut. * @@ -92,10 +100,11 @@ pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns); /** * Populate zone cut with a root zone using SBELT :rfc:`1034` * + * @param ctx resolution context (to fetch root hints) * @param cut zone cut to be populated * @return 0 or error code */ -int kr_zonecut_set_sbelt(struct kr_zonecut *cut); +int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut); /** * Populate zone cut address set from cache. diff --git a/modules/hints/README.rst b/modules/hints/README.rst index 0b83ced7e717c3222486517e219bda680d8d7706..e69bbc5ac850e4430dc76dffe4ba778dc3c7d1ac 100644 --- a/modules/hints/README.rst +++ b/modules/hints/README.rst @@ -4,6 +4,8 @@ Static hints ------------ This is a module providing static hints from ``/etc/hosts`` like file. +You can also use it to change root hints that are used as a safety belt or if the root NS +drops out of cache. Properties ^^^^^^^^^^ @@ -28,3 +30,34 @@ Properties :return: ``{ result: bool }`` Set hostname - address pair hint. + +.. function:: hints.root() + + :return: ``{ ['a.root-servers.net'] = { '1.2.3.4', '5.6.7.8', ...}, ... }`` + + .. tip:: If no parameters are passed, returns current root hints set. + +.. function:: hints.root(root_hints) + + :param table root_hints: new set of root hints i.e. ``{['name'] = 'addr', ...}`` + :return: ``{ ['a.root-servers.net'] = { '1.2.3.4', '5.6.7.8', ...}, ... }`` + + Replace current root hints and return the current table of root hints. + + Example: + + .. code-block:: lua + + > hints.root({ + ['l.root-servers.net.'] = '199.7.83.42', + ['m.root-servers.net.'] = '202.12.27.33' + }) + [l.root-servers.net.] => { + [1] => 199.7.83.42 + } + [m.root-servers.net.] => { + [1] => 202.12.27.33 + } + + .. tip:: A good rule of thumb is to select only a few fastest root hints. The server learns RTT and NS quality over time, and thus tries all servers available. You can help it by preselecting the candidates. + \ No newline at end of file diff --git a/modules/hints/hints.c b/modules/hints/hints.c index 8d42359480cf4dd34d8a317d50013f3399d71d1a..80f81fb2e80e92fa5511244483d40e835e814a86 100644 --- a/modules/hints/hints.c +++ b/modules/hints/hints.c @@ -28,7 +28,7 @@ #include <ccan/json/json.h> #include <ucw/mempool.h> -#include "lib/layer/iterate.h" +#include "daemon/engine.h" #include "lib/zonecut.h" #include "lib/module.h" #include "lib/layer.h" @@ -222,6 +222,24 @@ static char* hint_set(void *env, struct kr_module *module, const char *args) return result; } +/** @internal Pack address list into JSON array. */ +static JsonNode *pack_addrs(pack_t *pack) +{ + char buf[SOCKADDR_STRLEN]; + JsonNode *root = json_mkarray(); + uint8_t *addr = pack_head(*pack); + while (addr != pack_tail(*pack)) { + size_t len = pack_obj_len(addr); + int family = len == sizeof(struct in_addr) ? AF_INET : AF_INET6; + if (!inet_ntop(family, pack_obj_val(addr), buf, sizeof(buf))) { + break; + } + json_append_element(root, json_mkstring(buf)); + addr = pack_obj_next(addr); + } + return root; +} + /** * Retrieve address hint for given name. * @@ -240,21 +258,59 @@ static char* hint_get(void *env, struct kr_module *module, const char *args) return NULL; } - char buf[SOCKADDR_STRLEN]; - JsonNode *root = json_mkarray(); - uint8_t *addr = pack_head(*pack); - while (addr != pack_tail(*pack)) { - size_t len = pack_obj_len(addr); - int family = len == sizeof(struct in_addr) ? AF_INET : AF_INET6; - if (!inet_ntop(family, pack_obj_val(addr), buf, sizeof(buf))) { - break; - } - json_append_element(root, json_mkstring(buf)); - addr = pack_obj_next(addr); + char *result = NULL; + JsonNode *root = pack_addrs(pack); + if (root) { + result = json_encode(root); + json_delete(root); } + return result; +} + +/** Retrieve hint list. */ +static int pack_hint(const char *k, void *v, void *baton) +{ + char nsname_str[KNOT_DNAME_MAXLEN] = {'\0'}; + knot_dname_to_str(nsname_str, (const uint8_t *)k, sizeof(nsname_str)); + JsonNode *root_node = baton; + JsonNode *addr_list = pack_addrs((pack_t *)v); + if (!addr_list) { + return kr_error(ENOMEM); + } + json_append_member(root_node, nsname_str, addr_list); + return kr_ok(); +} - char *result = json_encode(root); - json_delete(root); +/** + * Get/set root hints set. + * + * Input: { name: [addr_list], ... } + * Output: current list + * + */ +static char* hint_root(void *env, struct kr_module *module, const char *args) +{ + struct engine *engine = env; + struct kr_context *ctx = &engine->resolver; + /* Replace root hints if parameter is set */ + if (args && strlen(args) > 0) { + JsonNode *node = NULL; + JsonNode *root_node = json_decode(args); + kr_zonecut_set(&ctx->root_hints, (const uint8_t *)""); + json_foreach(node, root_node) { + switch(node->tag) { + case JSON_STRING: add_pair(&ctx->root_hints, node->key, node->string_); break; + default: continue; + } + } + } + /* Return current root hints */ + char *result = NULL; + JsonNode *root_node = json_mkobject(); + if (map_walk(&ctx->root_hints.nsset, pack_hint, root_node) == 0) { + result = json_encode(root_node); + } + json_delete(root_node); return result; } @@ -298,6 +354,7 @@ struct kr_prop *hints_props(void) static struct kr_prop prop_list[] = { { &hint_set, "set", "Set {name, address} hint.", }, { &hint_get, "get", "Retrieve hint for given name.", }, + { &hint_root, "root", "Replace root hints set (empty value to return current list).", }, { NULL, NULL, NULL } }; return prop_list; diff --git a/tests/test_zonecut.c b/tests/test_zonecut.c index 24b9a8fec5408b9b9cf2f7ce0d637c06a6946395..202899c3a4341321802c34b74ccad07e6be2a763 100644 --- a/tests/test_zonecut.c +++ b/tests/test_zonecut.c @@ -29,14 +29,34 @@ static void test_zonecut_params(void **state) assert_int_not_equal(kr_zonecut_add(NULL, NULL, NULL), 0); 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_set_sbelt(NULL, NULL), 0); assert_int_not_equal(kr_zonecut_find_cached(NULL, NULL, NULL, NULL, 0), 0); } +static void test_zonecut_copy(void **state) +{ + const knot_dname_t *root = (const uint8_t *)""; + struct kr_zonecut cut1, cut2; + kr_zonecut_init(&cut1, root, NULL); + kr_zonecut_init(&cut2, root, NULL); + /* Insert some values */ + assert_int_equal(kr_zonecut_add(&cut1, (const uint8_t *)"dead", NULL), 0); + assert_int_equal(kr_zonecut_add(&cut1, (const uint8_t *)"beef", NULL), 0); + /* Copy */ + assert_int_equal(kr_zonecut_copy(&cut2, &cut1), 0); + /* Check if exist */ + assert_non_null(kr_zonecut_find(&cut2, (const uint8_t *)"dead")); + assert_non_null(kr_zonecut_find(&cut2, (const uint8_t *)"beef")); + assert_null(kr_zonecut_find(&cut2, (const uint8_t *)"corn")); + kr_zonecut_deinit(&cut1); + kr_zonecut_deinit(&cut2); +} + int main(void) { const UnitTest tests[] = { - unit_test(test_zonecut_params) + unit_test(test_zonecut_params), + unit_test(test_zonecut_copy) }; return run_tests(tests);