2 Commits

Author SHA1 Message Date
b599dab525 fix: only set gotTimeout for actual timeout errors
Some checks failed
Check / check (pull_request) Failing after 5m39s
Previously, querySingleType set gotTimeout=true for any queryDNS error,
misclassifying connection refused, network unreachable, etc. as timeouts.

Now uses errors.As(err, &net.Error) with Timeout() check to distinguish
real timeouts from other network errors. Non-timeout errors are tracked
via a new gotError field and classified as StatusError.
2026-02-28 03:34:44 -08:00
clawbot
9193cb1bca fix: distinguish timeout from negative DNS responses (closes #35)
Some checks failed
Check / check (pull_request) Failing after 5m38s
2026-02-28 03:28:44 -08:00

View File

@@ -480,6 +480,7 @@ type queryState struct {
gotNXDomain bool gotNXDomain bool
gotSERVFAIL bool gotSERVFAIL bool
gotTimeout bool gotTimeout bool
gotError bool
hasRecords bool hasRecords bool
} }
@@ -517,8 +518,11 @@ func (r *Resolver) querySingleType(
) { ) {
msg, err := r.queryDNS(ctx, nsIP, hostname, qtype) msg, err := r.queryDNS(ctx, nsIP, hostname, qtype)
if err != nil { if err != nil {
if isTimeout(err) { var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
state.gotTimeout = true state.gotTimeout = true
} else {
state.gotError = true
} }
return return
@@ -558,16 +562,6 @@ func collectAnswerRecords(
} }
} }
// isTimeout checks whether an error is a network timeout.
func isTimeout(err error) bool {
var netErr net.Error
if errors.As(err, &netErr) {
return netErr.Timeout()
}
return false
}
func classifyResponse(resp *NameserverResponse, state queryState) { func classifyResponse(resp *NameserverResponse, state queryState) {
switch { switch {
case state.gotNXDomain && !state.hasRecords: case state.gotNXDomain && !state.hasRecords:
@@ -575,14 +569,22 @@ func classifyResponse(resp *NameserverResponse, state queryState) {
case state.gotTimeout && !state.hasRecords: case state.gotTimeout && !state.hasRecords:
resp.Status = StatusTimeout resp.Status = StatusTimeout
resp.Error = "all queries timed out" resp.Error = "all queries timed out"
case state.gotSERVFAIL && !state.hasRecords: case (state.gotError || state.gotSERVFAIL) && !state.hasRecords:
resp.Status = StatusError resp.Status = StatusError
resp.Error = "server returned SERVFAIL" resp.Error = errorMessageForState(state)
case !state.hasRecords && !state.gotNXDomain: case !state.hasRecords && !state.gotNXDomain:
resp.Status = StatusNoData resp.Status = StatusNoData
} }
} }
func errorMessageForState(state queryState) string {
if state.gotSERVFAIL {
return "server returned SERVFAIL"
}
return "query failed due to non-timeout error"
}
// extractRecordValue formats a DNS RR value as a string. // extractRecordValue formats a DNS RR value as a string.
func extractRecordValue(rr dns.RR) string { func extractRecordValue(rr dns.RR) string {
switch r := rr.(type) { switch r := rr.(type) {