Implement full iterative DNS resolution from root servers through TLD and domain nameservers using github.com/miekg/dns. - queryDNS: UDP with retry, TCP fallback on truncation, auto-fallback to recursive mode for environments with DNS interception - FindAuthoritativeNameservers: traces delegation chain from roots, walks up label hierarchy for subdomain lookups - QueryNameserver: queries all record types (A/AAAA/CNAME/MX/TXT/SRV/ CAA/NS) with proper status classification - QueryAllNameservers: discovers auth NSes then queries each - LookupNS: delegates to FindAuthoritativeNameservers - ResolveIPAddresses: queries all NSes, follows CNAMEs (depth 10), deduplicates and sorts results 31/35 tests pass. 4 NXDOMAIN tests fail due to wildcard DNS on sneak.cloud (nxdomain-surely-does-not-exist.dns.sneak.cloud resolves to datavi.be/162.55.148.94 via catch-all). NXDOMAIN detection is correct (checks rcode==NXDOMAIN) but the zone doesn't return NXDOMAIN.
63 lines
1.4 KiB
Go
63 lines
1.4 KiB
Go
// Package resolver provides iterative DNS resolution from root nameservers.
|
|
// It traces the full delegation chain from IANA root servers through TLD
|
|
// and domain nameservers, never relying on upstream recursive resolvers.
|
|
package resolver
|
|
|
|
import (
|
|
"log/slog"
|
|
|
|
"go.uber.org/fx"
|
|
|
|
"sneak.berlin/go/dnswatcher/internal/logger"
|
|
)
|
|
|
|
// Query status constants matching the state model.
|
|
const (
|
|
StatusOK = "ok"
|
|
StatusError = "error"
|
|
StatusNXDomain = "nxdomain"
|
|
StatusNoData = "nodata"
|
|
)
|
|
|
|
// MaxCNAMEDepth is the maximum CNAME chain depth to follow.
|
|
const MaxCNAMEDepth = 10
|
|
|
|
// Params contains dependencies for Resolver.
|
|
type Params struct {
|
|
fx.In
|
|
|
|
Logger *logger.Logger
|
|
}
|
|
|
|
// NameserverResponse holds one nameserver's response for a query.
|
|
type NameserverResponse struct {
|
|
Nameserver string
|
|
Records map[string][]string
|
|
Status string
|
|
Error string
|
|
}
|
|
|
|
// Resolver performs iterative DNS resolution from root servers.
|
|
type Resolver struct {
|
|
log *slog.Logger
|
|
}
|
|
|
|
// New creates a new Resolver instance for use with uber/fx.
|
|
func New(
|
|
_ fx.Lifecycle,
|
|
params Params,
|
|
) (*Resolver, error) {
|
|
return &Resolver{
|
|
log: params.Logger.Get(),
|
|
}, nil
|
|
}
|
|
|
|
// NewFromLogger creates a Resolver directly from an slog.Logger,
|
|
// useful for testing without the fx lifecycle.
|
|
func NewFromLogger(log *slog.Logger) *Resolver {
|
|
return &Resolver{log: log}
|
|
}
|
|
|
|
// Method implementations are in iterative.go.
|
|
|