]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
iterate: follow CNAMEs in stub mode
authorMarek Vavruša <mvavrusa@cloudflare.com>
Sat, 9 Jun 2018 04:06:01 +0000 (21:06 -0700)
committerMarek Vavruša <mvavrusa@cloudflare.com>
Fri, 7 Sep 2018 17:45:21 +0000 (10:45 -0700)
There are two forwarder modes in the resolver - full forwarder,
and a stub mode. The full forwarder expects upstream to fully
solve the request (so the upstream must be a recursive resolver).

The stub forwarder mode is primarily useful for directing traffic
to a trusted resolver or authoritative (e.g. forward queries for
an internal zone). The upstream may not know the full answer to
the query, and may answer only from its authority. In that case
the resolver should follow the partially solved CNAME instead
of serving the partial answer.

lib/cache/api.c
lib/cache/peek.c
lib/layer/iterate.c
lib/resolve.c

index 1bc56615dcd14003d3825f931f83ae915ec82630..b1732fb19e36d5264e29865668c9e1efce212faf 100644 (file)
@@ -326,6 +326,7 @@ knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type, const uint8_t
 
 /** The inside for cache_peek(); implementation separated to ./peek.c */
 int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt);
+
 /** function for .produce phase */
 int cache_peek(kr_layer_t *ctx, knot_pkt_t *pkt)
 {
@@ -763,7 +764,6 @@ static int stash_nsec_p(const knot_dname_t *dname, const char *nsec_p_v,
        return kr_ok();
 }
 
-
 static int peek_exact_real(struct kr_cache *cache, const knot_dname_t *name, uint16_t type,
                        struct kr_cache_p *peek)
 {
@@ -795,6 +795,7 @@ static int peek_exact_real(struct kr_cache *cache, const knot_dname_t *name, uin
        };
        return kr_ok();
 }
+
 int kr_cache_peek_exact(struct kr_cache *cache, const knot_dname_t *name, uint16_t type,
                        struct kr_cache_p *peek)
 {      /* Just wrap with extra verbose logging. */
index 8cf93b3db040a294176be26f1737592323c66386..2e03db3486a34e9890fa782d413658a065e18ba2 100644 (file)
@@ -25,7 +25,7 @@
 static int found_exact_hit(kr_layer_t *ctx, knot_pkt_t *pkt, knot_db_val_t val,
                           uint8_t lowest_rank);
 static int closest_NS(struct kr_cache *cache, struct key *k, entry_list_t el,
-                       struct kr_query *qry, bool only_NS, bool is_DS);
+                       struct kr_query *qry, bool only_NS, bool is_DS, uint8_t rank_min);
 static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type,
                const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl);
 static int try_wild(struct key *k, struct answer *ans, const knot_dname_t *clencl_name,
@@ -184,7 +184,7 @@ int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt)
                return ctx->state;
        }
        entry_list_t el;
-       ret = closest_NS(cache, k, el, qry, false, qry->stype == KNOT_RRTYPE_DS);
+       ret = closest_NS(cache, k, el, qry, false, qry->stype == KNOT_RRTYPE_DS, lowest_rank);
        if (ret) {
                assert(ret == kr_error(ENOENT));
                if (ret != kr_error(ENOENT) || !el[0].len) {
@@ -599,7 +599,7 @@ int kr_cache_closest_apex(struct kr_cache *cache, const knot_dname_t *name, bool
                return kr_error(ret);
        entry_list_t el_;
        k->zname = name;
-       ret = closest_NS(cache, k, el_, NULL, true, is_DS);
+       ret = closest_NS(cache, k, el_, NULL, true, is_DS, KR_RANK_INITIAL | KR_RANK_AUTH);
        if (ret && ret != -abs(ENOENT))
                return ret;
        *apex = knot_dname_copy(k->zname, NULL);
@@ -625,7 +625,7 @@ static int check_NS_entry(struct key *k, knot_db_val_t entry, int i,
  * \return error code
  */
 static int closest_NS(struct kr_cache *cache, struct key *k, entry_list_t el,
-                       struct kr_query *qry, const bool only_NS, const bool is_DS)
+                       struct kr_query *qry, bool only_NS, bool is_DS, uint8_t rank_min)
 {
        /* get the current timestamp */
        const uint8_t *cache_scope = NULL;
index 37040528d235ad65de6d42bf96fad105313e77e3..0f032957f5e07ca83f98e1a3f97b720e6e5425ee 100644 (file)
@@ -491,7 +491,6 @@ static void finalize_answer(knot_pkt_t *pkt, struct kr_query *qry, struct kr_req
 static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral, const knot_dname_t **cname_ret)
 {
        struct kr_query *query = req->current_query;
-       assert(!(query->flags.STUB));
        /* Process answer type */
        const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER);
        const knot_dname_t *cname = NULL;
@@ -546,8 +545,15 @@ static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral,
                                        return state;
                                }
                        }
-                       uint8_t rank = get_initial_rank(rr, query, true,
-                                       query->flags.FORWARD || referral);
+                       uint8_t rank = 0;
+                       if (query->flags.STUB) {
+                               /* KR_RANK_AUTH: we don't have the records directly from
+                                * an authoritative source, but we do trust the server and it's
+                                * supposed to only send us authoritative records. */
+                               rank = KR_RANK_OMIT | KR_RANK_AUTH;
+                       } else {
+                               rank = get_initial_rank(rr, query, true, query->flags.FORWARD || referral);
+                       }
                        state = kr_ranked_rrarray_add(&req->answ_selected, rr,
                                                      rank, to_wire, query->uid, &req->pool);
                        if (state != kr_ok()) {
@@ -670,6 +676,77 @@ static int process_final(knot_pkt_t *pkt, struct kr_request *req,
        return kr_ok();
 }
 
+static int process_cname_chain(knot_pkt_t *pkt, struct kr_request *req, struct kr_query *query, const knot_dname_t *cname)
+{
+       const bool is_final = (query->parent == NULL);
+       int state = KR_STATE_DONE;
+
+       /* Check if target record has been already copied */
+       query->flags.CNAME = true;
+       if (is_final) {
+               state = process_final(pkt, req, cname);
+               if (state != kr_ok()) {
+                       VERBOSE_MSG("<= processing final response failed\n");
+                       return state;
+               }
+       } else if ((query->flags.FORWARD) &&
+                  ((query->stype == KNOT_RRTYPE_DS) ||
+                   (query->stype == KNOT_RRTYPE_NS))) {
+               /* CNAME'ed answer for DS or NS subquery.
+                * Treat it as proof of zonecut nonexistance. */
+               return KR_STATE_DONE;
+       }
+       VERBOSE_MSG("<= cname chain, following\n");
+       /* Check if the same query was followed in the same CNAME chain. */
+       for (const struct kr_query *q = query->cname_parent; q != NULL;
+                       q = q->cname_parent) {
+               if (q->sclass == query->sclass &&
+                   q->stype == query->stype   &&
+                   knot_dname_is_equal(q->sname, cname)) {
+                       VERBOSE_MSG("<= cname chain loop\n");
+                       return KR_STATE_FAIL;
+               }
+       }
+       struct kr_query *next = kr_rplan_push(&req->rplan, query->parent, cname, query->sclass, query->stype);
+       if (!next) {
+               return KR_STATE_FAIL;
+       }
+       next->flags.AWAIT_CUT = true;
+
+       /* Copy transitive flags from original query to CNAME followup. */
+       next->flags.TRACE = query->flags.TRACE;
+       next->flags.ALWAYS_CUT = query->flags.ALWAYS_CUT;
+       next->flags.NO_THROTTLE = query->flags.NO_THROTTLE;
+
+       if (query->flags.FORWARD) {
+               next->forward_flags.CNAME = true;
+               if (query->parent == NULL) {
+                       state = kr_nsrep_copy_set(&next->ns, &query->ns);
+                       if (state != kr_ok()) {
+                               return KR_STATE_FAIL;
+                       }
+               }
+       }
+       next->cname_parent = query;
+       /* Want DNSSEC if and only if it's posible to secure
+        * this name (i.e. iff it is covered by a TA) */
+       if (kr_ta_covers_qry(req->ctx, cname, query->stype)) {
+               next->flags.DNSSEC_WANT = true;
+       } else {
+               next->flags.DNSSEC_WANT = false;
+       }
+       if (!(query->flags.FORWARD) ||
+           (query->flags.DNSSEC_WEXPAND)) {
+               state = pick_authority(pkt, req, false);
+               if (state != kr_ok()) {
+                       return KR_STATE_FAIL;
+               }
+       }
+
+       return KR_STATE_DONE;
+}
+
+
 static int process_answer(knot_pkt_t *pkt, struct kr_request *req)
 {
        struct kr_query *query = req->current_query;
@@ -680,7 +757,6 @@ static int process_answer(knot_pkt_t *pkt, struct kr_request *req)
         * NOERROR  => found zone cut, retry, except the case described below
         * NXDOMAIN => parent is zone cut, retry as a workaround for bad authoritatives
         */
-       const bool is_final = (query->parent == NULL);
        const int pkt_class = kr_response_classify(pkt);
        const knot_dname_t * pkt_qname = knot_pkt_qname(pkt);
        if (!knot_dname_is_equal(pkt_qname, query->sname) &&
@@ -720,66 +796,7 @@ static int process_answer(knot_pkt_t *pkt, struct kr_request *req)
        query->flags.RESOLVED = true;
        /* Follow canonical name as next SNAME. */
        if (!knot_dname_is_equal(cname, query->sname)) {
-               /* Check if target record has been already copied */
-               query->flags.CNAME = true;
-               if (is_final) {
-                       state = process_final(pkt, req, cname);
-                       if (state != kr_ok()) {
-                               return state;
-                       }
-               } else if ((query->flags.FORWARD) &&
-                          ((query->stype == KNOT_RRTYPE_DS) ||
-                           (query->stype == KNOT_RRTYPE_NS))) {
-                       /* CNAME'ed answer for DS or NS subquery.
-                        * Treat it as proof of zonecut nonexistance. */
-                       return KR_STATE_DONE;
-               }
-               VERBOSE_MSG("<= cname chain, following\n");
-               /* Check if the same query was followed in the same CNAME chain. */
-               for (const struct kr_query *q = query->cname_parent; q != NULL;
-                               q = q->cname_parent) {
-                       if (q->sclass == query->sclass &&
-                           q->stype == query->stype   &&
-                           knot_dname_is_equal(q->sname, cname)) {
-                               VERBOSE_MSG("<= cname chain loop\n");
-                               return KR_STATE_FAIL;
-                       }
-               }
-               struct kr_query *next = kr_rplan_push(&req->rplan, query->parent, cname, query->sclass, query->stype);
-               if (!next) {
-                       return KR_STATE_FAIL;
-               }
-               next->flags.AWAIT_CUT = true;
-
-               /* Copy transitive flags from original query to CNAME followup. */
-               next->flags.TRACE = query->flags.TRACE;
-               next->flags.ALWAYS_CUT = query->flags.ALWAYS_CUT;
-               next->flags.NO_THROTTLE = query->flags.NO_THROTTLE;
-
-               if (query->flags.FORWARD) {
-                       next->forward_flags.CNAME = true;
-                       if (query->parent == NULL) {
-                               state = kr_nsrep_copy_set(&next->ns, &query->ns);
-                               if (state != kr_ok()) {
-                                       return KR_STATE_FAIL;
-                               }
-                       }
-               }
-               next->cname_parent = query;
-               /* Want DNSSEC if and only if it's posible to secure
-                * this name (i.e. iff it is covered by a TA) */
-               if (kr_ta_covers_qry(req->ctx, cname, query->stype)) {
-                       next->flags.DNSSEC_WANT = true;
-               } else {
-                       next->flags.DNSSEC_WANT = false;
-               }
-               if (!(query->flags.FORWARD) ||
-                   (query->flags.DNSSEC_WEXPAND)) {
-                       state = pick_authority(pkt, req, false);
-                       if (state != kr_ok()) {
-                               return KR_STATE_FAIL;
-                       }
-               }
+               return process_cname_chain(pkt, req, query, cname);
        } else if (!query->parent) {
                /* Answer for initial query */
                const bool to_wire = ((pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) != 0);
@@ -850,6 +867,20 @@ static int process_stub(knot_pkt_t *pkt, struct kr_request *req)
                return KR_STATE_FAIL;
        }
 
+       /* Unroll CNAME to check if it's fully resolved. */
+       const knot_dname_t *cname = NULL;
+       err = unroll_cname(pkt, req, false, &cname);
+       if (err != kr_ok()) {
+               VERBOSE_MSG("<= unrolling CNAME failed\n");
+               return KR_STATE_FAIL;
+       }
+
+       /* Follow canonical name as next SNAME. */
+       if (!knot_dname_is_equal(cname, query->sname)) {
+               VERBOSE_MSG("<= processing CNAME chain\n");
+               return process_cname_chain(pkt, req, query, cname);
+       }
+
        finalize_answer(pkt, query, req);
        return KR_STATE_DONE;
 }
index c57ff3e1781a41dfcb28508462b442c60e1f2bc3..7d66cdf9f21853ddb52a058a6cfb16d67423bf64 100644 (file)
@@ -689,9 +689,8 @@ static int query_finalize(struct kr_request *request, struct kr_query *qry, knot
                        ret = edns_create(pkt, request->answer, request);
                }
                if (ret == 0) {
-                       /* Stub resolution (ask for +rd and +do) */
+                       /* Stub resolution (ask for +do/+cd) */
                        if (qry->flags.STUB) {
-                               knot_wire_set_rd(pkt->wire);
                                if (knot_pkt_has_dnssec(request->answer)) {
                                        knot_edns_set_do(pkt->opt_rr);
                                }