From: Grigorii Demidov Date: Mon, 5 Dec 2016 12:02:13 +0000 (+0100) Subject: validate: support +cd X-Git-Tag: v1.2.0-rc1~62^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=042f105d90fb83a3ea85ae96d197ce0553c2a1ce;p=thirdparty%2Fknot-resolver.git validate: support +cd --- diff --git a/lib/cache.h b/lib/cache.h index fc06f5853..b6ac0fe42 100644 --- a/lib/cache.h +++ b/lib/cache.h @@ -39,15 +39,16 @@ enum kr_cache_tag { enum kr_cache_rank { KR_RANK_BAD = 0, /* BAD cache, do not use. */ KR_RANK_INSECURE = 1, /* Entry is DNSSEC insecure (e.g. RRSIG not exists). */ - KR_RANK_NONAUTH = 8, /* Entry from authority section (i.e. parent-side) */ - KR_RANK_AUTH = 16, /* Entry from answer (authoritative data) */ + KR_RANK_EXTRA = 4, /* Entry from additional section. */ + KR_RANK_NONAUTH = 8, /* Entry from authority section (i.e. parent-side). */ + KR_RANK_AUTH = 16, /* Entry from answer (authoritative data). */ KR_RANK_SECURE = 32, /* Entry is DNSSEC valid (e.g. RRSIG exists). */ /* @note Rank must not exceed 6 bits */ }; /** Cache entry flags */ enum kr_cache_flag { - KR_CACHE_FLAG_NONE = 0, + KR_CACHE_FLAG_NONE = 0, KR_CACHE_FLAG_WCARD_PROOF = 1, /* Entry contains either packet with wildcard * answer either record for which wildcard * expansion proof is needed */ diff --git a/lib/layer/pktcache.c b/lib/layer/pktcache.c index fc8999f3e..7520636dd 100644 --- a/lib/layer/pktcache.c +++ b/lib/layer/pktcache.c @@ -48,12 +48,14 @@ static void adjust_ttl(knot_rrset_t *rr, uint32_t drift) /** @internal Try to find a shortcut directly to searched packet. */ static int loot_pktcache(struct kr_cache *cache, knot_pkt_t *pkt, - struct kr_query *qry, uint8_t *flags) + struct kr_request *req, uint8_t *flags) { + struct kr_query *qry = req->current_query; uint32_t timestamp = qry->timestamp.tv_sec; const knot_dname_t *qname = qry->sname; uint16_t rrtype = qry->stype; - const bool want_secure = (qry->flags & QUERY_DNSSEC_WANT); + const bool want_secure = (qry->flags & QUERY_DNSSEC_WANT) && + !knot_wire_get_cd(req->answer->wire); struct kr_cache_entry *entry = NULL; int ret = kr_cache_peek(cache, KR_CACHE_PKT, qname, @@ -105,8 +107,10 @@ static int loot_pktcache(struct kr_cache *cache, knot_pkt_t *pkt, static int pktcache_peek(kr_layer_t *ctx, knot_pkt_t *pkt) { - struct kr_query *qry = ctx->req->current_query; - if (ctx->state & (KR_STATE_FAIL|KR_STATE_DONE) || (qry->flags & QUERY_NO_CACHE)) { + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + if (ctx->state & (KR_STATE_FAIL|KR_STATE_DONE) || + (qry->flags & QUERY_NO_CACHE)) { return ctx->state; /* Already resolved/failed */ } if (qry->ns.addr[0].ip.sa_family != AF_UNSPEC) { @@ -118,8 +122,8 @@ static int pktcache_peek(kr_layer_t *ctx, knot_pkt_t *pkt) /* Fetch either answer to original or minimized query */ uint8_t flags = 0; - struct kr_cache *cache = &ctx->req->ctx->cache; - int ret = loot_pktcache(cache, pkt, qry, &flags); + struct kr_cache *cache = &req->ctx->cache; + int ret = loot_pktcache(cache, pkt, req, &flags); if (ret == 0) { DEBUG_MSG(qry, "=> satisfied from cache\n"); qry->flags |= QUERY_CACHED|QUERY_NO_MINIMIZE; @@ -174,7 +178,8 @@ static uint32_t packet_ttl(knot_pkt_t *pkt, bool is_negative) static int pktcache_stash(kr_layer_t *ctx, knot_pkt_t *pkt) { - struct kr_query *qry = ctx->req->current_query; + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; /* Cache only answers that make query resolved (i.e. authoritative) * that didn't fail during processing and are negative. */ if (qry->flags & QUERY_CACHED || ctx->state & KR_STATE_FAIL) { @@ -199,6 +204,7 @@ static int pktcache_stash(kr_layer_t *ctx, knot_pkt_t *pkt) if (!qname) { return ctx->state; } + knot_db_val_t data = { pkt->wire, pkt->size }; struct kr_cache_entry header = { .timestamp = qry->timestamp.tv_sec, @@ -208,11 +214,17 @@ static int pktcache_stash(kr_layer_t *ctx, knot_pkt_t *pkt) .count = data.len }; - /* Set cache rank */ - if (qry->flags & QUERY_DNSSEC_WANT) { - header.rank = KR_RANK_SECURE; - } else if (qry->flags & QUERY_DNSSEC_INSECURE) { - header.rank = KR_RANK_INSECURE; + /* Set cache rank. + * If cd bit is set rank remains BAD. + * Otherwise - depends on flags. */ + if (!knot_wire_get_cd(req->answer->wire)) { + if (qry->flags & QUERY_DNSSEC_WANT) { + /* DNSSEC valid entry */ + header.rank = KR_RANK_SECURE; + } else { + /* Don't care about DNSSEC */ + header.rank = KR_RANK_INSECURE; + } } /* Set cache flags */ diff --git a/lib/layer/rrcache.c b/lib/layer/rrcache.c index d30c79e64..7488826e8 100644 --- a/lib/layer/rrcache.c +++ b/lib/layer/rrcache.c @@ -41,7 +41,7 @@ static inline bool is_expiring(const knot_rrset_t *rr, uint32_t drift) static int loot_rr(struct kr_cache *cache, knot_pkt_t *pkt, const knot_dname_t *name, uint16_t rrclass, uint16_t rrtype, struct kr_query *qry, - uint8_t *rank, uint8_t *flags, bool fetch_rrsig) + uint8_t *rank, uint8_t *flags, bool fetch_rrsig, uint8_t lowest_rank) { /* Check if record exists in cache */ int ret = 0; @@ -57,6 +57,10 @@ static int loot_rr(struct kr_cache *cache, knot_pkt_t *pkt, const knot_dname_t * return ret; } + if (rank && (*rank < lowest_rank)) { + return kr_error(ENOENT); + } + /* Mark as expiring if it has less than 1% TTL (or less than 5s) */ if (is_expiring(&cache_rr, drift)) { qry->flags |= QUERY_EXPIRING; @@ -89,15 +93,21 @@ static int loot_rr(struct kr_cache *cache, knot_pkt_t *pkt, const knot_dname_t * } /** @internal Try to find a shortcut directly to searched record. */ -static int loot_rrcache(struct kr_cache *cache, knot_pkt_t *pkt, struct kr_query *qry, uint16_t rrtype, bool dobit) +static int loot_rrcache(struct kr_cache *cache, knot_pkt_t *pkt, + struct kr_query *qry, uint16_t rrtype, const bool cdbit) { /* Lookup direct match first */ uint8_t rank = 0; uint8_t flags = 0; - int ret = loot_rr(cache, pkt, qry->sname, qry->sclass, rrtype, qry, &rank, &flags, 0); - if (ret != 0 && rrtype != KNOT_RRTYPE_CNAME) { /* Chase CNAME if no direct hit */ + uint8_t lowest_rank = cdbit ? KR_RANK_BAD : KR_RANK_INSECURE; + const bool dobit = (qry->flags & QUERY_DNSSEC_WANT); + int ret = loot_rr(cache, pkt, qry->sname, qry->sclass, rrtype, qry, + &rank, &flags, 0, lowest_rank); + if (ret != 0 && rrtype != KNOT_RRTYPE_CNAME) { + /* Chase CNAME if no direct hit */ rrtype = KNOT_RRTYPE_CNAME; - ret = loot_rr(cache, pkt, qry->sname, qry->sclass, rrtype, qry, &rank, &flags, 0); + ret = loot_rr(cache, pkt, qry->sname, qry->sclass, rrtype, qry, + &rank, &flags, 0, lowest_rank); } /* Record is flagged as INSECURE => doesn't have RRSIG. */ if (ret == 0 && (rank & KR_RANK_INSECURE)) { @@ -105,35 +115,37 @@ static int loot_rrcache(struct kr_cache *cache, knot_pkt_t *pkt, struct kr_query qry->flags &= ~QUERY_DNSSEC_WANT; /* Record may have RRSIG, try to find it. */ } else if (ret == 0 && dobit) { - ret = loot_rr(cache, pkt, qry->sname, qry->sclass, rrtype, qry, &rank, &flags, true); + ret = loot_rr(cache, pkt, qry->sname, qry->sclass, rrtype, qry, + &rank, &flags, true, lowest_rank); } return ret; } static int rrcache_peek(kr_layer_t *ctx, knot_pkt_t *pkt) { - struct kr_query *qry = ctx->req->current_query; + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; if (ctx->state & (KR_STATE_FAIL|KR_STATE_DONE) || (qry->flags & QUERY_NO_CACHE)) { return ctx->state; /* Already resolved/failed */ } if (qry->ns.addr[0].ip.sa_family != AF_UNSPEC) { return ctx->state; /* Only lookup before asking a query */ } - + const bool cd_is_set = knot_wire_get_cd(req->answer->wire); /* Reconstruct the answer from the cache, * it may either be a CNAME chain or direct answer. * Only one step of the chain is resolved at a time. */ - struct kr_cache *cache = &ctx->req->ctx->cache; + struct kr_cache *cache = &req->ctx->cache; int ret = -1; if (qry->stype != KNOT_RRTYPE_ANY) { - ret = loot_rrcache(cache, pkt, qry, qry->stype, (qry->flags & QUERY_DNSSEC_WANT)); + ret = loot_rrcache(cache, pkt, qry, qry->stype, cd_is_set); } else { /* ANY query are used by either qmail or certain versions of Firefox. * Probe cache for a few interesting records. */ static uint16_t any_types[] = { KNOT_RRTYPE_A, KNOT_RRTYPE_AAAA, KNOT_RRTYPE_MX }; for (size_t i = 0; i < sizeof(any_types)/sizeof(any_types[0]); ++i) { - if (loot_rrcache(cache, pkt, qry, any_types[i], (qry->flags & QUERY_DNSSEC_WANT)) == 0) { + if (loot_rrcache(cache, pkt, qry, any_types[i], cd_is_set) == 0) { ret = 0; /* At least single record matches */ } } @@ -188,10 +200,12 @@ static int commit_rr(const char *key, void *val, void *data) * it may be present in an otherwise secure answer but it * is only a hint for local state. */ if (rr->type != KNOT_RRTYPE_NS || (rank & KR_RANK_AUTH)) { - if (baton->qry->flags & QUERY_DNSSEC_WANT) + if (baton->qry->flags & QUERY_DNSSEC_WANT && + rank != KR_RANK_BAD) { rank |= KR_RANK_SECURE; + } } - if (baton->qry->flags & QUERY_DNSSEC_INSECURE) { + if (baton->qry->flags & QUERY_DNSSEC_INSECURE && rank != KR_RANK_BAD) { rank |= KR_RANK_INSECURE; } if (KEY_COVERING_RRSIG(key)) { @@ -233,24 +247,38 @@ static void stash_glue(map_t *stash, knot_pkt_t *pkt, const knot_dname_t *ns_nam !knot_dname_is_equal(rr->owner, ns_name)) { continue; } - kr_rrmap_add(stash, rr, KR_RANK_BAD, pool); + kr_rrmap_add(stash, rr, KR_RANK_EXTRA, pool); } } /* @internal DS is special and is present only parent-side */ -static void stash_ds(struct kr_query *qry, knot_pkt_t *pkt, map_t *stash, knot_mm_t *pool) +static void stash_ds(struct kr_request *req, knot_pkt_t *pkt, map_t *stash, knot_mm_t *pool) { + struct kr_query *qry = req->current_query; + uint8_t rank = KR_RANK_NONAUTH; + if (knot_wire_get_cd(req->answer->wire)) { + /* Signature validation is disabled, + * save it to the BAD cache */ + rank = KR_RANK_BAD; + } const knot_pktsection_t *authority = knot_pkt_section(pkt, KNOT_AUTHORITY); for (unsigned i = 0; i < authority->count; ++i) { const knot_rrset_t *rr = knot_pkt_rr(authority, i); if (rr->type == KNOT_RRTYPE_DS || rr->type == KNOT_RRTYPE_RRSIG) { - kr_rrmap_add(stash, rr, KR_RANK_AUTH, pool); + kr_rrmap_add(stash, rr, rank, pool); } } } -static int stash_authority(struct kr_query *qry, knot_pkt_t *pkt, map_t *stash, knot_mm_t *pool) +static int stash_authority(struct kr_request *req, knot_pkt_t *pkt, map_t *stash, knot_mm_t *pool) { + struct kr_query *qry = req->current_query; + uint8_t rank = KR_RANK_NONAUTH; + if (knot_wire_get_cd(req->answer->wire)) { + /* Signature validation is disabled, + * save authority to the BAD cache */ + rank = KR_RANK_BAD; + } const knot_pktsection_t *authority = knot_pkt_section(pkt, KNOT_AUTHORITY); for (unsigned i = 0; i < authority->count; ++i) { const knot_rrset_t *rr = knot_pkt_rr(authority, i); @@ -266,18 +294,25 @@ static int stash_authority(struct kr_query *qry, knot_pkt_t *pkt, map_t *stash, } } /* Stash record */ - kr_rrmap_add(stash, rr, KR_RANK_NONAUTH, pool); + kr_rrmap_add(stash, rr, rank, pool); } return kr_ok(); } -static int stash_answer(struct kr_query *qry, knot_pkt_t *pkt, map_t *stash, knot_mm_t *pool) +static int stash_answer(struct kr_request *req, knot_pkt_t *pkt, map_t *stash, knot_mm_t *pool) { + struct kr_query *qry = req->current_query; /* Work with QNAME, as minimised name data is cacheable. */ const knot_dname_t *cname_begin = knot_pkt_qname(pkt); if (!cname_begin) { cname_begin = qry->sname; } + uint8_t rank = KR_RANK_AUTH; + if (knot_wire_get_cd(req->answer->wire)) { + /* Signature validation is disabled. + * Save answer to the BAD cache. */ + rank = KR_RANK_BAD; + } /* Stash direct answers (equal to current QNAME/CNAME), * accept out-of-order RRSIGS. */ const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); @@ -292,7 +327,7 @@ static int stash_answer(struct kr_query *qry, knot_pkt_t *pkt, map_t *stash, kno if (!knot_dname_is_equal(rr->owner, cname)) { continue; } - kr_rrmap_add(stash, rr, KR_RANK_AUTH, pool); + kr_rrmap_add(stash, rr, rank, pool); /* Follow CNAME chain in current cut (if SECURE). */ if ((qry->flags & QUERY_DNSSEC_WANT) && rr->type == KNOT_RRTYPE_CNAME) { cname_chain_len += 1; @@ -303,7 +338,7 @@ static int stash_answer(struct kr_query *qry, knot_pkt_t *pkt, map_t *stash, kno /* Check if the same CNAME was already resolved */ if (next_cname) { char key[KR_RRKEY_LEN]; - int ret = kr_rrkey(key, next_cname, rr->type, KR_RANK_AUTH); + int ret = kr_rrkey(key, next_cname, rr->type, rank); if (ret != 0 || map_get(stash, key)) { DEBUG_MSG(qry, "<= cname chain loop\n"); next_cname = NULL; @@ -345,18 +380,18 @@ static int rrcache_stash(kr_layer_t *ctx, knot_pkt_t *pkt) int ret = 0; bool is_auth = knot_wire_get_aa(pkt->wire); if (is_auth) { - ret = stash_answer(qry, pkt, &stash, &req->pool); + ret = stash_answer(req, pkt, &stash, &req->pool); if (ret == 0) { - ret = stash_authority(qry, pkt, &stash, &req->pool); + ret = stash_authority(req, pkt, &stash, &req->pool); } /* Cache authority only if chasing referral/cname chain */ } else if (knot_pkt_section(pkt, KNOT_ANSWER)->count == 0 || qry->flags & QUERY_CNAME) { - ret = stash_authority(qry, pkt, &stash, &req->pool); + ret = stash_authority(req, pkt, &stash, &req->pool); } /* Cache DS records in referrals */ if (!is_auth && knot_pkt_has_dnssec(pkt)) { - stash_ds(qry, pkt, &stash, &req->pool); + stash_ds(req, pkt, &stash, &req->pool); } /* Cache stashed records */ if (ret == 0 && stash.root != NULL) { diff --git a/lib/layer/validate.c b/lib/layer/validate.c index df3e75e90..21f16b0ae 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -390,6 +390,10 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt) if (!(qry->flags & QUERY_DNSSEC_WANT) || (qry->flags & QUERY_STUB)) { return ctx->state; } + /* Pass-through if CD bit is set. */ + if (knot_wire_get_cd(req->answer->wire)) { + return ctx->state; + } /* Answer for RRSIG may not set DO=1, but all records MUST still validate. */ bool use_signatures = (knot_pkt_qtype(pkt) != KNOT_RRTYPE_RRSIG); if (!(qry->flags & QUERY_CACHED) && !knot_pkt_has_dnssec(pkt) && !use_signatures) { diff --git a/lib/resolve.c b/lib/resolve.c index 1819c0f4d..87b4567c3 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -486,6 +486,7 @@ static int resolve_query(struct kr_request *request, const knot_pkt_t *packet) const knot_dname_t *qname = knot_pkt_qname(packet); uint16_t qclass = knot_pkt_qclass(packet); uint16_t qtype = knot_pkt_qtype(packet); + bool cd_is_set = knot_wire_get_cd(packet->wire); struct kr_query *qry = NULL; if (qname != NULL) { @@ -516,7 +517,10 @@ static int resolve_query(struct kr_request *request, const knot_pkt_t *packet) knot_wire_clear_aa(answer->wire); knot_wire_set_ra(answer->wire); knot_wire_set_rcode(answer->wire, KNOT_RCODE_NOERROR); - if (qry->flags & QUERY_DNSSEC_WANT) { + + if (cd_is_set) { + knot_wire_set_cd(answer->wire); + } else if (qry->flags & QUERY_DNSSEC_WANT) { knot_wire_set_ad(answer->wire); } @@ -721,8 +725,11 @@ static int trust_chain_check(struct kr_request *request, struct kr_query *qry) qry->flags &= ~QUERY_DNSSEC_WANT; } /* Enable DNSSEC if enters a new island of trust. */ - bool want_secured = (qry->flags & QUERY_DNSSEC_WANT); - if (!want_secured && kr_ta_get(trust_anchors, qry->zone_cut.name)) { + 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, qry->zone_cut.name)) { qry->flags |= QUERY_DNSSEC_WANT; want_secured = true; WITH_DEBUG {