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);
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)) {
/* 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;
}
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)) {
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 */
} 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;
}
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;
* 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. */
}
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;
}
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;
}
}
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();
+}
*/
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);
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);
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)
{
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).
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 */
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;
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 */
}
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 */
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))
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
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,
}