dnsresolver

package module
v0.0.0-...-a976c05 Latest Latest
Warning

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

Go to latest
Published: Feb 3, 2022 License: Unlicense Imports: 14 Imported by: 0

README

Go DNS resolver

This library provides a system-independent[^1], non-caching DNS resolver.

It is useful in cases where up-to-date DNS records are required, and primarily intended for use in infrastructure automation tasks.

go-dns-resolver is built on top of the excellent miekg/dns package, but provides a much higher-level API. The priority here is usage ergonomics for the common cases, not completeness or performance.

Package documentation: https://pkg.go.dev/github.com/classmarkets/go-dns-resolver

[^1]: The system resolver is used to discover the root name servers unless configured otherwise; gotta start somewhere.

Examples

Query up-to-date A records:

Query the current A records for one.example.com.

ctx := context.Background() // cancel the context to abort in-flight queries

r := dnsresolver.New()

// go-dns-resolver always expects fully qualified domains, so the trailing
// dots is optional.
domain := "one.example.com"

recordSet, err := r.Query(ctx, "A", domain)
if errors.Is(err, dnsresolver.ErrNXDomain) {
    log.Println("Record not found")
} else if err != nil {
    log.Fatal(err)
}

// Display all DNS queries that have been sent. 
fmt.Println(recordSet.Trace.Dump())

fmt.Printf("%#v\n", recordSet.Values)     // []string{"203.0.113.12", "203.0.113.20", "203.0.113.80"}; the values of the A records
fmt.Printf("%#v\n", recordSet.ServerAddr) // "198.51.100.53:53"; the name server that answered the final query.
fmt.Printf("%#v\n", recordSet.TTL)        // 1 * time.Hour
Enable response caching

Response caching is very minimal by default, but can be enabled on a case-by-case basis.

Using the ObeyResponderAdvice cache policy caches all responses as advised by the name servers.

ctx := context.Background() // cancel the context to abort in-flight queries

r := dnsresolver.New()

// Cache NXDOMAIN responses for 1 minute, and everything else according to the
// TTLs of the DNS records.
r.CachePolicy = dnsresolver.ObeyResponderAdvice(1 * time.Minute)

r.Query(ctx, "A", "two.example.com") // pretty slow
r.Query(ctx, "A", "two.example.com") // almost instant

r.ClearCache()

r.Query(ctx, "A", "two.example.com") // pretty slow again
Configuring timeouts

By default, queries sent to private IP addresses (10.0.0.0/8, 192.168.0.0/16, fd00::/8, etc.) have a timeout of 100 milliseconds and all other queries have a timeout of 1 second. If a private subnet is not in fact on the local network, or some DNS servers are really slow, the timeout policy can be adjusted as necessary:

ctx := context.Background()

// total timeout for all required DNS queries
ctx, cancel := context.WithTimeout(ctx, 10 * time.Second)
defer cancel()

r := dnsresolver.New()

defaultPolicy := dnsresolver.DefaultTimeoutPolicy()

// 10.200.0.0/16 is a VPN, i.e. not local, and can be expected to be
// somewhat slow.
_, vpn, _ := net.ParseCIDR("10.200.0.0/16")

r.TimeoutPolicy = func(recordType, domainName, nameServerAddress string) time.Duration {
    ipStr, _, _ := net.SplitHostPort(nameServerAddress)
    ip := net.ParseIP(ipStr)

    if vpn.Contains(ip) {
        return 1 * time.Second
    } else {
        return defaultPolicy(recordType, domainName, nameServerAddress)
    }
}

r.Query(ctx, "A", "three.example.com")
Configuring bootstrap servers

go-dns-resolver does not include a hard-coded list of root name servers. Consequently, the root name servers have to be discovered by querying some other name server. By default, those "other name servers" are the name servers configured in the OS.

If determining the system name servers fails, or the system name servers cannot be trusted to deliver the correct root name servers, the set of initial name servers can be specified explicitly.

ctx := context.Background()

r := dnsresolver.New()

// Use Google's name servers to discover the root name servers, and if that
// fails try CloudFlare's server too. The ports are optional and default to 53.
r.SetBootstrapServers("8.8.8.8:53", "8.8.4.4:53", "1.1.1.1:53")

// Will send a query for "NS ." to 8.8.8.8, then start resolving
// four.example.com at the root name servers. Note that 8.8.8.8 will never see
// a query for four.example.com.
r.Query(ctx, "A", "four.example.com")

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrCircular = errors.New("circular reference")

ErrCircular is returned by Resolver.Query if CNAME records or name servers refer to one another. ErrCircular may be wrapped and must be tested for with errors.Is.

View Source
var ErrNXDomain = errors.New("NXDOMAIN response")

ErrNXDomain is returned by Resolver.Query if the final response of a query chain is a NXDOMAIN response. ErrNXDomain may be wrapped and must be tested for with errors.Is.

View Source
var PrivateNets = []*net.IPNet{
	mustParseCIDR("10.0.0.0/8"),
	mustParseCIDR("127.0.0.0/8"),
	mustParseCIDR("169.254.0.0/16"),
	mustParseCIDR("172.16.0.0/12"),
	mustParseCIDR("192.0.0.0/24"),
	mustParseCIDR("192.0.2.0/24"),
	mustParseCIDR("192.168.0.0/16"),
	mustParseCIDR("198.18.0.0/15"),
	mustParseCIDR("198.51.100.0/24"),
	mustParseCIDR("203.0.113.0/24"),
	mustParseCIDR("233.252.0.0/24"),
	mustParseCIDR("::1/128"),
	mustParseCIDR("2001:db8::/32"),
	mustParseCIDR("fd00::/8"),
	mustParseCIDR("fe80::/10"),
}

PrivateNets is used by DefaultTimeoutPolicy to return a low timeout for server addresses in one of these subnets.

Functions

This section is empty.

Types

type CachePolicy

type CachePolicy func(RecordSet) (ttl time.Duration)

CachePolicy determines how long a Resolver's cached DNS responses remain fresh.

func DefaultCachePolicy

func DefaultCachePolicy() CachePolicy

DefaultCachePolicy returns the default CachePolicy. It is used by Resolver.Query if Resolver.CachePolicy is nil.

DefaultCachePolicy obeys the server-returned TTL for responses delegating to the name servers for public suffixes (such as ".com", ".org", ".co.uk"; see https://publicsuffix.org/) and caches nothing else.

func ObeyResponderAdvice

func ObeyResponderAdvice(negativeTTL time.Duration) CachePolicy

ObeyResponderAdvice returns a CachePolicy that obeys the TTL advice that is returned by name servers, except for NXDOMAIN responses, which are cached for the duration of negativeTTL.

type RecordSet

type RecordSet struct {
	// Raw is the miekg/dns message that has been received from the server and
	// was used to construct this RecordSet. If no response has been received --
	// due to a network error, for instance -- Raw contains only the
	// QUESTION section.
	Raw dns.Msg

	// Name is the fully qualified domain name of this record set. The trailing
	// dot is omitted.
	//
	// Name is set even in case of network errors, in which case it is the
	// domainName argument to Resolver.Query.
	Name string

	// Type is the type of the DNS response returned by the name
	// server, such as "A", "AAAA", "SRV", etc.
	//
	// Type is set even in case of network errors, in which case it is the
	// recordType argument to Resolver.Query.
	//
	// If the response indicates an error, Type is set to a string
	// representation of that error, such as "NXDOMAIN", "SERVFAIL", etc.
	Type string

	// TTL is the smallest time-to-live of the records in this set, as returned
	// by the name server.
	TTL time.Duration

	// Values contains the values of each record in the DNS response, in the
	// order sent by the server. The values may be quoted, for instance in SPF
	// record sets.
	Values []string

	// ServerAddr contains the IP address and port of the name server that has
	// returned this record set.
	//
	// ServerAddr is set even in case of network errors.
	ServerAddr string

	// Age is the amount of time that has passed since the response was cached
	// by a Resolver.
	//
	// Age is
	// - negative if the RecordSet has not been added to the cache,
	// - zero if the query for this RecordSet caused it to be added to the
	//   cache,
	// - positive if it was present in the cache before the query for this
	//   RecordSet has started.
	Age time.Duration

	// RTT is the measured round-trip time for this record set, i.e. the
	// duration between sending the DNS query to the server and receiving the
	// response. This duration includes encoding the request packet(s) and
	// parsing the response packet(s). It does not include the time spent on
	// any other recursive queries, such as NS lookups.
	//
	// RTT is set even in case of network errors (but then excludes parsing the
	// response, obviously).
	RTT time.Duration

	// Trace reports all DNS queries that where necessary to retrieve this
	// RecordSet.
	Trace *Trace
}

RecordSet represents the response to a DNS query.

type Resolver

type Resolver struct {

	// TimeoutPolicy determines the round-trip timout for a single DNS query.
	// If nil, DefaultTimeoutPolicy() is used.
	TimeoutPolicy TimeoutPolicy

	// CachePolicy determines how long DNS responses remain in this resolver's
	// cache. If nil, DefaultCachePolicy() is used.
	//
	// The CachePolicy is consulted even for NXDOMAIN responses.
	//
	// If CachePolicy is changed after the cache has already been populated,
	// cached responses are still returned as appropriate. Use ClearCache to
	// clear the cache if desired.
	//
	// The cache size is limited to 10k entries, and the least recently used
	// records are evicted if necessary.
	CachePolicy CachePolicy

	// DisableIP4 and DisableIP6 prevent the resolver from contacting DNS
	// servers on IPv4 and IPv6 addresses, respectively.
	DisableIP4 bool
	DisableIP6 bool
	// contains filtered or unexported fields
}

Resolver resolves DNS queries recursively.

Concurrent calls to all methods are safe, but exported fields of the Resolver must not be changed until all method calls have returned, of course.

func New

func New() *Resolver

New returns a new Resolver that resolves all queries recursively starting at the root name servers, and uses the DefaultTimeoutPolicy and DefaultCachePolicy.

func (*Resolver) ClearCache

func (r *Resolver) ClearCache()

ClearCache removes any cached DNS responses.

func (*Resolver) Query

func (R *Resolver) Query(ctx context.Context, recordType string, domainName string) (RecordSet, error)

Query starts a recursive query for the given record type and DNS name.

Cancel the context to abort any inflight request. If canceled, the context's error is returned but it may be wrapped.

recordType is the type of the record set to query, such as "A", "AAAA", "SRV", etc.

domainName is always understood as a fully qualified domain, making the trailing dot optional. If recordType is "PTR", and domainName is a valid IPv4 or IPv6 address, the IP address is converted into the correct .arpa domain automatically, however, the Name field of the resulting RecordSet still contains the IP address.

Timeouts are applied according to the TimeoutPolicy. If a timeout occurs, context.DeadlineExceeded is returned but it may be wrapped and must be tested for with errors.Is.

Query populates the resolver's cache according to the configured CachePolicy, however matching existing items in the cache are used independently of the CachePolicy.

If a terminal error occurs, an incomplete record set is returned, along with an error.

Concurrent calls to Query are safe, but public fields of the Resolver must not be changed until all Query calls have returned.

Most name servers are setup redundantly, i.e. NS responses include multiple records. Such name servers are tried in the order they appear in in the response until one returns a response (even if the response indicates an error, such as NXDOMAIN). After any response other than SERVFAIL has been received, no other servers in the NS set are queried. For instance:

    QUERY            NAME SERVER               RESULT
1)  NS com.          @a.root-servers.org.  ->  a.gtld-servers.net.
                                               c.gtld-servers.net.
                                               b.gtld-servers.net.
                                               d.gtld-servers.net.

2)  NS example.com.  @a.gtld-servers.net.  ->  network timeout

3)  NS example.com.  @c.gtld-servers.net.  ->  SERVFAIL

4)  NS example.com.  @b.gtld-servers.net.  ->  NXDOMAIN

d.gtld-servers.net is not queried because b.gtld-servers.net. responded (albeit with an NXDOMAIN error).

func (*Resolver) SetBootstrapServers

func (r *Resolver) SetBootstrapServers(serverAddresses ...string) error

SetBootstrapServers specifies the IP addresses and, optionally, ports for the name servers that are used to discover the root name servers. By default the name servers configured in the operating system are used.

This method is intended mostly for testing this package, but is also useful if the operating system's resolver can't be trusted to query the root zone correctly, or if automatic detection fails.

If SetBootstrapServers has not been called when Query is first called, Resolver will attempt to discover the operating system's resolver(s). This is platform specific. For instance, on *nix systems, /etc/resolv.conf is parsed.

type TimeoutPolicy

type TimeoutPolicy func(recordType, domainName, nameServerAddress string) (timeout time.Duration)

TimeoutPolicy determines the round-trip timeout for a single DNS query.

recordType is the type of the record set to be queried, such as "A", "AAAA", "SRV", etc.

domainName is the fully qualified name to be queried, with the trailing dot is omitted.

nameServerAddress is the IP address and port of the server to query.

Any non-positive duration is understood as an infinite timeout.

func DefaultTimeoutPolicy

func DefaultTimeoutPolicy() TimeoutPolicy

DefaultTimeoutPolicy returns the default TimeoutPolicy. It is used by Resolver.Query if Resolver.TimeoutPolicy is nil.

DefaultTimeoutPolicy assumes low latency to addresses in PrivateNets (10.0.0.0/8, 192.168.0.0/16, fd00::/8, etc.) and causes requests to such addresses to timeout after 100 milliseconds and all other requests after 1 second.

type Trace

type Trace struct {
	Queries []*TraceNode
	// contains filtered or unexported fields
}

Trace reports all DNS queries that where necessary to retrieve a RecordSet. A trace typically starts with a NS query to one of the root name servers, but the first time a resolver is used the trace starts with a query to the name servers in the operating system to determine the list of root name servers.

func (*Trace) Dump

func (t *Trace) Dump() string

Dump returns a string representation of the trace.

The output is meant for human consumption and may change between releases of this package without notice.

Lines starting with a question mark indicate DNS requests. Lines starting with an exclamation mark indicate DNS responses. Lines starting with an X indicate network errors.

type TraceNode

type TraceNode struct {
	Server string

	Message *dns.Msg
	RTT     time.Duration
	Error   error
	Age     time.Duration

	Children []*TraceNode
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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