consul/agent/dns_service_lookup_test.go

4036 lines
102 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package agent
import (
"context"
"fmt"
"net"
"sort"
"strings"
"testing"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/sdk/testutil/retry"
"github.com/hashicorp/consul/testrpc"
)
func TestDNS_ServiceLookupNoMultiCNAME(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "198.18.0.1",
Service: &structs.NodeService{
Service: "db",
Port: 12345,
Address: "foo.node.consul",
},
}
var out struct{}
require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out))
}
// Register a second node node with the same service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "198.18.0.2",
Service: &structs.NodeService{
Service: "db",
Port: 12345,
Address: "bar.node.consul",
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
m := new(dns.Msg)
m.SetQuestion("db.service.consul.", dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.NoError(t, err)
// expect a CNAME and an A RR
require.Len(t, in.Answer, 2)
require.IsType(t, &dns.CNAME{}, in.Answer[0])
require.IsType(t, &dns.A{}, in.Answer[1])
})
}
}
func TestDNS_ServiceLookupPreferNoCNAME(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "198.18.0.1",
Service: &structs.NodeService{
Service: "db",
Port: 12345,
Address: "198.18.0.1",
},
}
var out struct{}
require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out))
}
// Register a second node node with the same service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "198.18.0.2",
Service: &structs.NodeService{
Service: "db",
Port: 12345,
Address: "bar.node.consul",
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
m := new(dns.Msg)
m.SetQuestion("db.service.consul.", dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.NoError(t, err)
// expect an A RR
require.Len(t, in.Answer, 1)
aRec, ok := in.Answer[0].(*dns.A)
require.Truef(t, ok, "Not an A RR")
require.Equal(t, "db.service.consul.", aRec.Hdr.Name)
require.Equal(t, "198.18.0.1", aRec.A.String())
})
}
}
func TestDNS_ServiceLookupMultiAddrNoCNAME(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "198.18.0.1",
Service: &structs.NodeService{
Service: "db",
Port: 12345,
Address: "198.18.0.1",
},
}
var out struct{}
require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out))
}
// Register a second node node with the same service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "198.18.0.2",
Service: &structs.NodeService{
Service: "db",
Port: 12345,
Address: "bar.node.consul",
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register a second node node with the same service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "baz",
Address: "198.18.0.3",
Service: &structs.NodeService{
Service: "db",
Port: 12345,
Address: "198.18.0.3",
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
m := new(dns.Msg)
m.SetQuestion("db.service.consul.", dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.NoError(t, err)
// expect two A RRs
require.Len(t, in.Answer, 2)
require.IsType(t, &dns.A{}, in.Answer[0])
require.Equal(t, "db.service.consul.", in.Answer[0].Header().Name)
isOneOfTheseIPs := func(ip net.IP) bool {
if ip.Equal(net.ParseIP("198.18.0.1")) || ip.Equal(net.ParseIP("198.18.0.3")) {
return true
}
return false
}
require.True(t, isOneOfTheseIPs(in.Answer[0].(*dns.A).A))
require.IsType(t, &dns.A{}, in.Answer[1])
require.Equal(t, "db.service.consul.", in.Answer[1].Header().Name)
require.True(t, isOneOfTheseIPs(in.Answer[1].(*dns.A).A))
})
}
}
func TestDNS_ServiceLookup(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
DNS: structs.QueryDNSOptions{
TTL: "3s",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if strings.Contains(question, "query") {
// The query should have the TTL associated with the query registration.
if srvRec.Hdr.Ttl != 3 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if aRec.Hdr.Ttl != 3 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
} else {
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if aRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
}
}
// Lookup a non-existing service/query, we should receive an SOA.
questions = []string{
"nodb.service.consul.",
"nope.query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Ns) != 1 {
t.Fatalf("Bad: %#v", in)
}
soaRec, ok := in.Ns[0].(*dns.SOA)
if !ok {
t.Fatalf("Bad: %#v", in.Ns[0])
}
if soaRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Ns[0])
}
}
})
}
}
// TestDNS_ServiceAddressWithTagLookup tests some specific cases that Nomad would exercise,
// Like registering a service w/o a Node. https://github.com/hashicorp/nomad/blob/1174019676ff3d65b39323eb0c7234fb1e09b80c/command/agent/consul/service_client.go#L1366-L1381
// Errors with this were reported in https://github.com/hashicorp/consul/issues/21325#issuecomment-2166845574
// Also we test that only one tag is valid in the URL.
func TestDNS_ServiceAddressWithTagLookup(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
{
// This emulates a Nomad service registration.
// Using an internal RPC for Catalog.Register will not trigger the same condition.
err := a.Client().Agent().ServiceRegister(&api.AgentServiceRegistration{
Kind: api.ServiceKindTypical,
ID: "db-1",
Name: "db",
Tags: []string{"primary"},
Address: "127.0.0.1",
Port: 12345,
Checks: make([]*api.AgentServiceCheck, 0),
})
require.NoError(t, err)
}
{
err := a.Client().Agent().ServiceRegister(&api.AgentServiceRegistration{
Kind: api.ServiceKindTypical,
ID: "db-2",
Name: "db",
Tags: []string{"secondary"},
Address: "127.0.0.2", // The address here has to be different, or the DNS server will dedupe it.
Port: 12345,
Checks: make([]*api.AgentServiceCheck, 0),
})
require.NoError(t, err)
}
// Query the service using a tag - this also checks that we're filtering correctly
questions := []string{
"_db._primary.service.dc1.consul.", // w/ RFC 2782 style syntax
"primary.db.service.dc1.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.NoError(t, err)
require.Len(t, in.Answer, 1, "Expected only one result in the Answer section")
srvRec, ok := in.Answer[0].(*dns.SRV)
require.True(t, ok, "Expected an SRV record in the Answer section")
require.Equal(t, uint16(12345), srvRec.Port)
require.Equal(t, "7f000001.addr.dc1.consul.", srvRec.Target)
aRec, ok := in.Extra[0].(*dns.A)
require.True(t, ok, "Expected an A record in the Extra section")
require.Equal(t, "7f000001.addr.dc1.consul.", aRec.Hdr.Name)
require.Equal(t, "127.0.0.1", aRec.A.String())
if strings.Contains(question, "query") {
// The query should have the TTL associated with the query registration.
require.Equal(t, uint32(3), srvRec.Hdr.Ttl)
require.Equal(t, uint32(3), aRec.Hdr.Ttl)
} else {
require.Equal(t, uint32(0), srvRec.Hdr.Ttl)
require.Equal(t, uint32(0), aRec.Hdr.Ttl)
}
}
// Multiple tags are not supported in the legacy DNS server
questions = []string{
"banana._db._primary.service.dc1.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.NoError(t, err)
require.Len(t, in.Answer, 0, "Expected no results in the Answer section")
// For v1dns, we combine the tags with a period, which results in NXDOMAIN
// For v2dns, we are also return NXDomain
// The reported issue says that v2dns this is returning valid results.
require.Equal(t, dns.RcodeNameError, in.Rcode)
}
})
}
}
func TestDNS_ServiceLookupWithInternalServiceAddress(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
node_name = "my.test-node"
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
// The service is using the consul DNS name as service address
// which triggers a lookup loop and a subsequent stack overflow
// crash.
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Address: "db.service.consul",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
// Looking up the service should not trigger a loop
m := new(dns.Msg)
m.SetQuestion("db.service.consul.", dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
wantAnswer := []dns.RR{
&dns.SRV{
Hdr: dns.RR_Header{Name: "db.service.consul.", Rrtype: 0x21, Class: 0x1, Rdlength: 0x1b},
Priority: 0x1,
Weight: 0x1,
Port: 12345,
Target: "foo.node.dc1.consul.",
},
}
require.Equal(t, wantAnswer, in.Answer, "answer")
wantExtra := []dns.RR{
&dns.A{
Hdr: dns.RR_Header{Name: "foo.node.dc1.consul.", Rrtype: 0x1, Class: 0x1, Rdlength: 0x4},
A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1
},
}
require.Equal(t, wantExtra, in.Extra, "extra")
})
}
}
func TestDNS_ConnectServiceLookup(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register
{
args := structs.TestRegisterRequestProxy(t)
args.Address = "127.0.0.55"
args.Service.Proxy.DestinationServiceName = "db"
args.Service.Address = ""
args.Service.Port = 12345
var out struct{}
require.Nil(t, a.RPC(context.Background(), "Catalog.Register", args, &out))
}
// Look up the service
questions := []string{
"db.connect.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.Nil(t, err)
require.Len(t, in.Answer, 1)
srvRec, ok := in.Answer[0].(*dns.SRV)
require.True(t, ok)
require.Equal(t, uint16(12345), srvRec.Port)
require.Equal(t, "foo.node.dc1.consul.", srvRec.Target)
require.Equal(t, uint32(0), srvRec.Hdr.Ttl)
cnameRec, ok := in.Extra[0].(*dns.A)
require.True(t, ok)
require.Equal(t, "foo.node.dc1.consul.", cnameRec.Hdr.Name)
require.Equal(t, uint32(0), srvRec.Hdr.Ttl)
require.Equal(t, "127.0.0.55", cnameRec.A.String())
}
})
}
}
func TestDNS_IngressServiceLookup(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register ingress-gateway service
{
args := structs.TestRegisterIngressGateway(t)
var out struct{}
require.Nil(t, a.RPC(context.Background(), "Catalog.Register", args, &out))
}
// Register db service
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Address: "",
Port: 80,
},
}
var out struct{}
require.Nil(t, a.RPC(context.Background(), "Catalog.Register", args, &out))
}
// Register proxy-defaults with 'http' protocol
{
req := structs.ConfigEntryRequest{
Op: structs.ConfigEntryUpsert,
Datacenter: "dc1",
Entry: &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{
"protocol": "http",
},
},
WriteRequest: structs.WriteRequest{Token: "root"},
}
var out bool
require.Nil(t, a.RPC(context.Background(), "ConfigEntry.Apply", req, &out))
require.True(t, out)
}
// Register ingress-gateway config entry
{
args := &structs.IngressGatewayConfigEntry{
Name: "ingress-gateway",
Kind: structs.IngressGateway,
Listeners: []structs.IngressListener{
{
Port: 8888,
Protocol: "http",
Services: []structs.IngressService{
{Name: "db"},
{Name: "api"},
},
},
},
}
req := structs.ConfigEntryRequest{
Op: structs.ConfigEntryUpsert,
Datacenter: "dc1",
Entry: args,
}
var out bool
require.Nil(t, a.RPC(context.Background(), "ConfigEntry.Apply", req, &out))
require.True(t, out)
}
// Look up the service
questions := []string{
"api.ingress.consul.",
"api.ingress.dc1.consul.",
"db.ingress.consul.",
"db.ingress.dc1.consul.",
}
for _, question := range questions {
t.Run(question, func(t *testing.T) {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeA)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.Nil(t, err)
require.Len(t, in.Answer, 1)
cnameRec, ok := in.Answer[0].(*dns.A)
require.True(t, ok)
require.Equal(t, question, cnameRec.Hdr.Name)
require.Equal(t, uint32(0), cnameRec.Hdr.Ttl)
require.Equal(t, "127.0.0.1", cnameRec.A.String())
})
}
})
}
}
func TestDNS_ExternalServiceLookup(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with an external service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "www.google.com",
Service: &structs.NodeService{
Service: "db",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service
questions := []string{
"db.service.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 || len(in.Extra) > 0 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "www.google.com." {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
}
})
}
}
func TestDNS_ExternalServiceToConsulCNAMELookup(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
domain = "CONSUL."
node_name = "test node"
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register the initial node with a service
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "web",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "web",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an external service pointing to the 'web' service
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "alias",
Address: "web.service.consul",
Service: &structs.NodeService{
Service: "alias",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly
questions := []string{
"alias.service.consul.",
"alias.service.CoNsUl.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "web.service.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if len(in.Extra) != 1 {
t.Fatalf("Bad: %#v", in)
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "web.service.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
}
})
}
}
func TestDNS_ExternalServiceToConsulCNAMENestedLookup(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
node_name = "test-node"
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register the initial node with a service
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "web",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "web",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an external service pointing to the 'web' service
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "alias",
Address: "web.service.consul",
Service: &structs.NodeService{
Service: "alias",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an external service pointing to the 'alias' service
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "alias2",
Address: "alias.service.consul",
Service: &structs.NodeService{
Service: "alias2",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly
questions := []string{
"alias2.service.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "alias.service.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if len(in.Extra) != 2 {
t.Fatalf("Bad: %#v", in)
}
cnameRec, ok := in.Extra[0].(*dns.CNAME)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if cnameRec.Hdr.Name != "alias.service.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if cnameRec.Target != "web.service.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if cnameRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
aRec, ok := in.Extra[1].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[1])
}
if aRec.Hdr.Name != "web.service.consul." {
t.Fatalf("Bad: %#v", in.Extra[1])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Extra[1])
}
if aRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[1])
}
}
})
}
}
func TestDNS_ServiceLookup_ServiceAddress_A(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Address: "127.0.0.2",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "7f000002.addr.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "7f000002.addr.dc1.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.2" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
}
})
}
}
func TestDNS_AltDomain_ServiceLookup_ServiceAddress_A(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
alt_domain = "test-domain"
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Address: "127.0.0.2",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []struct {
ask string
wantDomain string
}{
{"db.service.consul.", "consul."},
{id + ".query.consul.", "consul."},
{"db.service.test-domain.", "test-domain."},
{id + ".query.test-domain.", "test-domain."},
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question.ask, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "7f000002.addr.dc1."+question.wantDomain {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "7f000002.addr.dc1."+question.wantDomain {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.2" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
}
})
}
}
func TestDNS_ServiceLookup_ServiceAddress_SRV(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
recursor := makeRecursor(t, dns.Msg{
Answer: []dns.RR{
dnsCNAME("www.google.com", "google.com"),
dnsA("google.com", "1.2.3.4"),
},
})
defer recursor.Shutdown()
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
recursors = ["`+recursor.Addr+`"]
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service whose address isn't an IP.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Address: "www.google.com",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
// Specify prepared query name containing "." to test
// since that is technically supported (though atypical).
var id string
preparedQueryName := "query.name.with.dots"
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: preparedQueryName,
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
preparedQueryName + ".query.consul.",
fmt.Sprintf("_%s._tcp.query.consul.", id),
fmt.Sprintf("_%s._tcp.query.consul.", preparedQueryName),
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "www.google.com." {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
// Should have google CNAME
cnRec, ok := in.Extra[0].(*dns.CNAME)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if cnRec.Target != "google.com." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
// Check we recursively resolve
aRec, ok := in.Extra[1].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[1])
}
if aRec.A.String() != "1.2.3.4" {
t.Fatalf("Bad: %s", aRec.A.String())
}
}
})
}
}
func TestDNS_ServiceLookup_ServiceAddressIPV6(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Address: "2607:20:4005:808::200e",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "2607002040050808000000000000200e.addr.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
aRec, ok := in.Extra[0].(*dns.AAAA)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "2607002040050808000000000000200e.addr.dc1.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.AAAA.String() != "2607:20:4005:808::200e" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
}
})
}
}
func TestDNS_AltDomain_ServiceLookup_ServiceAddressIPV6(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
alt_domain = "test-domain"
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Address: "2607:20:4005:808::200e",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []struct {
ask string
want string
}{
{"db.service.consul.", "2607002040050808000000000000200e.addr.dc1.consul."},
{"db.service.test-domain.", "2607002040050808000000000000200e.addr.dc1.test-domain."},
{id + ".query.consul.", "2607002040050808000000000000200e.addr.dc1.consul."},
{id + ".query.test-domain.", "2607002040050808000000000000200e.addr.dc1.test-domain."},
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question.ask, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != question.want {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
aRec, ok := in.Extra[0].(*dns.AAAA)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != question.want {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.AAAA.String() != "2607:20:4005:808::200e" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
}
})
}
}
func TestDNS_ServiceLookup_WanTranslation(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a1 := NewTestAgent(t, `
datacenter = "dc1"
translate_wan_addrs = true
acl_datacenter = ""
`+experimentsHCL)
defer a1.Shutdown()
a2 := NewTestAgent(t, `
datacenter = "dc2"
translate_wan_addrs = true
acl_datacenter = ""
`+experimentsHCL)
defer a2.Shutdown()
// Join WAN cluster
addr := fmt.Sprintf("127.0.0.1:%d", a1.Config.SerfPortWAN)
_, err := a2.JoinWAN([]string{addr})
require.NoError(t, err)
retry.Run(t, func(r *retry.R) {
require.Len(r, a1.WANMembers(), 2)
require.Len(r, a2.WANMembers(), 2)
})
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc2",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
require.NoError(t, a2.RPC(context.Background(), "PreparedQuery.Apply", args, &id))
}
type testCase struct {
nodeTaggedAddresses map[string]string
serviceAddress string
serviceTaggedAddresses map[string]structs.ServiceAddress
dnsAddr string
expectedPort uint16
expectedAddress string
expectedARRName string
}
cases := map[string]testCase{
"node-addr-from-dc1": {
dnsAddr: a1.config.DNSAddrs[0].String(),
expectedPort: 8080,
expectedAddress: "127.0.0.1",
expectedARRName: "foo.node.dc2.consul.",
},
"node-wan-from-dc1": {
dnsAddr: a1.config.DNSAddrs[0].String(),
nodeTaggedAddresses: map[string]string{
"wan": "127.0.0.2",
},
expectedPort: 8080,
expectedAddress: "127.0.0.2",
expectedARRName: "7f000002.addr.dc2.consul.",
},
"service-addr-from-dc1": {
dnsAddr: a1.config.DNSAddrs[0].String(),
nodeTaggedAddresses: map[string]string{
"wan": "127.0.0.2",
},
serviceAddress: "10.0.1.1",
expectedPort: 8080,
expectedAddress: "10.0.1.1",
expectedARRName: "0a000101.addr.dc2.consul.",
},
"service-wan-from-dc1": {
dnsAddr: a1.config.DNSAddrs[0].String(),
nodeTaggedAddresses: map[string]string{
"wan": "127.0.0.2",
},
serviceAddress: "10.0.1.1",
serviceTaggedAddresses: map[string]structs.ServiceAddress{
"wan": {
Address: "198.18.0.1",
Port: 80,
},
},
expectedPort: 80,
expectedAddress: "198.18.0.1",
expectedARRName: "c6120001.addr.dc2.consul.",
},
"node-addr-from-dc2": {
dnsAddr: a2.config.DNSAddrs[0].String(),
expectedPort: 8080,
expectedAddress: "127.0.0.1",
expectedARRName: "foo.node.dc2.consul.",
},
"node-wan-from-dc2": {
dnsAddr: a2.config.DNSAddrs[0].String(),
nodeTaggedAddresses: map[string]string{
"wan": "127.0.0.2",
},
expectedPort: 8080,
expectedAddress: "127.0.0.1",
expectedARRName: "foo.node.dc2.consul.",
},
"service-addr-from-dc2": {
dnsAddr: a2.config.DNSAddrs[0].String(),
nodeTaggedAddresses: map[string]string{
"wan": "127.0.0.2",
},
serviceAddress: "10.0.1.1",
expectedPort: 8080,
expectedAddress: "10.0.1.1",
expectedARRName: "0a000101.addr.dc2.consul.",
},
"service-wan-from-dc2": {
dnsAddr: a2.config.DNSAddrs[0].String(),
nodeTaggedAddresses: map[string]string{
"wan": "127.0.0.2",
},
serviceAddress: "10.0.1.1",
serviceTaggedAddresses: map[string]structs.ServiceAddress{
"wan": {
Address: "198.18.0.1",
Port: 80,
},
},
expectedPort: 8080,
expectedAddress: "10.0.1.1",
expectedARRName: "0a000101.addr.dc2.consul.",
},
}
for name, tc := range cases {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
// Register a remote node with a service. This is in a retry since we
// need the datacenter to have a route which takes a little more time
// beyond the join, and we don't have direct access to the router here.
retry.Run(t, func(r *retry.R) {
args := &structs.RegisterRequest{
Datacenter: "dc2",
Node: "foo",
Address: "127.0.0.1",
TaggedAddresses: tc.nodeTaggedAddresses,
Service: &structs.NodeService{
Service: "db",
Address: tc.serviceAddress,
Port: 8080,
TaggedAddresses: tc.serviceTaggedAddresses,
},
}
var out struct{}
require.NoError(r, a2.RPC(context.Background(), "Catalog.Register", args, &out))
})
// Look up the SRV record via service and prepared query.
questions := []string{
"db.service.dc2.consul.",
id + ".query.dc2.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
addr := tc.dnsAddr
in, _, err := c.Exchange(m, addr)
require.NoError(t, err)
require.Len(t, in.Answer, 1)
srvRec, ok := in.Answer[0].(*dns.SRV)
require.True(t, ok, "Bad: %#v", in.Answer[0])
require.Equal(t, tc.expectedPort, srvRec.Port)
aRec, ok := in.Extra[0].(*dns.A)
require.True(t, ok, "Bad: %#v", in.Extra[0])
require.Equal(t, tc.expectedARRName, aRec.Hdr.Name)
require.Equal(t, tc.expectedAddress, aRec.A.String())
}
// Also check the A record directly
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeA)
c := new(dns.Client)
addr := tc.dnsAddr
in, _, err := c.Exchange(m, addr)
require.NoError(t, err)
require.Len(t, in.Answer, 1)
aRec, ok := in.Answer[0].(*dns.A)
require.True(t, ok, "Bad: %#v", in.Answer[0])
require.Equal(t, question, aRec.Hdr.Name)
require.Equal(t, tc.expectedAddress, aRec.A.String())
}
})
}
})
}
}
func TestDNS_ServiceLookup_CaseInsensitive(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
tests := []struct {
name string
config string
}{
// UDP + EDNS
{"normal", ""},
{"cache", `dns_config{ allow_stale=true, max_stale="3h", use_cache=true, "cache_max_age"="3h"}`},
{"cache-with-streaming", `
rpc{
enable_streaming=true
}
use_streaming_backend=true
dns_config{ allow_stale=true, max_stale="3h", use_cache=true, "cache_max_age"="3h"}
`},
}
for _, tst := range tests {
t.Run(fmt.Sprintf("A lookup %v", tst.name), func(t *testing.T) {
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, fmt.Sprintf("%s %s", tst.config, experimentsHCL))
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "Db",
Tags: []string{"Primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query, as well as a name.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "somequery",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Try some variations to make sure case doesn't matter.
questions := []string{
"primary.Db.service.consul.",
"primary.db.service.consul.",
"pRIMARY.dB.service.consul.",
"PRIMARY.dB.service.consul.",
"db.service.consul.",
"DB.service.consul.",
"Db.service.consul.",
"somequery.query.consul.",
"SomeQuery.query.consul.",
"SOMEQUERY.query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
retry.Run(t, func(r *retry.R) {
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
r.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
r.Fatalf("question %v, empty lookup: %#v", question, in)
}
})
}
})
}
})
}
}
// V2 DNS: we have deprecated support for service tags w/ periods
func TestDNS_ServiceLookup_TagPeriod(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(false) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register node
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"v1.primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
m1 := new(dns.Msg)
m1.SetQuestion("v1.primary2.db.service.consul.", dns.TypeSRV)
c1 := new(dns.Client)
in, _, err := c1.Exchange(m1, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 0 {
t.Fatalf("Bad: %#v", in)
}
m := new(dns.Msg)
m.SetQuestion("v1.primary.db.service.consul.", dns.TypeSRV)
c := new(dns.Client)
in, _, err = c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
})
}
}
// TestDNS_ServiceLookup_ExtraTags tests tag behavior.
// With v1dns, we still support period tags, but if a tag is not found it's an NXDOMAIN code.
// With v2dns, we do not support period tags, so it's an NXDOMAIN code because the name is not valid.
func TestDNS_ServiceLookup_ExtraTags(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register node
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
m1 := new(dns.Msg)
m1.SetQuestion("dummy.primary.db.service.consul.", dns.TypeSRV)
c1 := new(dns.Client)
in, _, err := c1.Exchange(m1, a.DNSAddr())
require.NoError(t, err)
require.Len(t, in.Answer, 0, "Expected no answer")
require.Equal(t, dns.RcodeNameError, in.Rcode)
})
}
}
func TestDNS_ServiceLookup_PreparedQueryNamePeriod(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register a prepared query with a period in the name.
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "some.query.we.like",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
var id string
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
m := new(dns.Msg)
m.SetQuestion("some.query.we.like.query.consul.", dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
})
}
}
func TestDNS_ServiceLookup_Dedup(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a single node with multiple instances of a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
args = &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
ID: "db2",
Service: "db",
Tags: []string{"replica"},
Port: 12345,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
args = &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
ID: "db3",
Service: "db",
Tags: []string{"replica"},
Port: 12346,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query, make sure only
// one IP is returned.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
aRec, ok := in.Answer[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Answer[0])
}
}
})
}
}
func TestDNS_ServiceLookup_Dedup_SRV(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a single node with multiple instances of a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
args = &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
ID: "db2",
Service: "db",
Tags: []string{"replica"},
Port: 12345,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
args = &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
ID: "db3",
Service: "db",
Tags: []string{"replica"},
Port: 12346,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query, make sure only
// one IP is returned and two unique ports are returned.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 2 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 && srvRec.Port != 12346 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
srvRec, ok = in.Answer[1].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[1])
}
if srvRec.Port != 12346 && srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Port == in.Answer[0].(*dns.SRV).Port {
t.Fatalf("should be a different port")
}
if srvRec.Target != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
}
})
}
}
func TestDNS_ServiceLookup_FilterCritical(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register nodes with health checks in various states.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "serf",
Name: "serf",
Status: api.HealthCritical,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
args2 := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "127.0.0.2",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "serf",
Name: "serf",
Status: api.HealthCritical,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args2, &out); err != nil {
t.Fatalf("err: %v", err)
}
args3 := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "127.0.0.2",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "db",
Name: "db",
ServiceID: "db",
Status: api.HealthCritical,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args3, &out); err != nil {
t.Fatalf("err: %v", err)
}
args4 := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "baz",
Address: "127.0.0.3",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args4, &out); err != nil {
t.Fatalf("err: %v", err)
}
args5 := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "quux",
Address: "127.0.0.4",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "db",
Name: "db",
ServiceID: "db",
Status: api.HealthWarning,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args5, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
// Only 4 and 5 are not failing, so we should get 2 answers
if len(in.Answer) != 2 {
t.Fatalf("Bad: %#v", in)
}
ips := make(map[string]bool)
for _, resp := range in.Answer {
aRec := resp.(*dns.A)
ips[aRec.A.String()] = true
}
if !ips["127.0.0.3"] {
t.Fatalf("Bad: %#v should contain 127.0.0.3 (state healthy)", in)
}
if !ips["127.0.0.4"] {
t.Fatalf("Bad: %#v should contain 127.0.0.4 (state warning)", in)
}
}
})
}
}
func TestDNS_ServiceLookup_OnlyFailing(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register nodes with all health checks in a critical state.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "serf",
Name: "serf",
Status: api.HealthCritical,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
args2 := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "127.0.0.2",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "serf",
Name: "serf",
Status: api.HealthCritical,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args2, &out); err != nil {
t.Fatalf("err: %v", err)
}
args3 := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "127.0.0.2",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "db",
Name: "db",
ServiceID: "db",
Status: api.HealthCritical,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args3, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
// All 3 are failing, so we should get 0 answers and an NXDOMAIN response
if len(in.Answer) != 0 {
t.Fatalf("Bad: %#v", in)
}
if in.Rcode != dns.RcodeNameError {
t.Fatalf("Bad: %#v", in)
}
}
})
}
}
func TestDNS_ServiceLookup_OnlyPassing(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
dns_config {
only_passing = true
}
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register nodes with health checks in various states.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "db",
Name: "db",
ServiceID: "db",
Status: api.HealthPassing,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
args2 := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "127.0.0.2",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "db",
Name: "db",
ServiceID: "db",
Status: api.HealthWarning,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args2, &out); err != nil {
t.Fatalf("err: %v", err)
}
args3 := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "baz",
Address: "127.0.0.3",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
Check: &structs.HealthCheck{
CheckID: "db",
Name: "db",
ServiceID: "db",
Status: api.HealthCritical,
},
}
if err := a.RPC(context.Background(), "Catalog.Register", args3, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "db",
OnlyPassing: true,
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
// Only 1 is passing, so we should only get 1 answer
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
resp := in.Answer[0]
aRec := resp.(*dns.A)
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Answer[0])
}
}
newCfg := *a.Config
newCfg.DNSOnlyPassing = false
err := a.reloadConfigInternal(&newCfg)
require.NoError(t, err)
// only_passing is now false. we should now get two nodes
m := new(dns.Msg)
m.SetQuestion("db.service.consul.", dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.NoError(t, err)
require.Equal(t, 2, len(in.Answer))
ips := []string{in.Answer[0].(*dns.A).A.String(), in.Answer[1].(*dns.A).A.String()}
sort.Strings(ips)
require.Equal(t, []string{"127.0.0.1", "127.0.0.2"}, ips)
})
}
}
func TestDNS_ServiceLookup_Randomize(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a large number of nodes.
for i := 0; i < generateNumNodes; i++ {
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: fmt.Sprintf("foo%d", i),
Address: fmt.Sprintf("127.0.0.%d", i+1),
Service: &structs.NodeService{
Service: "web",
Port: 8000,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "web",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query. Ensure the
// response is randomized each time.
questions := []string{
"web.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
uniques := map[string]struct{}{}
for i := 0; i < 10; i++ {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeANY)
c := &dns.Client{Net: "udp"}
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
// Response length should be truncated and we should get
// an A record for each response.
if len(in.Answer) != defaultNumUDPResponses {
t.Fatalf("Bad: %#v", len(in.Answer))
}
// Collect all the names.
var names []string
for _, rec := range in.Answer {
switch v := rec.(type) {
case *dns.SRV:
names = append(names, v.Target)
case *dns.A:
names = append(names, v.A.String())
}
}
nameS := strings.Join(names, "|")
// Tally the results.
uniques[nameS] = struct{}{}
}
// Give some wiggle room. Since the responses are randomized and
// there is a finite number of combinations, requiring 0
// duplicates every test run eventually gives us failures.
if len(uniques) < 2 {
t.Fatalf("unique response ratio too low: %d/10\n%v", len(uniques), uniques)
}
}
})
}
}
func TestDNS_ServiceLookup_Truncate(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
dns_config {
enable_truncate = true
}
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a large number of nodes.
for i := 0; i < generateNumNodes; i++ {
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: fmt.Sprintf("foo%d", i),
Address: fmt.Sprintf("127.0.0.%d", i+1),
Service: &structs.NodeService{
Service: "web",
Port: 8000,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "web",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query. Ensure the
// response is truncated each time.
questions := []string{
"web.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
// Check for the truncate bit
if !in.Truncated {
t.Fatalf("should have truncate bit")
}
}
})
}
}
func TestDNS_ServiceLookup_LargeResponses(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
dns_config {
enable_truncate = true
}
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
longServiceName := "this-is-a-very-very-very-very-very-long-name-for-a-service"
// Register a lot of nodes.
for i := 0; i < 4; i++ {
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: fmt.Sprintf("foo%d", i),
Address: fmt.Sprintf("127.0.0.%d", i+1),
Service: &structs.NodeService{
Service: longServiceName,
Tags: []string{"primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: longServiceName,
Service: structs.ServiceQuery{
Service: longServiceName,
Tags: []string{"primary"},
},
},
}
var id string
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"_" + longServiceName + "._primary.service.consul.",
longServiceName + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if !in.Truncated {
t.Fatalf("should have truncate bit")
}
// Make sure the response size is RFC 1035-compliant for UDP messages
if in.Len() > 512 {
t.Fatalf("Bad: %d", in.Len())
}
// We should only have two answers now
if len(in.Answer) != 2 {
t.Fatalf("Bad: %d", len(in.Answer))
}
// Make sure the ADDITIONAL section matches the ANSWER section.
if len(in.Answer) != len(in.Extra) {
t.Fatalf("Bad: %d vs. %d", len(in.Answer), len(in.Extra))
}
for i := 0; i < len(in.Answer); i++ {
srv, ok := in.Answer[i].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[i])
}
a, ok := in.Extra[i].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[i])
}
if srv.Target != a.Hdr.Name {
t.Fatalf("Bad: %#v %#v", srv, a)
}
}
// Check for the truncate bit
if !in.Truncated {
t.Fatalf("should have truncate bit")
}
}
})
}
}
func testDNSServiceLookupResponseLimits(t *testing.T, answerLimit int, qType uint16,
expectedService, expectedQuery, expectedQueryID int, additionalHCL string) (bool, error) {
a := NewTestAgent(t, `
node_name = "test-node"
dns_config {
udp_answer_limit = `+fmt.Sprintf("%d", answerLimit)+`
}
`+additionalHCL)
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
choices := perfectlyRandomChoices(generateNumNodes, pctNodesWithIPv6)
for i := 0; i < generateNumNodes; i++ {
nodeAddress := fmt.Sprintf("127.0.0.%d", i+1)
if choices[i] {
nodeAddress = fmt.Sprintf("fe80::%d", i+1)
}
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: fmt.Sprintf("foo%d", i),
Address: nodeAddress,
Service: &structs.NodeService{
Service: "api-tier",
Port: 8080,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
return false, fmt.Errorf("err: %v", err)
}
}
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "api-tier",
Service: structs.ServiceQuery{
Service: "api-tier",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
return false, fmt.Errorf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"api-tier.service.consul.",
"api-tier.query.consul.",
id + ".query.consul.",
}
for idx, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, qType)
c := &dns.Client{Net: "udp"}
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
return false, fmt.Errorf("err: %v", err)
}
switch idx {
case 0:
if (expectedService > 0 && len(in.Answer) != expectedService) ||
(expectedService < -1 && len(in.Answer) < lib.AbsInt(expectedService)) {
return false, fmt.Errorf("%d/%d answers received for type %v for %s, sz:=%d", len(in.Answer), answerLimit, qType, question, in.Len())
}
case 1:
if (expectedQuery > 0 && len(in.Answer) != expectedQuery) ||
(expectedQuery < -1 && len(in.Answer) < lib.AbsInt(expectedQuery)) {
return false, fmt.Errorf("%d/%d answers received for type %v for %s, sz:=%d", len(in.Answer), answerLimit, qType, question, in.Len())
}
case 2:
if (expectedQueryID > 0 && len(in.Answer) != expectedQueryID) ||
(expectedQueryID < -1 && len(in.Answer) < lib.AbsInt(expectedQueryID)) {
return false, fmt.Errorf("%d/%d answers received for type %v for %s, sz:=%d", len(in.Answer), answerLimit, qType, question, in.Len())
}
default:
panic("abort")
}
}
return true, nil
}
func checkDNSService(
t *testing.T,
generateNumNodes int,
aRecordLimit int,
qType uint16,
expectedResultsCount int,
udpSize uint16,
) {
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
node_name = "test-node"
dns_config {
a_record_limit = `+fmt.Sprintf("%d", aRecordLimit)+`
udp_answer_limit = `+fmt.Sprintf("%d", aRecordLimit)+`
}
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
choices := perfectlyRandomChoices(generateNumNodes, pctNodesWithIPv6)
for i := 0; i < generateNumNodes; i++ {
nodeAddress := fmt.Sprintf("127.0.0.%d", i+1)
if choices[i] {
nodeAddress = fmt.Sprintf("fe80::%d", i+1)
}
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: fmt.Sprintf("foo%d", i),
Address: nodeAddress,
Service: &structs.NodeService{
Service: "api-tier",
Port: 8080,
},
}
var out struct{}
require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out))
}
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "api-tier",
Service: structs.ServiceQuery{
Service: "api-tier",
},
},
}
require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &id))
}
// Look up the service directly and via prepared query.
questions := []string{
"api-tier.service.consul.",
"api-tier.query.consul.",
id + ".query.consul.",
}
for _, question := range questions {
question := question
t.Run("question: "+question, func(t *testing.T) {
m := new(dns.Msg)
m.SetQuestion(question, qType)
protocol := "tcp"
if udpSize > 0 {
protocol = "udp"
}
if udpSize > 512 {
m.SetEdns0(udpSize, true)
}
c := &dns.Client{Net: protocol, UDPSize: 8192}
in, _, err := c.Exchange(m, a.DNSAddr())
require.NoError(t, err)
t.Logf("DNS Response for %+v - %+v", m, in)
require.Equal(t, expectedResultsCount, len(in.Answer),
"%d/%d answers received for type %v for %s (%s)", len(in.Answer), expectedResultsCount, qType, question, protocol)
})
}
})
}
}
func TestDNS_ServiceLookup_ARecordLimits(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
tests := []struct {
name string
aRecordLimit int
expectedAResults int
expectedAAAAResults int
expectedANYResults int
expectedSRVResults int
numNodesTotal int
udpSize uint16
_unused_udpAnswerLimit int // NOTE: this field is not used
}{
// UDP + EDNS
{"udp-edns-1", 1, 1, 1, 1, 30, 30, 8192, 3},
{"udp-edns-2", 2, 2, 2, 2, 30, 30, 8192, 3},
{"udp-edns-3", 3, 3, 3, 3, 30, 30, 8192, 3},
{"udp-edns-4", 4, 4, 4, 4, 30, 30, 8192, 3},
{"udp-edns-5", 5, 5, 5, 5, 30, 30, 8192, 3},
{"udp-edns-6", 6, 6, 6, 6, 30, 30, 8192, 3},
{"udp-edns-max", 6, 2, 1, 3, 3, 3, 8192, 3},
// All UDP without EDNS have a limit of 2 answers due to udpAnswerLimit
// Even SRV records are limit to 2 records
{"udp-limit-1", 1, 1, 0, 1, 1, 1, 512, 2},
{"udp-limit-2", 2, 1, 1, 2, 2, 2, 512, 2},
// AAAA results limited by size of payload
{"udp-limit-3", 3, 1, 1, 2, 2, 2, 512, 2},
{"udp-limit-4", 4, 1, 1, 2, 2, 2, 512, 2},
{"udp-limit-5", 5, 1, 1, 2, 2, 2, 512, 2},
{"udp-limit-6", 6, 1, 1, 2, 2, 2, 512, 2},
{"udp-limit-max", 6, 1, 1, 2, 2, 2, 512, 2},
// All UDP without EDNS and no udpAnswerLimit
// Size of records is limited by UDP payload
{"udp-1", 1, 1, 0, 1, 1, 1, 512, 0},
{"udp-2", 2, 1, 1, 2, 2, 2, 512, 0},
{"udp-3", 3, 1, 1, 2, 2, 2, 512, 0},
{"udp-4", 4, 1, 1, 2, 2, 2, 512, 0},
{"udp-5", 5, 1, 1, 2, 2, 2, 512, 0},
{"udp-6", 6, 1, 1, 2, 2, 2, 512, 0},
// Only 3 A and 3 SRV records on 512 bytes
{"udp-max", 6, 1, 1, 2, 2, 2, 512, 0},
{"tcp-1", 1, 1, 1, 1, 30, 30, 0, 0},
{"tcp-2", 2, 2, 2, 2, 30, 30, 0, 0},
{"tcp-3", 3, 3, 3, 3, 30, 30, 0, 0},
{"tcp-4", 4, 4, 4, 4, 30, 30, 0, 0},
{"tcp-5", 5, 5, 5, 5, 30, 30, 0, 0},
{"tcp-6", 6, 6, 6, 6, 30, 30, 0, 0},
{"tcp-max", 6, 1, 1, 2, 2, 2, 0, 0},
}
for _, test := range tests {
test := test // capture loop var
t.Run(test.name, func(t *testing.T) {
// All those queries should have at max queriesLimited elements
t.Run("A", func(t *testing.T) {
checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeA, test.expectedAResults, test.udpSize)
})
t.Run("AAAA", func(t *testing.T) {
checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeAAAA, test.expectedAAAAResults, test.udpSize)
})
t.Run("ANY", func(t *testing.T) {
checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeANY, test.expectedANYResults, test.udpSize)
})
// No limits but the size of records for SRV records, since not subject to randomization issues
t.Run("SRV", func(t *testing.T) {
checkDNSService(t, test.expectedSRVResults, test.aRecordLimit, dns.TypeSRV, test.numNodesTotal, test.udpSize)
})
})
}
}
func TestDNS_ServiceLookup_AnswerLimits(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
// Build a matrix of config parameters (udpAnswerLimit), and the
// length of the response per query type and question. Negative
// values imply the test must return at least the abs(value) number
// of records in the answer section. This is required because, for
// example, on OS-X and Linux, the number of answers returned in a
// 512B response is different even though both platforms are x86_64
// and using the same version of Go.
//
// TODO(sean@): Why is it not identical everywhere when using the
// same compiler?
tests := []struct {
name string
udpAnswerLimit int
expectedAService int
expectedAQuery int
expectedAQueryID int
expectedAAAAService int
expectedAAAAQuery int
expectedAAAAQueryID int
expectedANYService int
expectedANYQuery int
expectedANYQueryID int
}{
{"0", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{"1", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{"2", 2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
{"3", 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
{"4", 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
{"5", 5, 5, 5, 5, 5, 5, 5, 5, 5, 5},
{"6", 6, 6, 6, 6, 6, 6, 5, 6, 6, -5},
{"7", 7, 7, 7, 6, 7, 7, 5, 7, 7, -5},
{"8", 8, 8, 8, 6, 8, 8, 5, 8, 8, -5},
{"9", 9, 8, 8, 6, 8, 8, 5, 8, 8, -5},
{"20", 20, 8, 8, 6, 8, 8, 5, 8, -5, -5},
{"30", 30, 8, 8, 6, 8, 8, 5, 8, -5, -5},
}
for _, test := range tests {
test := test // capture loop var
t.Run(fmt.Sprintf("A lookup %v", test), func(t *testing.T) {
ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeA, test.expectedAService, test.expectedAQuery, test.expectedAQueryID, experimentsHCL)
if !ok {
t.Fatalf("Expected service A lookup %s to pass: %v", test.name, err)
}
})
t.Run(fmt.Sprintf("AAAA lookup %v", test), func(t *testing.T) {
ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeAAAA, test.expectedAAAAService, test.expectedAAAAQuery, test.expectedAAAAQueryID, experimentsHCL)
if !ok {
t.Fatalf("Expected service AAAA lookup %s to pass: %v", test.name, err)
}
})
t.Run(fmt.Sprintf("ANY lookup %v", test), func(t *testing.T) {
ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeANY, test.expectedANYService, test.expectedANYQuery, test.expectedANYQueryID, experimentsHCL)
if !ok {
t.Fatalf("Expected service ANY lookup %s to pass: %v", test.name, err)
}
})
}
})
}
}
func TestDNS_ServiceLookup_CNAME(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
recursor := makeRecursor(t, dns.Msg{
Answer: []dns.RR{
dnsCNAME("www.google.com", "google.com"),
dnsA("google.com", "1.2.3.4"),
},
})
defer recursor.Shutdown()
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
recursors = ["`+recursor.Addr+`"]
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a name for an address.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "google",
Address: "www.google.com",
Service: &structs.NodeService{
Service: "search",
Port: 80,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "search",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"search.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
// Service CNAME, google CNAME, google A record
if len(in.Answer) != 3 {
t.Fatalf("Bad: %#v", in)
}
// Should have service CNAME
cnRec, ok := in.Answer[0].(*dns.CNAME)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if cnRec.Target != "www.google.com." {
t.Fatalf("Bad: %#v", in.Answer[0])
}
// Should have google CNAME
cnRec, ok = in.Answer[1].(*dns.CNAME)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[1])
}
if cnRec.Target != "google.com." {
t.Fatalf("Bad: %#v", in.Answer[1])
}
// Check we recursively resolve
if _, ok := in.Answer[2].(*dns.A); !ok {
t.Fatalf("Bad: %#v", in.Answer[2])
}
}
})
}
}
func TestDNS_ServiceLookup_ServiceAddress_CNAME(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
recursor := makeRecursor(t, dns.Msg{
Answer: []dns.RR{
dnsCNAME("www.google.com", "google.com"),
dnsA("google.com", "1.2.3.4"),
},
})
defer recursor.Shutdown()
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
recursors = ["`+recursor.Addr+`"]
`+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a name for an address.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "google",
Address: "1.2.3.4",
Service: &structs.NodeService{
Service: "search",
Port: 80,
Address: "www.google.com",
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Service: structs.ServiceQuery{
Service: "search",
},
},
}
if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"search.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeANY)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
// Service CNAME, google CNAME, google A record
if len(in.Answer) != 3 {
t.Fatalf("Bad: %#v", in)
}
// Should have service CNAME
cnRec, ok := in.Answer[0].(*dns.CNAME)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if cnRec.Target != "www.google.com." {
t.Fatalf("Bad: %#v", in.Answer[0])
}
// Should have google CNAME
cnRec, ok = in.Answer[1].(*dns.CNAME)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[1])
}
if cnRec.Target != "google.com." {
t.Fatalf("Bad: %#v", in.Answer[1])
}
// Check we recursively resolve
if _, ok := in.Answer[2].(*dns.A); !ok {
t.Fatalf("Bad: %#v", in.Answer[2])
}
}
})
}
}
func TestDNS_ServiceLookup_TTL(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `
dns_config {
service_ttl = {
"d*" = "42s"
"db" = "10s"
"db*" = "66s"
"*" = "5s"
}
allow_stale = true
max_stale = "1s"
}
`+experimentsHCL)
defer a.Shutdown()
for idx, service := range []string{"db", "dblb", "dk", "api"} {
nodeName := fmt.Sprintf("foo%d", idx)
address := fmt.Sprintf("127.0.0.%d", idx)
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: nodeName,
Address: address,
Service: &structs.NodeService{
Service: service,
Tags: []string{"primary"},
Port: 12345 + idx,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
c := new(dns.Client)
expectResult := func(dnsQuery string, expectedTTL uint32) {
t.Run(dnsQuery, func(t *testing.T) {
m := new(dns.Msg)
m.SetQuestion(dnsQuery, dns.TypeSRV)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v, len is %d", in, len(in.Answer))
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Hdr.Ttl != expectedTTL {
t.Fatalf("Bad: %#v", in.Answer[0])
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Ttl != expectedTTL {
t.Fatalf("Bad: %#v", in.Extra[0])
}
})
}
// Should have its exact TTL
expectResult("db.service.consul.", 10)
// Should match db*
expectResult("dblb.service.consul.", 66)
// Should match d*
expectResult("dk.service.consul.", 42)
// Should match *
expectResult("api.service.consul.", 5)
})
}
}
func TestDNS_ServiceLookup_SRV_RFC(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register node
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
questions := []string{
"_db._primary.service.dc1.consul.",
"_db._primary.service.consul.",
"_db._primary.dc1.consul.",
"_db._primary.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
}
})
}
}
func TestDNS_ServiceLookup_SRV_RFC_TCP_Default(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register node
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
questions := []string{
"_db._tcp.service.dc1.consul.",
"_db._tcp.service.consul.",
"_db._tcp.dc1.consul.",
"_db._tcp.consul.",
}
for _, question := range questions {
t.Run(question, func(t *testing.T) {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}
srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Answer[0])
}
aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "foo.node.dc1.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Ttl != 0 {
t.Fatalf("Bad: %#v", in.Extra[0])
}
})
}
})
}
}
func initDNSToken(t *testing.T, rpc RPC) {
t.Helper()
reqToken := structs.ACLTokenSetRequest{
Datacenter: "dc1",
ACLToken: structs.ACLToken{
SecretID: "279d4735-f8ca-4d48-b5cc-c00a9713bbf8",
Policies: nil,
TemplatedPolicies: []*structs.ACLTemplatedPolicy{{TemplateName: "builtin/dns"}},
},
WriteRequest: structs.WriteRequest{Token: "root"},
}
err := rpc.RPC(context.Background(), "ACL.TokenSet", &reqToken, &structs.ACLToken{})
require.NoError(t, err)
}
func TestDNS_ServiceLookup_FilterACL(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
tests := []struct {
token string
results int
}{
{"root", 1},
{"anonymous", 0},
{"dns", 1},
}
for _, tt := range tests {
t.Run("ACLToken == "+tt.token, func(t *testing.T) {
hcl := `
primary_datacenter = "dc1"
acl {
enabled = true
default_policy = "deny"
down_policy = "deny"
tokens {
initial_management = "root"
`
if tt.token == "dns" {
// Create a UUID for dns token since it doesn't have an alias
dnsToken := "279d4735-f8ca-4d48-b5cc-c00a9713bbf8"
hcl = hcl + `
default = "anonymous"
dns = "` + dnsToken + `"
`
} else {
hcl = hcl + `
default = "` + tt.token + `"
`
}
hcl = hcl + `
}
}
`
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, hcl+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
if tt.token == "dns" {
initDNSToken(t, a)
}
// Register a service
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "foo",
Port: 12345,
},
WriteRequest: structs.WriteRequest{Token: "root"},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
// Set up the DNS query
c := new(dns.Client)
m := new(dns.Msg)
m.SetQuestion("foo.service.consul.", dns.TypeA)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
if len(in.Answer) != tt.results {
t.Fatalf("Bad: %#v", in)
}
})
}
})
}
}
func TestDNS_ServiceLookup_MetaTXT(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = true } `+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "127.0.0.1",
NodeMeta: map[string]string{
"key": "value",
},
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
m := new(dns.Msg)
m.SetQuestion("db.service.consul.", dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
wantAdditional := []dns.RR{
&dns.A{
Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4},
A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1
},
&dns.TXT{
Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa},
Txt: []string{"key=value"},
},
}
require.Equal(t, wantAdditional, in.Extra)
})
}
}
func TestDNS_ServiceLookup_SuppressTXT(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
for name, experimentsHCL := range getVersionHCL(true) {
t.Run(name, func(t *testing.T) {
a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false } `+experimentsHCL)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Register a node with a service.
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "bar",
Address: "127.0.0.1",
NodeMeta: map[string]string{
"key": "value",
},
Service: &structs.NodeService{
Service: "db",
Tags: []string{"primary"},
Port: 12345,
},
}
var out struct{}
if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
m := new(dns.Msg)
m.SetQuestion("db.service.consul.", dns.TypeSRV)
c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}
wantAdditional := []dns.RR{
&dns.A{
Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4},
A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1
},
}
require.Equal(t, wantAdditional, in.Extra)
})
}
}