nspool

package module
v2.0.6 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2025 License: MIT Imports: 21 Imported by: 0

README

nspool — DNS Resolver Pool with Health Checking

GoDoc Go Report Card

A Go library for managing a pool of DNS resolvers with automatic health checking, failover, and retry logic.

Features

  • Health Checking: Periodic validation of resolver availability
  • Automatic Failover: Queries automatically retry on different resolvers
  • Configurable: Timeouts, retry counts, worker pools, and health check behavior
  • Thread-Safe: Concurrent refresh and query operations
  • Refresh Hooks: Pre and post-refresh callbacks for logging and metrics
  • Viper Integration: Dynamic configuration from config files

Installation

go get github.com/nerdlem/nspool/v2

Quick Start

import "github.com/nerdlem/nspool/v2"

// Create pool with resolver addresses (resolvers start as available)
nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53", "8.8.8.8:53"})

// Configure health checking
nsp.SetHealthDomainSuffix("example.com")
nsp.SetMinResolvers(1)

// Optionally perform health check to validate resolvers
if err := nsp.Refresh(); err != nil {
    log.Fatal(err)
}

// Query DNS using the pool
msg := new(dns.Msg)
msg.SetQuestion("github.com.", dns.TypeA)
response, _, err := nsp.Exchange(msg)

Use Cases

Basic Resolver Pool

Create and use a pool of DNS resolvers with automatic health checking.

Configuration from Viper

Load resolver configuration from config files with hot-reload support.

Custom Health Checks

Implement custom validation logic to verify resolver responses.

Refresh Hooks

Add pre/post-refresh callbacks for logging, metrics, or conditional refresh.

Auto-Refresh

Automatically refresh resolver health at regular intervals.

Quiet Mode for Production

Reduce log verbosity by suppressing demotion messages while still tracking critical state changes (suspensions and reinstatements).

nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53", "8.8.8.8:53"})
nsp.SetLogger(logger)

// Enable quiet mode - only logs when resolvers are suspended or reinstated
nsp.SetQuietResolverStateChange(true)

// Set error thresholds
nsp.SetResolverErrorThreshold(0.05)    // 5% error rate triggers weight reduction (logged only if quiet mode is off)
nsp.SetResolverDisableThreshold(0.20)  // 20% error rate triggers suspension (always logged)

Documentation

Full documentation with examples is available at pkg.go.dev/github.com/nerdlem/nspool/v2.

Important Note

This code should only be used with recursive resolvers you control, operate, or are authorized to use. Sending large volumes of DNS queries to public resolvers may be considered abusive and can result in complaints or blacklisting.

License

MIT License - see LICENSE file for details.

Author

Luis E. Muñoz

Documentation

Overview

Example

Example demonstrates basic usage of nspool with a resolver pool.

// Create pool with resolver addresses
nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53", "8.8.8.8:53"})

// Configure health checking
nsp.SetHealthDomainSuffix("example.com")
nsp.SetMinResolvers(1)

// Perform initial health check
if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

// Create DNS query
msg := new(dns.Msg)
msg.SetQuestion("github.com.", dns.TypeA)

// Query using the pool
response, _, err := nsp.Exchange(msg)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Received %d answers\n", len(response.Answer))

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNilPool is returned when methods are invoked against a nil Pool.
	ErrNilPool = fmt.Errorf("nil pool")
	// ErrNilDNSClient is returned when the internal DNS Client is nil.
	ErrNilDNSClient = fmt.Errorf("nil dns client")
	// ErrNoHcSuffix is returned whenever a health check is required and
	// there is no HealthDomainSuffix() set.
	ErrNoHcSuffix = fmt.Errorf("health check domain suffix not set")
	// ErrInsufficientResolvers is returned when there are not enough available resolvers
	// to satisfy the minimum resolver requirement.
	ErrInsufficientResolvers = fmt.Errorf("insufficient available resolvers")
	// ErrNilHealthCheck is returned when Refresh() is called and the health check
	// function is nil.
	ErrNilHealthCheck = fmt.Errorf("nil health check function")
	// ErrRefreshAbortedByPreHook is returned when the pre-refresh hook returns false,
	// preventing the refresh operation from proceeding.
	ErrRefreshAbortedByPreHook = fmt.Errorf("refresh aborted by pre-hook")
)

Functions

func AddPort added in v2.0.4

func AddPort(addr string) string

AddPort ensures a resolver address has a port. If no port is present, it appends the default DNS port :53. This handles both IPv4 and most IPv6 cases using a naive colon check from the end of the string.

This function is useful when working with resolver addresses that may or may not include port numbers.

func DefaultHealthCheckFunction

func DefaultHealthCheckFunction(ans dns.Msg, t time.Duration, resolver string, p *Pool) bool

DefaultHealthCheckFunction provides default health check behaviour, which simply verifies tha the response indicated success. If the debug attribute is enabled and a logger has been provided, it will log data about the response including the resolver that was checked.

Example

ExampleDefaultHealthCheckFunction demonstrates the default health check behavior.

// Create a pool
nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53"})
nsp.SetHealthDomainSuffix("example.com")

// The default health check function is used automatically
// It simply verifies the response has RCODE = NOERROR

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

fmt.Println("Using default health check")

func DefaultHealthLabelGenerator

func DefaultHealthLabelGenerator() string

DefaultHealthLabelGenerator is the dafault function to generate labels used for resolver health checking. It returns a 16 character ASCII label made up of random characters from a restricted alphabet built into the function. The result of this function will be concatenated with the default suffix to generate the final FQDNs that will be used to test the resolvers.

func DefaultRefreshPostHook added in v2.0.3

func DefaultRefreshPostHook(p *Pool)

DefaultRefreshPostHook is the default post-refresh hook that does nothing. This function serves as an example and documentation for implementing custom post-refresh hooks.

Example usage:

nsp.SetRefreshPostHook(func(p *nspool.Pool) {
    log.Printf("Refresh complete: %d available, %d unavailable",
        p.AvailableCount(), p.UnavailableCount())
})
Example

ExampleDefaultRefreshPostHook demonstrates the default post-hook behavior.

// The default post-hook is a no-op
nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53"})
nsp.SetHealthDomainSuffix("example.com")

// You can explicitly set it if desired
nsp.SetRefreshPostHook(nspool.DefaultRefreshPostHook)

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

fmt.Println("Using default post-hook")

func DefaultRefreshPreHook added in v2.0.3

func DefaultRefreshPreHook(p *Pool) bool

DefaultRefreshPreHook is the default pre-refresh hook that always allows the refresh to proceed. This function serves as an example and documentation for implementing custom pre-refresh hooks.

Example usage:

nsp.SetRefreshPreHook(func(p *nspool.Pool) bool {
    // Only allow refresh during off-peak hours
    hour := time.Now().Hour()
    return hour < 8 || hour > 18
})
Example

ExampleDefaultRefreshPreHook demonstrates the default pre-hook behavior.

// The default pre-hook always returns true (allows refresh)
nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53"})
nsp.SetHealthDomainSuffix("example.com")

// You can explicitly set it if desired
nsp.SetRefreshPreHook(nspool.DefaultRefreshPreHook)

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

fmt.Println("Using default pre-hook")

Types

type DNSClientLike added in v2.0.5

type DNSClientLike interface {
	Exchange(*dns.Msg, string) (*dns.Msg, time.Duration, error)
	ExchangeContext(context.Context, *dns.Msg, string) (*dns.Msg, time.Duration, error)
}

DNSClientLike is an interface that should accept dns.Client. This is used to facilitate testing and allowing easier overriding to support special use cases.

type FileArray

type FileArray []string

FileArray is a string slice that can be populated from a file or direct values in configuration. It supports three use cases:

  1. Single file reference: "@/path/to/file[.gz|.bz2|.xz]" -> [lines from file] Each non-empty, non-comment line in the file becomes an element. The file can be optionally compressed with gzip (.gz), bzip2 (.bz2), or xz (.xz) compression - decompression happens automatically.

  2. String slice: ["ns1:53", "ns2:53"] -> ["ns1:53", "ns2:53"] Array values are used directly.

  3. Single string: "ns1:53" -> ["ns1:53"] Single string becomes a one-element slice.

func NewFileArray

func NewFileArray(input interface{}) (FileArray, error)

NewFileArray creates a FileArray from a string or []string input. If the input is a string starting with "@", it reads from the file. The file can be optionally compressed with gzip (.gz), bzip2 (.bz2), or xz (.xz) compression - decompression happens automatically.

func (FileArray) StringSlice

func (f FileArray) StringSlice() []string

StringSlice returns the underlying string slice

func (*FileArray) UnmarshalJSON added in v2.0.2

func (f *FileArray) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler for FileArray

func (*FileArray) UnmarshalTOML added in v2.0.2

func (f *FileArray) UnmarshalTOML(data interface{}) error

UnmarshalTOML implements the interface for TOML decoding.

func (*FileArray) UnmarshalText added in v2.0.2

func (f *FileArray) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler for FileArray

type HealthCheckFunction

type HealthCheckFunction func(ans dns.Msg, elapsed time.Duration, resolver string, p *Pool) bool

HealthCheckFunction is the signature for the function that powers resolver health check. It receives the DNS response to the health check query, the time it took to get the answer, the resolver address being checked, and a pointer to the nspool that is running the health check. It must return true to indicate a healthy, usable resolver. False marks the resolver as unavailable for use.

type HealthLabelGenerator

type HealthLabelGenerator func() string

HealthLabelGenerator is the signature for a function that generates the label used for resolver health checks. It must return a string that will be used to construct the name that will be queried on each candidate resolver during health checks.

type Pool

type Pool struct {

	// Client is a pointer to the dns.Client that will be used for sending all queries.
	// This value is automatically set by the constructors to a vainilla dns.Client
	// object. It is exposed to allow the caller to further customize behavior.
	// Setting this value to nil will cause all operations to return an error.
	Client DNSClientLike
	// contains filtered or unexported fields
}

Pool represents a new nspool object.

func NewFromPoolSlice

func NewFromPoolSlice(res []string) *Pool

NewFromPoolSlice returns a newly configured Pool primed with the resolvers explicitly provided in the call. All resolvers are initially marked as available. Use Refresh() to validate resolver health if needed.

Example

ExampleNewFromPoolSlice demonstrates creating a pool with explicit resolver addresses.

// Create pool with specific resolvers
nsp := nspool.NewFromPoolSlice([]string{
	"1.1.1.1:53",
	"8.8.8.8:53",
	"9.9.9.9:53",
})

// Configure health checking
nsp.SetHealthDomainSuffix("example.com")

// Perform health check
if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

fmt.Printf("Available resolvers: %d\n", nsp.AvailableCount())

func NewFromViper

func NewFromViper(tag string) *Pool

NewFromViper returns a newly configured Pool primed with the resolvers from viper using the provided tag. If the value in viper starts with '@', it will be treated as a file reference and the resolvers will be read from that file. All resolvers are initially marked as available. Use Refresh() to validate resolver health if needed.

func (*Pool) AutoRefresh

func (p *Pool) AutoRefresh(t time.Duration)

AutoRefresh enables automatic refresh of resolver health checks at the interval specified by hcAutoRefreshInterval. The behavior is as follows:

  • If hcAutoRefreshInterval is <= 0, automatic refresh is disabled and any existing auto-refresh goroutine is stopped.
  • If hcAutoRefreshInterval is > 0, a new goroutine is launched that will periodically call Refresh() at the specified interval.
  • Any existing auto-refresh goroutine is stopped before starting a new one.
  • The first health check is performed immediately upon enabling auto-refresh.
  • All Refresh() errors are logged if a logger is configured.

The auto-refresh goroutine can be stopped by either:

  • Calling AutoRefresh with an interval <= 0
  • Garbage collecting the Pool (the goroutine will exit automatically)

If you need to manually trigger a refresh while auto-refresh is active, you can still call Refresh() directly at any time.

Example

ExamplePool_AutoRefresh demonstrates automatic periodic health checking.

nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53", "8.8.8.8:53"})
nsp.SetHealthDomainSuffix("example.com")

// Perform initial refresh
if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

// Start auto-refresh every 5 minutes
nsp.AutoRefresh(5 * time.Minute)

// Stop auto-refresh when done
defer nsp.AutoRefresh(0)

// Use the pool for queries...
fmt.Println("Auto-refresh enabled")

func (*Pool) AvailableCount

func (p *Pool) AvailableCount() int

AvailableCount returns the number of currently available resolvers.

func (*Pool) AvailableResolvers

func (p *Pool) AvailableResolvers() []string

AvailableResolvers returns a copy of the list of currently available resolvers. The returned slice can be safely modified without affecting the pool's internal state. Returns nil if the pool is nil.

func (*Pool) Debug

func (p *Pool) Debug() bool

Debug returns the current value of the debug flag.

func (*Pool) Exchange

func (p *Pool) Exchange(m *dns.Msg) (*dns.Msg, time.Duration, error)

Exchange performs a DNS query using a randomly selected resolver from the pool. It will retry the query up to maxQueryRetries times if a resolver fails to respond. The query will timeout after queryTimeout duration for each attempt. Returns the DNS response, elapsed time, and any error encountered.

func (*Pool) ExchangeContext

func (p *Pool) ExchangeContext(ctx context.Context, m *dns.Msg) (*dns.Msg, time.Duration, error)

ExchangeContext performs a DNS query using a randomly selected resolver from the pool. It will retry the query up to maxQueryRetries times if a resolver fails to respond. The context controls the overall timeout for all retry attempts. Returns the DNS response, elapsed time, and any error encountered.

Example

ExamplePool_ExchangeContext demonstrates DNS queries with context timeout.

nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53"})
nsp.SetHealthDomainSuffix("example.com")

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// Create DNS query
msg := new(dns.Msg)
msg.SetQuestion("github.com.", dns.TypeA)

// Query with context
response, rtt, err := nsp.ExchangeContext(ctx, msg)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Query completed in %v with %d answers\n", rtt, len(response.Answer))

func (*Pool) ExchangeWeighted added in v2.0.4

func (p *Pool) ExchangeWeighted(m *dns.Msg) (*dns.Msg, time.Duration, error)

ExchangeWeighted performs a DNS query using a weighted randomly selected resolver from the pool. It will retry the query up to maxQueryRetries times if a resolver fails to respond. Resolver selection is weighted based on error rates, with poorly-performing resolvers being less likely to be selected. Returns the DNS response, elapsed time, and any error encountered.

func (*Pool) ExchangeWeightedContext added in v2.0.4

func (p *Pool) ExchangeWeightedContext(ctx context.Context, m *dns.Msg) (*dns.Msg, time.Duration, error)

ExchangeWeightedContext performs a DNS query using a weighted randomly selected resolver from the pool. It will retry the query up to maxQueryRetries times if a resolver fails to respond. The context controls the overall timeout for all retry attempts. Resolver selection is weighted based on error rates, and query results are automatically recorded for performance tracking. Returns the DNS response, elapsed time, and any error encountered.

func (*Pool) GetRandomResolver

func (p *Pool) GetRandomResolver() (string, error)

GetRandomResolver returns a randomly selected resolver from the pool of available resolvers. It returns an error if the number of available resolvers is less than the minimum required threshold. The returned resolver address will have `:53` appended if no port is present.

Example

ExamplePool_GetRandomResolver demonstrates getting a resolver address directly.

nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53", "8.8.8.8:53"})
nsp.SetHealthDomainSuffix("example.com")

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

// Get a random resolver
resolver, err := nsp.GetRandomResolver()
if err != nil {
	log.Fatal(err)
}

// Use it directly with dns.Client
client := new(dns.Client)
msg := new(dns.Msg)
msg.SetQuestion("github.com.", dns.TypeA)

response, _, err := client.Exchange(msg, resolver)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Query via %s returned %d answers\n", resolver, len(response.Answer))

func (*Pool) GetRandomResolverWeighted added in v2.0.4

func (p *Pool) GetRandomResolverWeighted() (string, error)

GetRandomResolverWeighted returns a randomly selected resolver from the pool, taking into account error rates and disabled resolvers. It respects the cooldown period for re-enabling disabled resolvers.

func (*Pool) GetResolverStats added in v2.0.4

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

GetResolverStats returns performance statistics for all resolvers that have served at least one query.

func (*Pool) HealthCheckFunction added in v2.0.2

func (p *Pool) HealthCheckFunction() HealthCheckFunction

HealthCheckFunction returns the current function used to evaluate resolver health. Returns nil if the pool is nil.

func (*Pool) HealthCheckQType

func (p *Pool) HealthCheckQType() uint16

HealthCheckQType returns the DNS query type used for health checks. If not explicitly set, defaults to TypeA.

func (*Pool) HealthCheckWorkerCount added in v2.0.2

func (p *Pool) HealthCheckWorkerCount() int

HealthCheckWorkerCount returns the number of worker goroutines used for parallel health checks. A value of 0 or negative will still be returned as-is, but during Refresh operations these values will result in using a single worker. Defaults to 64.

func (*Pool) HealthDomainSuffix

func (p *Pool) HealthDomainSuffix() string

HealthDomainSuffix returns the currently configured health-check domain suffix.

func (*Pool) InstallSignalHandler added in v2.0.4

func (p *Pool) InstallSignalHandler(sig os.Signal)

InstallSignalHandler sets up a signal handler that logs pool statistics when the specified signal is received. The handler logs the number of total resolvers, available resolvers, and unavailable resolvers. This can be useful for monitoring pool health during long-running operations.

Only one signal handler can be installed at a time. Multiple calls will replace the existing handler with a new one for the specified signal. The signal handler runs in a goroutine until StopSignalHandler() is called or the Pool is garbage collected.

Example:

pool.InstallSignalHandler(syscall.SIGUSR1)

On Unix systems, you can send the signal with: kill -USR1 <pid>

func (*Pool) LastRefreshed

func (p *Pool) LastRefreshed() time.Time

LastRefreshed returns the time of the last successful refresh operation. Returns zero time if no refresh has been performed or if the pool is nil.

func (*Pool) Logger added in v2.0.2

func (p *Pool) Logger() *logrus.Logger

Logger returns the current logger used by the pool. Returns nil if no logger is set or if the pool is nil.

func (*Pool) MaxQueryRetries added in v2.0.2

func (p *Pool) MaxQueryRetries() int

MaxQueryRetries returns the maximum number of times a query will be retried with different resolvers before giving up. This affects both health checks and regular DNS queries. A value of 0 means only one attempt will be made (no retries).

func (*Pool) MinResolvers

func (p *Pool) MinResolvers() int

MinResolvers returns the minimum number of resolvers that must be available for the pool to be considered operational. A value of 0 means no minimum is required.

func (*Pool) QuietResolverStateChange added in v2.0.6

func (p *Pool) QuietResolverStateChange() bool

QuietResolverStateChange returns the current value of the quiet resolver state change flag. When true, only suspension (removal from pool) and reinstatement (addition back to pool) events are logged. Weight reduction (demotion) events are suppressed.

func (*Pool) RecordResolverQuery added in v2.0.4

func (p *Pool) RecordResolverQuery(resolver string, success bool)

RecordResolverQuery records the result of a query to a specific resolver. This is used to track resolver performance and automatically disable poorly performing resolvers based on configured thresholds.

func (*Pool) Refresh

func (p *Pool) Refresh() error

Refresh implements health checking of the candidate resolvers. It will iterate over each candidate through a workerpool issuing queries in parallel fashion. Candidate resolvers that pass the test will be added to the availableResolvers slice while failing resolvers will be added to the unavailableResolvers slice.

The whole Refresh() cycle is protected so there can only be one Refresh() running at a time. Concurrent attempts will serialize. Refresh() will return an error if HealthDomainSuffix() has not been called with a valid domain name suffix.

If a pre-refresh hook is set, it will be called before the refresh starts. If the hook returns false, the refresh is aborted and ErrRefreshAbortedByPreHook is returned.

If a post-refresh hook is set, it will be called after the refresh completes, regardless of success or failure (except when aborted by the pre-hook).

If a logger has been set, significant events will be logged as Info(). If the debug flag has been set via SetDebug(), more detailed logging will be provided via Debug().

func (*Pool) RefreshNameServerTimeout

func (p *Pool) RefreshNameServerTimeout(t time.Duration)

RefreshNameServerTimeout sets the timeout interval to use when performing resolver health checks. Will refuse negative timeout intervals.

func (*Pool) RefreshPostHook added in v2.0.3

func (p *Pool) RefreshPostHook() RefreshPostHook

RefreshPostHook returns the current post-refresh hook function. Returns nil if no hook is set or if the pool is nil.

func (*Pool) RefreshPreHook added in v2.0.3

func (p *Pool) RefreshPreHook() RefreshPreHook

RefreshPreHook returns the current pre-refresh hook function. Returns nil if no hook is set or if the pool is nil.

func (*Pool) SetDebug

func (p *Pool) SetDebug(enabled bool)

SetDebug sets the debug flag for internal module debug logging.

func (*Pool) SetHealthCheckFunction added in v2.0.2

func (p *Pool) SetHealthCheckFunction(fn HealthCheckFunction)

SetHealthCheckFunction sets the function that will be used to evaluate resolver health during refresh operations. If nil is provided, the default health check function will be used.

Example

ExamplePool_SetHealthCheckFunction demonstrates using a custom health check function.

nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53"})
nsp.SetHealthDomainSuffix("example.com")

// Custom health check that validates response content
customCheck := func(ans dns.Msg, t time.Duration, resolver string, p *nspool.Pool) bool {
	// Must respond within 2 seconds
	if t > 2*time.Second {
		return false
	}

	// Must have NOERROR response
	if ans.Rcode != dns.RcodeSuccess {
		return false
	}

	// Must have at least one answer
	if len(ans.Answer) == 0 {
		return false
	}

	return true
}

nsp.SetHealthCheckFunction(customCheck)

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

fmt.Printf("Healthy resolvers: %d\n", nsp.AvailableCount())

func (*Pool) SetHealthCheckQType

func (p *Pool) SetHealthCheckQType(qtype uint16)

SetHealthCheckQType sets the DNS query type to be used for health checks.

func (*Pool) SetHealthCheckWorkerCount added in v2.0.2

func (p *Pool) SetHealthCheckWorkerCount(count int)

SetHealthCheckWorkerCount sets the number of worker goroutines used for parallel health checks. Values of 0 or negative will result in using a single worker. The default is 64. This setting affects the number of concurrent health checks performed during Refresh().

Example

ExamplePool_SetHealthCheckWorkerCount demonstrates configuring health check concurrency.

nsp := nspool.NewFromPoolSlice([]string{
	"1.1.1.1:53",
	"8.8.8.8:53",
	"9.9.9.9:53",
})
nsp.SetHealthDomainSuffix("example.com")

// Use 10 workers for health checks
nsp.SetHealthCheckWorkerCount(10)

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

fmt.Printf("Health checks use %d workers\n", nsp.HealthCheckWorkerCount())

func (*Pool) SetHealthDomainSuffix

func (p *Pool) SetHealthDomainSuffix(suffix string)

SetHealthDomainSuffix sets the health-check domain suffix. You must initialize the health-check domain suffix in order for automatic health-check to work. Calling Refresh() without this step will result in simply marking all resolvers as unavailable.

func (*Pool) SetLogger added in v2.0.2

func (p *Pool) SetLogger(l *logrus.Logger)

SetLogger sets the logger to be used for debug and error messages. Pass nil to disable logging.

Example

ExamplePool_SetLogger demonstrates enabling logging with logrus.

nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53"})
nsp.SetHealthDomainSuffix("example.com")

// Create and configure logger
logger := logrus.New()
logger.SetLevel(logrus.InfoLevel)

// Enable logging
nsp.SetLogger(logger)
nsp.SetDebug(true)

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

fmt.Println("Logging enabled")

func (*Pool) SetMaxQueryRetries added in v2.0.2

func (p *Pool) SetMaxQueryRetries(retries int)

SetMaxQueryRetries sets the maximum number of times a query will be retried with different resolvers before giving up. This affects both health checks and regular DNS queries. A value of 0 means only one attempt will be made (no retries). Negative values are treated as 0.

Example

ExamplePool_SetMaxQueryRetries demonstrates configuring query retry behavior.

nsp := nspool.NewFromPoolSlice([]string{
	"1.1.1.1:53",
	"8.8.8.8:53",
	"9.9.9.9:53",
})
nsp.SetHealthDomainSuffix("example.com")

// Set maximum retries to 5 (up to 6 total attempts)
nsp.SetMaxQueryRetries(5)

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

fmt.Printf("Max retries set to %d\n", nsp.MaxQueryRetries())

func (*Pool) SetMinResolvers

func (p *Pool) SetMinResolvers(min int)

SetMinResolvers sets the minimum number of resolvers that must be available for the pool to be considered operational. A value of 0 means no minimum is required. Negative values are treated as 0.

func (*Pool) SetQuietResolverStateChange added in v2.0.6

func (p *Pool) SetQuietResolverStateChange(quiet bool)

SetQuietResolverStateChange sets the quiet resolver state change flag. When set to true, only suspension and reinstatement events are logged. Weight reduction (demotion) events are suppressed, reducing log verbosity during normal operations while still tracking critical state changes.

Example

ExamplePool_SetQuietResolverStateChange demonstrates how to enable quiet mode to suppress verbose demotion messages while still logging critical state changes.

// Create a pool with some resolvers
nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53", "8.8.8.8:53"})

// Set up logging
logger := logrus.New()
logger.SetLevel(logrus.InfoLevel)
nsp.SetLogger(logger)

// Configure error thresholds
nsp.SetResolverErrorThreshold(0.05)   // 5% error rate triggers weight reduction
nsp.SetResolverDisableThreshold(0.20) // 20% error rate triggers suspension

// Enable quiet mode to suppress demotion messages
// Only suspension and reinstatement events will be logged
nsp.SetQuietResolverStateChange(true)

// Configure health checking
nsp.SetHealthDomainSuffix("example.com")

// Perform health check
if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

// During normal operations, resolver weight reductions (demotions) will not be logged,
// but suspensions (removals from pool) and reinstatements (additions back) will still
// be logged for visibility into critical pool state changes.

fmt.Println("Quiet mode enabled - only critical state changes will be logged")
Output:

Quiet mode enabled - only critical state changes will be logged

func (*Pool) SetRefreshPostHook added in v2.0.3

func (p *Pool) SetRefreshPostHook(fn RefreshPostHook)

SetRefreshPostHook sets the function that will be called after each Refresh() operation completes, regardless of success or failure. Pass nil to disable the post-hook.

Example

ExamplePool_SetRefreshPostHook demonstrates using a post-refresh hook for logging.

nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53", "8.8.8.8:53"})
nsp.SetHealthDomainSuffix("example.com")

// Log after refresh
nsp.SetRefreshPostHook(func(p *nspool.Pool) {
	log.Printf("Refresh complete: %d available, %d unavailable",
		p.AvailableCount(), p.UnavailableCount())
})

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}

func (*Pool) SetRefreshPreHook added in v2.0.3

func (p *Pool) SetRefreshPreHook(fn RefreshPreHook)

SetRefreshPreHook sets the function that will be called before each Refresh() operation. If the hook returns false, the refresh will be aborted and Refresh() will return ErrRefreshAbortedByPreHook. Pass nil to disable the pre-hook.

Example

ExamplePool_SetRefreshPreHook demonstrates using a pre-refresh hook for logging.

nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53", "8.8.8.8:53"})
nsp.SetHealthDomainSuffix("example.com")

// Log before refresh
nsp.SetRefreshPreHook(func(p *nspool.Pool) bool {
	log.Printf("Starting refresh with %d resolvers",
		p.AvailableCount()+p.UnavailableCount())
	return true
})

if err := nsp.Refresh(); err != nil {
	log.Fatal(err)
}
Example (Conditional)

ExamplePool_SetRefreshPreHook_conditional demonstrates conditional refresh using pre-hook.

nsp := nspool.NewFromPoolSlice([]string{"1.1.1.1:53"})
nsp.SetHealthDomainSuffix("example.com")

// Only allow refresh during off-peak hours
nsp.SetRefreshPreHook(func(p *nspool.Pool) bool {
	hour := time.Now().Hour()
	if hour >= 8 && hour <= 18 {
		log.Println("Skipping refresh during peak hours")
		return false
	}
	return true
})

err := nsp.Refresh()
if err == nspool.ErrRefreshAbortedByPreHook {
	log.Println("Refresh was skipped by policy")
}

func (*Pool) SetResolverCooldownPeriod added in v2.0.4

func (p *Pool) SetResolverCooldownPeriod(period time.Duration)

SetResolverCooldownPeriod sets the time period after which a disabled resolver can be re-enabled. Set to 0 to disable cooldown (resolvers stay disabled). Default is 1 hour.

func (*Pool) SetResolverDisableThreshold added in v2.0.4

func (p *Pool) SetResolverDisableThreshold(threshold float64)

SetResolverDisableThreshold sets the error rate threshold at which a resolver is completely disabled. Set to 0 to disable this behavior. Default is 0.20 (20%).

func (*Pool) SetResolverErrorThreshold added in v2.0.4

func (p *Pool) SetResolverErrorThreshold(threshold float64)

SetResolverErrorThreshold sets the error rate threshold at which a resolver's weight begins to be reduced. Set to 0 to disable this behavior. Default is 0.05 (5%).

func (*Pool) StopSignalHandler added in v2.0.4

func (p *Pool) StopSignalHandler()

StopSignalHandler stops the currently running signal handler goroutine if one is active. If no signal handler is running, this method does nothing. This is useful for cleaning up resources when the signal handler is no longer needed, or before installing a new handler.

func (*Pool) UnavailableCount

func (p *Pool) UnavailableCount() int

UnavailableCount returns the number of currently unavailable resolvers.

func (*Pool) UnavailableResolvers

func (p *Pool) UnavailableResolvers() []string

UnavailableResolvers returns a copy of the list of currently unavailable resolvers. The returned slice can be safely modified without affecting the pool's internal state. Returns nil if the pool is nil.

type RefreshPostHook added in v2.0.3

type RefreshPostHook func(*Pool)

RefreshPostHook is the signature for a function called after Refresh() completes. It receives a pointer to the Pool that was just refreshed. This hook has no return value and is called regardless of whether the refresh succeeded or failed. It can be used to log results, update metrics, or trigger other actions based on the refresh outcome.

type RefreshPreHook added in v2.0.3

type RefreshPreHook func(*Pool) bool

RefreshPreHook is the signature for a function called before Refresh() starts. It receives a pointer to the Pool being refreshed and must return a boolean. If it returns false, the Refresh() operation is aborted and Refresh() returns an error. This hook can be used to implement conditions under which refresh should not proceed, or to log/track refresh attempts.

type ResolverMetrics added in v2.0.4

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

ResolverMetrics tracks performance statistics for a single resolver.

type ResolverStats added in v2.0.4

type ResolverStats struct {
	Resolver   string
	Total      int64
	Failures   int64
	ErrorRate  float64
	LastError  time.Time
	LastUsed   time.Time
	DisabledAt time.Time
}

ResolverStats provides a snapshot of resolver metrics for reporting.

Jump to

Keyboard shortcuts

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