plugin/dns64: preserve A fallback response codes (#8619)

* plugin/dns64: preserve fallback response codes

Signed-off-by: xinjun.jiang <xinjun.jiang@daocloud.io>

* plugin/dns64: mark written fallback replies as handled

Signed-off-by: xinjun.jiang <xinjun.jiang@daocloud.io>

---------

Signed-off-by: xinjun.jiang <xinjun.jiang@daocloud.io>
This commit is contained in:
jxj
2026-10-12 12:10:48 +08:00
committed by GitHub
parent 123829d3b3
commit 13c27392fd
3 changed files with 127 additions and 1 deletions

View File

@@ -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

View File

@@ -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)
}
})
}
}

86
test/dns64_test.go Normal file
View File

@@ -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)
}
}
}
}
})
}
})
}
}