check / check (push) Canceled after 0s
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; one given no address has failed. The README's DNS Resolution Strategy says which addresses are asked. Model: opus-5-5
160 lines
3.9 KiB
Go
160 lines
3.9 KiB
Go
package resolver
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// NewWithFailingTCP returns a Resolver whose TCP client gives up before
|
|
// it can connect, so the retry over TCP of every truncated reply fails.
|
|
func NewWithFailingTCP(log *slog.Logger) *Resolver {
|
|
r := NewFromLogger(log)
|
|
r.tcp = &tcpClient{timeout: time.Nanosecond}
|
|
|
|
return r
|
|
}
|
|
|
|
// NewWithQueryTimeout returns a Resolver whose queries over UDP give up
|
|
// after timeout, so a test that asks an address where nothing answers
|
|
// does not wait out the usual timeout.
|
|
func NewWithQueryTimeout(log *slog.Logger, timeout time.Duration) *Resolver {
|
|
r := NewFromLogger(log)
|
|
r.client = &udpClient{timeout: timeout}
|
|
|
|
return r
|
|
}
|
|
|
|
// FollowDelegation exports followDelegation for testing.
|
|
func (r *Resolver) FollowDelegation(
|
|
ctx context.Context,
|
|
domain string,
|
|
zone string,
|
|
nameservers []string,
|
|
servers []string,
|
|
) ([]string, error) {
|
|
return r.followDelegation(ctx, domain, zone, nameservers, servers)
|
|
}
|
|
|
|
// FindAuthoritativeNameserversFrom exports findAuthoritativeNameservers
|
|
// for testing.
|
|
func (r *Resolver) FindAuthoritativeNameserversFrom(
|
|
ctx context.Context,
|
|
domain string,
|
|
servers []string,
|
|
) ([]string, error) {
|
|
return r.findAuthoritativeNameservers(ctx, domain, servers)
|
|
}
|
|
|
|
// ResolveNSIterative exports resolveNSIterative for testing.
|
|
func (r *Resolver) ResolveNSIterative(
|
|
ctx context.Context,
|
|
domain string,
|
|
) ([]string, error) {
|
|
return r.resolveNSIterative(ctx, domain)
|
|
}
|
|
|
|
// ExtractRecordValue exports extractRecordValue for testing.
|
|
func ExtractRecordValue(rr dns.RR) string {
|
|
return extractRecordValue(rr)
|
|
}
|
|
|
|
// CollectAnswerRecords exports collectAnswerRecords for testing.
|
|
func CollectAnswerRecords(msg *dns.Msg, resp *NameserverResponse) {
|
|
var state queryState
|
|
|
|
collectAnswerRecords(msg, resp, &state)
|
|
}
|
|
|
|
// UsableReply exports usableReply for testing.
|
|
func UsableReply(resp *dns.Msg, zone string, name string) bool {
|
|
return usableReply(resp, zone, name)
|
|
}
|
|
|
|
// NSSetFrom exports nsSetFrom for testing.
|
|
func NSSetFrom(resp *dns.Msg, domain string) []string {
|
|
return nsSetFrom(resp, domain)
|
|
}
|
|
|
|
// CollectIPs exports collectIPs for testing.
|
|
func CollectIPs(
|
|
results map[string]*NameserverResponse,
|
|
) ([]string, string, error) {
|
|
return collectIPs(results)
|
|
}
|
|
|
|
// QueryServers exports queryServers for testing.
|
|
func (r *Resolver) QueryServers(
|
|
ctx context.Context,
|
|
servers []string,
|
|
zone string,
|
|
name string,
|
|
qtype uint16,
|
|
) (*dns.Msg, error) {
|
|
return r.queryServers(ctx, servers, zone, name, qtype)
|
|
}
|
|
|
|
// QueryTypes exports queryTypes for testing.
|
|
func (r *Resolver) QueryTypes(
|
|
ctx context.Context,
|
|
nsHostname string,
|
|
nsIPs []string,
|
|
hostname string,
|
|
qtypes []uint16,
|
|
) (*NameserverResponse, error) {
|
|
return r.queryTypes(ctx, nsHostname, nsIPs, hostname, qtypes)
|
|
}
|
|
|
|
// QueryEachNS exports queryEachNS for testing.
|
|
func (r *Resolver) QueryEachNS(
|
|
ctx context.Context,
|
|
nameservers []string,
|
|
hostname string,
|
|
) (map[string]*NameserverResponse, error) {
|
|
return r.queryEachNS(ctx, nameservers, hostname, recordTypes())
|
|
}
|
|
|
|
// ResolveNSIPs exports resolveNSIPs for testing, looking each name up
|
|
// as a lookup that no other lookup started.
|
|
func (r *Resolver) ResolveNSIPs(
|
|
ctx context.Context,
|
|
nsNames []string,
|
|
) []string {
|
|
ips, _ := r.resolveNSIPs(ctx, nsNames, 1)
|
|
|
|
return ips
|
|
}
|
|
|
|
// MaxLookupDepth exports maxLookupDepth for testing.
|
|
const MaxLookupDepth = maxLookupDepth
|
|
|
|
// QueryZone exports queryZone for testing.
|
|
func (r *Resolver) QueryZone(
|
|
ctx context.Context,
|
|
given []string,
|
|
withoutAddresses []string,
|
|
zone string,
|
|
name string,
|
|
qtype uint16,
|
|
depth int,
|
|
) (*dns.Msg, error) {
|
|
return r.queryZone(
|
|
ctx, given, withoutAddresses, zone, name, qtype, depth,
|
|
)
|
|
}
|
|
|
|
// RootServerList exports rootServerList for testing.
|
|
func RootServerList() []string {
|
|
return rootServerList()
|
|
}
|
|
|
|
// Shuffled exports shuffled for testing.
|
|
func Shuffled(
|
|
servers []string,
|
|
shuffle func(n int, swap func(i, j int)),
|
|
) []string {
|
|
return shuffled(servers, shuffle)
|
|
}
|