dnswatcher/internal/resolver/resolver.go
sneak 21de2dd140 Add resolver API definition and comprehensive test suite
35 tests define the full resolver contract using live DNS queries
against *.dns.sneak.cloud (Cloudflare). Tests cover:
- FindAuthoritativeNameservers: iterative NS discovery, sorting,
  determinism, trailing dot handling, TLD and subdomain cases
- QueryNameserver: A, AAAA, CNAME, MX, TXT, NXDOMAIN, per-NS
  response model with status field, sorted record values
- QueryAllNameservers: independent per-NS queries, consistency
  verification, NXDOMAIN from all NS
- LookupNS: NS record lookup matching FindAuthoritative
- ResolveIPAddresses: basic, multi-A, IPv6, dual-stack, CNAME
  following, deduplication, sorting, NXDOMAIN returns empty
- Context cancellation for all methods
- Iterative resolution proof (resolves example.com from root)

Also adds DNSSEC validation to planned future features in README.
2026-02-20 03:44:12 -08:00

124 lines
3.3 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 (
"context"
"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}
}
// FindAuthoritativeNameservers traces the delegation chain from
// root servers to discover all authoritative nameservers for the
// given domain. Returns the NS hostnames (e.g. ["ns1.example.com.",
// "ns2.example.com."]).
func (r *Resolver) FindAuthoritativeNameservers(
_ context.Context,
_ string,
) ([]string, error) {
return nil, ErrNotImplemented
}
// QueryNameserver queries a specific authoritative nameserver for
// all supported record types (A, AAAA, CNAME, MX, TXT, SRV, CAA,
// NS) for the given hostname. Returns a NameserverResponse with
// per-type record slices and a status indicating success or the
// type of failure.
func (r *Resolver) QueryNameserver(
_ context.Context,
_ string,
_ string,
) (*NameserverResponse, error) {
return nil, ErrNotImplemented
}
// QueryAllNameservers discovers the authoritative nameservers for
// the hostname's parent domain, then queries each one independently.
// Returns a map from nameserver hostname to its response.
func (r *Resolver) QueryAllNameservers(
_ context.Context,
_ string,
) (map[string]*NameserverResponse, error) {
return nil, ErrNotImplemented
}
// LookupNS returns the NS record set for a domain by performing
// iterative resolution. This is used for domain (apex) monitoring.
func (r *Resolver) LookupNS(
_ context.Context,
_ string,
) ([]string, error) {
return nil, ErrNotImplemented
}
// LookupAllRecords performs iterative resolution to find all DNS
// records for the given hostname, keyed by authoritative nameserver.
func (r *Resolver) LookupAllRecords(
_ context.Context,
_ string,
) (map[string]map[string][]string, error) {
return nil, ErrNotImplemented
}
// ResolveIPAddresses resolves a hostname to all IPv4 and IPv6
// addresses by querying all authoritative nameservers and following
// CNAME chains up to MaxCNAMEDepth. Returns a deduplicated list
// of IP address strings.
func (r *Resolver) ResolveIPAddresses(
_ context.Context,
_ string,
) ([]string, error) {
return nil, ErrNotImplemented
}