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"
)
const (
defaultNameserver = "udp://127.0.0.1:53"
)
// defaultNameserver is the fallback DNS resolver address used when no exchangers are configured.
const defaultNameserver = "udp://127.0.0.1:53"
func init() {
registry.HandlerRegistry().Register("dns", NewHandler)
@@ -50,6 +49,8 @@ type dnsHandler struct {
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 {
options := handler.Options{}
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) {
if err = h.parseMetadata(md); err != nil {
return
@@ -134,6 +137,8 @@ func (h *dnsHandler) Forward(hop 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) {
defer conn.Close()
@@ -190,6 +195,10 @@ func (h *dnsHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
return rate_limiter.ErrRateLimit
}
if h.md.readTimeout > 0 {
conn.SetReadDeadline(time.Now().Add(h.md.readTimeout))
}
b := bufpool.Get(h.md.bufferSize)
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]))
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
}
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -10,10 +10,13 @@ import (
)
const (
defaultTimeout = 5 * time.Second
// defaultTimeout is the fallback timeout for DNS exchanges.
defaultTimeout = 5 * time.Second
// defaultBufferSize is the fallback buffer size for DNS message I/O.
defaultBufferSize = 1024
)
// metadata holds parsed DNS handler configuration.
type metadata struct {
readTimeout time.Duration
ttl time.Duration