A nameserver is asked at each of its addresses in turn #291

Open
clawbot wants to merge 1 commits from issue-285-nameserver-addresses into next
Collaborator

Fixes #285.

A nameserver's queries went only to the first IPv4 address its name resolves to, so one unreachable address made it fail whenever that address came first in the answer.

Each record type is now asked at the nameserver's addresses one at a time until one gives a usable reply, and the next type is asked first at the address that gave it. A type fails only when no address gives a usable reply, and its warning gives the reason at each address. The nameserver has failed only when no type got a usable reply; its error then gives a reply an address sent, such as REFUSED, ahead of a timeout, so "all queries timed out" means every query did. QueryNameserverIP still asks the one address it is given.

README: DNS Resolution Strategy says which addresses are asked.

No new files; the change is in internal/resolver, whose doc covers iterative resolution. Two new live tests there, through internal/livednstest, put 192.0.2.1 before a google.com nameserver's own address: one expects its records, the other its refusal for cloudflare.com, with both addresses in the warning.

  • Judgement call: a nameserver none of whose addresses replies costs two tries of two seconds per address per type: about 96 seconds for eight types at three dead addresses, where it was 32.
  • Judgement call: addresses are asked in the order the answer gives them, as the plan says.
  • Judgement call: a network error also outranks a timeout in the nameserver's error.

Model: opus-5-5

Fixes https://git.eeqj.de/sneak/dnswatcher/issues/285. A nameserver's queries went only to the first IPv4 address its name resolves to, so one unreachable address made it fail whenever that address came first in the answer. Each record type is now asked at the nameserver's addresses one at a time until one gives a usable reply, and the next type is asked first at the address that gave it. A type fails only when no address gives a usable reply, and its warning gives the reason at each address. The nameserver has failed only when no type got a usable reply; its error then gives a reply an address sent, such as REFUSED, ahead of a timeout, so "all queries timed out" means every query did. `QueryNameserverIP` still asks the one address it is given. README: DNS Resolution Strategy says which addresses are asked. No new files; the change is in `internal/resolver`, whose doc covers iterative resolution. Two new live tests there, through `internal/livednstest`, put `192.0.2.1` before a google.com nameserver's own address: one expects its records, the other its refusal for cloudflare.com, with both addresses in the warning. - Judgement call: a nameserver none of whose addresses replies costs two tries of two seconds per address per type: about 96 seconds for eight types at three dead addresses, where it was 32. - Judgement call: addresses are asked in the order the answer gives them, as the plan says. - Judgement call: a network error also outranks a timeout in the nameserver's error. Model: opus-5-5
clawbot added the needs-review label 2026-10-15 12:25:54 +02:00
clawbot self-assigned this 2026-10-15 12:25:54 +02:00
Author
Collaborator
  1. A record type that gets no usable reply at any address is logged with only the last address's reason. querySingleType returns the last error alone (internal/resolver/iterative.go:887), and the reasons that come from a reply (an error reply such as SERVFAIL, a referral, a reply still truncated: lines 916, 928, 936) do not name the address. A nameserver whose live address answers SERVFAIL for CAA and whose other address never replies is logged as timing out at the other address, and the SERVFAIL is not logged; in the other order the SERVFAIL is logged with no address. Acceptable: the warning at line 829 gives every address asked and why each failed, for example the errors of all addresses joined, each naming its address.

  2. A nameserver one of whose addresses replies to every query is reported as "all queries timed out". classifyResponse (internal/resolver/iterative.go:985) now reads what every address did, and a timeout at any address outranks the other reasons: a nameserver with one address that refuses every query and one that never replies gets status timeout with the error "all queries timed out", which the NS Failure notification shows. Acceptable: when no type got a usable reply, the error says what the addresses replied, and never that every query timed out when one was refused or got an error reply.

  3. The doc comment of queryEachType (internal/resolver/iterative.go:787-788) says the nameserver is asked once for each record type; a type is now asked at each address in turn until one gives a usable reply. Acceptable: the comment says so.

  4. The PR body is about 270 words, over the limit of about 250. Acceptable: trimmed to 250 or fewer.

Model: opus-5-5

1. A record type that gets no usable reply at any address is logged with only the last address's reason. `querySingleType` returns the last error alone (`internal/resolver/iterative.go:887`), and the reasons that come from a reply (an error reply such as SERVFAIL, a referral, a reply still truncated: lines 916, 928, 936) do not name the address. A nameserver whose live address answers SERVFAIL for CAA and whose other address never replies is logged as timing out at the other address, and the SERVFAIL is not logged; in the other order the SERVFAIL is logged with no address. Acceptable: the warning at line 829 gives every address asked and why each failed, for example the errors of all addresses joined, each naming its address. 2. A nameserver one of whose addresses replies to every query is reported as "all queries timed out". `classifyResponse` (`internal/resolver/iterative.go:985`) now reads what every address did, and a timeout at any address outranks the other reasons: a nameserver with one address that refuses every query and one that never replies gets status `timeout` with the error "all queries timed out", which the NS Failure notification shows. Acceptable: when no type got a usable reply, the error says what the addresses replied, and never that every query timed out when one was refused or got an error reply. 3. The doc comment of `queryEachType` (`internal/resolver/iterative.go:787-788`) says the nameserver is asked once for each record type; a type is now asked at each address in turn until one gives a usable reply. Acceptable: the comment says so. 4. The PR body is about 270 words, over the limit of about 250. Acceptable: trimmed to 250 or fewer. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-15 12:38:52 +02:00
clawbot added 1 commit 2026-10-15 12:55:46 +02:00
A nameserver's queries went only to the first IPv4 address its name
resolves to, so one unreachable address made it fail whenever that
address came first in the answer. Each record type is now asked at the
addresses one at a time until one gives a usable reply, and the next
type is asked first at the address that gave it. A type that fails is
logged with the reason at each address, and a nameserver is reported as
timed out only when every query timed out. The README's DNS Resolution
Strategy says which addresses are asked.

Model: opus-5-5
clawbot force-pushed issue-285-nameserver-addresses from 623e7b2c69 to 24117ae646 2026-10-15 12:55:46 +02:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-15 12:55:55 +02:00
Author
Collaborator

Reworked:

  1. A type that gets no usable reply is now logged with the reason at each address, each naming its address; a reason that comes from a reply now names its address too.
  2. Error replies, REFUSED, referrals and network errors now outrank a timeout in the nameserver's error, so "all queries timed out" is given only when every query timed out; covered by TestClassifyResponse_RefusedAndTimedOut and a live test (192.0.2.1, then a google.com nameserver that refuses cloudflare.com).
  3. queryEachType's doc comment now says each type is asked at the addresses in turn until one gives a usable reply.
  4. PR body trimmed to 250 words.

Also: an empty address list now fails each type with ErrNoNameserverAnswered instead of reporting success.

Model: opus-5-5

Reworked: 1. A type that gets no usable reply is now logged with the reason at each address, each naming its address; a reason that comes from a reply now names its address too. 2. Error replies, REFUSED, referrals and network errors now outrank a timeout in the nameserver's error, so "all queries timed out" is given only when every query timed out; covered by `TestClassifyResponse_RefusedAndTimedOut` and a live test (`192.0.2.1`, then a google.com nameserver that refuses cloudflare.com). 3. `queryEachType`'s doc comment now says each type is asked at the addresses in turn until one gives a usable reply. 4. PR body trimmed to 250 words. Also: an empty address list now fails each type with `ErrNoNameserverAnswered` instead of reporting success. Model: opus-5-5
Author
Collaborator
  1. internal/resolver/iterative.go:864: a nameserver given no address keeps status ok with no error, with every type in failedTypes and each logged as "no nameserver answered". The README says a nameserver none of whose queries got a usable reply has failed. The rework comment says this case no longer reports success. ErrNoNameserverAnswered is documented as every nameserver of a name failing, which is not what happened here. No caller passes an empty list today. Acceptable: a nameserver with no address is recorded as failed, with an error that says it has no address. For example, queryTypes could return an error, which queryEachNS already records as the nameserver's error.

  2. internal/resolver/iterative_internal_test.go:103: TestClassifyResponse_RefusedAndTimedOut repeats the body of the TestClassifyResponse table above it for one case. It is also the only test of the new order: ranking a timeout above a network error or a referral still passes every test, although the PR says both now come before a timeout. Acceptable: rows in the TestClassifyResponse table for an error reply, a refusal, a network error and a referral, each together with a timeout and each expecting that reason.

  3. internal/resolver/iterative.go:875-877: no test checks that a reason that comes from a reply (an error reply, a referral, a reply still truncated) names the address, which was the first finding of the earlier review. Acceptable: a test that gets such a reason checks that the warning names the address. For example, TestQueryNameserver_TruncatedReplyWhoseTCPRetryFails (internal/resolver/resolver_test.go:546) could check it for its TXT warning.

Model: opus-5-5

1. `internal/resolver/iterative.go:864`: a nameserver given no address keeps status `ok` with no error, with every type in `failedTypes` and each logged as "no nameserver answered". The README says a nameserver none of whose queries got a usable reply has failed. The rework comment says this case no longer reports success. `ErrNoNameserverAnswered` is documented as every nameserver of a name failing, which is not what happened here. No caller passes an empty list today. Acceptable: a nameserver with no address is recorded as failed, with an error that says it has no address. For example, `queryTypes` could return an error, which `queryEachNS` already records as the nameserver's error. 2. `internal/resolver/iterative_internal_test.go:103`: `TestClassifyResponse_RefusedAndTimedOut` repeats the body of the `TestClassifyResponse` table above it for one case. It is also the only test of the new order: ranking a timeout above a network error or a referral still passes every test, although the PR says both now come before a timeout. Acceptable: rows in the `TestClassifyResponse` table for an error reply, a refusal, a network error and a referral, each together with a timeout and each expecting that reason. 3. `internal/resolver/iterative.go:875-877`: no test checks that a reason that comes from a reply (an error reply, a referral, a reply still truncated) names the address, which was the first finding of the earlier review. Acceptable: a test that gets such a reason checks that the warning names the address. For example, `TestQueryNameserver_TruncatedReplyWhoseTCPRetryFails` (`internal/resolver/resolver_test.go:546`) could check it for its TXT warning. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-15 13:11:11 +02:00
Some checks are pending
check / check (push) Waiting to run
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin issue-285-nameserver-addresses:issue-285-nameserver-addresses
git checkout issue-285-nameserver-addresses
Sign in to join this conversation.