]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
lib: full forwarding mode support
authorGrigorii Demidov <grigorii.demidov@nic.cz>
Wed, 26 Apr 2017 10:09:00 +0000 (12:09 +0200)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Thu, 1 Jun 2017 14:27:16 +0000 (16:27 +0200)
daemon/lua/kres-gen.lua
lib/layer/iterate.c
lib/layer/validate.c
lib/nsrep.c
lib/nsrep.h
lib/resolve.c
lib/rplan.h
modules/policy/policy.lua

index ff1fcfd9ba81e9cf9b40be823e5b4bac80e8e3f2..c0dc75e8a69d18ceb615631bde7ac6d68fe7f428 100644 (file)
@@ -162,7 +162,7 @@ struct kr_context {
        struct kr_zonecut root_hints;
        char _stub[];
 };
-struct query_flag {static const int NO_MINIMIZE = 1; static const int NO_THROTTLE = 2; static const int NO_IPV6 = 4; static const int NO_IPV4 = 8; static const int TCP = 16; static const int RESOLVED = 32; static const int AWAIT_IPV4 = 64; static const int AWAIT_IPV6 = 128; static const int AWAIT_CUT = 256; static const int SAFEMODE = 512; static const int CACHED = 1024; static const int NO_CACHE = 2048; static const int EXPIRING = 4096; static const int ALLOW_LOCAL = 8192; static const int DNSSEC_WANT = 16384; static const int DNSSEC_BOGUS = 32768; static const int DNSSEC_INSECURE = 65536; static const int STUB = 131072; static const int ALWAYS_CUT = 262144; static const int DNSSEC_WEXPAND = 524288; static const int PERMISSIVE = 1048576; static const int STRICT = 2097152; static const int BADCOOKIE_AGAIN = 4194304; static const int CNAME = 8388608; static const int REORDER_RR = 16777216; static const int TRACE = 33554432; static const int NO_0X20 = 67108864; static const int DNSSEC_NODS = 134217728; static const int DNSSEC_OPTOUT = 268435456; static const int NONAUTH = 536870912;};
+struct query_flag {static const int NO_MINIMIZE = 1; static const int NO_THROTTLE = 2; static const int NO_IPV6 = 4; static const int NO_IPV4 = 8; static const int TCP = 16; static const int RESOLVED = 32; static const int AWAIT_IPV4 = 64; static const int AWAIT_IPV6 = 128; static const int AWAIT_CUT = 256; static const int SAFEMODE = 512; static const int CACHED = 1024; static const int NO_CACHE = 2048; static const int EXPIRING = 4096; static const int ALLOW_LOCAL = 8192; static const int DNSSEC_WANT = 16384; static const int DNSSEC_BOGUS = 32768; static const int DNSSEC_INSECURE = 65536; static const int STUB = 131072; static const int ALWAYS_CUT = 262144; static const int DNSSEC_WEXPAND = 524288; static const int PERMISSIVE = 1048576; static const int STRICT = 2097152; static const int BADCOOKIE_AGAIN = 4194304; static const int CNAME = 8388608; static const int REORDER_RR = 16777216; static const int TRACE = 33554432; static const int NO_0X20 = 67108864; static const int DNSSEC_NODS = 134217728; static const int DNSSEC_OPTOUT = 268435456; static const int NONAUTH = 536870912; static const int FORWARD = 1073741824;};
 int knot_dname_size(const knot_dname_t *);
 knot_dname_t *knot_dname_from_str(uint8_t *, const char *, size_t);
 char *knot_dname_to_str(char *, const knot_dname_t *, size_t);
index e955aa77dff648c458280d93bee028c7961b1428..d3c79ae66316cb5c0ec0596c3e50e09ea5391f5e 100644 (file)
@@ -362,6 +362,18 @@ static int process_authority(knot_pkt_t *pkt, struct kr_request *req)
        int result = KR_STATE_CONSUME;
        const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY);
 
+       if (qry->flags & QUERY_FORWARD) {
+               for (unsigned i = 0; i < ns->count; ++i) {
+                       const knot_rrset_t *rr = knot_pkt_rr(ns, i);
+                       if (rr->type == KNOT_RRTYPE_SOA || rr->type == KNOT_RRTYPE_NS) {
+                               if (knot_dname_in(rr->owner, knot_pkt_qname(pkt))) {
+                                       qry->zone_cut.name = knot_dname_copy(rr->owner, &req->pool);
+                               }
+                       }
+               }
+               return KR_STATE_CONSUME;
+       }
+
 #ifdef STRICT_MODE
        /* AA, terminate resolution chain. */
        if (knot_wire_get_aa(pkt->wire)) {
@@ -605,7 +617,8 @@ static int process_answer(knot_pkt_t *pkt, struct kr_request *req)
 
        /* This answer didn't improve resolution chain, therefore must be authoritative (relaxed to negative). */
        if (!is_authoritative(pkt, query)) {
-               if (pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) {
+               if (!(query->flags & QUERY_FORWARD) &&
+                   pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) {
                        VERBOSE_MSG("<= lame response: non-auth sent negative response\n");
                        return KR_STATE_FAIL;
                }
@@ -647,8 +660,15 @@ static int process_answer(knot_pkt_t *pkt, struct kr_request *req)
                if (!next) {
                        return KR_STATE_FAIL;
                }
-               next->flags |= QUERY_AWAIT_CUT;
-
+               if (query->flags & QUERY_FORWARD) {
+                       next->flags |= QUERY_FORWARD;
+                       state = kr_nsrep_copy_set(&next->ns, &query->ns);
+                       if (state != kr_ok()) {
+                               return KR_STATE_FAIL;
+                       }
+               } else {
+                       next->flags |= QUERY_AWAIT_CUT;
+               }
                /* 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)) {
index ea293e11362870263d490977abdfa9497c312d40..52b7fa810c2341fb6c4b0f782927eb86ea8dc07b 100644 (file)
@@ -384,6 +384,9 @@ static int update_delegation(struct kr_request *req, struct kr_query *qry, knot_
                        qry->flags |= QUERY_DNSSEC_NODS;
                }
                return ret;
+       } else if (qry->flags & QUERY_FORWARD && qry->parent) {
+               struct kr_query *parent = qry->parent;
+               parent->zone_cut.name = knot_dname_copy(qry->sname, parent->zone_cut.pool);
        }
 
        /* Extend trust anchor */
@@ -454,6 +457,13 @@ static int rrsig_not_found(kr_layer_t *ctx, const knot_rrset_t *rr)
        } else {
                next->flags |= QUERY_AWAIT_CUT;
        }
+       if (qry->flags & QUERY_FORWARD) {
+               int state = kr_nsrep_copy_set(&next->ns, &qry->ns);
+               if (state != kr_ok()) {
+                       return KR_STATE_FAIL;
+               }
+               next->flags &= ~QUERY_AWAIT_CUT;
+       }
        next->flags |= QUERY_DNSSEC_WANT;
        return KR_STATE_YIELD;
 }
@@ -538,6 +548,104 @@ static bool check_empty_answer(kr_layer_t *ctx, knot_pkt_t *pkt)
        return ((an->count != 0) && (num_entries == 0)) ? false : true;
 }
 
+static void unsigned_forward(kr_layer_t *ctx, knot_pkt_t *pkt)
+{
+       struct kr_request *req = ctx->req;
+       struct kr_query *qry = req->current_query;
+       const uint16_t qtype = knot_pkt_qtype(pkt);
+
+       if (qtype != KNOT_RRTYPE_DS) {
+               struct kr_rplan *rplan = &req->rplan;
+               struct kr_query *next = kr_rplan_push(rplan, qry, qry->sname, qry->sclass, KNOT_RRTYPE_DS);
+               int state = kr_nsrep_copy_set(&next->ns, &qry->ns);
+               if (state != kr_ok()) {
+                       return;
+               }
+               kr_zonecut_set(&next->zone_cut, qry->zone_cut.name);
+               kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut);
+               next->flags |= QUERY_DNSSEC_WANT;
+               return;
+       }
+       return;
+
+
+       /* We are in forwarding mode and have got unsigned answer.
+        * Check if answer is empty or not */
+       if ((qtype == KNOT_RRTYPE_DS) || (qtype == KNOT_RRTYPE_RRSIG)) {
+               /* We are already trying to detect zone security status.
+                * Answer for DS \ RRSIG query can contains
+                * CNAME instead of target qtype.
+                * Ask for DS for parent zone in this case */
+               if ((knot_pkt_section(pkt, KNOT_ANSWER)->count != 0) &&
+                   !(qry->flags & QUERY_CNAME)) {
+                       /* Answer isn't empty and doesn't contain CNAME.
+                        * Do nothing */
+                       return;
+               }
+       } else if ((knot_pkt_section(pkt, KNOT_ANSWER)->count != 0) &&
+                   !(qry->flags & QUERY_CNAME)) {
+               /* Answer isn't empty. */
+               return;
+       }
+
+       /* AUTHORITY can contain SOA */
+       if (knot_pkt_section(pkt, KNOT_AUTHORITY)->count > 1) {
+               return;
+       }
+
+       const knot_dname_t *qname  = NULL;
+       bool has_soa  = false;
+       if (knot_pkt_section(pkt, KNOT_AUTHORITY)->count == 1) {
+               const knot_pktsection_t *sec = knot_pkt_section(pkt, KNOT_AUTHORITY);
+               const knot_rrset_t *rr = knot_pkt_rr(sec, 0);
+               if (rr->type != KNOT_RRTYPE_SOA) {
+                       return;
+               }
+               qname = rr->owner;
+               has_soa = true;
+       }
+
+       /* check_signer() is going to return KR_STATE_YIELD.
+        * This will cause refetching DS for current zone name by check_trust_chain()
+        * (i.e. - we do not receive RRSIG at all and
+        * want to figure out with zone security status).
+        * Here we set current zone name. When qtype is :
+        * 1) RRSIG + SOA - use SOA owner
+        * 2) RRSIG       - use qname
+        * 3) DS + SOA    - next-label(SOA owner)
+        * 4) DS          - use next-label(qname) */
+       if (qtype != KNOT_RRTYPE_DS) {
+               /* We have got empty answer.
+                * It is necessary to figure out with zone security status.
+                * If there is SOA. use SOA owner since it is exact zone name.
+                * If no, try the owner of packet qname. */
+               if (!has_soa) {
+                       qname = knot_pkt_qname(pkt);
+               }
+       } else {
+               /* We already trying to figure out with security status.
+                * In previous steps DS query was spawned.
+                * Here we have got empty answer for DS query.
+                * If there is SOA. use next-label (SOA owner),
+                * otherwise use next-label (packet qname) */
+               if (has_soa) {
+                       if (knot_dname_is_equal(qname, knot_pkt_qname(pkt)) && qname[0] != '\0') {
+                               qname = knot_wire_next_label(qname, NULL);
+                       }
+               } else {
+                       qname = knot_pkt_qname(pkt);
+                       if (qname[0] != '\0') {
+                               qname = knot_wire_next_label(qname, NULL);
+                       }
+               }
+       }
+       if (qname[0] != '\0') {
+               qry->zone_cut.name = knot_dname_copy(qname, &req->pool);
+       }
+
+       return;
+}
+
 static int check_signer(kr_layer_t *ctx, knot_pkt_t *pkt)
 {
        struct kr_request *req = ctx->req;
@@ -565,6 +673,9 @@ static int check_signer(kr_layer_t *ctx, knot_pkt_t *pkt)
                                 * for both parent and child,
                                 * and child zone is not signed. */
                                qry->zone_cut.name = knot_dname_copy(qname, &req->pool);
+                       } else if (qry->flags & QUERY_FORWARD) {
+                               unsigned_forward(ctx, pkt);
+                               return KR_STATE_YIELD;
                        }
                } else if (knot_dname_is_sub(signer, qry->zone_cut.name)) {
                        /* Key signer is below current cut, advance and refetch keys. */
@@ -580,8 +691,11 @@ static int check_signer(kr_layer_t *ctx, knot_pkt_t *pkt)
                        }
                        qry->zone_cut.name = knot_dname_copy(signer, &req->pool);
                } /* else zone cut matches, but DS/DNSKEY doesn't => refetch. */
-               VERBOSE_MSG(qry, ">< cut changed, needs revalidation\n");
-               return KR_STATE_YIELD;
+               if (qry->stype != KNOT_RRTYPE_DS) {
+                       /* zone cut matches, but DS/DNSKEY doesn't => refetch. */
+                       VERBOSE_MSG(qry, ">< cut changed, needs revalidation\n");
+                       return KR_STATE_YIELD;
+               }
        }
        return KR_STATE_DONE;
 }
@@ -803,7 +917,7 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt)
                qtype = KNOT_RRTYPE_DS;
        }
        /* Update parent query zone cut */
-       if (qry->parent) {
+       if (qry->parent && pkt_rcode != KNOT_RCODE_NXDOMAIN) {
                if (update_parent_keys(qry, qtype) != 0) {
                        return KR_STATE_FAIL;
                }
index 8139a223cfdef81d50b0378d41e71ee576344d2c..564caf30c540a1b5525775d35836e861c02e3da3 100644 (file)
@@ -321,3 +321,17 @@ int kr_nsrep_update_rep(struct kr_nsrep *ns, unsigned reputation, kr_nsrep_lru_t
        }
        return kr_ok();
 }
+
+int kr_nsrep_copy_set(struct kr_nsrep *dst, const struct kr_nsrep *src)
+{
+       if (!dst || !src ) {
+               return kr_error(EINVAL);
+       }
+
+       memcpy(dst, src, sizeof(struct kr_nsrep));
+       dst->name = (const uint8_t *)"";
+       dst->score = KR_NS_UNKNOWN;
+       dst->reputation = 0;
+
+       return kr_ok();
+}
index a0d9aeb90d56081cce1673a14f2968de7e35cee1..302d984c1dc25f0614f2c5c7c8a6af7d6990d7b4 100644 (file)
@@ -142,3 +142,11 @@ int kr_nsrep_update_rtt(struct kr_nsrep *ns, const struct sockaddr *addr,
  */
 KR_EXPORT
 int kr_nsrep_update_rep(struct kr_nsrep *ns, unsigned reputation, kr_nsrep_lru_t *cache);
+/**
+ * Copy NSSET reputation information and resets score.
+ *
+ * @param  dst          updated NS representation
+ * @param  src          source NS representation
+ * @return              0 on success, error code on failure
+ */
+int kr_nsrep_copy_set(struct kr_nsrep *dst, const struct kr_nsrep *src);
index 1012fbb74ed7d52f1d81246e06d2c5e9b24d82e1..f951a08f42b2ced2b75ebd75cf6cdaf5f6a27d8b 100644 (file)
@@ -659,6 +659,10 @@ static int query_finalize(struct kr_request *request, struct kr_query *qry, knot
                                        knot_wire_set_cd(pkt->wire);
                                }
                        /* Full resolution (ask for +cd and +do) */
+                       } else if (qry->flags & QUERY_FORWARD) {
+                               knot_wire_set_rd(pkt->wire);
+                               knot_edns_set_do(pkt->opt_rr);
+                               knot_wire_set_cd(pkt->wire);
                        } else if (qry->flags & QUERY_DNSSEC_WANT) {
                                knot_edns_set_do(pkt->opt_rr);
                                knot_wire_set_cd(pkt->wire);
@@ -918,6 +922,120 @@ static struct kr_query *zone_cut_subreq(struct kr_rplan *rplan, struct kr_query
        return next;
 }
 
+static int forward_trust_chain_check(struct kr_request *request, struct kr_query *qry, bool resume)
+{
+       struct kr_rplan *rplan = &request->rplan;
+       map_t *trust_anchors = &request->ctx->trust_anchors;
+       map_t *negative_anchors = &request->ctx->negative_anchors;
+
+       assert(qry->flags & QUERY_FORWARD);
+       printf("QUERY_RESOLVED? %i\n", (qry->flags & QUERY_RESOLVED));
+       printf("QUERY_NO_MINIMIZE? %i\n", (qry->flags & QUERY_NO_MINIMIZE));
+
+       if (qry->flags & QUERY_DNSSEC_INSECURE) {
+               return KR_STATE_PRODUCE;
+       }
+
+       const knot_dname_t* wanted_name = qry->zone_cut.name;
+       bool nods = false;
+       if (qry->parent == NULL && !resume) {
+               wanted_name = qry->sname;
+               int cut_labels = knot_dname_labels(qry->zone_cut.name, NULL);
+               int wanted_name_labels = knot_dname_labels(wanted_name, NULL);
+               while(wanted_name[0] && wanted_name_labels > cut_labels + 1) {
+                       wanted_name = knot_wire_next_label(wanted_name, NULL);
+                       wanted_name_labels -= 1;
+               }
+               nods = (wanted_name == qry->sname);
+       }
+
+       kr_dname_print(qry->zone_cut.name, "zone_cut : ", "\n");
+       kr_dname_print(wanted_name, "wanted_name : ", "\n");
+       for (int i = 0; i < request->rplan.resolved.len; ++i) {
+               struct kr_query *q = request->rplan.resolved.at[i];
+               if (q->parent == qry &&
+                   q->sclass == qry->sclass &&
+                   q->stype == KNOT_RRTYPE_DS &&
+                   (q->flags & QUERY_DNSSEC_NODS) &&
+                   knot_dname_is_equal(q->sname, wanted_name)) {
+                       nods = true;
+                       VERBOSE_MSG(qry, "stop chasing trust chain\n");
+                       break;
+               }
+       }
+
+       nods = nods ||
+              ((qry->stype == KNOT_RRTYPE_DS) &&
+              knot_dname_is_equal(wanted_name, qry->sname));
+
+       /* Disable DNSSEC if it enters NTA. */
+       if (kr_ta_get(negative_anchors, wanted_name)){
+               VERBOSE_MSG(qry, ">< negative TA, going insecure\n");
+               qry->flags &= ~QUERY_DNSSEC_WANT;
+       }
+
+       /* Enable DNSSEC if enters a new island of trust. */
+       bool want_secured = (qry->flags & QUERY_DNSSEC_WANT) &&
+                           !knot_wire_get_cd(request->answer->wire);
+       if (!(qry->flags & QUERY_DNSSEC_WANT) &&
+           !knot_wire_get_cd(request->answer->wire) &&
+           kr_ta_get(trust_anchors, wanted_name)) {
+               qry->flags |= QUERY_DNSSEC_WANT;
+               want_secured = true;
+               WITH_VERBOSE {
+               char qname_str[KNOT_DNAME_MAXLEN];
+               knot_dname_to_str(qname_str, wanted_name, sizeof(qname_str));
+               VERBOSE_MSG(qry, ">< TA: '%s'\n", qname_str);
+               }
+       }
+
+       if (want_secured && !qry->zone_cut.trust_anchor) {
+               knot_rrset_t *ta_rr = kr_ta_get(trust_anchors, wanted_name);
+               if (!ta_rr) {
+                       char name[] = "\0";
+                       ta_rr = kr_ta_get(trust_anchors, (knot_dname_t*)name);
+               }
+               qry->zone_cut.trust_anchor = knot_rrset_copy(ta_rr, qry->zone_cut.pool);
+       }
+
+       const bool has_ta = (qry->zone_cut.trust_anchor != NULL);
+       const knot_dname_t *ta_name = (has_ta ? qry->zone_cut.trust_anchor->owner : NULL);
+       const bool refetch_ta = (!has_ta || !knot_dname_is_equal(wanted_name, ta_name));
+       if (!nods && want_secured && refetch_ta) {
+               struct kr_query *next = kr_rplan_push(rplan, qry, wanted_name, qry->sclass, KNOT_RRTYPE_DS);
+               if (!next) {
+                       return KR_STATE_FAIL;
+               }
+               int state = kr_nsrep_copy_set(&next->ns, &qry->ns);
+               if (state != kr_ok()) {
+                       return KR_STATE_FAIL;
+               }
+               kr_zonecut_set(&next->zone_cut, qry->zone_cut.name);
+               kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut);
+               next->flags |= QUERY_DNSSEC_WANT;
+               return KR_STATE_DONE;
+       }
+
+       /* Try to fetch missing DNSKEY.
+        * Do not fetch if this is a DNSKEY subrequest to avoid circular dependency. */
+       const bool is_dnskey_subreq = kr_rplan_satisfies(qry, ta_name, KNOT_CLASS_IN, KNOT_RRTYPE_DNSKEY);
+       const bool refetch_key = has_ta && (!qry->zone_cut.key || !knot_dname_is_equal(ta_name, qry->zone_cut.key->owner));
+       printf("has_ta %i\n", has_ta);
+       printf("want_secured %i refetch_key %i is_dnskey_subreq %i\n", want_secured, refetch_key, is_dnskey_subreq);
+       if (want_secured && refetch_key && !is_dnskey_subreq) {
+               struct kr_query *next = zone_cut_subreq(rplan, qry, ta_name, KNOT_RRTYPE_DNSKEY);
+               if (!next) {
+                       return KR_STATE_FAIL;
+               }
+               int state = kr_nsrep_copy_set(&next->ns, &qry->ns);
+               if (state != kr_ok()) {
+                       return KR_STATE_FAIL;
+               }
+               return KR_STATE_DONE;
+       }
+       return KR_STATE_PRODUCE;
+}
+
 /* @todo: Validator refactoring, keep this in driver for now. */
 static int trust_chain_check(struct kr_request *request, struct kr_query *qry)
 {
@@ -970,7 +1088,15 @@ static int trust_chain_check(struct kr_request *request, struct kr_query *qry)
                if (!next) {
                        return KR_STATE_FAIL;
                }
-               next->flags |= QUERY_AWAIT_CUT|QUERY_DNSSEC_WANT;
+               if (qry->flags & QUERY_FORWARD) {
+                       int state = kr_nsrep_copy_set(&next->ns, &qry->ns);
+                       if (state != kr_ok()) {
+                               return KR_STATE_FAIL;
+                       }
+               } else {
+                       next->flags |= QUERY_AWAIT_CUT;
+               }
+               next->flags |= QUERY_DNSSEC_WANT;
                return KR_STATE_DONE;
        }
        /* Try to fetch missing DNSKEY (either missing or above current cut).
@@ -996,6 +1122,12 @@ static int zone_cut_check(struct kr_request *request, struct kr_query *qry, knot
                return KR_STATE_PRODUCE;
        }
 
+       /* Forwarding to upstream resolver mode.
+        * Since forwarding targets already are in qry->ns -
+        * cut fetching is not needed. */
+       if (qry->flags & QUERY_FORWARD) {
+               return forward_trust_chain_check(request, qry, false);
+       }
        if (!(qry->flags & QUERY_AWAIT_CUT)) {
                /* The query was resolved from cache.
                 * Spawn DS \ DNSKEY requests if needed and exit */
@@ -1064,7 +1196,10 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
        struct kr_query *qry = array_tail(rplan->pending);
        if (qry->deferred != NULL) {
                /* @todo: Refactoring validator, check trust chain before resuming. */
-               switch(trust_chain_check(request, qry)) {
+               int state = (qry->flags & QUERY_FORWARD) ?
+                           forward_trust_chain_check(request, qry, true) :
+                           trust_chain_check(request, qry);
+               switch(state) {
                case KR_STATE_FAIL: return KR_STATE_FAIL;
                case KR_STATE_DONE: return KR_STATE_PRODUCE;
                default: break;
@@ -1138,12 +1273,13 @@ ns_election:
                return KR_STATE_FAIL;
        }
 
-       const bool retry = (qry->flags & (QUERY_TCP|QUERY_STUB|QUERY_BADCOOKIE_AGAIN));
+       const bool retry = (qry->flags & (QUERY_TCP|QUERY_STUB|QUERY_FORWARD|QUERY_BADCOOKIE_AGAIN));
        if (qry->flags & (QUERY_AWAIT_IPV4|QUERY_AWAIT_IPV6)) {
                kr_nsrep_elect_addr(qry, request->ctx);
        } else if (!qry->ns.name || !retry) { /* Keep NS when requerying/stub/badcookie. */
                /* Root DNSKEY must be fetched from the hints to avoid chicken and egg problem. */
-               if (qry->sname[0] == '\0' && qry->stype == KNOT_RRTYPE_DNSKEY) {
+               if (qry->sname[0] == '\0' && qry->stype == KNOT_RRTYPE_DNSKEY
+                   && !(qry->stype | QUERY_FORWARD)) {
                        kr_zonecut_set_sbelt(request->ctx, &qry->zone_cut);
                        qry->flags |= QUERY_NO_THROTTLE; /* Pick even bad SBELT servers */
                }
index 368833dac62709aa82b6cd33f4d3a80960abe939..ce5c2f6ca6055e59ca9a3dfd86c0db84087f1953 100644 (file)
@@ -57,6 +57,7 @@
        X(DNSSEC_OPTOUT,   1 << 28) /**< Closest encloser proof has optout */ \
        X(NONAUTH,         1 << 29) /**< Non-authoritative in-bailiwick records are enough.
                                     *   TODO: utilize this also outside cache. */ \
+       X(FORWARD,         1 << 30) /**< Forward all queries to upstream; validate answers */ \
                        /* 1 << 31       Used by ../modules/dns64/dns64.lua */
 
 /** Query flags */
index d3ae783d2a980ab2d80488533de51057240aa38f..8385585fb293fd852ffd4ceb1a2b6aae3aa163d6 100644 (file)
@@ -65,12 +65,12 @@ local function mirror(target)
 end
 
 -- Forward request, and solve as stub query
-local function forward(target)
+local function stub(target)
        local list = {}
        if type(target) == 'table' then
                for _, v in pairs(target) do
                        table.insert(list, addr2sock(v))
-                       assert(#list <= 4, 'at most 4 FORWARD targets are supported')
+                       assert(#list <= 4, 'at most 4 STUB targets are supported')
                end
        else
                table.insert(list, addr2sock(target))
@@ -85,6 +85,28 @@ local function forward(target)
        end
 end
 
+-- Forward request and all subrequests to upstream; validate answers
+local function forward(target)
+       local list = {}
+       if type(target) == 'table' then
+               for _, v in pairs(target) do
+                       table.insert(list, addr2sock(v))
+                       assert(#list <= 4, 'at most 4 FORWARD targets are supported')
+               end
+       else
+               table.insert(list, addr2sock(target))
+       end
+       return function(state, req)
+               req = kres.request_t(req)
+               local qry = req:current()
+               req.options = bit.bor(bit.bor(req.options, kres.query.FORWARD), kres.query.NO_MINIMIZE)
+               qry.flags = bit.band(bit.bor(qry.flags, kres.query.FORWARD), bit.bnot(kres.query.ALWAYS_CUT))
+               qry.flags = bit.bor(qry.flags, kres.query.NO_MINIMIZE)
+               qry:nslist(list)
+               return state
+       end
+end
+
 -- Rewrite records in packet
 local function reroute(tbl, names)
        -- Import renumbering rules
@@ -110,7 +132,7 @@ end
 local policy = {
        -- Policies
        PASS = 1, DENY = 2, DROP = 3, TC = 4, QTRACE = 5,
-       FORWARD = forward, REROUTE = reroute, MIRROR = mirror, FLAGS = flags,
+       FORWARD = forward, STUB = stub, REROUTE = reroute, MIRROR = mirror, FLAGS = flags,
        -- Special values
        ANY = 0,
 }