| author | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-10-23 18:42:17 UTC |
| committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-10-23 18:59:12 UTC |
| parent | 786704dfee7a4ccf2bc2f5c64d93a80d70e471e3 |
| internal/dnsserver/caching_test.go | +55 | -0 |
| internal/dnsserver/resolver.go | +10 | -9 |
diff --git a/internal/dnsserver/caching_test.go b/internal/dnsserver/caching_test.go index 9256461..d4a10a3 100644 --- a/internal/dnsserver/caching_test.go +++ b/internal/dnsserver/caching_test.go @@ -51,6 +51,61 @@ func TestBasic(t *testing.T) { } } +// Test the headers and OPT record of cache hit replies. +func TestHitReply(t *testing.T) { + r := testutil.NewTestResolver() + c := NewCachingResolver(r) + c.Init() + resetStats() + + r.Response = newReply(mustNewRR(t, "test. A 1.2.3.4")) + + tr := trace.New("test", "TestHitReply") + defer tr.Finish() + + // Plain query: no OPT in the reply. + req := newQuery("test.", dns.TypeA) + req.RecursionDesired = false + c.Query(req, tr) + resp, err := c.Query(req, tr) + if err != nil { + t.Fatalf("query failed: %v", err) + } + if !statsEquals(2, 1, 1) { + t.Errorf("bad stats: %v", dumpStats()) + } + if resp.Id != req.Id || !resp.Response || !resp.RecursionAvailable || + resp.RecursionDesired || resp.Authoritative || + resp.Rcode != dns.RcodeSuccess { + t.Errorf("bad reply header: %v", resp.MsgHdr) + } + if opt := resp.IsEdns0(); opt != nil { + t.Errorf("unexpected OPT in reply: %v", opt) + } + + // EDNS0 query with DO: the reply should have an OPT, with DO set. + req = newQuery("test.", dns.TypeA) + req.SetEdns0(1232, true) + c.Query(req, tr) + resp, err = c.Query(req, tr) + if err != nil { + t.Fatalf("query failed: %v", err) + } + if !statsEquals(4, 2, 2) { + t.Errorf("bad stats: %v", dumpStats()) + } + if !resp.RecursionDesired { + t.Errorf("RD not copied from the request: %v", resp.MsgHdr) + } + opt := resp.IsEdns0() + if opt == nil { + t.Fatalf("no OPT in reply: %v", resp) + } + if opt.UDPSize() != 1232 || !opt.Do() { + t.Errorf("bad OPT in reply: %v", opt) + } +} + // Test TTL handling. func TestTTL(t *testing.T) { r := testutil.NewTestResolver() diff --git a/internal/dnsserver/resolver.go b/internal/dnsserver/resolver.go index eed5726..936f3ec 100644 --- a/internal/dnsserver/resolver.go +++ b/internal/dnsserver/resolver.go @@ -361,15 +361,16 @@ func (c *cachingResolver) Query(r *dns.Msg, tr *trace.Trace) (*dns.Msg, error) { tr.Printf("cache hit") stats.cacheHits.Add(1) - reply := &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Id: r.Id, - Response: true, - Authoritative: false, - Rcode: dns.RcodeSuccess, - }, - Question: r.Question, - Answer: answer, + reply := new(dns.Msg) + reply.SetReply(r) + reply.RecursionAvailable = true + reply.Answer = answer + + // If the client used EDNS0, it expects an OPT record in the reply. + // We mirror its UDP size, and the DO bit (which is in the key, so + // the answer matches it). + if opt := r.IsEdns0(); opt != nil { + reply.SetEdns0(opt.UDPSize(), opt.Do()) } return reply, nil