coredns/plugin/dns64/dns64_test.go
2022-07-10 11:06:33 -07:00

556 lines
15 KiB
Go

package dns64
import (
"context"
"fmt"
"net"
"reflect"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
func To6(prefix, address string) (net.IP, error) {
_, pref, _ := net.ParseCIDR(prefix)
addr := net.ParseIP(address)
return to6(pref, addr)
}
func TestRequestShouldIntercept(t *testing.T) {
tests := []struct {
name string
allowIpv4 bool
remoteIP string
msg *dns.Msg
want bool
}{
{
name: "should intercept request from IPv6 network - AAAA - IN",
allowIpv4: true,
remoteIP: "::1",
msg: new(dns.Msg).SetQuestion("example.com", dns.TypeAAAA),
want: true,
},
{
name: "should intercept request from IPv4 network - AAAA - IN",
allowIpv4: true,
remoteIP: "127.0.0.1",
msg: new(dns.Msg).SetQuestion("example.com", dns.TypeAAAA),
want: true,
},
{
name: "should not intercept request from IPv4 network - AAAA - IN",
allowIpv4: false,
remoteIP: "127.0.0.1",
msg: new(dns.Msg).SetQuestion("example.com", dns.TypeAAAA),
want: false,
},
{
name: "should not intercept request from IPv6 network - A - IN",
allowIpv4: false,
remoteIP: "::1",
msg: new(dns.Msg).SetQuestion("example.com", dns.TypeA),
want: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
h := DNS64{AllowIPv4: tc.allowIpv4}
rec := dnstest.NewRecorder(&test.ResponseWriter{RemoteIP: tc.remoteIP})
r := request.Request{W: rec, Req: tc.msg}
actual := h.requestShouldIntercept(&r)
if actual != tc.want {
t.Fatalf("Expected %v, but got %v", tc.want, actual)
}
})
}
}
func TestTo6(t *testing.T) {
v6, err := To6("64:ff9b::/96", "64.64.64.64")
if err != nil {
t.Error(err)
}
if v6.String() != "64:ff9b::4040:4040" {
t.Errorf("%d", v6)
}
v6, err = To6("64:ff9b::/64", "64.64.64.64")
if err != nil {
t.Error(err)
}
if v6.String() != "64:ff9b::40:4040:4000:0" {
t.Errorf("%d", v6)
}
v6, err = To6("64:ff9b::/56", "64.64.64.64")
if err != nil {
t.Error(err)
}
if v6.String() != "64:ff9b:0:40:40:4040::" {
t.Errorf("%d", v6)
}
v6, err = To6("64::/32", "64.64.64.64")
if err != nil {
t.Error(err)
}
if v6.String() != "64:0:4040:4040::" {
t.Errorf("%d", v6)
}
}
func TestResponseShould(t *testing.T) {
var tests = []struct {
resp dns.Msg
translateAll bool
expected bool
}{
// If there's an AAAA record, then no
{
resp: dns.Msg{
MsgHdr: dns.MsgHdr{
Rcode: dns.RcodeSuccess,
},
Answer: []dns.RR{
test.AAAA("example.com. IN AAAA ::1"),
},
},
expected: false,
},
// If there's no AAAA, then true
{
resp: dns.Msg{
MsgHdr: dns.MsgHdr{
Rcode: dns.RcodeSuccess,
},
Ns: []dns.RR{
test.SOA("example.com. IN SOA foo bar 1 1 1 1 1"),
},
},
expected: true,
},
// Failure, except NameError, should be true
{
resp: dns.Msg{
MsgHdr: dns.MsgHdr{
Rcode: dns.RcodeNotImplemented,
},
Ns: []dns.RR{
test.SOA("example.com. IN SOA foo bar 1 1 1 1 1"),
},
},
expected: true,
},
// NameError should be false
{
resp: dns.Msg{
MsgHdr: dns.MsgHdr{
Rcode: dns.RcodeNameError,
},
Ns: []dns.RR{
test.SOA("example.com. IN SOA foo bar 1 1 1 1 1"),
},
},
expected: false,
},
// If there's an AAAA record, but translate_all is configured, then yes
{
resp: dns.Msg{
MsgHdr: dns.MsgHdr{
Rcode: dns.RcodeSuccess,
},
Answer: []dns.RR{
test.AAAA("example.com. IN AAAA ::1"),
},
},
translateAll: true,
expected: true,
},
}
d := DNS64{}
for idx, tc := range tests {
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
d.TranslateAll = tc.translateAll
actual := d.responseShouldDNS64(&tc.resp)
if actual != tc.expected {
t.Fatalf("Expected %v got %v", tc.expected, actual)
}
})
}
}
func TestDNS64(t *testing.T) {
var cases = []struct {
// a brief summary of the test case
name string
// the request
req *dns.Msg
// the initial response from the "downstream" server
initResp *dns.Msg
// A response to provide
aResp *dns.Msg
// the expected ultimate result
resp *dns.Msg
}{
{
// no AAAA record, yes A record. Do DNS64
name: "standard flow",
req: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
RecursionDesired: true,
Opcode: dns.OpcodeQuery,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
},
initResp: &dns.Msg{ //success, no answers
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Ns: []dns.RR{test.SOA("example.com. 70 IN SOA foo bar 1 1 1 1 1")},
},
aResp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 43,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}},
Answer: []dns.RR{
test.A("example.com. 60 IN A 192.0.2.42"),
test.A("example.com. 5000 IN A 192.0.2.43"),
},
},
resp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Answer: []dns.RR{
test.AAAA("example.com. 60 IN AAAA 64:ff9b::192.0.2.42"),
// override RR ttl to SOA ttl, since it's lower
test.AAAA("example.com. 70 IN AAAA 64:ff9b::192.0.2.43"),
},
},
},
{
// name exists, but has neither A nor AAAA record
name: "a empty",
req: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
RecursionDesired: true,
Opcode: dns.OpcodeQuery,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
},
initResp: &dns.Msg{ //success, no answers
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Ns: []dns.RR{test.SOA("example.com. 3600 IN SOA foo bar 1 7200 900 1209600 86400")},
},
aResp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 43,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}},
Ns: []dns.RR{test.SOA("example.com. 3600 IN SOA foo bar 1 7200 900 1209600 86400")},
},
resp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Ns: []dns.RR{test.SOA("example.com. 3600 IN SOA foo bar 1 7200 900 1209600 86400")},
Answer: []dns.RR{}, // just to make comparison happy
},
},
{
// Query error other than NameError
name: "non-nxdomain error",
req: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
RecursionDesired: true,
Opcode: dns.OpcodeQuery,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
},
initResp: &dns.Msg{ // failure
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeRefused,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
},
aResp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 43,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}},
Answer: []dns.RR{
test.A("example.com. 60 IN A 192.0.2.42"),
test.A("example.com. 5000 IN A 192.0.2.43"),
},
},
resp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Answer: []dns.RR{
test.AAAA("example.com. 60 IN AAAA 64:ff9b::192.0.2.42"),
test.AAAA("example.com. 600 IN AAAA 64:ff9b::192.0.2.43"),
},
},
},
{
// nxdomain (NameError): don't even try an A request.
name: "nxdomain",
req: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
RecursionDesired: true,
Opcode: dns.OpcodeQuery,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
},
initResp: &dns.Msg{ // failure
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeNameError,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Ns: []dns.RR{test.SOA("example.com. 3600 IN SOA foo bar 1 7200 900 1209600 86400")},
},
resp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeNameError,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Ns: []dns.RR{test.SOA("example.com. 3600 IN SOA foo bar 1 7200 900 1209600 86400")},
},
},
{
// AAAA record exists
name: "AAAA record",
req: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
RecursionDesired: true,
Opcode: dns.OpcodeQuery,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
},
initResp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Answer: []dns.RR{
test.AAAA("example.com. 60 IN AAAA ::1"),
test.AAAA("example.com. 5000 IN AAAA ::2"),
},
},
resp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Answer: []dns.RR{
test.AAAA("example.com. 60 IN AAAA ::1"),
test.AAAA("example.com. 5000 IN AAAA ::2"),
},
},
},
{
// no AAAA records, A record response truncated.
name: "truncated A response",
req: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
RecursionDesired: true,
Opcode: dns.OpcodeQuery,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
},
initResp: &dns.Msg{ //success, no answers
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Ns: []dns.RR{test.SOA("example.com. 70 IN SOA foo bar 1 1 1 1 1")},
},
aResp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 43,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Truncated: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}},
Answer: []dns.RR{
test.A("example.com. 60 IN A 192.0.2.42"),
test.A("example.com. 5000 IN A 192.0.2.43"),
},
},
resp: &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: 42,
Opcode: dns.OpcodeQuery,
RecursionDesired: true,
Truncated: true,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{{Name: "example.com.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}},
Answer: []dns.RR{
test.AAAA("example.com. 60 IN AAAA 64:ff9b::192.0.2.42"),
// override RR ttl to SOA ttl, since it's lower
test.AAAA("example.com. 70 IN AAAA 64:ff9b::192.0.2.43"),
},
},
},
}
_, pfx, _ := net.ParseCIDR("64:ff9b::/96")
for idx, tc := range cases {
t.Run(fmt.Sprintf("%d_%s", idx, tc.name), func(t *testing.T) {
d := DNS64{
Next: &fakeHandler{t, tc.initResp},
Prefix: pfx,
Upstream: &fakeUpstream{t, tc.req.Question[0].Name, tc.aResp},
}
rec := dnstest.NewRecorder(&test.ResponseWriter{RemoteIP: "::1"})
rc, err := d.ServeDNS(context.Background(), rec, tc.req)
if err != nil {
t.Fatal(err)
}
actual := rec.Msg
if actual.Rcode != rc {
t.Fatalf("ServeDNS should return real result code %q != %q", actual.Rcode, rc)
}
if !reflect.DeepEqual(actual, tc.resp) {
t.Fatalf("Final answer should match expected %q != %q", actual, tc.resp)
}
})
}
}
type fakeHandler struct {
t *testing.T
reply *dns.Msg
}
func (fh *fakeHandler) ServeDNS(_ context.Context, w dns.ResponseWriter, _ *dns.Msg) (int, error) {
if fh.reply == nil {
panic("fakeHandler ServeDNS with nil reply")
}
w.WriteMsg(fh.reply)
return fh.reply.Rcode, nil
}
func (fh *fakeHandler) Name() string {
return "fake"
}
type fakeUpstream struct {
t *testing.T
qname string
resp *dns.Msg
}
func (fu *fakeUpstream) Lookup(_ context.Context, _ request.Request, name string, typ uint16) (*dns.Msg, error) {
if fu.qname == "" {
fu.t.Fatalf("Unexpected A lookup for %s", name)
}
if name != fu.qname {
fu.t.Fatalf("Wrong A lookup for %s, expected %s", name, fu.qname)
}
if typ != dns.TypeA {
fu.t.Fatalf("Wrong lookup type %d, expected %d", typ, dns.TypeA)
}
return fu.resp, nil
}