Files
zooqueen 66369c5a2d build the gate the way the binary is built, and fix what that exposed
Nine packages failed. Eight of them for one reason: `go test ./...` ran without
the tags the Makefile ships with (GOTAGS = grpcnotrace metrics), and
luxfi/metric is `//go:build !metrics` — every counter, gauge and histogram is a
no-op unless that tag is set. So metrics, metrics/vars and the kubernetes
object counters were all asserting on numbers that could never move:

  RequestCount was not incremented. Before: 0.000000, After: 0.000000

The shipping binary does set it, so this was never a production defect — but a
gate that builds something other than what ships cannot see one either.

The rest were real, and none of them are about metrics:

  test/metric_naming_test.go declared a local `type metric` while importing
    the package named metric, which every other file here imports too. The
    local type is the newcomer; renamed it visitor, for the ast.Visitor it is.
    Its BuildFQName call went back to prometheus, which is whose name format is
    under test — luxfi/metric has no three-part equivalent, only
    AppendNamespace(namespace, name).

  Three %q verbs applied to numbers — an MX preference, an Rcode, a SOA
    serial. Newer vet rejects them, and `go test` runs vet, so three packages
    failed to build without ever reaching a test.

  TestImportOrdering walked operator/, which is its own module
    (github.com/hanzoai/dns-operator) and imports no coredns, so the
    std -> coredns -> 3rd shape it checks for has no middle group to find.
    Skipped like pb/ and vendor/ already are, for the same reason.

Eight of the nine pass now. The ninth is test/, where seven metrics
integration tests scrape /metrics and find nothing — the counters record but
are not exposed. That is the other half of the luxfi/metric migration and
wants its own change.
2026-08-10 20:08:49 -07:00

557 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 %d != %d", 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
}