@@ -66,6 +66,28 @@ class SearchLookup : public DNS::Operation, public DNS::Handler
6666 * @var Operation
6767 */
6868 Operation *_operation = nullptr ;
69+
70+ /* *
71+ * Optional empty response (with no matching records) that we found for one of the
72+ * lookups in the process (because we rather report an empty record set than a NXDOMAIN
73+ * result, because we want to tell the caller that at least *something* exists).
74+ * @var Response
75+ */
76+ std::optional<Response> _response;
77+
78+
79+ /* *
80+ * Cache the response for later use
81+ * @param response
82+ */
83+ void cache (const Response &response)
84+ {
85+ // no need to cache if we already have something, and invalid responses are not cached either
86+ if (_response || response.rcode () != 0 ) return ;
87+
88+ // cache the response
89+ _response.emplace (response);
90+ }
6991
7092 /* *
7193 * Method that is called when a raw response is received
@@ -77,11 +99,12 @@ class SearchLookup : public DNS::Operation, public DNS::Handler
7799 // an mxdomain error should trigger a next lookup
78100 if (response.rcode () == ns_r_nxdomain && proceed ()) return ;
79101
80- // if there are no matching records, we also want to do the next lookup
81- if (response.records (ns_s_an, _type) == 0 && proceed ()) return ;
82-
83- // pass on to user-space
84- _handler->onReceived (this , response);
102+ // if there are no matching records, we also want to do the next lookup,
103+ // but we do remember this empty response because it could be better than the final nxdomain
104+ if (response.records (ns_s_an, _type) == 0 && proceed ()) return cache (response);
105+
106+ // pass on to user-space (note that we do not pass the nxdomain response if we have a better alternative)
107+ _handler->onReceived (this , response.rcode () != ns_r_nxdomain || !_response ? response : *_response);
85108
86109 // self destruct, as this query is finished
87110 delete this ;
0 commit comments