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
- Variables
- func AcquireBuf(size uint16) []byte
- func QuestionMatches(req dns.Question, resp []dns.Question) bool
- func ReadFrameInto(conn net.Conn, dst []byte) (int, error)
- func ReleaseBuf(buf []byte)
- func WriteFrameFrom(conn net.Conn, p []byte) (int, error)
- func WriteFramePrefixed(conn net.Conn, buf []byte, payloadLen int) (int, error)
- type CancelInterrupt
- type Client
- type Conn
- func (co *Conn) BeginCancelInterrupt(ctx context.Context) CancelInterrupt
- func (co *Conn) Exchange(m *dns.Msg) (r *dns.Msg, rtt time.Duration, err error)
- func (co *Conn) ExchangeContext(ctx context.Context, m *dns.Msg) (r *dns.Msg, rtt time.Duration, err error)
- func (co *Conn) ExchangeInterruptible(ctx context.Context, g *InterruptGroup, m *dns.Msg) (r *dns.Msg, rtt time.Duration, err error)
- func (co *Conn) Read(p []byte) (n int, err error)
- func (co *Conn) ReadMsg() (*dns.Msg, error)
- func (co *Conn) Write(p []byte) (int, error)
- func (co *Conn) WriteMsg(m *dns.Msg) (err error)
- type InterruptGroup
Constants ¶
const FramePrefixLen = 2
FramePrefixLen is the stream frame header size.
Variables ¶
var ErrFrameTooLarge = errors.New("dnsclient: frame exceeds maximum message size")
ErrFrameTooLarge refuses to write a frame the 2-byte length prefix cannot describe.
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 ¶
AcquireBuf returns a buffer from the appropriate pool.
func QuestionMatches ¶
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
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 WriteFrameFrom ¶ added in v1.8.0
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
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.
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 ¶
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 ¶
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 ¶
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.
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.