556 lines
15 KiB
Go
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
|
|
}
|