dns

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: GPL-3.0 Imports: 21 Imported by: 0

Documentation

Overview

Package dns resolves candidate names, classifies lookup outcomes, detects wildcard zones, paces queries and simulates resolution for -simulate.

Index

Constants

View Source
const (
	TransportUDP   = "udp"   // ip:port; UDP with TCP fallback on truncation
	TransportTLS   = "tls"   // tls://host[:853]; DNS over TLS (RFC 7858)
	TransportHTTPS = "https" // https://host/path; DNS over HTTPS (RFC 8484)
)

Transports a -dns-server can use (#86).

Variables

View Source
var DefaultTypes = []string{"A", "AAAA"}

DefaultTypes is the record-type set used when none is specified; it preserves the historical LookupHost behavior (A and AAAA).

View Source
var ErrWildcardInconclusive = errors.New("wildcard check inconclusive")

ErrWildcardInconclusive is returned (wrapped) by CheckWildcard when a probe neither resolved nor got a definitive negative, so the zone may or may not be a wildcard. Callers must not treat it as "no wildcard" (#47).

Functions

func CloseIdleConnections added in v0.9.0

func CloseIdleConnections(r *net.Resolver)

CloseIdleConnections closes the DoT connections and idle DoH connections a resolver from NewResolver keeps for reuse. The resolver stays usable. A scan calls it when it ends, so a long-lived program that scans repeatedly does not hold connections open between scans.

func NewResolver added in v0.7.0

func NewResolver(timeout time.Duration, dnsServer string) *net.Resolver

NewResolver returns a PreferGo resolver that always dials dnsServer. The Dial hook honors the network argument so a truncated UDP response can fall back to TCP.

Go's resolver dials a fresh connection for every query it sends, including its own internal retries (attempts x system nameservers on SERVFAIL, REFUSED or socket errors), so each dial is one wire query. Under a lookup started by ResolveTypes, a dial beyond the slots reserved up front takes another rate slot, which keeps -rate exact however many queries Go decides to send (#50).

func ParseResolverList added in v0.9.0

func ParseResolverList(lines []string) ([]string, error)

ParseResolverList parses resolver addresses, one per line or entry, as ip or ip:port (port 53 by default). Blank lines and # comments are skipped.

func ParseTypes added in v0.6.0

func ParseTypes(s string) ([]string, error)

ParseTypes parses a comma-separated record-type list (for example "A,AAAA,CNAME") into a normalized, de-duplicated, uppercase slice.

func RandomLabel added in v0.9.0

func RandomLabel() string

RandomLabel returns a 32-character random hex label, the kind wildcard probes use: a name no real zone contains.

func ResolveDomain

func ResolveDomain(ctx context.Context, resolver *net.Resolver, domain string, timeout time.Duration, logf Logf) bool

ResolveDomain performs a single DNS lookup for the given domain using the specified resolver and timeout. It returns true if the domain resolves (A/AAAA).

func ResolveDomainWithRetry

func ResolveDomainWithRetry(ctx context.Context, resolver *net.Resolver, domain string, timeout time.Duration, logf Logf, maxAttempts int, types []string) ([]Record, Outcome)

ResolveDomainWithRetry calls ResolveWithLog up to maxAttempts times, respecting ctx cancellation between attempts with a linear backoff delay. It returns the resolved records and a classified outcome for the last attempt.

func StderrLogf added in v0.8.0

func StderrLogf(format string, args ...any)

StderrLogf writes verbose lines straight to stderr, unsynchronized. It is the fallback for callers that set scan.Config.Verbose without a Logf.

func TakeoverHint added in v0.9.0

func TakeoverHint(ctx context.Context, resolver *net.Resolver, records []Record, timeout time.Duration, attempts int) string

TakeoverHint inspects a result's CNAME records and returns a takeover marker: "dangling" when the CNAME target does not resolve (NXDOMAIN), "provider:<name>" when it points at a takeover-prone service, and "dangling:<name>" when both hold. It returns "" when there is no CNAME or nothing suspicious. The target is resolved with resolver; an inconclusive lookup (timeout, SERVFAIL) is not called dangling.

func TakeoverProvider added in v0.9.0

func TakeoverProvider(target string) string

TakeoverProvider returns the takeover-prone service a CNAME target belongs to, or "" when it matches none.

func Transport added in v0.9.0

func Transport(server string) string

Transport names the transport a -dns-server value selects.

func WithLimiter added in v0.8.0

func WithLimiter(ctx context.Context, l *RateLimiter) context.Context

WithLimiter attaches l to ctx. Every lookup ResolveTypes performs under that ctx (scan jobs, retries, wildcard probes, the preflight) first takes one slot per DNS query it is about to send, before its own timeout starts, so waiting for the rate can never turn into a spurious timeout.

func WithQueryCounter added in v0.9.0

func WithQueryCounter(ctx context.Context, c *atomic.Int64) context.Context

WithQueryCounter attaches c to ctx; every DNS query a resolver from NewResolver dials under that ctx (retries included) increments it, so a scan can report the queries it actually sent (#70).

Types

type Logf added in v0.8.0

type Logf func(format string, args ...any)

Logf receives verbose per-lookup lines. The dns package never writes to stderr itself, so the caller can serialize these lines with its own output (for example the CLI progress line). A nil Logf discards them.

type Outcome added in v0.7.0

type Outcome int

Outcome is a coarse classification of one resolver attempt so callers can tell a definitive negative (NXDOMAIN) apart from an infrastructure failure.

const (
	// OutcomeFound means the lookup returned records of a requested type.
	OutcomeFound Outcome = iota
	// OutcomeNXDomain is a definitive negative: the name does not exist, or it
	// exists but has no records of the requested types (NODATA). Go's resolver
	// already reports NODATA as not-found for A/AAAA; CNAME is folded in here too.
	OutcomeNXDomain
	OutcomeTimeout
	OutcomeRefused
	OutcomeOther
	// OutcomeCanceled means the scan context was cancelled mid-lookup. It says
	// nothing about the resolver and must not be counted as a failure.
	OutcomeCanceled
)

Lookup outcomes, from a definitive answer to an interrupted lookup.

func Classify added in v0.7.0

func Classify(err error) Outcome

Classify maps a resolver error onto a coarse outcome so callers can tell a definitive negative (NXDOMAIN) apart from an infrastructure failure.

func (Outcome) String added in v0.9.0

func (o Outcome) String() string

String names an outcome for messages.

type Pool added in v0.9.0

type Pool struct {
	// contains filtered or unexported fields
}

Pool spreads lookups over several resolvers, round-robin over the healthy ones. A resolver whose recent failure rate passes poolHealthFailPct is benched for poolBenchFor, then given another chance. One that is caught lying is flagged and never used again in the run.

func NewPool added in v0.9.0

func NewPool(addrs []string, timeout time.Duration) *Pool

NewPool builds a pool of resolvers from NewResolver.

func (*Pool) AddCanary added in v0.9.0

func (p *Pool) AddCanary(name string)

AddCanary records a name the trusted resolver confirmed exists, for checking that pool members do not hide existing names.

func (*Pool) Disagree added in v0.9.0

func (p *Pool) Disagree(a PoolAnswer, trusted Outcome)

Disagree reports that the trusted resolver contradicted a pool answer: a hit it says does not exist was injected by the member that answered, and a name it confirms was hidden by the member whose NXDOMAIN the pool overruled. Either counts against that member (#105).

func (*Pool) Resolve added in v0.9.0

func (p *Pool) Resolve(ctx context.Context, domain string, timeout time.Duration, logf Logf, attempts int, types []string) PoolAnswer

Resolve looks domain up through the pool, up to attempts times, each attempt on a different healthy resolver where possible. An NXDOMAIN from a member that has not passed a canary check is asked of a second member; if that one finds the name, its answer is returned and the first member is recorded as contradicted for the caller's Disagree.

func (*Pool) Size added in v0.9.0

func (p *Pool) Size() int

Size returns the number of resolvers in the pool.

func (*Pool) Stats added in v0.9.0

func (p *Pool) Stats() []ResolverStats

Stats returns a snapshot of every member's accounting, in pool order.

type PoolAnswer added in v0.9.0

type PoolAnswer struct {
	Records []Record
	Outcome Outcome
	Addr    string // the resolver that answered; "" when none did
	// NoResolver is set when every member has been flagged as lying, so
	// the caller must use its trusted resolver instead.
	NoResolver bool
	// contains filtered or unexported fields
}

PoolAnswer is a pool lookup's result.

type PoolResolver added in v0.9.0

type PoolResolver struct {
	Addr string
	// contains filtered or unexported fields
}

PoolResolver is one member of a Pool.

type RateLimiter added in v0.8.0

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter spaces events at least 1/rate apart across all callers. It hands out reservations instead of draining a ticker, so a busy worker pool cannot lose ticks and fall below the configured rate. A nil *RateLimiter never waits.

func NewRateLimiter added in v0.8.0

func NewRateLimiter(perSecond int) *RateLimiter

NewRateLimiter returns a limiter for perSecond events, or nil (unlimited) when perSecond <= 0.

func (*RateLimiter) Wait added in v0.8.0

func (l *RateLimiter) Wait(ctx context.Context) error

Wait blocks until the caller's reserved slot arrives or ctx is done.

type Record added in v0.6.0

type Record struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

Record is a single resolved DNS record. Type is "A", "AAAA", "CNAME", etc. Value is the IP address (for A/AAAA) or target name (for CNAME).

func CheckWildcard

func CheckWildcard(ctx context.Context, resolver *net.Resolver, domain string, timeout time.Duration, types []string, attempts int) (bool, []Record, error)

CheckWildcard probes the domain with two random subdomains, looking up the same record types the scan will request (empty means DefaultTypes), so a CNAME wildcard is fingerprinted when -type includes CNAME. Each probe is tried up to attempts times. If either probe resolves the domain is treated as wildcard (conservative). The returned record slice is the union of both probe answers and is the fingerprint used to filter later results.

The check is conclusive only when every probe ended found or NXDOMAIN/NODATA. Otherwise the error wraps ErrWildcardInconclusive. Returns (isWildcard, fingerprint, error).

func FingerprintWildcard added in v0.9.0

func FingerprintWildcard(ctx context.Context, resolver *net.Resolver, domain string, timeout time.Duration, types []string, attempts int) (bool, []Record, error)

FingerprintWildcard is CheckWildcard for the scan root, where the fingerprint filters every result. It starts with five probes, and when their answers differ (a wildcard served from a rotating CDN or load-balancer pool) it keeps probing until the fingerprint has not grown for max(8, 4 x its size) consecutive probes, so the whole pool is learned rather than the two addresses two probes happened to see (#48).

func ResolveTypes added in v0.6.0

func ResolveTypes(ctx context.Context, resolver *net.Resolver, domain string, timeout time.Duration, types []string) ([]Record, time.Duration, error)

ResolveTypes performs per-type DNS lookups for the requested record types and returns the matching records, the time spent in lookups, and the most severe lookup error (if any). An empty types slice falls back to DefaultTypes. The caller should reuse a single *net.Resolver across lookups (see scan.Run).

Each type gets its own timeout, so a slow A answer cannot starve AAAA or CNAME. If ctx carries a RateLimiter (see WithLimiter), each type first waits for one slot per query it will send; the timeout starts only after that.

func ResolveWithLog added in v0.6.0

func ResolveWithLog(ctx context.Context, resolver *net.Resolver, domain string, timeout time.Duration, logf Logf, types []string) ([]Record, time.Duration, error)

ResolveWithLog wraps ResolveTypes with the verbose per-lookup logging used by the CLI, returning the resolved records for the requested types.

func SimulateResolve added in v0.6.0

func SimulateResolve(domain string, hitRate int, seed uint64, logf Logf, types []string) ([]Record, bool)

SimulateResolve returns a synthetic DNS result without performing any network I/O, along with synthetic records for the requested types when the domain "resolves". Exactly hitRate percent (0-100) of names resolve, whatever their prefix.

The outcome, records and reported timing are a pure function of (seed, domain), so the same seed reproduces the same scan regardless of worker scheduling. Callers pick a random seed when the user does not supply one.

type ResolverStats added in v0.9.0

type ResolverStats struct {
	Addr     string `json:"address"`
	Lookups  int64  `json:"lookups"`
	Found    int64  `json:"found"`
	NXDomain int64  `json:"nxdomain"`
	Failed   int64  `json:"failed"`  // timeout, refused or other
	Benched  int64  `json:"benched"` // times it was taken out of rotation
	// Honesty checks (#105).
	Canaries     int64 `json:"canaries"`     // canary checks answered
	Contradicted int64 `json:"contradicted"` // answers another resolver proved wrong
	Flagged      bool  `json:"flagged"`      // taken out of rotation as lying
}

ResolverStats is one pool member's accounting for the run-quality report.

type TakeoverCache added in v0.9.0

type TakeoverCache struct {
	// contains filtered or unexported fields
}

TakeoverCache is TakeoverHint with one lookup per CNAME target for the life of the cache (a scan): many names aliased to the same CDN or SaaS host cost one query and one rate slot, not one each (#121). Concurrent callers for the same target share the lookup. An inconclusive outcome (timeout, SERVFAIL, cancellation) is not kept, so a later name retries.

func (*TakeoverCache) Hint added in v0.9.0

func (c *TakeoverCache) Hint(ctx context.Context, resolver *net.Resolver, records []Record, timeout time.Duration, attempts int) string

Hint is TakeoverHint through the cache.

func (*TakeoverCache) Lookups added in v0.9.0

func (c *TakeoverCache) Lookups() int64

Lookups reports how many target lookups the cache sent.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL