/* Copyright (C) CZ.NIC, z.s.p.o. * SPDX-License-Identifier: GPL-3.0-or-later */ #include "lib/selection_iter.h" #include "lib/selection.h" #include "lib/generic/trie.h" #include "lib/generic/pack.h" #include "lib/zonecut.h" #include "lib/resolve.h" #define VERBOSE_MSG(qry, ...) kr_log_q((qry), SELECTION, __VA_ARGS__) /// To be held per query and locally. Allocations are in the kr_request's mempool. struct iter_local_state { trie_t *names; /// knot_dname_t -> struct iter_name_state * trie_t *addresses; /// IP address -> struct address_state * knot_dname_t *zonecut; /** Used to distinguish old and valid records in tries. */ unsigned int generation; enum kr_selection_error last_error; unsigned int no_ns_addr_count; }; enum record_state { RECORD_UNKNOWN, RECORD_RESOLVED, RECORD_TRIED }; // To be held per NS name and locally struct iter_name_state { unsigned int generation; enum record_state a_state; enum record_state aaaa_state; }; void iter_local_state_alloc(struct knot_mm *mm, void **local_state) { *local_state = mm_calloc(mm, 1, sizeof(struct iter_local_state)); } static struct address_state *get_address_state(struct iter_local_state *local_state, const struct kr_transport *transport) { if (!transport) { return NULL; } uint8_t *address = ip_to_bytes(&transport->address, transport->address_len); trie_val_t *address_state = trie_get_try(local_state->addresses, (char *)address, transport->address_len); if (!address_state) { kr_assert(transport->deduplicated); /* Transport was chosen by a different query. */ return NULL; } return *address_state; } static void unpack_state_from_zonecut(struct iter_local_state *local_state, struct kr_query *qry) { struct kr_zonecut *zonecut = &qry->zone_cut; struct knot_mm *mm = &qry->request->pool; bool zcut_changed = false; if (local_state->names == NULL || local_state->addresses == NULL) { /* Local state initialization. */ memset(local_state, 0, sizeof(struct iter_local_state)); local_state->names = trie_create(mm); local_state->addresses = trie_create(mm); } else { zcut_changed = !knot_dname_is_equal(zonecut->name, local_state->zonecut); } local_state->zonecut = zonecut->name; local_state->generation++; if (zcut_changed) { local_state->no_ns_addr_count = 0; } trie_it_t *it; const unsigned int current_generation = local_state->generation; for (it = trie_it_begin(zonecut->nsset); !trie_it_finished(it); trie_it_next(it)) { knot_dname_t *dname = (knot_dname_t *)trie_it_key(it, NULL); pack_t *addresses = *trie_it_val(it); trie_val_t *val = trie_get_ins(local_state->names, (char *)dname, knot_dname_size(dname)); if (!*val) { /* We encountered this name for the first time. */ *val = mm_calloc(mm, 1, sizeof(struct iter_name_state)); } struct iter_name_state *name_state = *val; name_state->generation = current_generation; if (zcut_changed) { /* Set name as unresolved as they might have fallen out * of cache (TTL expired). */ name_state->a_state = RECORD_UNKNOWN; name_state->aaaa_state = RECORD_UNKNOWN; } /* Iterate over all addresses of this NS (if any). */ for (uint8_t *obj = pack_head(*addresses); obj != pack_tail(*addresses); obj = pack_obj_next(obj)) { uint8_t *address = pack_obj_val(obj); size_t address_len = pack_obj_len(obj); trie_val_t *tval = trie_get_ins(local_state->addresses, (char *)address, address_len); if (!*tval) { /* We have have not seen this address before. */ *tval = mm_calloc(mm, 1, sizeof(struct address_state)); } struct address_state *address_state = *tval; address_state->generation = current_generation; address_state->ns_name = dname; if (address_len == sizeof(struct in_addr)) { name_state->a_state = RECORD_RESOLVED; } else if (address_len == sizeof(struct in6_addr)) { name_state->aaaa_state = RECORD_RESOLVED; } union kr_sockaddr tmp_address; bytes_to_ip(address, address_len, 0, &tmp_address); update_address_state(address_state, &tmp_address, address_len, qry); } } trie_it_free(it); kr_cache_commit(&qry->request->ctx->cache); } static int get_valid_addresses(struct iter_local_state *local_state, struct choice choices[]) { unsigned count = 0; trie_it_t *it; for (it = trie_it_begin(local_state->addresses); !trie_it_finished(it); trie_it_next(it)) { size_t address_len; uint8_t *address = (uint8_t *)trie_it_key(it, &address_len); struct address_state *address_state = *trie_it_val(it); if (address_state->generation == local_state->generation && !address_state->broken) { choices[count] = (struct choice){ .address_len = address_len, .address_state = address_state, }; bytes_to_ip(address, address_len, 0, &choices[count].address); count++; } } trie_it_free(it); return count; } static int get_resolvable_names(struct iter_local_state *local_state, struct to_resolve resolvable[], struct kr_query *qry) { /* Further resolution is not possible until we get `. DNSKEY` record; * we have to choose one of the known addresses here. */ if (qry->sname[0] == '\0' && qry->stype == KNOT_RRTYPE_DNSKEY) { return 0; } unsigned count = 0; trie_it_t *it; for (it = trie_it_begin(local_state->names); !trie_it_finished(it); trie_it_next(it)) { struct iter_name_state *name_state = *trie_it_val(it); if (name_state->generation != local_state->generation) continue; knot_dname_t *name = (knot_dname_t *)trie_it_key(it, NULL); if (qry->stype == KNOT_RRTYPE_DNSKEY && knot_dname_in_bailiwick(name, qry->sname) > 0) { /* Resolving `domain. DNSKEY` can't trigger the * resolution of `sub.domain. A/AAAA` since it * will cause a cycle. */ continue; } /* FIXME: kr_rplan_satisfies(qry,…) should have been here, but this leads to failures on * iter_ns_badip.rpl, this is because the test requires the resolver to switch to parent * side after a record in cache expires. Only way to do this in the current zonecut setup is * to requery the same query twice in the row. So we have to allow that and only check the * rplan from parent upwards. */ bool a_in_rplan = kr_rplan_satisfies(qry->parent, name, KNOT_CLASS_IN, KNOT_RRTYPE_A); bool aaaa_in_rplan = kr_rplan_satisfies(qry->parent, name, KNOT_CLASS_IN, KNOT_RRTYPE_AAAA); if (name_state->a_state == RECORD_UNKNOWN && !qry->flags.NO_IPV4 && !a_in_rplan) { resolvable[count++] = (struct to_resolve){ name, KR_TRANSPORT_RESOLVE_A }; } if (name_state->aaaa_state == RECORD_UNKNOWN && !qry->flags.NO_IPV6 && !aaaa_in_rplan) { resolvable[count++] = (struct to_resolve){ name, KR_TRANSPORT_RESOLVE_AAAA }; } } trie_it_free(it); return count; } static void update_name_state(knot_dname_t *name, enum kr_transport_protocol type, trie_t *names) { size_t name_len = knot_dname_size(name); trie_val_t *val = trie_get_try(names, (char *)name, name_len); if (!val) { return; } struct iter_name_state *name_state = (struct iter_name_state *)*val; switch (type) { case KR_TRANSPORT_RESOLVE_A: name_state->a_state = RECORD_TRIED; break; case KR_TRANSPORT_RESOLVE_AAAA: name_state->aaaa_state = RECORD_TRIED; break; default: kr_assert(false); } } void iter_choose_transport(struct kr_query *qry, struct kr_transport **transport) { struct knot_mm *mempool = &qry->request->pool; struct iter_local_state *local_state = (struct iter_local_state *) qry->server_selection.local_state->private; unpack_state_from_zonecut(local_state, qry); struct choice choices[trie_weight(local_state->addresses) + 1/*avoid 0*/]; /* We may try to resolve A and AAAA record for each name, so therefore * 2*trie_weight(…) is here. */ struct to_resolve resolvable[2 * trie_weight(local_state->names)]; // Filter valid addresses and names from the tries int choices_len = get_valid_addresses(local_state, choices); int resolvable_len = get_resolvable_names(local_state, resolvable, qry); bool * const force_resolve_p = &qry->server_selection.local_state->force_resolve; // Print some stats into debug logs. if (kr_log_is_debug_qry(SELECTION, qry)) { int v4_choices = 0; for (int i = 0; i < choices_len; ++i) if (choices[i].address.ip.sa_family == AF_INET) ++v4_choices; int v4_resolvable = 0; for (int i = 0; i < resolvable_len; ++i) if (resolvable[i].type == KR_TRANSPORT_RESOLVE_A) ++v4_resolvable; VERBOSE_MSG(qry, "=> id: '%05u' choosing from addresses: %d v4 + %d v6; " "names to resolve: %d v4 + %d v6; " "force_resolve: %d; NO6: IPv6 is %s\n", qry->id, v4_choices, choices_len - v4_choices, v4_resolvable, resolvable_len - v4_resolvable, (int)*force_resolve_p, no6_is_bad() ? "KO" : "OK"); } if (*force_resolve_p && resolvable_len) { choices_len = 0; *force_resolve_p = false; } bool tcp = qry->flags.TCP || qry->server_selection.local_state->truncated; *transport = select_transport(choices, choices_len, resolvable, resolvable_len, qry->server_selection.local_state->timeouts, mempool, tcp, NULL); bool nxnsattack_mitigation = false; if (*transport) { switch ((*transport)->protocol) { case KR_TRANSPORT_RESOLVE_A: case KR_TRANSPORT_RESOLVE_AAAA: if (++local_state->no_ns_addr_count > KR_COUNT_NO_NSADDR_LIMIT) { *transport = NULL; nxnsattack_mitigation = true; break; } /* Note that we tried resolving this name to not try it again. */ update_name_state((*transport)->ns_name, (*transport)->protocol, local_state->names); break; case KR_TRANSPORT_TLS: case KR_TRANSPORT_TCP: /* We need to propagate this to flags since it's used in * other parts of the resolver. */ qry->flags.TCP = true; case KR_TRANSPORT_UDP: /* fall through */ local_state->no_ns_addr_count = 0; break; default: kr_assert(false); break; } if (*transport && (*transport)->protocol == KR_TRANSPORT_TCP && !qry->server_selection.local_state->truncated && qry->server_selection.local_state->force_udp) { // Last chance on broken TCP. (*transport)->protocol = KR_TRANSPORT_UDP; qry->flags.TCP = false; } } if (*transport == NULL && local_state->last_error == KR_SELECTION_DNSSEC_ERROR) { /* Last selected server had broken DNSSEC and now we have no more * servers to ask. We signal this to the rest of resolver by * setting DNSSEC_BOGUS flag. */ qry->flags.DNSSEC_BOGUS = true; } if (kr_log_is_debug_qry(SELECTION, qry)) { KR_DNAME_GET_STR(zonecut_str, qry->zone_cut.name); if (*transport) { KR_DNAME_GET_STR(ns_name, (*transport)->ns_name); const enum kr_transport_protocol proto = *transport ? (*transport)->protocol : -1; const char *ns_str = kr_straddr(&(*transport)->address.ip); const char *ip_version; switch (proto) { case KR_TRANSPORT_RESOLVE_A: case KR_TRANSPORT_RESOLVE_AAAA: ip_version = (proto == KR_TRANSPORT_RESOLVE_A) ? "A" : "AAAA"; VERBOSE_MSG(qry, "=> id: '%05u' choosing to resolve %s: '%s' zone cut: '%s'\n", qry->id, ip_version, ns_name, zonecut_str); break; default: VERBOSE_MSG(qry, "=> id: '%05u' choosing: '%s'@'%s'" " with timeout %u ms zone cut: '%s'\n", qry->id, ns_name, ns_str ? ns_str : "", (*transport)->timeout, zonecut_str); break; } } else { const char *nxns_msg = nxnsattack_mitigation ? " (stopped due to mitigation for NXNSAttack CVE-2020-12667)" : ""; VERBOSE_MSG(qry, "=> id: '%05u' no suitable transport, zone cut: '%s'%s\n", qry->id, zonecut_str, nxns_msg ); } } } void iter_error(struct kr_query *qry, const struct kr_transport *transport, enum kr_selection_error sel_error) { if (!qry->server_selection.initialized) { return; } struct iter_local_state *local_state = qry->server_selection.local_state->private; struct address_state *addr_state = get_address_state(local_state, transport); local_state->last_error = sel_error; error(qry, addr_state, transport, sel_error); } void iter_update_rtt(struct kr_query *qry, const struct kr_transport *transport, unsigned rtt) { if (!qry->server_selection.initialized) { return; } struct iter_local_state *local_state = qry->server_selection.local_state->private; struct address_state *addr_state = get_address_state(local_state, transport); update_rtt(qry, addr_state, transport, rtt); }