Documentation
¶
Overview ¶
Package fanout - parallel proxying DNS messages to upstream resolvers.
Supported transport protocols:
- DNS/UDP (plain, default)
- DNS/TCP (plain)
- DoT - DNS-over-TLS (RFC 7858) — tls:// prefix or "tls" directive
- DoH - DNS-over-HTTPS (RFC 8484) — https:// prefix (HTTP/2 transport)
- DoH3 - DNS-over-HTTPS (RFC 8484) — h3:// prefix (HTTP/3 / QUIC transport, RFC 9114)
- DoQ - DNS-over-QUIC (RFC 9250) — quic:// prefix
Index ¶
Constants ¶
const ( // TCPTLS net type for a DNS-over-TLS Client (DoT, RFC 7858). TCPTLS = "tcp-tls" // TCP net type for a Client (plain DNS over TCP). TCP = "tcp" // UDP net type for a Client (plain DNS over UDP). UDP = "udp" // DOH net type for a DNS-over-HTTPS Client (DoH, RFC 8484 over HTTP/2). DOH = "dns-over-https" // DOH3 net type for a DNS-over-HTTPS Client using HTTP/3 over QUIC (DoH3, RFC 8484 + RFC 9114). DOH3 = "dns-over-https3" // DOQ net type for a DNS-over-QUIC Client (DoQ, RFC 9250). DOQ = "dns-over-quic" )
Variables ¶
var ( RequestCount = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: pluginName, Name: "request_count_total", Help: "Number of request attempts started per upstream.", }, []string{metricLabelTo}) ErrorCount = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: pluginName, Name: "request_error_count_total", Help: "Number of request attempts that ended with an upstream error, grouped by bounded error class.", }, []string{metricLabelError, metricLabelTo}) CancelCount = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: pluginName, Name: "request_cancel_count_total", Help: "Number of request attempts that were canceled locally before a final upstream outcome was received.", }, []string{metricLabelTo}) SuccessCount = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: pluginName, Name: "request_success_count_total", Help: "Number of request attempts that completed with a valid DNS response per upstream.", }, []string{metricLabelTo}) WinCount = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: pluginName, Name: "response_win_count_total", Help: "Number of selected upstream responses that fanout returned downstream per upstream.", }, []string{metricLabelTo}) RcodeCount = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: pluginName, Name: "response_rcode_count_total", Help: "Number of responses per response code per upstream.", }, []string{metricLabelRcode, metricLabelTo}) RequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ Namespace: plugin.Namespace, Subsystem: pluginName, Name: "request_duration_seconds", Buckets: plugin.TimeBuckets, Help: "Histogram of the time request attempts with a valid DNS response took.", }, []string{"to"}) QueryCount = promauto.NewCounter(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: pluginName, Name: "query_count_total", Help: "Number of downstream queries handled by fanout (queries matching the configured FROM zone).", }) QueryFailureCount = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: pluginName, Name: "query_failure_count_total", Help: "Number of downstream queries that fanout answered with a failure, grouped by reason.", }, []string{metricLabelReason}) )
Variables declared for monitoring.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client interface {
// Request sends one DNS request to the upstream and returns the DNS response.
//
// The same *dns.Msg (r.Req) is shared concurrently across all fan-out
// workers for a single query, so implementations MUST treat r.Req as
// read-only: reading fields and packing it to wire format is safe, but
// mutating it — for example zeroing the ID for DoH/DoQ per RFC 8484 /
// RFC 9250, or adding EDNS options — would be a data race and would corrupt
// the requests sent to the other upstreams. Operate on r.Req.Copy() if
// mutation is ever required.
Request(context.Context, *request.Request) (*dns.Msg, error)
// Endpoint returns the configured upstream endpoint string.
Endpoint() string
// Net returns the transport identifier used for this upstream.
Net() string
// SetTLSConfig replaces the TLS settings used for future connections.
SetTLSConfig(*tls.Config)
}
Client represents one configured upstream DNS endpoint.
Implementations send a single DNS request to a single upstream over one transport family and expose metadata fanout uses for logging, metrics, and runtime TLS reconfiguration. Callers typically obtain a Client via NewClient, NewDoHClient, NewDoH3Client, or NewDoQClient during setup.
func NewClient ¶
NewClient creates a plain DNS client for addr over net.
The addr parameter should be a host:port pair understood by the DNS client. The net parameter should be one of UDP, TCP, or TCPTLS. Fanout typically uses UDP or TCP during parsing and switches to TCPTLS later via SetTLSConfig when a Corefile upstream requires DNS-over-TLS.
The returned Client reuses healthy TCP/TLS connections through an internal transport pool. Address validation happens lazily when Request dials.
func NewDoH3Client ¶ added in v1.12.0
NewDoH3Client creates a new DNS-over-HTTPS client using HTTP/3 (QUIC) transport. The endpoint must be a full HTTPS URL (e.g. "https://dns.google/dns-query").
func NewDoHClient ¶ added in v1.12.0
NewDoHClient creates a new DNS-over-HTTPS client for the given endpoint URL. The endpoint must be a full URL (e.g. "https://dns.google/dns-query"). The client uses HTTP/2 with a connection-pooling transport for performance.
func NewDoQClient ¶ added in v1.12.0
NewDoQClient creates a new DNS-over-QUIC client for the given address. The address should be in host:port format (e.g. "dns.example.com:853").
type Domain ¶
type Domain interface {
// Get returns the child node for one label, or nil if absent.
Get(string) Domain
// AddString inserts a fully-qualified domain name into the trie.
AddString(string)
// Add attaches a child node for one label.
Add(string, Domain)
// Contains reports whether the trie contains the given fully-qualified name.
Contains(string) bool
// IsFinal reports whether this node marks the end of an excluded name.
IsFinal() bool
// Finish marks the current node as the end of an excluded name.
Finish()
}
Domain stores excluded DNS names in a suffix trie for fast lookup. Fanout builds this structure from except and except-file directives and checks it for each incoming query before contacting upstream resolvers.
type Fanout ¶
type Fanout struct {
ExcludeDomains Domain
Timeout time.Duration
Debug bool
Race bool
RaceContinueOnErrorResponse bool
From string
// Attempts is the number of times to retry a failed upstream request.
// A value of 0 means infinite retries (bounded only by Timeout).
Attempts int
WorkerCount int
ServerSelectionPolicy policy
TapPlugin *dnstap.Dnstap
Next plugin.Handler
// contains filtered or unexported fields
}
Fanout represents a plugin instance that can do async requests to list of DNS servers.
func New ¶
func New() *Fanout
New returns reference to new Fanout plugin instance with default configs.
func (*Fanout) AddClient ¶
AddClient is used to add a new DNS server to the fanout. It also increments WorkerCount and serverCount. For bulk initialization during setup, use addClient instead.
func (*Fanout) OnShutdown ¶
OnShutdown stops all configured clients and releases their resources.
type SequentialPolicy ¶
type SequentialPolicy struct {
}
SequentialPolicy selects upstream clients in the order they were configured. Use it when deterministic first-to-last probing is preferred over randomized selection.
type Transport ¶
type Transport interface {
// Dial returns a pooled connection when available or establishes a new one.
Dial(ctx context.Context, net string) (*dns.Conn, error)
// Yield returns a healthy connection to the pool for reuse.
// Only call this for connections that completed a successful request-response cycle.
// For failed connections, call conn.Close() instead.
Yield(conn *dns.Conn)
// SetTLSConfig replaces the TLS settings used for future TLS dials.
SetTLSConfig(*tls.Config)
// Close drains the connection pool and releases resources.
Close()
}
Transport manages reusable network connections to one upstream endpoint.
Fanout clients use Transport to dial the upstream, apply TLS settings, and recycle only healthy TCP/TLS connections across requests. Callers normally use NewClient instead of working with Transport directly unless they are testing low-level connection behavior.
func NewTransport ¶
NewTransport creates a transport for a single upstream address.
The addr parameter should be a host:port pair. Returned transports keep a small pool of reusable TCP/TLS connections for that endpoint.
type WeightedPolicy ¶
type WeightedPolicy struct {
// contains filtered or unexported fields
}
WeightedPolicy selects upstream clients randomly according to loadFactor. The loadFactor slice must contain one weight per configured client in the same order as the upstream list.