From: Grigorii Demidov Date: Thu, 31 Mar 2016 16:41:08 +0000 (+0200) Subject: dnssec: wildcard answer proof X-Git-Tag: v1.0.0~42 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9162b03513bfec574def6ce86cbc620406ff385c;p=thirdparty%2Fknot-resolver.git dnssec: wildcard answer proof --- diff --git a/lib/dnssec.c b/lib/dnssec.c index 91680adbf..28ed8fb5d 100644 --- a/lib/dnssec.c +++ b/lib/dnssec.c @@ -129,17 +129,17 @@ static int wildcard_radix_len_diff(const knot_dname_t *expanded, return knot_dname_labels(expanded, NULL) - knot_rrsig_labels(&rrsigs->rrs, sig_pos); } -int kr_rrset_validate(const knot_pkt_t *pkt, knot_section_t section_id, - const knot_rrset_t *covered, const knot_rrset_t *keys, - const knot_dname_t *zone_name, uint32_t timestamp, - bool has_nsec3) +int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx, const knot_rrset_t *covered) { - if (!pkt || !covered || !keys || !zone_name) { + if (!vctx) { + return kr_error(EINVAL); + } + if (!vctx->pkt || !covered || !vctx->keys || !vctx->zone_name) { return kr_error(EINVAL); } - for (unsigned i = 0; i < keys->rrs.rr_count; ++i) { - int ret = kr_rrset_validate_with_key(pkt, section_id, covered, keys, i, NULL, zone_name, timestamp, has_nsec3); + for (unsigned i = 0; i < vctx->keys->rrs.rr_count; ++i) { + int ret = kr_rrset_validate_with_key(vctx, covered, i, NULL); if (ret == 0) { return ret; } @@ -148,19 +148,24 @@ int kr_rrset_validate(const knot_pkt_t *pkt, knot_section_t section_id, return kr_error(ENOENT); } -int kr_rrset_validate_with_key(const knot_pkt_t *pkt, knot_section_t section_id, - const knot_rrset_t *covered, const knot_rrset_t *keys, - size_t key_pos, const struct dseckey *key, - const knot_dname_t *zone_name, uint32_t timestamp, - bool has_nsec3) +int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx, + const knot_rrset_t *covered, + size_t key_pos, const struct dseckey *key) { + const knot_pkt_t *pkt = vctx->pkt; + knot_section_t section_id = vctx->section_id; + const knot_rrset_t *keys = vctx->keys; + const knot_dname_t *zone_name = vctx->zone_name; + uint32_t timestamp = vctx->timestamp; + bool has_nsec3 = vctx->has_nsec3; struct dseckey *created_key = NULL; if (key == NULL) { const knot_rdata_t *krr = knot_rdataset_at(&keys->rrs, key_pos); int ret = kr_dnssec_key_from_rdata(&created_key, keys->owner, knot_rdata_data(krr), knot_rdata_rdlen(krr)); if (ret != 0) { - return ret; + vctx->result = ret; + return vctx->result; } key = created_key; } @@ -211,21 +216,72 @@ int kr_rrset_validate_with_key(const knot_pkt_t *pkt, knot_section_t section_id, if (ret != 0) { continue; } + vctx->flags |= KR_DNSSEC_VFLG_WEXPAND; } /* Validated with current key, OK */ kr_dnssec_key_free(&created_key); - return kr_ok(); + vctx->result = kr_ok(); + return vctx->result; } } /* No applicable key found, cannot be validated. */ kr_dnssec_key_free(&created_key); - return kr_error(ENOENT); + vctx->result = kr_error(ENOENT); + return vctx->result; } -int kr_dnskeys_trusted(const knot_pkt_t *pkt, knot_section_t section_id, const knot_rrset_t *keys, - const knot_rrset_t *ta, const knot_dname_t *zone_name, uint32_t timestamp, - bool has_nsec3) +int kr_section_check_wcard(kr_rrset_validation_ctx_t *vctx) { + const knot_pkt_t *pkt = vctx->pkt; + knot_section_t section_id = vctx->section_id; + const knot_dname_t *zone_name = vctx->zone_name; + const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(sec, i); + if (rr->type == KNOT_RRTYPE_RRSIG) { + continue; + } + if ((rr->type == KNOT_RRTYPE_NS) && (vctx->section_id == KNOT_AUTHORITY)) { + continue; + } + if (!knot_dname_in(zone_name, rr->owner)) { + continue; + } + int covered_labels = knot_dname_labels(rr->owner, NULL); + if (knot_dname_is_wildcard(rr->owner)) { + /* The asterisk does not count, RFC4034 3.1.3, paragraph 3. */ + --covered_labels; + } + for (unsigned j = 0; j < sec->count; ++j) { + const knot_rrset_t *rrsig = knot_pkt_rr(sec, j); + if (rrsig->type != KNOT_RRTYPE_RRSIG) { + continue; + } + if ((rr->rclass != rrsig->rclass) || !knot_dname_is_equal(rr->owner, rrsig->owner)) { + continue; + } + for (uint16_t k = 0; k < rrsig->rrs.rr_count; ++k) { + if (knot_rrsig_type_covered(&rrsig->rrs, k) != rr->type) { + continue; + } + int rrsig_labels = knot_rrsig_labels(&rrsig->rrs, k); + if (rrsig_labels > covered_labels) { + return kr_error(EINVAL); + } + if (rrsig_labels < covered_labels) { + vctx->flags |= KR_DNSSEC_VFLG_WEXPAND; + } + } + } + } + return kr_ok(); +} + +int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rrset_t *ta) +{ + const knot_pkt_t *pkt = vctx->pkt; + const knot_rrset_t *keys = vctx->keys; + if (!pkt || !keys || !ta) { return kr_error(EINVAL); } @@ -250,15 +306,17 @@ int kr_dnskeys_trusted(const knot_pkt_t *pkt, knot_section_t section_id, const k kr_dnssec_key_free(&key); continue; } - if (kr_rrset_validate_with_key(pkt, section_id, keys, keys, i, key, zone_name, timestamp, has_nsec3) != 0) { + if (kr_rrset_validate_with_key(vctx, keys, i, key) != 0) { kr_dnssec_key_free(&key); continue; } kr_dnssec_key_free(&key); - return kr_ok(); + assert (vctx->result == 0); + return vctx->result; } /* No useable key found */ - return kr_error(ENOENT); + vctx->result = kr_error(ENOENT); + return vctx->result; } bool kr_dnssec_key_zsk(const uint8_t *dnskey_rdata) diff --git a/lib/dnssec.h b/lib/dnssec.h index b6d41df96..17a80306f 100644 --- a/lib/dnssec.h +++ b/lib/dnssec.h @@ -41,55 +41,50 @@ void kr_crypto_reinit(void); /** Opaque DNSSEC key pointer. */ struct dseckey; +#define KR_DNSSEC_VFLG_WEXPAND 0x01 + +/** DNSSEC validation context. */ +struct kr_rrset_validation_ctx { + const knot_pkt_t *pkt; /*!< Packet to be validated. */ + knot_section_t section_id; /*!< Section to work with. */ + const knot_rrset_t *keys; /*!< DNSKEY RRSet. */ + const knot_dname_t *zone_name; /*!< Name of the zone containing the RRSIG RRSet. */ + uint32_t timestamp; /*!< Validation time. */ + bool has_nsec3; /*!< Whether to use NSEC3 validation. */ + uint32_t flags; /*!< Output - Flags. */ + int result; /*!< Output - 0 or error code. */ +}; + +typedef struct kr_rrset_validation_ctx kr_rrset_validation_ctx_t; + /** * Validate RRSet. - * @param pkt Packet to be validated. - * @param section_id Section to work with. - * @param covered RRSet covered by a signature. It must be in canonical format. - * @param keys DNSKEY RRSet. - * @param zone_name Name of the zone containing the RRSIG RRSet. - * @param timestamp Validation time. - * @param has_nsec3 Whether to use NSEC3 validation. - * @return 0 or error code. + * @param vctx Pointer to validation context. + * @param covered RRSet covered by a signature. It must be in canonical format. + * @return 0 or error code, same as vctx->result. */ -int kr_rrset_validate(const knot_pkt_t *pkt, knot_section_t section_id, - const knot_rrset_t *covered, const knot_rrset_t *keys, - const knot_dname_t *zone_name, uint32_t timestamp, - bool has_nsec3); +int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx, + const knot_rrset_t *covered); /** * Validate RRSet using a specific key. - * @param pkt Packet to be validated. - * @param section_id Section to work with. - * @param covered RRSet covered by a signature. It must be in canonical format. - * @param keys DNSKEY RRSet. - * @param key_pos Position of the key to be validated with. - * @param key Key to be used to validate. If NULL, then key from DNSKEY RRSet is used. - * @param zone_name Name of the zone containing the RRSIG RRSet. - * @param timestamp Validation time. - * @param has_nsec3 Whether to use NSEC3 validation. - * @return 0 or error code. + * @param vctx Pointer to validation context. + * @param covered RRSet covered by a signature. It must be in canonical format. + * @param key_pos Position of the key to be validated with. + * @param key Key to be used to validate. + * If NULL, then key from DNSKEY RRSet is used. + * @return 0 or error code, same as vctx->result. */ -int kr_rrset_validate_with_key(const knot_pkt_t *pkt, knot_section_t section_id, - const knot_rrset_t *covered, const knot_rrset_t *keys, - size_t key_pos, const struct dseckey *key, - const knot_dname_t *zone_name, uint32_t timestamp, - bool has_nsec3); - +int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx, + const knot_rrset_t *covered, + size_t key_pos, const struct dseckey *key); /** * Check whether the DNSKEY rrset matches the supplied trust anchor RRSet. - * @param pkt Packet to be validated. - * @param section_id Section to work with. - * @param keys DNSKEY RRSet to check. - * @param ta Trust anchor RRSet against which to validate the DNSKEY RRSet. - * @param zone_name Name of the zone containing the RRSet. - * @param timestamp Time stamp. - * @param has_nsec3 Whether to use NSEC3 validation. - * @return 0 or error code. + * @param vctx Pointer to validation context. + * @param ta Trust anchor RRSet against which to validate the DNSKEY RRSet. + * @return 0 or error code, same as vctx->result. */ -int kr_dnskeys_trusted(const knot_pkt_t *pkt, knot_section_t section_id, const knot_rrset_t *keys, - const knot_rrset_t *ta, const knot_dname_t *zone_name, uint32_t timestamp, - bool has_nsec3); +int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rrset_t *ta); /** Return true if the DNSKEY can be used as a ZSK. */ KR_EXPORT KR_PURE @@ -124,6 +119,14 @@ KR_EXPORT KR_PURE int kr_dnssec_key_match(const uint8_t *key_a_rdata, size_t key_a_rdlen, const uint8_t *key_b_rdata, size_t key_b_rdlen); +/** Return 0 if wildcard expansion occurs in specified section. + * @param vctx Pointer to validation context. + * @note vctx->keys, vctx->timestamp, vctx->has_nsec3 has no meanings. + * @return 0 if wildcard expansion occurs or an error code. + */ +KR_EXPORT KR_PURE +int kr_section_check_wcard(kr_rrset_validation_ctx_t *vctx); + /** * Construct a DNSSEC key. * @param key Pointer to be set to newly created DNSSEC key. diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c index cb2ab6b46..773e418c8 100644 --- a/lib/layer/iterate.c +++ b/lib/layer/iterate.c @@ -315,7 +315,7 @@ static void finalize_answer(knot_pkt_t *pkt, struct kr_query *qry, struct kr_req const uint16_t qtype = knot_pkt_qtype(answer); struct kr_zonecut *cut = &qry->zone_cut; int pkt_class = kr_response_classify(pkt); - if (pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) { + if ((pkt_class & (PKT_NXDOMAIN|PKT_NODATA))) { const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY); for (unsigned i = 0; i < ns->count; ++i) { const knot_rrset_t *rr = knot_pkt_rr(ns, i); @@ -414,17 +414,31 @@ static int process_answer(knot_pkt_t *pkt, struct kr_request *req) query->flags |= QUERY_RESOLVED; /* Follow canonical name as next SNAME. */ if (!knot_dname_is_equal(cname, query->sname)) { - DEBUG_MSG("<= cname chain, following\n"); - /* Check if already resolved */ - if (cname && !knot_dname_is_equal(cname, query->sname)) { - for (int i = 0; i < req->rplan.resolved.len; ++i) { - struct kr_query * q = req->rplan.resolved.at[i]; - if (q->sclass == query->sclass && - q->stype == query->stype && - knot_dname_is_equal(q->sname, cname)) { - DEBUG_MSG("<= cname chain loop\n"); - return KNOT_STATE_FAIL; + /* Check if target record has been already copied */ + if (is_final) { + const knot_pktsection_t *an = knot_pkt_section(req->answer, KNOT_ANSWER); + for (unsigned i = 0; i < an->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(an, i); + if (!knot_dname_is_equal(rr->owner, cname)) { + continue; + } + if ((rr->rclass != query->sclass) || + (rr->type != query->stype)) { + continue; } + finalize_answer(pkt, query, req); + return KNOT_STATE_DONE; + } + } + DEBUG_MSG("<= cname chain, following\n"); + /* Check if the same query was already resolved */ + for (int i = 0; i < req->rplan.resolved.len; ++i) { + struct kr_query * q = req->rplan.resolved.at[i]; + if (q->sclass == query->sclass && + q->stype == query->stype && + knot_dname_is_equal(q->sname, cname)) { + DEBUG_MSG("<= cname chain loop\n"); + return KNOT_STATE_FAIL; } } struct kr_query *next = kr_rplan_push(&req->rplan, query->parent, cname, query->sclass, query->stype); diff --git a/lib/layer/pktcache.c b/lib/layer/pktcache.c index b9b6db957..c047951e6 100644 --- a/lib/layer/pktcache.c +++ b/lib/layer/pktcache.c @@ -171,11 +171,14 @@ static int pktcache_stash(knot_layer_t *ctx, knot_pkt_t *pkt) if (!knot_wire_get_aa(pkt->wire) || knot_pkt_qclass(pkt) != KNOT_CLASS_IN) { return ctx->state; } - /* Cache only NODATA/NXDOMAIN or metatype/RRSIG answers. */ + /* Cache only NODATA/NXDOMAIN or metatype/RRSIG or + * wildcard expanded answers. */ const uint16_t qtype = knot_pkt_qtype(pkt); - const bool is_eligible = (knot_rrtype_is_metatype(qtype) || qtype == KNOT_RRTYPE_RRSIG); + const bool is_eligible = (knot_rrtype_is_metatype(qtype) || + qtype == KNOT_RRTYPE_RRSIG); int pkt_class = kr_response_classify(pkt); - if (!(is_eligible || (pkt_class & (PKT_NODATA|PKT_NXDOMAIN)))) { + if (!(is_eligible || (pkt_class & (PKT_NODATA|PKT_NXDOMAIN)) || + (qry->flags & QUERY_DNSSEC_WEXPAND))) { return ctx->state; } uint32_t ttl = packet_ttl(pkt); diff --git a/lib/layer/rrcache.c b/lib/layer/rrcache.c index 7226bfc51..d75dff328 100644 --- a/lib/layer/rrcache.c +++ b/lib/layer/rrcache.c @@ -302,6 +302,12 @@ static int rrcache_stash(knot_layer_t *ctx, knot_pkt_t *pkt) if (knot_wire_get_tc(pkt->wire)) { return ctx->state; } + /* Do not cache wildcard expanded anwsers, + * as they must deal with packet cache */ + if (qry->flags & QUERY_DNSSEC_WEXPAND) { + return ctx->state; + } + /* Cache only positive answers, not meta types or RRSIG. */ const uint16_t qtype = knot_pkt_qtype(pkt); const bool is_eligible = !(knot_rrtype_is_metatype(qtype) || qtype == KNOT_RRTYPE_RRSIG); @@ -351,7 +357,6 @@ static int rrcache_stash(knot_layer_t *ctx, knot_pkt_t *pkt) } } } - return ctx->state; } diff --git a/lib/layer/validate.c b/lib/layer/validate.c index e745ea226..a587e1f74 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -74,37 +74,25 @@ static bool pkt_has_type(const knot_pkt_t *pkt, uint16_t type) return section_has_type(knot_pkt_section(pkt, KNOT_ADDITIONAL), type); } - -/** @internal Baton for validate_section */ -struct validate_baton { - const knot_pkt_t *pkt; - knot_section_t section_id; - const knot_rrset_t *keys; - const knot_dname_t *zone_name; - uint32_t timestamp; - bool has_nsec3; - int result; -}; - static int validate_rrset(const char *key, void *val, void *data) { knot_rrset_t *rr = val; - struct validate_baton *baton = data; - - if (baton->result != 0) { - return baton->result; + kr_rrset_validation_ctx_t *vctx = data; + if (vctx->result != 0) { + return vctx->result; } - baton->result = kr_rrset_validate(baton->pkt, baton->section_id, rr, - baton->keys, baton->zone_name, - baton->timestamp, baton->has_nsec3); - return baton->result; + + return kr_rrset_validate(vctx, rr); } -static int validate_section(struct kr_query *qry, knot_pkt_t *answer, - knot_section_t section_id, knot_mm_t *pool, - bool has_nsec3) +static int validate_section(kr_rrset_validation_ctx_t *vctx, knot_mm_t *pool) { - const knot_pktsection_t *sec = knot_pkt_section(answer, section_id); + if (!vctx) { + return kr_error(EINVAL); + } + + const knot_pktsection_t *sec = knot_pkt_section(vctx->pkt, + vctx->section_id); if (!sec) { return kr_ok(); } @@ -122,11 +110,11 @@ static int validate_section(struct kr_query *qry, knot_pkt_t *answer, if (rr->type == KNOT_RRTYPE_RRSIG) { continue; } - if ((rr->type == KNOT_RRTYPE_NS) && (section_id == KNOT_AUTHORITY)) { + if ((rr->type == KNOT_RRTYPE_NS) && (vctx->section_id == KNOT_AUTHORITY)) { continue; } /* Only validate answers from current cut, records above the cut are stripped. */ - if (!knot_dname_in(qry->zone_cut.name, rr->owner)) { + if (!knot_dname_in(vctx->zone_name, rr->owner)) { continue; } ret = kr_rrmap_add(&stash, rr, 0, pool); @@ -135,24 +123,16 @@ static int validate_section(struct kr_query *qry, knot_pkt_t *answer, } } - struct validate_baton baton = { - .pkt = answer, - .section_id = section_id, - .keys = qry->zone_cut.key, - /* Can't use qry->zone_cut.name directly, as this name can - * change when updating cut information before validation. - */ - .zone_name = qry->zone_cut.key ? qry->zone_cut.key->owner : NULL, - .timestamp = qry->timestamp.tv_sec, - .has_nsec3 = has_nsec3, - .result = 0 - }; + /* Can't use qry->zone_cut.name directly, as this name can + * change when updating cut information before validation. + */ + vctx->zone_name = vctx->keys ? vctx->keys->owner : NULL; - ret = map_walk(&stash, &validate_rrset, &baton); + ret = map_walk(&stash, &validate_rrset, vctx); if (ret != 0) { return ret; } - ret = baton.result; + ret = vctx->result; fail: return ret; @@ -165,14 +145,67 @@ static int validate_records(struct kr_query *qry, knot_pkt_t *answer, knot_mm_t return kr_error(EBADMSG); } - int ret = validate_section(qry, answer, KNOT_ANSWER, pool, has_nsec3); + kr_rrset_validation_ctx_t vctx = { + .pkt = answer, + .section_id = KNOT_ANSWER, + .keys = qry->zone_cut.key, + .zone_name = qry->zone_cut.name, + .timestamp = qry->timestamp.tv_sec, + .has_nsec3 = has_nsec3, + .flags = 0, + .result = 0 + }; + + int ret = validate_section(&vctx, pool); if (ret != 0) { return ret; } - return validate_section(qry, answer, KNOT_AUTHORITY, pool, has_nsec3); + uint32_t an_flags = vctx.flags; + vctx.section_id = KNOT_AUTHORITY; + /* zone_name can be changed by validate_section(), restore it */ + vctx.zone_name = qry->zone_cut.name; + vctx.flags = 0; + vctx.result = 0; + + ret = validate_section(&vctx, pool); + if (ret != 0) { + return ret; + } + + /* Records were validated. + * If there is wildcard expansion in answer, flag the query. + */ + if (an_flags & KR_DNSSEC_VFLG_WEXPAND) { + qry->flags |= QUERY_DNSSEC_WEXPAND; + } + + return ret; } +static int check_wcard_expanded(struct kr_query *qry, knot_pkt_t *pkt, knot_section_t section_id) +{ + kr_rrset_validation_ctx_t vctx = { + .pkt = pkt, + .section_id = section_id, + .keys = NULL, + .zone_name = qry->zone_cut.name, + .timestamp = 0, + .has_nsec3 = false, + .flags = 0, + .result = 0 + }; + int ret = kr_section_check_wcard(&vctx); + if (ret != 0) { + return ret; + } + if (vctx.flags & KR_DNSSEC_VFLG_WEXPAND) { + qry->flags |= QUERY_DNSSEC_WEXPAND; + } + return kr_ok(); +} + + static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer, bool has_nsec3) { /* Merge DNSKEY records from answer that are below/at current cut. */ @@ -203,13 +236,28 @@ static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer, bool has_ns /* Check if there's a key for current TA. */ if (updated_key && !(qry->flags & QUERY_CACHED)) { - int ret = kr_dnskeys_trusted(answer, KNOT_ANSWER, qry->zone_cut.key, - qry->zone_cut.trust_anchor, qry->zone_cut.name, - qry->timestamp.tv_sec, has_nsec3); + + kr_rrset_validation_ctx_t vctx = { + .pkt = answer, + .section_id = KNOT_ANSWER, + .keys = qry->zone_cut.key, + .zone_name = qry->zone_cut.name, + .timestamp = qry->timestamp.tv_sec, + .has_nsec3 = has_nsec3, + .flags = 0, + .result = 0 + }; + int ret = kr_dnskeys_trusted(&vctx, qry->zone_cut.trust_anchor); if (ret != 0) { knot_rrset_free(&qry->zone_cut.key, qry->zone_cut.pool); return ret; } + + if (vctx.flags & KR_DNSSEC_VFLG_WEXPAND) + { + qry->flags |= QUERY_DNSSEC_WEXPAND; + } + } return kr_ok(); } @@ -458,10 +506,24 @@ static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) * Do not revalidate data from cache, as it's already trusted. */ if (!(qry->flags & QUERY_CACHED)) { ret = validate_records(qry, pkt, req->rplan.pool, has_nsec3); - if (ret != 0) { - DEBUG_MSG(qry, "<= couldn't validate RRSIGs\n"); - qry->flags |= QUERY_DNSSEC_BOGUS; - return KNOT_STATE_FAIL; + } else { + /* Records already were validated. + * Check if wildcard answer. */ + ret = check_wcard_expanded(qry, pkt, KNOT_ANSWER); + } + if (ret != 0) { + DEBUG_MSG(qry, "<= couldn't validate RRSIGs\n"); + qry->flags |= QUERY_DNSSEC_BOGUS; + return KNOT_STATE_FAIL; + } + + if ((qry->parent == NULL) && (qry->flags & QUERY_DNSSEC_WEXPAND)) { + /* Wildcard expansion detected for final query. + * Copy authority. */ + const knot_pktsection_t *auth = knot_pkt_section(pkt, KNOT_AUTHORITY); + for (unsigned i = 0; i < auth->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(auth, i); + kr_rrarray_add(&req->authority, rr, &pkt->mm); } } diff --git a/lib/rplan.h b/lib/rplan.h index baf4728ac..17df1475c 100644 --- a/lib/rplan.h +++ b/lib/rplan.h @@ -43,7 +43,8 @@ X(DNSSEC_BOGUS, 1 << 15) /**< Query response is DNSSEC bogus. */ \ X(DNSSEC_INSECURE, 1 << 16) /**< Query response is DNSSEC insecure. */ \ X(STUB, 1 << 17) /**< Stub resolution, accept received answer as solved. */ \ - X(ALWAYS_CUT, 1 << 18) /**< Always recover zone cut (even if cached). */ + X(ALWAYS_CUT, 1 << 18) /**< Always recover zone cut (even if cached). */ \ + X(DNSSEC_WEXPAND, 1 << 19) /**< Query response has wildcard expansion. */ /** Query flags */ enum kr_query_flag { @@ -151,4 +152,4 @@ struct kr_query *kr_rplan_resolved(struct kr_rplan *rplan); /** Return query predecessor. */ KR_EXPORT KR_PURE -struct kr_query *kr_rplan_next(struct kr_query *qry); \ No newline at end of file +struct kr_query *kr_rplan_next(struct kr_query *qry);