fix(handler/dns): apply readTimeout, fix async context leak, add 40 tests

- Apply parsed readTimeout as connection read deadline (was dead code)
- Use context.WithoutCancel for async cache refresh goroutine
- Add doc comments to all exported symbols
- Add 40 tests covering metadata parsing, init, rate limiting,
  exchanger selection, host lookup, cache, bypass, async refresh,
  and concurrent request handling
This commit is contained in:
ginuerzh
2026-05-26 20:55:55 +08:00
parent b3424b769e
commit dd76e9f685
3 changed files with 1038 additions and 5 deletions
+13 -4
View File
@@ -32,9 +32,8 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
const ( // defaultNameserver is the fallback DNS resolver address used when no exchangers are configured.
defaultNameserver = "udp://127.0.0.1:53" const defaultNameserver = "udp://127.0.0.1:53"
)
func init() { func init() {
registry.HandlerRegistry().Register("dns", NewHandler) registry.HandlerRegistry().Register("dns", NewHandler)
@@ -50,6 +49,8 @@ type dnsHandler struct {
recorder recorder.RecorderObject recorder recorder.RecorderObject
} }
// NewHandler creates a DNS handler that resolves DNS queries using configured
// nameserver exchangers, with optional host mapper, caching, and async refresh.
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{} options := handler.Options{}
for _, opt := range opts { for _, opt := range opts {
@@ -62,6 +63,8 @@ func NewHandler(opts ...handler.Option) handler.Handler {
} }
} }
// Init initializes the handler from metadata, building exchangers from the
// configured nameservers or hop nodes.
func (h *dnsHandler) Init(md md.Metadata) (err error) { func (h *dnsHandler) Init(md md.Metadata) (err error) {
if err = h.parseMetadata(md); err != nil { if err = h.parseMetadata(md); err != nil {
return return
@@ -134,6 +137,8 @@ func (h *dnsHandler) Forward(hop hop.Hop) {
h.hop = hop h.hop = hop
} }
// Handle processes a DNS query connection: reads the query, resolves it through
// cache/host-mapper/exchanger, and writes the response back.
func (h *dnsHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) { func (h *dnsHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
@@ -190,6 +195,10 @@ func (h *dnsHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
return rate_limiter.ErrRateLimit return rate_limiter.ErrRateLimit
} }
if h.md.readTimeout > 0 {
conn.SetReadDeadline(time.Now().Add(h.md.readTimeout))
}
b := bufpool.Get(h.md.bufferSize) b := bufpool.Get(h.md.bufferSize)
defer bufpool.Put(b) defer bufpool.Put(b)
@@ -306,7 +315,7 @@ func (h *dnsHandler) request(ctx context.Context, msg []byte, ro *xrecorder.Hand
h.cache.RefreshTTL(resolver_util.NewCacheKey(&mq.Question[0])) h.cache.RefreshTTL(resolver_util.NewCacheKey(&mq.Question[0]))
log.Debugf("exchange message %d (async): %s", mq.Id, mq.Question[0].String()) log.Debugf("exchange message %d (async): %s", mq.Id, mq.Question[0].String())
go h.exchange(ctx, ex, &mq) go h.exchange(context.WithoutCancel(ctx), ex, &mq)
return reply, nil return reply, nil
} }
File diff suppressed because it is too large Load Diff
+3
View File
@@ -10,10 +10,13 @@ import (
) )
const ( const (
// defaultTimeout is the fallback timeout for DNS exchanges.
defaultTimeout = 5 * time.Second defaultTimeout = 5 * time.Second
// defaultBufferSize is the fallback buffer size for DNS message I/O.
defaultBufferSize = 1024 defaultBufferSize = 1024
) )
// metadata holds parsed DNS handler configuration.
type metadata struct { type metadata struct {
readTimeout time.Duration readTimeout time.Duration
ttl time.Duration ttl time.Duration