fix: wrong DoQ resolver rewriting upstream responses with SetReply

The DoQ resolver called SetReply on the already-unpacked upstream
response. SetReply is meant to build a reply from a request, so it
forces the RCODE to NOERROR and overwrites the Question with the
request's question. This masked upstream failures from the proxy's
failover logic (a SERVFAIL looked like a successful empty response) and
corrupted the Question section of the response served to clients.

Restore only the downstream transaction ID instead (RFC 9250 section
4.2.1 puts the DNS Message ID at 0 on the wire), preserving the upstream
RCODE, Question, and answer sections untouched. This matches how the DoH
and DoT resolvers return unpacked upstream responses.

Refs github.com/Control-D-Inc/ctrld/issues/322
This commit is contained in:
Cuong Manh Le
2026-07-13 20:52:57 +07:00
parent 265573c744
commit 9cf8bc3b5b
2 changed files with 111 additions and 1 deletions
+9 -1
View File
@@ -251,7 +251,15 @@ func (p *doqConnPool) doResolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, er
if err := answer.Unpack(buf[2 : 2+respLen]); err != nil {
return nil, err
}
answer.SetReply(msg)
// RFC 9250 section 4.2.1 requires the DNS Message ID to be 0 on the wire,
// so restore the downstream transaction ID for the client. Do NOT use
// SetReply here: it rewrites the RCODE to NOERROR and overwrites the
// Question with the request's, which would mask upstream failures from the
// failover logic (a SERVFAIL would look like success) and let a
// wrong-question answer pass validation and poison the cache. Preserve the
// upstream RCODE, Question, and answer sections untouched so the proxy can
// evaluate them. See github.com/Control-D-Inc/ctrld/issues/322.
answer.Id = msg.Id
return answer, nil
}