recursive

package module
v0.18.2 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2025 License: MIT Imports: 20 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 "com." from 13 servers
[0      1]  SENDING udp4: @192.36.148.17 NS "com." SETCOOKIE:"...7d6ab" => NOERROR [0+13+27 A/N/E] (2ms, 1555 bytes)
[3      0] DELEGATION ANSWER "com.": NOERROR with 13 records
[3      0] DELEGATION QUERY "microsoft.com." from 26 servers
[3      1]  SENDING udp4: @192.5.6.30 NS "microsoft.com." => NOERROR [0+4+2 A/N/E] (23ms, 267 bytes)
[27     0] DELEGATION ANSWER "microsoft.com.": NOERROR with 4 records
[27     0] DELEGATION QUERY "www.microsoft.com." from 1 servers
[27     1]  SENDING udp4: @150.171.10.39 NS "www.microsoft.com." => NOERROR [1+0+1 A/N/E] (12ms, 110 bytes AUTH)
[39     0] DELEGATION ANSWER "www.microsoft.com.": NOERROR with 0 records
[39     0] QUERY A "www.microsoft.com." from 1 servers
[39     1]  SENDING udp4: @150.171.10.39 A "www.microsoft.com." => NOERROR [1+0+1 A/N/E] (12ms, 110 bytes AUTH)
[52     1]  CNAME @150.171.10.39 A "www.microsoft.com." => "www.microsoft.com-c-3.edgekey.net."
[52     1]  DELEGATION QUERY "net." from 13 servers
[52     2]   SENDING udp4: @192.36.148.17 NS "net." COOKIE:"...e92c8|...7d6ab" => NOERROR [0+13+27 A/N/E] (2ms, 1555 bytes)
[54     1]  DELEGATION ANSWER "net.": NOERROR with 13 records
[54     1]  DELEGATION QUERY "edgekey.net." from 26 servers
[54     2]   SENDING udp4: @192.5.6.30 NS "edgekey.net." => NOERROR [0+8+17 A/N/E] (23ms, 943 bytes)
[77     1]  DELEGATION ANSWER "edgekey.net.": NOERROR with 8 records
[77     1]  DELEGATION QUERY "com-c-3.edgekey.net." from 16 servers
[77     2]   SENDING udp4: @23.61.199.64 NS "com-c-3.edgekey.net." => NOERROR [0+1+1 A/N/E] (2ms, 132 bytes AUTH)
[80     1]  DELEGATION ANSWER "com-c-3.edgekey.net.": NOERROR with 0 records
[80     1]  DELEGATION QUERY "microsoft.com-c-3.edgekey.net." from 16 servers
[80     2]   SENDING udp4: @23.61.199.64 NS "microsoft.com-c-3.edgekey.net." => NOERROR [0+1+1 A/N/E] (2ms, 142 bytes AUTH)
[81     1]  DELEGATION ANSWER "microsoft.com-c-3.edgekey.net.": NOERROR with 0 records
[81     1]  DELEGATION QUERY "www.microsoft.com-c-3.edgekey.net." from 16 servers
[81     2]   SENDING udp4: @23.61.199.64 NS "www.microsoft.com-c-3.edgekey.net." => NOERROR [1+0+1 A/N/E] (2ms, 165 bytes AUTH)
[84     1]  DELEGATION ANSWER "www.microsoft.com-c-3.edgekey.net.": NOERROR with 0 records
[84     1]  QUERY A "www.microsoft.com-c-3.edgekey.net." from 16 servers
[84     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)
[85     2]   CNAME @23.61.199.64 A "www.microsoft.com-c-3.edgekey.net." => "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net."
[85     2]   DELEGATION QUERY "net." from 13 servers
[85     3]    CACHED: NS "net." => NOERROR [0+13+27 A/N/E] (1555 bytes)
[85     2]   DELEGATION ANSWER "net.": NOERROR with 13 records
[85     2]   DELEGATION QUERY "akadns.net." from 26 servers
[85     3]    SENDING udp4: @192.5.6.30 NS "akadns.net." => NOERROR [0+9+11 A/N/E] (25ms, 807 bytes)
[111    2]   DELEGATION ANSWER "akadns.net.": NOERROR with 9 records
[111    2]   DELEGATION QUERY "globalredir.akadns.net." from 10 servers
[111    3]    SENDING udp4: @2.16.40.130 NS "globalredir.akadns.net." => NOERROR [0+1+1 A/N/E] (31ms, 137 bytes AUTH)
[142    2]   DELEGATION ANSWER "globalredir.akadns.net.": NOERROR with 0 records
[142    2]   DELEGATION QUERY "net.globalredir.akadns.net." from 10 servers
[142    3]    SENDING udp4: @2.16.40.130 NS "net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (31ms, 111 bytes AUTH)
[173    2]   DELEGATION ANSWER "net.globalredir.akadns.net.": NOERROR with 0 records
[173    2]   DELEGATION QUERY "edgekey.net.globalredir.akadns.net." from 10 servers
[173    3]    SENDING udp4: @2.16.40.130 NS "edgekey.net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (31ms, 127 bytes AUTH)
[205    2]   DELEGATION ANSWER "edgekey.net.globalredir.akadns.net.": NOERROR with 0 records
[205    2]   DELEGATION QUERY "com-c-3.edgekey.net.globalredir.akadns.net." from 10 servers
[205    3]    SENDING udp4: @2.16.40.130 NS "com-c-3.edgekey.net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (31ms, 143 bytes AUTH)
[236    2]   DELEGATION ANSWER "com-c-3.edgekey.net.globalredir.akadns.net.": NOERROR with 0 records
[236    2]   DELEGATION QUERY "microsoft.com-c-3.edgekey.net.globalredir.akadns.net." from 10 servers
[236    3]    SENDING udp4: @2.16.40.130 NS "microsoft.com-c-3.edgekey.net.globalredir.akadns.net." => NOERROR [1+0+1 A/N/E] (30ms, 163 bytes AUTH)
[266    2]   DELEGATION ANSWER "microsoft.com-c-3.edgekey.net.globalredir.akadns.net.": NOERROR with 0 records
[266    2]   DELEGATION QUERY "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net." from 10 servers
[266    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] (31ms, 181 bytes AUTH)
[297    2]   DELEGATION ANSWER "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net.": NOERROR with 0 records
[297    2]   QUERY A "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net." from 10 servers
[297    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] (31ms, 181 bytes AUTH)
[328    3]    CNAME @2.16.40.130 A "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net." => "e13678.dscb.akamaiedge.net."
[328    3]    DELEGATION QUERY "net." from 13 servers
[328    4]     CACHED: NS "net." => NOERROR [0+13+27 A/N/E] (1555 bytes)
[328    3]    DELEGATION ANSWER "net.": NOERROR with 13 records
[329    3]    DELEGATION QUERY "akamaiedge.net." from 26 servers
[329    4]     SENDING udp4: @192.5.6.30 NS "akamaiedge.net." => NOERROR [0+8+10 A/N/E] (22ms, 771 bytes)
[351    3]    DELEGATION ANSWER "akamaiedge.net.": NOERROR with 8 records
[351    3]    DELEGATION QUERY "dscb.akamaiedge.net." from 9 servers
[351    4]     SENDING udp4: @2.16.40.192 NS "dscb.akamaiedge.net." => NOERROR [0+8+10 A/N/E] (31ms, 825 bytes)
[382    3]    DELEGATION ANSWER "dscb.akamaiedge.net.": NOERROR with 8 records
[382    3]    DELEGATION QUERY "e13678.dscb.akamaiedge.net." from 9 servers
[382    4]     SENDING udp4: @23.218.92.38 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 0 records
[384    3]    QUERY A "e13678.dscb.akamaiedge.net." from 9 servers
[384    4]     SENDING udp4: @23.218.92.38 A "e13678.dscb.akamaiedge.net." => NOERROR [1+0+1 A/N/E] (2ms, 97 bytes AUTH)
[386    3]    ANSWER @23.218.92.38 A "e13678.dscb.akamaiedge.net." => NOERROR [1+0+1 A/N/E] (97 bytes AUTH)
[386    2]   ANSWER @23.218.92.38 A "www.microsoft.com-c-3.edgekey.net.globalredir.akadns.net." => NOERROR [2+0+2 A/N/E] (234 bytes AUTH)
[386    1]  ANSWER @23.218.92.38 A "www.microsoft.com-c-3.edgekey.net." => NOERROR [3+0+3 A/N/E] (325 bytes AUTH)
[386    0] ANSWER @23.218.92.38 A "www.microsoft.com." => NOERROR [4+0+4 A/N/E] (384 bytes AUTH)

;;; ----------------------------------------------------------------------
; <<>> recursive <<>> @23.218.92.38 A www.microsoft.com
;; opcode: QUERY, status: NOERROR, id: 819
;; 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/2I2bmFgYGRgYWBgYGEuLy/nzM1MLsovzk8rYU7OzwVJMWITZmVgZGDgE2BQRpVkT87P1U3WNWZPTUlPzU6tZM5LLWEgQgnEPOYWBivCirnTc/KTEnOKUlMyi9gSsxNT8oqJtAaXTrjtMmyphsZm5hYsKcXJSVyJ2Ym5iZkg/WBV+ORAwcTAwCDCwMIkdCyVgUGT5QIDFGgKMOBjAgIAAP//JkaWNYABAAA=
;; SERVER: 23.218.92.38
;; 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 = 6 * time.Hour // six hours
View Source
const DefaultMinTTL = 10 * time.Second // ten seconds
View Source
const DefaultNXTTL = time.Hour // one hour
View Source
const MaxQtype = 260

Variables

View Source
var (
	// ErrInvalidCookie is returned if the DNS cookie from the server is invalid.
	ErrInvalidCookie = errors.New("invalid cookie")
	// ErrMaxDepth is returned when recursive resolving exceeds the allowed limit.
	ErrMaxDepth = fmt.Errorf("recursion depth exceeded %d", maxDepth)
	// ErrMaxSteps is returned when resolving exceeds the step limit.
	ErrMaxSteps = fmt.Errorf("resolve steps exceeded %d", maxSteps)
	// 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
)
View Source
var ErrExtendedErrorCode = extendedErrorCodeError(0)
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 time.Duration // always cache responses for at least this long
	MaxTTL time.Duration // never cache responses for longer than this (excepting successful NS responses)
	NXTTL  time.Duration // cache NXDOMAIN responses for this long
	// 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()

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)

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)

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) HitRatio added in v0.1.2

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

HitRatio returns the hit ratio as a percentage.

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
	// 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