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.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"log/slog"
|
||||
|
||||
"go.uber.org/fx"
|
||||
@@ -11,8 +12,16 @@ import (
|
||||
"sneak.berlin/go/dnswatcher/internal/logger"
|
||||
)
|
||||
|
||||
// ErrNotImplemented indicates the resolver is not yet implemented.
|
||||
var ErrNotImplemented = errors.New("resolver not yet implemented")
|
||||
// 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 {
|
||||
@@ -21,12 +30,20 @@ type Params struct {
|
||||
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.
|
||||
// New creates a new Resolver instance for use with uber/fx.
|
||||
func New(
|
||||
_ fx.Lifecycle,
|
||||
params Params,
|
||||
@@ -36,8 +53,48 @@ func New(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LookupNS performs iterative resolution to find authoritative
|
||||
// nameservers for the given domain.
|
||||
// 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,
|
||||
@@ -55,7 +112,9 @@ func (r *Resolver) LookupAllRecords(
|
||||
}
|
||||
|
||||
// ResolveIPAddresses resolves a hostname to all IPv4 and IPv6
|
||||
// addresses, following CNAME chains.
|
||||
// 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,
|
||||
|
||||
Reference in New Issue
Block a user