diff --git a/plugin/dns64/dns64.go b/plugin/dns64/dns64.go index ef646f9b1..b454f3d5a 100644 --- a/plugin/dns64/dns64.go +++ b/plugin/dns64/dns64.go @@ -63,7 +63,7 @@ func (d *DNS64) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) RequestsTranslatedCount.WithLabelValues(metrics.WithServer(ctx)).Inc() w.WriteMsg(msg) - return msg.Rcode, nil + return dns.RcodeSuccess, nil } // Name implements the Handler interface. @@ -134,6 +134,7 @@ func (d *DNS64) DoDNS64(ctx context.Context, w dns.ResponseWriter, r *dns.Msg, o func (d *DNS64) Synthesize(origReq, origResponse, resp *dns.Msg) *dns.Msg { ret := dns.Msg{} ret.SetReply(origReq) + ret.Rcode = resp.Rcode // persist truncated state of AAAA response ret.Truncated = resp.Truncated diff --git a/plugin/dns64/dns64_test.go b/plugin/dns64/dns64_test.go index d7ada8ee8..3da3d4944 100644 --- a/plugin/dns64/dns64_test.go +++ b/plugin/dns64/dns64_test.go @@ -581,3 +581,42 @@ func TestDNS64NilUpstreamResponse(t *testing.T) { t.Error("Expected error when upstream returns nil response, got nil") } } + +func TestDNS64FallbackError(t *testing.T) { + _, prefix, _ := net.ParseCIDR("64:ff9b::/96") + for _, rcode := range []int{dns.RcodeSuccess, dns.RcodeServerFailure, dns.RcodeRefused, dns.RcodeFormatError, dns.RcodeNameError, dns.RcodeNotImplemented} { + t.Run(dns.RcodeToString[rcode], func(t *testing.T) { + req := new(dns.Msg) + req.SetQuestion("example.com.", dns.TypeAAAA) + initial := new(dns.Msg) + initial.SetReply(req) + fallback := new(dns.Msg) + fallback.SetRcode(req, rcode) + fallback.Id++ + fallback.Question[0].Qtype = dns.TypeA + fallback.Ns = []dns.RR{test.SOA("example.com. 60 IN SOA ns.example.com. hostmaster.example.com. 1 1 1 1 1")} + d := DNS64{ + Next: &fakeHandler{t, initial}, + Prefix: prefix, + Upstream: &fakeUpstream{t, req.Question[0].Name, fallback}, + } + rec := dnstest.NewRecorder(&test.ResponseWriter{RemoteIP: "::1"}) + got, err := d.ServeDNS(t.Context(), rec, req) + if err != nil { + t.Fatal(err) + } + if got != dns.RcodeSuccess { + t.Errorf("written fallback %s returned handler code %s, want NOERROR", dns.RcodeToString[rcode], dns.RcodeToString[got]) + } + if rec.Msg.Rcode != rcode { + t.Errorf("fallback %s returned response code %s", dns.RcodeToString[rcode], dns.RcodeToString[rec.Msg.Rcode]) + } + if rec.Msg.Id != req.Id || !reflect.DeepEqual(rec.Msg.Question, req.Question) { + t.Errorf("response did not preserve the original AAAA query: %s", rec.Msg) + } + if !reflect.DeepEqual(rec.Msg.Ns, fallback.Ns) { + t.Errorf("response did not preserve fallback authority records: %s", rec.Msg) + } + }) + } +} diff --git a/test/dns64_test.go b/test/dns64_test.go new file mode 100644 index 000000000..f40eb9868 --- /dev/null +++ b/test/dns64_test.go @@ -0,0 +1,86 @@ +package test + +import ( + "fmt" + "testing" + "time" + + "github.com/miekg/dns" +) + +func TestDNS64FallbackError(t *testing.T) { + for _, rcode := range []int{dns.RcodeSuccess, dns.RcodeServerFailure, dns.RcodeRefused, dns.RcodeFormatError, dns.RcodeNameError, dns.RcodeNotImplemented} { + t.Run(dns.RcodeToString[rcode], func(t *testing.T) { + corefile := fmt.Sprintf(`.:0 { + bind 127.0.0.1 + dns64 { + allow_ipv4 + } + template IN AAAA example.org { + rcode NOERROR + } + template IN A example.org { + rcode %s + } + template IN TXT example.org { + answer "{{ .Name }} 60 IN TXT \"connection reused\"" + } + }`, dns.RcodeToString[rcode]) + i, udp, tcp, err := CoreDNSServerAndPorts(corefile) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { CoreDNSServerStop(i) }) + + for _, transport := range []struct { + net string + addr string + }{{"udp", udp}, {"tcp", tcp}} { + t.Run(transport.net, func(t *testing.T) { + client := &dns.Client{Net: transport.net} + conn, err := client.Dial(transport.addr) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + for attempt := range 3 { + for _, tc := range []struct { + qtype uint16 + id uint16 + code int + }{ + {dns.TypeAAAA, uint16(101 + attempt), rcode}, + {dns.TypeTXT, uint16(202 + attempt), dns.RcodeSuccess}, + } { + query := new(dns.Msg) + query.SetQuestion("example.org.", tc.qtype) + query.Id = tc.id + if err := conn.SetDeadline(time.Now().Add(2 * time.Second)); err != nil { + t.Fatal(err) + } + if err := conn.WriteMsg(query); err != nil { + t.Fatal(err) + } + response, err := conn.ReadMsg() + if err != nil { + t.Fatal(err) + } + if response.Id != query.Id || len(response.Question) != 1 || response.Question[0] != query.Question[0] { + t.Fatalf("query ID %d (%s) received an unexpected reply: %s", query.Id, dns.TypeToString[tc.qtype], response) + } + if response.Rcode != tc.code { + t.Errorf("query %s returned %s, want %s", dns.TypeToString[tc.qtype], dns.RcodeToString[response.Rcode], dns.RcodeToString[tc.code]) + } + if tc.qtype == dns.TypeTXT { + if len(response.Answer) != 1 || response.Answer[0].String() != "example.org.\t60\tIN\tTXT\t\"connection reused\"" { + t.Errorf("connection reuse did not return the TXT answer: %s", response) + } + } + } + } + }) + } + }) + } +}