dnsclient

package
v1.8.2 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package dnsclient owns SDNS's upstream DNS transport: wire framing, buffer pooling, dialing, deadlines and exchange policy (ID match, question-section guard, UDP->TCP truncation fallback). It is written clean-room and depends on github.com/miekg/dns only as the message codec (dns.Msg / Pack / Unpack), not as a transport.

Index

Constants

View Source
const FramePrefixLen = 2

FramePrefixLen is the stream frame header size.

Variables

View Source
var ErrFrameTooLarge = errors.New("dnsclient: frame exceeds maximum message size")

ErrFrameTooLarge refuses to write a frame the 2-byte length prefix cannot describe.

View Source
var ErrQuestion = errors.New("dns: response question did not match request")

ErrQuestion is returned by (*Conn).Exchange when the response's question section does not match the outstanding request. Accepting a mismatched question lets a malicious upstream plant a cache entry under an unrelated name (issue #469).

Functions

func AcquireBuf

func AcquireBuf(size uint16) []byte

AcquireBuf returns a buffer from the appropriate pool.

func QuestionMatches

func QuestionMatches(req dns.Question, resp []dns.Question) bool

QuestionMatches reports whether the response's question section answers the outstanding request question. DNS names are compared case-insensitively because they are not case-sensitive on the wire.

func ReadFrameInto added in v1.8.0

func ReadFrameInto(conn net.Conn, dst []byte) (int, error)

ReadFrameInto reads one length-prefixed frame into dst and returns the payload length. Partial reads are completed. The prefix is staged in dst's first bytes and overwritten by the payload, which keeps the read allocation-free — a stack prefix array would escape through the reader interface. dst therefore needs capacity for at least the prefix.

func ReleaseBuf

func ReleaseBuf(buf []byte)

ReleaseBuf returns buf to the appropriate pool.

func WriteFrameFrom added in v1.8.0

func WriteFrameFrom(conn net.Conn, p []byte) (int, error)

WriteFrameFrom writes p as one length-prefixed frame in a single gathered write. The returned count includes the prefix bytes. Callers who own prefix headroom in their buffer use WriteFramePrefixed instead, which needs no gather and no allocation.

func WriteFramePrefixed added in v1.8.0

func WriteFramePrefixed(conn net.Conn, buf []byte, payloadLen int) (int, error)

WriteFramePrefixed writes buf's payload as one frame using buf's own prefix headroom: buf[0:2] is writable scratch and the payload occupies buf[2 : 2+payloadLen]. One conn.Write, no gather, no allocation — the strict-path TX buffer is laid out with this headroom for exactly this call. The returned count includes the prefix bytes.

Types

type CancelInterrupt added in v1.7.4

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

CancelInterrupt identifies one connection-cancellation registration. Stop must complete before the Conn or its underlying connection is reused.

func (CancelInterrupt) Stop added in v1.7.4

func (h CancelInterrupt) Stop()

Stop detaches the cancellation callback and waits if it has already started. Stop has one owner and must not be called concurrently for the same handle. Sequential repeated calls and stale-generation handles are no-ops.

type Client

type Client struct {
	Proto     string        // "udp" | "tcp" | "tcp-tls" | "doh"; empty means "udp"
	Timeout   time.Duration // per-exchange dial+read+write budget; 0 means none
	TLSConfig *tls.Config   // DoT (tcp-tls) server config
	DoHURL    string        // DoH endpoint URL
	DoHClient *http.Client  // DoH HTTP client (reused transport / HTTP2 pool)

	// BeforeAttempt runs immediately before each wire transport attempt.
	// It is inherited by the transparent UDP-to-TCP fallback, allowing
	// request-wide work accounting to reject that second attempt before it
	// dials. Nil preserves the historical behaviour.
	BeforeAttempt func(proto string) error

	// SkipQuestionCheck disables the response question-section guard.
	// The guard is on by default; leave this false unless a caller has
	// a specific reason to accept mismatched questions.
	SkipQuestionCheck bool
}

Client is a high-level, dial-per-Exchange DNS client for callers that don't maintain their own connection pool — the forwarder, failover, and the config IPv6 probe. The resolver hot path uses Conn directly so it keeps its own pooling, circuit breaker and retry policy.

The zero value with Proto unset behaves as plain UDP. The question- section guard is on by default; the response transaction ID is always validated.

func (*Client) Exchange

func (c *Client) Exchange(ctx context.Context, req *dns.Msg, addr string) (*dns.Msg, time.Duration, error)

Exchange sends req to addr over c.Proto and returns the validated response. A truncated UDP answer transparently retries over TCP.

type Conn

type Conn struct {
	net.Conn        // underlying connection
	UDPSize  uint16 // minimum receive buffer for UDP messages
	// contains filtered or unexported fields
}

Conn represents a connection to a DNS server. It wraps a net.Conn (either a connected UDP socket or a TCP/TLS stream) and tracks the negotiated UDP receive size.

func (*Conn) BeginCancelInterrupt added in v1.7.4

func (co *Conn) BeginCancelInterrupt(ctx context.Context) CancelInterrupt

BeginCancelInterrupt makes a synchronous connection operation observe context cancellation after dialing has completed. It captures the current underlying connection rather than the reusable wrapper, and Stop joins an already-started callback before reuse is allowed.

A Conn supports one active interrupt registration at a time; overlapping registration is a connection-reuse invariant violation and panics. The zero handle returned for a context without a Done channel has no allocation or cleanup cost.

func (*Conn) Exchange

func (co *Conn) Exchange(m *dns.Msg) (r *dns.Msg, rtt time.Duration, err error)

Exchange performs a synchronous query over co: it writes m, reads the response, and validates the transaction ID and question section. The caller is responsible for dialing co and setting any deadline before calling Exchange.

func (*Conn) ExchangeContext added in v1.7.4

func (co *Conn) ExchangeContext(ctx context.Context, m *dns.Msg) (r *dns.Msg, rtt time.Duration, err error)

ExchangeContext performs Exchange with a cancellation interrupt bound to ctx. The caller still owns dialing and the ordinary network deadline. The deferred Stop makes the interrupt lifecycle panic-safe and guarantees that the connection is reusable only after a started cancellation callback ends.

func (*Conn) ExchangeInterruptible added in v1.8.0

func (co *Conn) ExchangeInterruptible(ctx context.Context, g *InterruptGroup, m *dns.Msg) (r *dns.Msg, rtt time.Duration, err error)

ExchangeInterruptible performs Exchange bound to g's cancellation domain, falling back to the per-operation ExchangeContext registration when the group is nil or full. The caller still owns dialing and the ordinary network deadline.

func (*Conn) Read

func (co *Conn) Read(p []byte) (n int, err error)

Read implements net.Conn. For a UDP connection it reads a single datagram. For a stream connection it reads the 2-byte length prefix (RFC 1035 §4.2.2) and then exactly that many bytes into p.

func (*Conn) ReadMsg

func (co *Conn) ReadMsg() (*dns.Msg, error)

ReadMsg reads a single DNS message from co. The buffer is always returned to the pool, even on a timed-out UDP read or a truncated TCP read, so failed upstream reads never leak the buffer. On success only the bytes actually read are unpacked — feeding Unpack the trailing capacity of a pooled UDP buffer would let stale bytes from a previous use bleed into the parsed message.

func (*Conn) Write

func (co *Conn) Write(p []byte) (int, error)

Write implements net.Conn. For UDP it writes p as a single datagram. For a stream connection it prefixes p with its 2-byte length.

func (*Conn) WriteMsg

func (co *Conn) WriteMsg(m *dns.Msg) (err error)

WriteMsg packs m into a pooled buffer and writes it to co.

type InterruptGroup added in v1.8.0

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

InterruptGroup makes a set of synchronous connection operations observe one context's cancellation through a single registration. Each per-exchange context.AfterFunc costs an afterFuncCtx, a closure and a children-map entry; a group pays that once per cancellation domain and hands out array slots.

The reuse contract matches CancelInterrupt.Stop: once Disarm returns, the group never touches that connection again, and a fire already touching it has finished — both guaranteed by the group mutex.

func NewInterruptGroup added in v1.8.0

func NewInterruptGroup(ctx context.Context) *InterruptGroup

NewInterruptGroup registers one cancellation callback on ctx and returns the group armed connections are interrupted through. A context without a Done channel needs no interruption; the nil group it returns makes ExchangeInterruptible take the ordinary path.

func (*InterruptGroup) Close added in v1.8.0

func (g *InterruptGroup) Close()

Close detaches the context registration. It does not join a fire already in flight: any connection still armed belongs to an operation that has not disarmed yet, and interrupting it remains correct — per-connection reuse safety is disarm's guarantee, not Close's. Callers cancel their domain before closing, so stragglers are interrupted, not stranded.

Jump to

Keyboard shortcuts

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