recursive

package module
v0.22.10 Latest Latest
Warning

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

Go to latest
Published: Dec 11, 2025 License: MIT Imports: 21 Imported by: 0

README

build coverage goreport Docs

recursive

Recursive DNS resolver with QNAME minimization and optional cache.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/linkdata/recursive"
	"github.com/miekg/dns"
)

func main() {
	rec := recursive.New(nil)
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()
	msg, srv, err := rec.DnsResolve(ctx, "one.one.one.one", dns.TypeA)
	if err != nil {
		panic(err)
	}
	fmt.Println(msg)
    fmt.Println(";; SERVER ", srv)
}

The resolver can log query details. To protect privacy, logs only include the first few characters of the client and server cookie values.

Some servers don't handle QNAME minimization well. In that case, we fall back to normal resolution.

You can have the library generate a detailed log:

$ go run ./cmd/cli -debug A www.microsoft.com
[0      0] DELEGATION QUERY "www.microsoft.com."
[0      1]  SENDING udp4: @199.7.83.42 NS "com." => NOERROR [0+13+27 A/N/E] (4ms, 1527 bytes)
[5      1]  SENDING udp4: @192.5.6.30 NS "microsoft.com." => NOERROR [0+4+2 A/N/E] (27ms, 267 bytes)
[32     1]  SENDING udp4: @150.171.10.39 NS "www.microsoft.com." => NOERROR [1+0+1 A/N/E] (13ms, 110 bytes AUTH)
[46     0] DELEGATION ANSWER "www.microsoft.com.": NOERROR with 1 servers
[46     0] QUERY A "www.microsoft.com." from 1 servers
[46     1]  SENDING udp4: @150.171.10.39 A "www.microsoft.com." => NOERROR [1+0+1 A/N/E] (13ms, 110 bytes AUTH)
[60     1]  CNAME @150.171.10.39 A "www.microsoft.com." => "www.microsoft.com-c-3.edgekey.net."
[60     1]  DELEGATION QUERY "www.microsoft.com-c-3.edgekey.net."
[60     2]   SENDING udp4: @199.7.83.42 NS "net." => NOERROR [0+13+27 A/N/E] (4ms, 1527 bytes)
[64     2]   SENDING udp4: @192.5.6.30 NS "edgekey.net." => NOERROR [0+8+17 A/N/E] (24ms, 943 bytes)
[88     2]   SENDING udp4: @23.61.199.64 NS "com-c-3.edgekey.net." => NOERROR [0+1+1 A/N/E] (3ms, 132 bytes AUTH)
[91     2]   SENDING udp4: @23.61.199.64 NS "microsoft.com-c-3.edgekey.net." => NOERROR [0+1+1 A/N/E] (3ms, 142 bytes AUTH)
[94     2]   SENDING udp4: @23.61.199.64 NS "www.microsoft.com-c-3.edgekey.net." => NOERROR [1+0+1 A/N/E] (3ms, 165 bytes AUTH)
[97     1]  DELEGATION ANSWER "www.microsoft.com-c-3.edgekey.net.": NOERROR with 16 servers
[97     1]  QUERY A "www.microsoft.com-c-3.edgekey.net." from 16 servers
[97     2]   SENDING udp4: @23.61.199.64 A "www.microsoft.com-c-3.edgekey.net." => NOERROR [1+0+1 A/N/E] (2ms, 165 bytes AUTH)
[99     2]   CNAME @23.61.199.64 A "www.microsoft.com-c-3.edgekey.net." => "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net."
[99     2]   DELEGATION QUERY "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net."
[99     3]    CACHED: NS "net." => NOERROR [0+13+27 A/N/E] (1527 bytes)
[99     3]    SENDING udp4: @192.5.6.30 NS "akadns.net." => NOERROR [0+9+11 A/N/E] (25ms, 807 bytes)
[125    3]    SENDING udp4: @2.16.40.130 NS "globalredir.akadns.net." => NOERROR [0+1+1 A/N/E] (29ms, 137 bytes AUTH)
[154    3]    SENDING udp4: @2.16.40.130 NS "net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (32ms, 111 bytes AUTH)
[186    3]    SENDING udp4: @2.16.40.130 NS "edgekey.net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (25ms, 127 bytes AUTH)
[212    3]    SENDING udp4: @2.16.40.130 NS "com-c-3.edgekey.net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (32ms, 143 bytes AUTH)
[244    3]    SENDING udp4: @2.16.40.130 NS "microsoft.com-c-3.edgekey.net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (25ms, 163 bytes AUTH)
[270    3]    SENDING udp4: @2.16.40.130 NS "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (28ms, 181 bytes AUTH)
[298    2]   DELEGATION ANSWER "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net.": NOERROR with 10 servers
[298    2]   QUERY A "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net." from 10 servers
[298    3]    SENDING udp4: @2.16.40.130 A "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (28ms, 181 bytes AUTH)
[327    3]    CNAME @2.16.40.130 A "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net." => "e13678.dscb.akamaiedge.net."
[327    3]    DELEGATION QUERY "e13678.dscb.akamaiedge.net."
[327    4]     CACHED: NS "net." => NOERROR [0+13+27 A/N/E] (1527 bytes)
[327    4]     SENDING udp4: @192.5.6.30 NS "akamaiedge.net." => NOERROR [0+8+10 A/N/E] (22ms, 771 bytes)
[348    4]     SENDING udp4: @2.16.40.192 NS "dscb.akamaiedge.net." => NOERROR [0+8+10 A/N/E] (32ms, 825 bytes)
[381    4]     SENDING udp4: @23.218.92.39 NS "e13678.dscb.akamaiedge.net." => NOERROR [0+1+1 A/N/E] (2ms, 152 bytes AUTH)
[384    3]    DELEGATION ANSWER "e13678.dscb.akamaiedge.net.": NOERROR with 9 servers
[384    3]    QUERY A "e13678.dscb.akamaiedge.net." from 9 servers
[384    4]     SENDING udp4: @23.218.92.39 A "e13678.dscb.akamaiedge.net." => NOERROR [1+0+1 A/N/E] (3ms, 97 bytes AUTH)
[387    3]    ANSWER @23.218.92.39 A "e13678.dscb.akamaiedge.net." => NOERROR [1+0+1 A/N/E] (97 bytes AUTH)
[387    2]   ANSWER @23.218.92.39 A "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net." => NOERROR [2+0+2 A/N/E] (234 bytes AUTH)
[387    1]  ANSWER @23.218.92.39 A "www.microsoft.com-c-3.edgekey.net." => NOERROR [3+0+3 A/N/E] (325 bytes AUTH)
[387    0] ANSWER @23.218.92.39 A "www.microsoft.com." => NOERROR [4+0+4 A/N/E] (384 bytes AUTH)

;;; ----------------------------------------------------------------------
; <<>> recursive <<>> @23.218.92.39 A www.microsoft.com
;; opcode: QUERY, status: NOERROR, id: 55642
;; flags: qr aa; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 4

;; OPT PSEUDOSECTION:
; EDNS: version 0; flags:; udp: 4096

;; QUESTION SECTION:
;www.microsoft.com.     IN       A

;; ANSWER SECTION:
www.microsoft.com.      3600    IN      CNAME   www.microsoft.com-c-3.edgekey.net.
www.microsoft.com-c-3.edgekey.net.      900     IN      CNAME   www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net.
www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net.       900     IN      CNAME   e13678.dscb.akamaiedge.net.
e13678.dscb.akamaiedge.net.     20      IN      A       2.18.198.101

;; ADDITIONAL SECTION:

;; GZPACK: H4sIAAAAAAAA/7oZ1cLAwMjAwsDAwMJcXl7OmZuZXJRfnJ9WwpycnwuSYsQmzMrAyMDAJ8CgjCrJnpyfq5usa8yempKemp1ayZyXWsJAhBKIecwtDFaEFXOn5+QnJeYUpaZkFrElZiem5BUTaQ0unXDbZdhSDY3NzC1YUoqTk7gSsxNzEzNB+sGq8MmBgomBgUGEgYVJ6FgqA4MmywUGKNAUYMDHBAQAAP//DDE9MYABAAA=
;; SERVER: 23.218.92.39
;; CACHE: size 22, hit ratio 8.33%

Documentation

Overview

package recursive provides a minimal iterative DNS resolver with QNAME minimization using github.com/miekg/dns for wire format and transport.

Index

Constants

View Source
const DefaultMaxTTL = 7 * 24 * 60 * 60
View Source
const DefaultMinTTL = 10
View Source
const DefaultNXTTL = 60 * 60

Variables

View Source
var (
	// ErrInvalidCookie is returned if the DNS cookie from the server is invalid.
	ErrInvalidCookie = errors.New("invalid cookie")
	// ErrMismatchedQuestion is returned when a response question does not match the query.
	ErrMismatchedQuestion = errors.New("mismatched response question")
	// ErrMaxDepth is returned when recursive resolving exceeds the allowed limit.
	ErrMaxDepth = errors.New("recursion depth exceeded 16")
	// ErrMaxSteps is returned when resolving exceeds the step limit.
	ErrMaxSteps = errors.New("resolve steps exceeded 4096")
	// ErrNoResponse is returned when no authoritative server could be successfully queried.
	// It is equivalent to SERVFAIL.
	ErrNoResponse = errors.New("no authoritative response")

	DefaultCache          = NewCache()
	DefaultTimeout        = time.Second * 3
	DefaultDNSPort uint16 = 53
	DefaultMsgSize uint16 = 1232 // default UDP message size
)
View Source
var ErrExtendedErrorCode = extendedErrorCodeError(0)
View Source
var ErrInvalidCacheEntry = errors.New("invalid cache entry")
View Source
var ErrWrongMagic = errors.New("wrong magic number")
View Source
var Roots4 = []netip.Addr{
	netip.AddrFrom4([4]byte([]byte{0xaa, 0xf7, 0xaa, 0x2})),
	netip.AddrFrom4([4]byte([]byte{0xc0, 0x5, 0x5, 0xf1})),
	netip.AddrFrom4([4]byte([]byte{0xc0, 0x21, 0x4, 0xc})),
	netip.AddrFrom4([4]byte([]byte{0xc0, 0x24, 0x94, 0x11})),
	netip.AddrFrom4([4]byte([]byte{0xc0, 0x3a, 0x80, 0x1e})),
	netip.AddrFrom4([4]byte([]byte{0xc0, 0x70, 0x24, 0x4})),
	netip.AddrFrom4([4]byte([]byte{0xc0, 0xcb, 0xe6, 0xa})),
	netip.AddrFrom4([4]byte([]byte{0xc1, 0x0, 0xe, 0x81})),
	netip.AddrFrom4([4]byte([]byte{0xc6, 0x29, 0x0, 0x4})),
	netip.AddrFrom4([4]byte([]byte{0xc6, 0x61, 0xbe, 0x35})),
	netip.AddrFrom4([4]byte([]byte{0xc7, 0x7, 0x53, 0x2a})),
	netip.AddrFrom4([4]byte([]byte{0xc7, 0x7, 0x5b, 0xd})),
	netip.AddrFrom4([4]byte([]byte{0xca, 0xc, 0x1b, 0x21})),
}
View Source
var Roots6 = []netip.Addr{
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x5, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x53})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x5, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x5, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0xd})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x5, 0x0, 0x0, 0x2d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x5, 0x0, 0x0, 0x2f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x5, 0x0, 0x0, 0x9f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x42})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x5, 0x0, 0x0, 0xa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x5, 0x3, 0xc, 0x27, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x30})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x5, 0x3, 0xba, 0x3e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x30})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x7, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0x7, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x53})),
	netip.AddrFrom16([16]byte([]byte{0x20, 0x1, 0xd, 0xc3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x35})),
	netip.AddrFrom16([16]byte([]byte{0x28, 0x1, 0x1, 0xb8, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb})),
}

Functions

func ErrorFromExtendedErrorCode added in v0.16.0

func ErrorFromExtendedErrorCode(code uint16) (err error)

ErrorFromExtendedErrorCode returns the canonical Go error for the provided Extended Error Code. It returns ErrExtendedErrorCode if there is no known mapping.

func ExtendedErrorCodeFromError added in v0.16.0

func ExtendedErrorCodeFromError(err error) (rcode uint16)

ExtendedErrorCodeFromError attempts to map a Go error to a DNS Extended Rcode. The function understands well-known errors from the os, io, and net packages (including their wrapper types) and returns dns.ExtendedErrorCodeOther if no mapping is known.

Types

type Cache added in v0.1.2

type Cache struct {
	MinTTL int64 // always cache responses for at least this long seconds
	MaxTTL int64 // never cache responses for longer than this seconds (excepting successful NS responses)
	NXTTL  int64 // cache NXDOMAIN responses for this long seconds
	// contains filtered or unexported fields
}

func NewCache added in v0.1.2

func NewCache() *Cache

func (*Cache) Clean added in v0.1.2

func (cache *Cache) Clean()

Clean removes stale entries from the cache.

func (*Cache) CleanAllow added in v0.22.6

func (cache *Cache) CleanAllow(t time.Time, allowfn func(msg *dns.Msg, ttl time.Duration) bool)

CleanAllow calls allowfn with 't' as the time to use to determine staleness. If allowfn returns false, the cache entry is removed.

func (*Cache) CleanBefore added in v0.21.3

func (cache *Cache) CleanBefore(t time.Time)

CleanBefore removes entries that expired before t from the cache.

func (*Cache) Clear added in v0.1.4

func (cache *Cache) Clear()

func (*Cache) DnsGet added in v0.2.0

func (cache *Cache) DnsGet(qname string, qtype uint16) (msg *dns.Msg)

DnsGet returns a caches DNS message if one exists that has not expired.

If an expired message is found, it is removed from the cache and nil is returned.

func (*Cache) DnsResolve added in v0.2.0

func (cache *Cache) DnsResolve(ctx context.Context, qname string, qtype uint16) (msg *dns.Msg, srv netip.Addr, err error)

func (*Cache) DnsSet added in v0.2.0

func (cache *Cache) DnsSet(msg *dns.Msg)

DnsSet add a DNS message to the cache.

Does nothing if the message has the Zero flag set, or does not have exactly one Question.

func (*Cache) Entries added in v0.2.0

func (cache *Cache) Entries() (n int)

Entries returns the number of entries in the cache.

func (*Cache) Get added in v0.1.2

func (cache *Cache) Get(qname string, qtype uint16, allowfn func(msg *dns.Msg, ttl time.Duration) bool) (msg *dns.Msg, stale bool)

Get allows filtering DNS entries from the cache and control of eviction.

If and entry is found, and allowfn returns true, the entry is returned. Otherwise, the entry is purged from the cache,

The default allowfn returns false if the entry is stale.

func (*Cache) HitRatio added in v0.1.2

func (cache *Cache) HitRatio() (n float64)

HitRatio returns the hit ratio as a percentage.

func (*Cache) Merge added in v0.21.1

func (cache *Cache) Merge(other *Cache)

Merge inserts all entries from other into cache. If an entry exists in both, the one that expires last wins.

func (*Cache) ReadFrom added in v0.21.3

func (cache *Cache) ReadFrom(r io.Reader) (n int64, err error)

func (*Cache) Walk added in v0.20.0

func (cache *Cache) Walk(fn func(msg *dns.Msg, expires time.Time) (err error)) (err error)

Walk calls fn for each entry in the cache. If fn returns an error, it stops and returns that error.

func (*Cache) WriteTo added in v0.21.3

func (cache *Cache) WriteTo(w io.Writer) (n int64, err error)

type CachedNetError added in v0.22.10

type CachedNetError struct {
	When     time.Time
	Err      error
	Protocol string
	Address  netip.Addr
}

func (*CachedNetError) Error added in v0.22.10

func (ne *CachedNetError) Error() string

func (*CachedNetError) Unwrap added in v0.22.10

func (ne *CachedNetError) Unwrap() error

type Cacher added in v0.1.4

type Cacher interface {
	// DnsSet stores msg for the supplied question. Implementations may keep a
	// private copy, but the cached instance must have dns.Msg.Zero set to true
	// before it is returned by DnsGet.
	DnsSet(msg *dns.Msg)

	// DnsGet returns the cached dns.Msg pointer for the given qname and qtype, or
	// nil if no entry exists. The returned message MUST keep dns.Msg.Zero set to
	// true to signal it originated from cache, and callers MUST treat it as
	// immutable by copying it before applying any mutations.
	DnsGet(qname string, qtype uint16) *dns.Msg
}

type CachingResolver added in v0.2.0

type CachingResolver interface {
	Resolver
	Cacher
}

type Recursive added in v0.1.6

type Recursive struct {
	proxy.ContextDialer               // context dialer to use
	Cacher                            // cache to use for DnsResolve
	Timeout             time.Duration // default is DefaultTimeout
	DNSPort             uint16        // default is DefaultDNSPort
	Deterministic       bool          // if true, always query nameservers in the same order
	MsgSize             uint16        // UDP message size
	// contains filtered or unexported fields
}

func New

func New(dialer proxy.ContextDialer) *Recursive

New returns a new Recursive resolver using the given ContextDialer and has DefaultCache as it's cache.

It calls OrderRoots before returning.

func NewWithOptions

func NewWithOptions(dialer proxy.ContextDialer, cache Cacher, roots4, roots6 []netip.Addr, rateLimiter <-chan struct{}) *Recursive

NewWithOptions returns a new Recursive resolver using the given ContextDialer and using the given Cacher as the cache when calling DnsResolve. It does not call OrderRoots.

Passing nil for dialer will use a net.Dialer. Passing nil for the roots will use the default set of roots. Passing nil for the rateLimiter means no rate limiting

func (*Recursive) DnsResolve added in v0.2.0

func (r *Recursive) DnsResolve(ctx context.Context, qname string, qtype uint16) (msg *dns.Msg, srv netip.Addr, err error)

func (*Recursive) GetRoots added in v0.9.1

func (r *Recursive) GetRoots() (root4, root6 []netip.Addr)

GetRoots returns the current set of root servers in use.

func (*Recursive) OrderRoots added in v0.2.0

func (r *Recursive) OrderRoots(ctx context.Context)

func (*Recursive) OrderRootsTimeout added in v0.18.0

func (r *Recursive) OrderRootsTimeout(ctx context.Context, cutoff time.Duration)

OrderRootsTimeout sorts the root server list by their current latency and removes those that don't respond within cutoff.

func (*Recursive) ResetCookies added in v0.5.0

func (r *Recursive) ResetCookies()

ResetCookies generates a new DNS client cookie and clears the known DNS server cookies.

func (*Recursive) ResolveWithOptions added in v0.1.6

func (r *Recursive) ResolveWithOptions(ctx context.Context, cache Cacher, logw io.Writer, qname string, qtype uint16) (msg *dns.Msg, origin netip.Addr, err error)

ResolveWithOptions performs iterative resolution with QNAME minimization for qname/qtype.

type Resolver

type Resolver interface {
	DnsResolve(ctx context.Context, qname string, qtype uint16) (msg *dns.Msg, srv netip.Addr, err error)
}

Directories

Path Synopsis
cmd
cli command
genhints command

Jump to

Keyboard shortcuts

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