resolver: a nameserver with a failed record type is not nodata (closes #253)
check / check (push) Successful in 1m10s
check / check (push) Successful in 1m10s
classifyResponse set nodata when every record type that answered had no records, even when another type's query got no usable reply. That type is listed in FailedTypes and its records are unknown, so the nameserver has not said it has none. It now stays ok, the status README describes for a nameserver with a failed type, and nodata is set only when no type failed. The watcher saved nodata as ok already, so saved state is unchanged; the live test that rejects nodata no longer fails when one of a nameserver's queries is lost. Model: opus-5-5
This commit is contained in:
@@ -19,6 +19,8 @@ trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-02: a nameserver whose query for one record type failed while the
|
||||||
|
others answered with no records is `ok`, not `nodata` (closes #253).
|
||||||
- 2026-10-02: a domain that does not exist is shown so, with no nameservers; no
|
- 2026-10-02: a domain that does not exist is shown so, with no nameservers; no
|
||||||
name gets a parent's nameservers when its own did not answer (closes #222).
|
name gets a parent's nameservers when its own did not answer (closes #222).
|
||||||
- 2026-10-02: the refused-query test sends one query to four operators' public
|
- 2026-10-02: the refused-query test sends one query to four operators' public
|
||||||
|
|||||||
@@ -941,7 +941,9 @@ func isTimeout(err error) bool {
|
|||||||
|
|
||||||
// classifyResponse sets the nameserver's status. One that answered no
|
// classifyResponse sets the nameserver's status. One that answered no
|
||||||
// record type has failed, and Error says why; one that answered some has
|
// record type has failed, and Error says why; one that answered some has
|
||||||
// the status of those answers.
|
// the status of those answers. It has no data only when every type
|
||||||
|
// answered with no records: a type in FailedTypes may have records, so a
|
||||||
|
// nameserver with one stays ok.
|
||||||
func classifyResponse(resp *NameserverResponse, state queryState) {
|
func classifyResponse(resp *NameserverResponse, state queryState) {
|
||||||
switch {
|
switch {
|
||||||
case state.gotNXDomain && !state.hasRecords:
|
case state.gotNXDomain && !state.hasRecords:
|
||||||
@@ -961,7 +963,8 @@ func classifyResponse(resp *NameserverResponse, state queryState) {
|
|||||||
case state.gotReferral && !state.answered:
|
case state.gotReferral && !state.answered:
|
||||||
resp.Status = StatusError
|
resp.Status = StatusError
|
||||||
resp.Error = "server returned a referral"
|
resp.Error = "server returned a referral"
|
||||||
case !state.hasRecords && !state.gotNXDomain:
|
// An NXDOMAIN reply with no records was taken by the first case.
|
||||||
|
case !state.hasRecords && len(resp.FailedTypes) == 0:
|
||||||
resp.Status = StatusNoData
|
resp.Status = StatusNoData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,60 +11,77 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// TestClassifyResponse sets a nameserver's status from the results of
|
// TestClassifyResponse sets a nameserver's status from the results of
|
||||||
// its queries, built here. One that answered some record types, even
|
// its queries and the record types whose query failed, built here. One
|
||||||
// with no records, has not failed when its query for another type got
|
// that answered some record types, even with no records, has not failed
|
||||||
// no usable reply, whatever the reason; one whose every query got none
|
// when its query for another type got no usable reply, whatever the
|
||||||
// has.
|
// reason, and is ok, not nodata: that type may have records. One whose
|
||||||
|
// every query got none has failed. Only one whose every type answered
|
||||||
|
// with no records is nodata.
|
||||||
func TestClassifyResponse(t *testing.T) {
|
func TestClassifyResponse(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
results queryState
|
results queryState
|
||||||
wantStatus string
|
failedTypes []string
|
||||||
wantError string
|
wantStatus string
|
||||||
|
wantError string
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
"every type answered with no records",
|
||||||
|
queryState{answered: true},
|
||||||
|
nil,
|
||||||
|
StatusNoData, "",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"some types answered with no records, another timed out",
|
"some types answered with no records, another timed out",
|
||||||
queryState{answered: true, gotTimeout: true},
|
queryState{answered: true, gotTimeout: true},
|
||||||
StatusNoData, "",
|
[]string{"A"},
|
||||||
|
StatusOK, "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"some types answered with no records, another got SERVFAIL",
|
"some types answered with no records, another got SERVFAIL",
|
||||||
queryState{
|
queryState{
|
||||||
answered: true, gotErrorReply: true, errorReply: "SERVFAIL",
|
answered: true, gotErrorReply: true, errorReply: "SERVFAIL",
|
||||||
},
|
},
|
||||||
StatusNoData, "",
|
[]string{"A"},
|
||||||
|
StatusOK, "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"some types answered with no records, another was refused",
|
"some types answered with no records, another was refused",
|
||||||
queryState{answered: true, gotRefused: true},
|
queryState{answered: true, gotRefused: true},
|
||||||
StatusNoData, "",
|
[]string{"A"},
|
||||||
|
StatusOK, "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"some types answered with no records, another got a network error",
|
"some types answered with no records, another got a network error",
|
||||||
queryState{answered: true, netErr: syscall.ECONNREFUSED},
|
queryState{answered: true, netErr: syscall.ECONNREFUSED},
|
||||||
StatusNoData, "",
|
[]string{"A"},
|
||||||
|
StatusOK, "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"some types answered with no records, another's reply was " +
|
"some types answered with no records, another's reply was " +
|
||||||
"truncated and its retry over TCP failed",
|
"truncated and its retry over TCP failed",
|
||||||
queryState{answered: true, netErr: ErrTruncated},
|
queryState{answered: true, netErr: ErrTruncated},
|
||||||
StatusNoData, "",
|
[]string{"TXT"},
|
||||||
|
StatusOK, "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"some types answered with no records, another got a referral",
|
"some types answered with no records, another got a referral",
|
||||||
queryState{answered: true, gotReferral: true},
|
queryState{answered: true, gotReferral: true},
|
||||||
StatusNoData, "",
|
[]string{"A"},
|
||||||
|
StatusOK, "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"every query timed out",
|
"every query timed out",
|
||||||
queryState{gotTimeout: true},
|
queryState{gotTimeout: true},
|
||||||
|
[]string{"A", "AAAA", "CNAME"},
|
||||||
StatusTimeout, "all queries timed out",
|
StatusTimeout, "all queries timed out",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"every query got NOTIMP",
|
"every query got NOTIMP",
|
||||||
queryState{gotErrorReply: true, errorReply: "NOTIMP"},
|
queryState{gotErrorReply: true, errorReply: "NOTIMP"},
|
||||||
|
[]string{"A", "AAAA", "CNAME"},
|
||||||
StatusError, "server returned NOTIMP",
|
StatusError, "server returned NOTIMP",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -73,11 +90,12 @@ func TestClassifyResponse(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
resp := &NameserverResponse{Status: StatusOK}
|
resp := &NameserverResponse{Status: StatusOK, FailedTypes: tt.failedTypes}
|
||||||
classifyResponse(resp, tt.results)
|
classifyResponse(resp, tt.results)
|
||||||
|
|
||||||
assert.Equal(t, tt.wantStatus, resp.Status)
|
assert.Equal(t, tt.wantStatus, resp.Status)
|
||||||
assert.Equal(t, tt.wantError, resp.Error)
|
assert.Equal(t, tt.wantError, resp.Error)
|
||||||
|
assert.Equal(t, tt.failedTypes, resp.FailedTypes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user