- Fix in depth for serve-expired responses from cachedb, that it
does not store bogus. Thanks to Qifan Zhang, Palo Alto Networks,
for the report.
+ - Fix lame server detection, for selfpointed glue records.
+ Thanks to Shuhan Zhang, Dan Li, and Baojun Liu from Tsinghua
+ University for the report.
18 May 2026: Wouter
- Fix for mixed class referrals, the resolver uses the query
enum response_type
response_type_from_server(int rdset,
struct dns_msg* msg, struct query_info* request, struct delegpt* dp,
- int* empty_nodata_found)
+ int* empty_nodata_found, int msg_lame_empty, int msg_lame_referral)
{
uint8_t* origzone = (uint8_t*)"\000"; /* the default */
struct ub_packed_rrset_key* s;
/* If the message is NXDOMAIN, then it answers the question. */
if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN) {
+ if(msg->rep->an_numrrsets == 0 &&
+ msg->rep->ns_numrrsets == 0 &&
+ msg_lame_empty)
+ return RESPONSE_TYPE_LAME;
/* make sure its not recursive when we don't want it to */
if( (msg->rep->flags&BIT_RA) &&
!(msg->rep->flags&BIT_AA) && !rdset)
if(FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR)
return RESPONSE_TYPE_THROWAWAY;
+ if(msg->rep->an_numrrsets == 0 && msg->rep->ns_numrrsets == 0 &&
+ msg_lame_empty)
+ return RESPONSE_TYPE_LAME;
+
/* Note: TC bit has already been handled */
if(dp) {
* which gives ns==zone delegation from cache
* without AA bit as well, with nodata nosoa*/
/* real answer must be +AA and SOA RFC(2308),
- * so this is wrong, and we SERVFAIL it if
- * this is the only possible reply, if it
- * is misdeployed the THROWAWAY makes us pick
- * the next server from the selection */
- if(msg->rep->an_numrrsets==0 &&
+ * this is picked up as lame_referral by the
+ * sanitize step, so it can spot if there
+ * was data in the answer section before
+ * removal. If such data is then removed we
+ * do not want to turn that answer into lame.
+ * But if it was not there, it can be lame. */
+ if(msg_lame_referral &&
+ msg->rep->an_numrrsets==0 &&
!(msg->rep->flags&BIT_AA) && !rdset)
- return RESPONSE_TYPE_THROWAWAY;
+ return RESPONSE_TYPE_LAME;
return RESPONSE_TYPE_ANSWER;
}
/* If we are getting a referral upwards (or to
* @param dp: The delegation point that was being queried
* when the response was returned.
* @param empty_nodata_found: flag to keep track of empty nodata detection.
+ * @param msg_lame_empty: The scrubber indicates that this empty message
+ * is lame, before it became empty.
+ * @param msg_lame_referral: returned true if the reply has a referral before
+ * scrub.
* @return the response type (CNAME or ANSWER).
*/
enum response_type response_type_from_server(int rdset,
struct dns_msg* msg, struct query_info* request, struct delegpt* dp,
- int* empty_nodata_found);
+ int* empty_nodata_found, int msg_lame_empty, int msg_lame_referral);
#endif /* ITERATOR_ITER_RESPTYPE_H */
return cn;
}
+/** Check if the packet has type NS in answer or authority section */
+static int
+pkt_contains_ns(struct msg_parse* msg)
+{
+ struct rrset_parse* rrset;
+ for(rrset = msg->rrset_first; rrset; rrset = rrset->rrset_all_next) {
+ if(rrset->type == LDNS_RR_TYPE_NS &&
+ (rrset->section == LDNS_SECTION_ANSWER ||
+ rrset->section == LDNS_SECTION_AUTHORITY))
+ return 1;
+ }
+ return 0;
+}
+
/** check if DNAME applies to a name */
static int
pkt_strict_sub(sldns_buffer* pkt, uint8_t* sname, uint8_t* dr)
* @param env: module environment with config and cache.
* @param ie: iterator environment with private address data.
* @param qstate: for setting errinf for EDE error messages.
+ * @param pkt_before_NS: if the packet had type NS before scrub. If that
+ * is removed now, that indicates this may have been lame.
+ * @param msg_lame_empty: returned true if the empty packet is lame.
+ * @param msg_lame_referral: returned true if the reply has a referral before
+ * scrub.
+ * @param rdset: if RD bit was sent in query sent by unbound.
* @return 0 on error.
*/
static int
scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg,
struct query_info* qinfo, uint8_t* zonename, struct module_env* env,
- struct iter_env* ie, struct module_qstate* qstate)
+ struct iter_env* ie, struct module_qstate* qstate,
+ int pkt_before_NS, int* msg_lame_empty, int* msg_lame_referral,
+ int rdset)
{
int del_addi = 0; /* if additional-holding rrsets are deleted, we
do not trust the normalized additional-A-AAAA any more */
prev = rrset;
rrset = rrset->rrset_all_next;
}
+
+ /* If the packet is empty now, but it was not before. And there
+ * was type NS in authority, then that indicates the answer is lame. */
+ if(msg->rrset_first == NULL && pkt_before_NS) {
+ *msg_lame_empty = 1;
+ verbose(VERB_ALGO, "sanitize: empty message had referral to NS before, marked as lame");
+ } else if(pkt_before_NS && msg->an_rrsets==0 &&
+ !(msg->flags&BIT_AA) && !rdset) {
+ /* If the packet is now a referral, not really a nodata,
+ * then if it was also with an empty answer section before,
+ * it is also lame. */
+ *msg_lame_referral = 1;
+ verbose(VERB_ALGO, "sanitize: message has referral not answer, marked as lame");
+ }
+
return 1;
}
scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
struct query_info* qinfo, uint8_t* zonename, struct regional* region,
struct module_env* env, struct module_qstate* qstate,
- struct iter_env* ie)
+ struct iter_env* ie, int* msg_lame_empty, int* msg_lame_referral,
+ int rdset)
{
+ int pkt_before_NS;
/* basic sanity checks */
log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS,
qinfo->qclass);
+ *msg_lame_empty = 0;
+ *msg_lame_referral = 0;
if(msg->qdcount > 1)
return 0;
if( !(msg->flags&BIT_QR) )
return 0;
}
+ /* If the packet contains type NS in authority before scrub,
+ * like a self referral. With the answer section empty, it
+ * was not AA, the query was not sent with RD, with NS in auth,
+ * and no SOA in auth. For a negative answer, type SOA is present.
+ * This detects certain lameness if after has removed that. */
+ pkt_before_NS = msg->an_rrsets == 0 &&
+ !(msg->flags&BIT_AA) && !rdset &&
+ pkt_contains_ns(msg) && !soa_in_auth(msg);
+
/* normalize the response, this cleans up the additional. */
if(!scrub_normalize(pkt, msg, qinfo, region, env, zonename))
return 0;
/* delete all out-of-zone information */
- if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate))
+ if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate,
+ pkt_before_NS, msg_lame_empty, msg_lame_referral, rdset))
return 0;
return 1;
}
* @param env: module environment with config settings and cache.
* @param qstate: for setting errinf for EDE error messages.
* @param ie: iterator module environment data.
+ * @param msg_lame_empty: returned true if the empty packet is lame.
+ * @param msg_lame_referral: returned true if the reply has a referral before
+ * scrub.
+ * @param rdset: if RD bit was sent in query sent by unbound.
* @return: false if the message is total waste. true if scrubbed with success.
*/
int scrub_message(struct sldns_buffer* pkt, struct msg_parse* msg,
struct query_info* qinfo, uint8_t* zonename, struct regional* regional,
struct module_env* env, struct module_qstate* qstate,
- struct iter_env* ie);
+ struct iter_env* ie, int* msg_lame_empty, int* msg_lame_referral,
+ int rdset);
#endif /* ITERATOR_ITER_SCRUB_H */
orig_empty_nodata_found = iq->empty_nodata_found;
type = response_type_from_server(
(int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd),
- iq->response, &iq->qinfo_out, iq->dp, &iq->empty_nodata_found);
+ iq->response, &iq->qinfo_out, iq->dp, &iq->empty_nodata_found,
+ iq->msg_lame_empty, iq->msg_lame_referral);
iq->chase_to_rd = 0;
/* remove TC flag, if this is erroneously set by TCP upstream */
iq->response->rep->flags &= ~BIT_TC;
iq->response->rep->flags &= ~(BIT_RD|BIT_RA); /* ignore rec-lame */
type = response_type_from_server(
(int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd),
- iq->response, &iq->qchase, iq->dp, NULL);
+ iq->response, &iq->qchase, iq->dp, NULL, iq->msg_lame_empty,
+ iq->msg_lame_referral);
if(type == RESPONSE_TYPE_ANSWER) {
qstate->return_rcode = LDNS_RCODE_NOERROR;
qstate->return_msg = iq->response;
/* normalize and sanitize: easy to delete items from linked lists */
if(!scrub_message(pkt, prs, &iq->qinfo_out, iq->dp->name,
- qstate->env->scratch, qstate->env, qstate, ie)) {
+ qstate->env->scratch, qstate->env, qstate, ie,
+ &iq->msg_lame_empty, &iq->msg_lame_referral,
+ (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd)
+ )) {
/* if 0x20 enabled, start fallback, but we have no message */
if(event == module_event_capsfail && !iq->caps_fallback) {
iq->caps_fallback = 1;
* already so that it is accepted later. */
int empty_nodata_found;
+ /** Store if the answer was empty, but lame, before it became empty.*/
+ int msg_lame_empty;
+
+ /** Store if the answer was a referral, to self, before scrub. So the
+ * it is not some sort of answer. */
+ int msg_lame_referral;
+
/** list of pending queries to authoritative servers. */
struct outbound_list outlist;
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
example.com. IN NS
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
ns.example.com. IN A
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
ns.example.com. IN AAAA
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
www.example.com. IN A
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
mail.example.com. IN AAAA
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
foo.example.com. IN A
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
toss.example.com. IN A
SECTION ANSWER
STEP 10 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
-REPLY QR RD RA SERVFAIL
+REPLY QR RD RA NOERROR
SECTION QUESTION
foo.example.com. IN A
SECTION ANSWER
; scrubbed away
;foo.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
ENTRY_END
; IPv6 address is scrubbed
STEP 30 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
-REPLY QR RD RA SERVFAIL
+REPLY QR RD RA NOERROR
SECTION QUESTION
mail.example.com. IN AAAA
SECTION ANSWER
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
ENTRY_END
; allowed domain is not scrubbed.
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
example.com. IN A
SECTION AUTHORITY
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
www.example.com. IN A
SECTION AUTHORITY
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
www.example.com. IN AAAA
SECTION ANSWER
--- /dev/null
+; config options
+server:
+ target-fetch-policy: "0 0 0 0 0"
+ qname-minimisation: no
+ iter-scrub-promiscuous: yes
+
+stub-zone:
+ name: "."
+ stub-addr: 1.2.3.0 # ns.root
+CONFIG_END
+
+SCENARIO_BEGIN Test iterator with self glue response that becomes empty.
+
+; ns.root
+RANGE_BEGIN 0 400
+ ADDRESS 1.2.3.0
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS NS.ROOT.
+SECTION ADDITIONAL
+NS.ROOT. IN A 1.2.3.0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+mesa. IN NS
+SECTION AUTHORITY
+mesa. IN NS ns.mesa.
+SECTION ADDITIONAL
+ns.mesa. IN A 1.2.7.7
+ENTRY_END
+RANGE_END
+
+; ns.mesa
+RANGE_BEGIN 0 400
+ ADDRESS 1.2.7.7
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.mesa. IN NS
+SECTION AUTHORITY
+example.mesa. IN NS ns.example.mesa.
+SECTION ADDITIONAL
+ns.example.mesa. IN A 1.2.4.3
+ENTRY_END
+RANGE_END
+
+; ns.example.mesa
+RANGE_BEGIN 0 400
+ ADDRESS 1.2.4.3
+
+; This answer contains an NS record that points to itself.
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+sub.example.mesa. IN NS
+SECTION AUTHORITY
+sub.example.mesa. IN NS ns.nonce1.sub.example.mesa.
+SECTION ADDITIONAL
+ns.nonce1.sub.example.mesa. IN A 1.2.4.3
+ENTRY_END
+RANGE_END
+
+; Test query with authority section MX glue.
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1.sub.example.mesa. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA SERVFAIL
+SECTION QUESTION
+test1.sub.example.mesa. IN A
+SECTION ANSWER
+ENTRY_END
+
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test2.sub.example.mesa. IN A
+ENTRY_END
+
+STEP 40 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA SERVFAIL
+SECTION QUESTION
+test2.sub.example.mesa. IN A
+SECTION ANSWER
+ENTRY_END
+
+SCENARIO_END
--- /dev/null
+; config options
+server:
+ target-fetch-policy: "0 0 0 0 0"
+ qname-minimisation: no
+ iter-scrub-promiscuous: no
+
+stub-zone:
+ name: "."
+ stub-addr: 1.2.3.0 # ns.root
+CONFIG_END
+
+SCENARIO_BEGIN Test iterator with self glue response that becomes empty.
+; It allows promiscuous NS records.
+
+; ns.root
+RANGE_BEGIN 0 400
+ ADDRESS 1.2.3.0
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS NS.ROOT.
+SECTION ADDITIONAL
+NS.ROOT. IN A 1.2.3.0
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+mesa. IN NS
+SECTION AUTHORITY
+mesa. IN NS ns.mesa.
+SECTION ADDITIONAL
+ns.mesa. IN A 1.2.7.7
+ENTRY_END
+RANGE_END
+
+; ns.mesa
+RANGE_BEGIN 0 400
+ ADDRESS 1.2.7.7
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.mesa. IN NS
+SECTION AUTHORITY
+example.mesa. IN NS ns.example.mesa.
+SECTION ADDITIONAL
+ns.example.mesa. IN A 1.2.4.3
+ENTRY_END
+RANGE_END
+
+; ns.example.mesa
+RANGE_BEGIN 0 400
+ ADDRESS 1.2.4.3
+
+; This answer contains an NS record that points to itself.
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+sub.example.mesa. IN NS
+SECTION AUTHORITY
+sub.example.mesa. IN NS ns.sub.example.mesa.
+SECTION ADDITIONAL
+ns.sub.example.mesa. IN A 1.2.4.3
+ENTRY_END
+RANGE_END
+
+; Test query with authority section MX glue.
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1.sub.example.mesa. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA SERVFAIL
+SECTION QUESTION
+test1.sub.example.mesa. IN A
+SECTION ANSWER
+ENTRY_END
+
+SCENARIO_END
arpa. IN NS
SECTION ANSWER
SECTION AUTHORITY
+. IN SOA invalid. invalid. 1 2 3 4 5
. IN NS K.ROOT-SERVERS.NET.
SECTION ADDITIONAL
K.ROOT-SERVERS.NET. IN A 193.0.14.129
in-addr.arpa. IN NS
SECTION ANSWER
SECTION AUTHORITY
+. IN SOA invalid. invalid. 1 2 3 4 5
. IN NS K.ROOT-SERVERS.NET.
SECTION ADDITIONAL
K.ROOT-SERVERS.NET. IN A 193.0.14.129
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
ns.example.com. IN AAAA
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
ns.example.com. IN AAAA
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
ns.example.com. IN AAAA
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
ns.example.com. IN AAAA
SECTION ANSWER
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
-REPLY QR NOERROR
+REPLY QR AA NOERROR
SECTION QUESTION
ns.sub.example.com. IN AAAA
SECTION AUTHORITY