// 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" StatusTimeout = "timeout" ) // 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. // FailedTypes lists the record types whose query got no usable reply, // and Records holds nothing for them: their records are not known. When // no record type got one, Status and Error say the nameserver failed. type NameserverResponse struct { Nameserver string Records map[string][]string FailedTypes []string Status string Error string } // Resolver performs iterative DNS resolution from root servers. // rootServers are the addresses every walk from the root starts at. type Resolver struct { log *slog.Logger client DNSClient tcp DNSClient rootServers []string } // 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(), client: &udpClient{timeout: queryTimeoutDuration}, tcp: &tcpClient{timeout: queryTimeoutDuration}, rootServers: rootServerList(), }, 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, client: &udpClient{timeout: queryTimeoutDuration}, tcp: &tcpClient{timeout: queryTimeoutDuration}, rootServers: rootServerList(), } } // NewWithRootServers creates a Resolver as NewFromLogger does, except // that every walk from the root starts at servers instead of at the root // servers. Tests use it to show what dnswatcher does when no root server // answers, or when something on the network answers in their place. func NewWithRootServers(log *slog.Logger, servers []string) *Resolver { r := NewFromLogger(log) r.rootServers = servers return r } // Method implementations are in iterative.go.