diff --git a/agent/dns_node_lookup_test.go b/agent/dns_node_lookup_test.go new file mode 100644 index 000000000000..4a5e7b4df908 --- /dev/null +++ b/agent/dns_node_lookup_test.go @@ -0,0 +1,663 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package agent + +import ( + "context" + "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/testrpc" + "github.com/miekg/dns" + "github.com/stretchr/testify/require" + "testing" +) + +func TestDNS_NodeLookup(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + TaggedAddresses: map[string]string{ + "wan": "127.0.0.2", + }, + NodeMeta: map[string]string{ + "key": "value", + }, + } + + 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("foo.node.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 2) + require.Len(t, in.Extra, 0) + + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok, "First answer is not an A record") + require.Equal(t, "127.0.0.1", aRec.A.String()) + require.Equal(t, uint32(0), aRec.Hdr.Ttl) + + txt, ok := in.Answer[1].(*dns.TXT) + require.True(t, ok, "Second answer is not a TXT record") + require.Len(t, txt.Txt, 1) + require.Equal(t, "key=value", txt.Txt[0]) + + // Re-do the query, but only for an A RR + + m = new(dns.Msg) + m.SetQuestion("foo.node.consul.", dns.TypeA) + + c = new(dns.Client) + in, _, err = c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 1) + require.Len(t, in.Extra, 1) + + aRec, ok = in.Answer[0].(*dns.A) + require.True(t, ok, "Answer is not an A record") + require.Equal(t, "127.0.0.1", aRec.A.String()) + require.Equal(t, uint32(0), aRec.Hdr.Ttl) + + txt, ok = in.Extra[0].(*dns.TXT) + require.True(t, ok, "Extra record is not a TXT record") + require.Len(t, txt.Txt, 1) + require.Equal(t, "key=value", txt.Txt[0]) + + // Re-do the query, but specify the DC + m = new(dns.Msg) + m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) + + c = new(dns.Client) + in, _, err = c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 2) + require.Len(t, in.Extra, 0) + + aRec, ok = in.Answer[0].(*dns.A) + require.True(t, ok, "First answer is not an A record") + require.Equal(t, "127.0.0.1", aRec.A.String()) + require.Equal(t, uint32(0), aRec.Hdr.Ttl) + + _, ok = in.Answer[1].(*dns.TXT) + require.True(t, ok, "Second answer is not a TXT record") + + // lookup a non-existing node, we should receive a SOA + m = new(dns.Msg) + m.SetQuestion("nofoo.node.dc1.consul.", dns.TypeANY) + + c = new(dns.Client) + in, _, err = c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Ns, 1) + soaRec, ok := in.Ns[0].(*dns.SOA) + require.True(t, ok, "NS RR is not a SOA record") + require.Equal(t, uint32(0), soaRec.Hdr.Ttl) +} + +func TestDNS_CaseInsensitiveNodeLookup(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "Foo", + Address: "127.0.0.1", + } + + 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("fOO.node.dc1.consul.", 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("empty lookup: %#v", in) + } +} + +func TestDNS_NodeLookup_PeriodName(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register node with period in name + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo.bar", + Address: "127.0.0.1", + } + + 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("foo.bar.node.consul.", 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_NodeLookup_AAAA(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "::4242:4242", + } + + 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("bar.node.consul.", dns.TypeAAAA) + + 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.AAAA) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.AAAA.String() != "::4242:4242" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } +} + +func TestDNS_NodeLookup_CNAME(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + recursor := makeRecursor(t, dns.Msg{ + Answer: []dns.RR{ + dnsCNAME("www.google.com", "google.com"), + dnsA("google.com", "1.2.3.4"), + dnsTXT("google.com", []string{"my_txt_value"}), + }, + }) + defer recursor.Shutdown() + + a := NewTestAgent(t, ` + recursors = ["`+recursor.Addr+`"] + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "www.google.com", + } + + 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("google.node.consul.", dns.TypeANY) + m.SetEdns0(8192, true) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + wantAnswer := []dns.RR{ + &dns.CNAME{ + Hdr: dns.RR_Header{Name: "google.node.consul.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x10}, + Target: "www.google.com.", + }, + &dns.CNAME{ + Hdr: dns.RR_Header{Name: "www.google.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Rdlength: 0x2}, + Target: "google.com.", + }, + &dns.A{ + Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0x1, 0x2, 0x3, 0x4}, // 1.2.3.4 + }, + &dns.TXT{ + Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xd}, + Txt: []string{"my_txt_value"}, + }, + } + require.Equal(t, wantAnswer, in.Answer) +} + +func TestDNS_NodeLookup_TXT(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + a := NewTestAgent(t, ``) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "127.0.0.1", + NodeMeta: map[string]string{ + "rfc1035-00": "value0", + "key0": "value1", + }, + } + + 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("google.node.consul.", dns.TypeTXT) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Should have the 1 TXT record reply + if len(in.Answer) != 2 { + t.Fatalf("Bad: %#v", in) + } + + txtRec, ok := in.Answer[0].(*dns.TXT) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if len(txtRec.Txt) != 1 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } +} + +func TestDNS_NodeLookup_TXT_DontSuppress(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false }`) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "127.0.0.1", + NodeMeta: map[string]string{ + "rfc1035-00": "value0", + "key0": "value1", + }, + } + + 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("google.node.consul.", dns.TypeTXT) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Should have the 1 TXT record reply + if len(in.Answer) != 2 { + t.Fatalf("Bad: %#v", in) + } + + txtRec, ok := in.Answer[0].(*dns.TXT) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if len(txtRec.Txt) != 1 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } +} + +func TestDNS_NodeLookup_ANY(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + a := NewTestAgent(t, ``) + 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", + }, + } + + 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("bar.node.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + wantAnswer := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "bar.node.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.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, + Txt: []string{"key=value"}, + }, + } + require.Equal(t, wantAnswer, in.Answer) +} + +func TestDNS_NodeLookup_ANY_DontSuppressTXT(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false }`) + 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", + }, + } + + 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("bar.node.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + wantAnswer := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "bar.node.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.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, + Txt: []string{"key=value"}, + }, + } + require.Equal(t, wantAnswer, in.Answer) +} + +func TestDNS_NodeLookup_A_SuppressTXT(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false }`) + 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", + }, + } + + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) + + m := new(dns.Msg) + m.SetQuestion("bar.node.consul.", dns.TypeA) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + + wantAnswer := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 + }, + } + require.Equal(t, wantAnswer, in.Answer) + + // ensure TXT RR suppression + require.Len(t, in.Extra, 0) +} + +func TestDNS_NodeLookup_TTL(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + recursor := makeRecursor(t, dns.Msg{ + Answer: []dns.RR{ + dnsCNAME("www.google.com", "google.com"), + dnsA("google.com", "1.2.3.4"), + }, + }) + defer recursor.Shutdown() + + a := NewTestAgent(t, ` + recursors = ["`+recursor.Addr+`"] + dns_config { + node_ttl = "10s" + allow_stale = true + max_stale = "1s" + } + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + } + + 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("foo.node.consul.", 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]) + } + if aRec.Hdr.Ttl != 10 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + + // Register node with IPv6 + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "::4242:4242", + } + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + // Check an IPv6 record + m = new(dns.Msg) + m.SetQuestion("bar.node.consul.", dns.TypeANY) + + 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) + } + + aaaaRec, ok := in.Answer[0].(*dns.AAAA) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aaaaRec.AAAA.String() != "::4242:4242" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aaaaRec.Hdr.Ttl != 10 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + + // Register node with CNAME + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "www.google.com", + } + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m = new(dns.Msg) + m.SetQuestion("google.node.consul.", dns.TypeANY) + + in, _, err = c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Should have the CNAME record + a few A records + if len(in.Answer) < 2 { + t.Fatalf("Bad: %#v", in) + } + + 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]) + } + if cnRec.Hdr.Ttl != 10 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } +} diff --git a/agent/dns_service_lookup_test.go b/agent/dns_service_lookup_test.go new file mode 100644 index 000000000000..769ff4f9ae94 --- /dev/null +++ b/agent/dns_service_lookup_test.go @@ -0,0 +1,3970 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package agent + +import ( + "context" + "fmt" + "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" + "github.com/miekg/dns" + "github.com/stretchr/testify/require" + "sort" + "strings" + "testing" +) + +func TestDNS_ServiceReverseLookup(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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, + Address: "127.0.0.2", + }, + } + + 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("2.0.0.127.in-addr.arpa.", 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) + } + + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "consul", nil)+"." { + t.Fatalf("Bad: %#v", ptrRec) + } +} + +func TestDNS_ServiceReverseLookup_IPV6(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "2001:db8::1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + Address: "2001:db8::ff00:42:8329", + }, + } + + 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("9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", 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) + } + + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "consul", nil)+"." { + t.Fatalf("Bad: %#v", ptrRec) + } +} + +func TestDNS_ServiceReverseLookup_CustomDomain(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, ` + domain = "custom" + `) + 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, + Address: "127.0.0.2", + }, + } + + 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("2.0.0.127.in-addr.arpa.", 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) + } + + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "custom", nil)+"." { + t.Fatalf("Bad: %#v", ptrRec) + } +} + +func TestDNS_ServiceReverseLookupNodeAddress(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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, + Address: "127.0.0.1", + }, + } + + 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("1.0.0.127.in-addr.arpa.", 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) + } + + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", ptrRec) + } +} + +func TestDNS_ServiceLookupNoMultiCNAME(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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 a CNAME and 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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 a CNAME and an A RR + require.Len(t, in.Answer, 2) + require.IsType(t, &dns.A{}, in.Answer[0]) + require.IsType(t, &dns.A{}, in.Answer[1]) +} + +func TestDNS_ServiceLookup(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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", + }, + }, + } + 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) + } + 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]) + } + } + + // 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]) + } + + } +} + +func TestDNS_ServiceLookupWithInternalServiceAddress(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, ` + node_name = "my.test-node" + `) + 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") + } + + t.Parallel() + + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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 { + 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") + } + + t.Parallel() + a := NewTestAgent(t, ` + domain = "CONSUL." + node_name = "test node" + `) + 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") + } + + t.Parallel() + a := NewTestAgent(t, ` + node_name = "test-node" + `) + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, ` + alt_domain = "test-domain" + `) + 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") + } + + t.Parallel() + recursor := makeRecursor(t, dns.Msg{ + Answer: []dns.RR{ + dnsCNAME("www.google.com", "google.com"), + dnsA("google.com", "1.2.3.4"), + }, + }) + defer recursor.Shutdown() + + a := NewTestAgent(t, ` + recursors = ["`+recursor.Addr+`"] + `) + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, ` + alt_domain = "test-domain" + `) + 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") + } + + t.Parallel() + a1 := NewTestAgent(t, ` + datacenter = "dc1" + translate_wan_addrs = true + acl_datacenter = "" + `) + defer a1.Shutdown() + + a2 := NewTestAgent(t, ` + datacenter = "dc2" + translate_wan_addrs = true + acl_datacenter = "" + `) + 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_CaseInsensitiveServiceLookup(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + 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) { + a := NewTestAgent(t, tst.config) + 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) + } + }) + } + }) + } +} + +func TestDNS_ServiceLookup_TagPeriod(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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]) + } +} + +func TestDNS_ServiceLookup_PreparedQueryNamePeriod(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, ` + dns_config { + only_passing = true + } + `) + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, ` + dns_config { + enable_truncate = true + } + `) + 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") + } + + t.Parallel() + a := NewTestAgent(t, ` + dns_config { + enable_truncate = true + } + `) + 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) (bool, error) { + a := NewTestAgent(t, ` + node_name = "test-node" + dns_config { + udp_answer_limit = `+fmt.Sprintf("%d", answerLimit)+` + } + `) + 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, +) { + a := NewTestAgent(t, ` + node_name = "test-node" + dns_config { + a_record_limit = `+fmt.Sprintf("%d", aRecordLimit)+` + udp_answer_limit = `+fmt.Sprintf("%d", aRecordLimit)+` + } + `) + 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") + } + + t.Parallel() + 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) { + t.Parallel() + + // All those queries should have at max queriesLimited elements + + t.Run("A", func(t *testing.T) { + t.Parallel() + checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeA, test.expectedAResults, test.udpSize) + }) + + t.Run("AAAA", func(t *testing.T) { + t.Parallel() + checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeAAAA, test.expectedAAAAResults, test.udpSize) + }) + + t.Run("ANY", func(t *testing.T) { + t.Parallel() + 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) { + t.Parallel() + 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") + } + + t.Parallel() + // 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) { + t.Parallel() + ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeA, test.expectedAService, test.expectedAQuery, test.expectedAQueryID) + 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) { + t.Parallel() + ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeAAAA, test.expectedAAAAService, test.expectedAAAAQuery, test.expectedAAAAQueryID) + 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) { + t.Parallel() + ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeANY, test.expectedANYService, test.expectedANYQuery, test.expectedANYQueryID) + 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") + } + + t.Parallel() + recursor := makeRecursor(t, dns.Msg{ + Answer: []dns.RR{ + dnsCNAME("www.google.com", "google.com"), + dnsA("google.com", "1.2.3.4"), + }, + }) + defer recursor.Shutdown() + + a := NewTestAgent(t, ` + recursors = ["`+recursor.Addr+`"] + `) + 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") + } + + t.Parallel() + recursor := makeRecursor(t, dns.Msg{ + Answer: []dns.RR{ + dnsCNAME("www.google.com", "google.com"), + dnsA("google.com", "1.2.3.4"), + }, + }) + defer recursor.Shutdown() + + a := NewTestAgent(t, ` + recursors = ["`+recursor.Addr+`"] + `) + 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") + } + + t.Parallel() + a := NewTestAgent(t, ` + dns_config { + service_ttl = { + "d*" = "42s" + "db" = "10s" + "db*" = "66s" + "*" = "5s" + } + allow_stale = true + max_stale = "1s" + } + `) + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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") + } + + t.Parallel() + a := NewTestAgent(t, "") + 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 { + 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") + } + + t.Parallel() + 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 + ` + } + } + ` + + a := NewTestAgent(t, hcl) + 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") + } + + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = true }`) + 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") + } + + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false }`) + 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) +} diff --git a/agent/dns_test.go b/agent/dns_test.go index 3c447db36ee2..1998c9aa64ac 100644 --- a/agent/dns_test.go +++ b/agent/dns_test.go @@ -3,6 +3,15 @@ package agent +/* Note: this file got to be 10k lines long and caused multiple IDE issues + * as well as GitHub's UI unable to display diffs with large changes to this file. + * This file has been broken up by moving: + * - Node Lookup tests into dns_node_lookup_test.go + * - Service Lookup tests into dn_service_lookup_test.go + * + * Please be aware of the size of each of these files and add tests / break + * up tests accordingly. + */ import ( "context" "errors" @@ -11,7 +20,6 @@ import ( "math/rand" "net" "reflect" - "sort" "strings" "testing" "time" @@ -25,7 +33,6 @@ import ( "github.com/hashicorp/consul/agent/consul" agentdns "github.com/hashicorp/consul/agent/dns" "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" @@ -220,7 +227,79 @@ func TestDNS_EmptyAltDomain(t *testing.T) { require.Empty(t, in.Answer) } -func TestDNS_NodeLookup(t *testing.T) { +func TestDNSCycleRecursorCheck(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + // Start a DNS recursor that returns a SERVFAIL + server1 := makeRecursor(t, dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}, + }) + // Start a DNS recursor that returns the result + defer server1.Shutdown() + server2 := makeRecursor(t, dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + dnsA("www.google.com", "172.21.45.67"), + }, + }) + defer server2.Shutdown() + // Mock the agent startup with the necessary configs + agent := NewTestAgent(t, + `recursors = ["`+server1.Addr+`", "`+server2.Addr+`"] + `) + defer agent.Shutdown() + // DNS Message init + m := new(dns.Msg) + m.SetQuestion("google.com.", dns.TypeA) + // Agent request + client := new(dns.Client) + in, _, _ := client.Exchange(m, agent.DNSAddr()) + wantAnswer := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "www.google.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0xAC, 0x15, 0x2D, 0x43}, // 172 , 21, 45, 67 + }, + } + require.Equal(t, wantAnswer, in.Answer) +} +func TestDNSCycleRecursorCheckAllFail(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + // Start 3 DNS recursors that returns a REFUSED status + server1 := makeRecursor(t, dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeRefused}, + }) + defer server1.Shutdown() + server2 := makeRecursor(t, dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeRefused}, + }) + defer server2.Shutdown() + server3 := makeRecursor(t, dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeRefused}, + }) + defer server3.Shutdown() + // Mock the agent startup with the necessary configs + agent := NewTestAgent(t, + `recursors = ["`+server1.Addr+`", "`+server2.Addr+`","`+server3.Addr+`"] + `) + defer agent.Shutdown() + // DNS dummy message initialization + m := new(dns.Msg) + m.SetQuestion("google.com.", dns.TypeA) + // Agent request + client := new(dns.Client) + in, _, _ := client.Exchange(m, agent.DNSAddr()) + // Verify if we hit SERVFAIL from Consul + require.Equal(t, dns.RcodeServerFailure, in.Rcode) +} + +func TestDNS_EDNS0(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } @@ -234,13 +313,7 @@ func TestDNS_NodeLookup(t *testing.T) { args := &structs.RegisterRequest{ Datacenter: "dc1", Node: "foo", - Address: "127.0.0.1", - TaggedAddresses: map[string]string{ - "wan": "127.0.0.2", - }, - NodeMeta: map[string]string{ - "key": "value", - }, + Address: "127.0.0.2", } var out struct{} @@ -249,77 +322,123 @@ func TestDNS_NodeLookup(t *testing.T) { } m := new(dns.Msg) - m.SetQuestion("foo.node.consul.", dns.TypeANY) + m.SetEdns0(12345, true) + m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) c := new(dns.Client) in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Answer, 2) - require.Len(t, in.Extra, 0) - - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok, "First answer is not an A record") - require.Equal(t, "127.0.0.1", aRec.A.String()) - require.Equal(t, uint32(0), aRec.Hdr.Ttl) - - txt, ok := in.Answer[1].(*dns.TXT) - require.True(t, ok, "Second answer is not a TXT record") - require.Len(t, txt.Txt, 1) - require.Equal(t, "key=value", txt.Txt[0]) + if err != nil { + t.Fatalf("err: %v", err) + } - // Re-do the query, but only for an A RR + if len(in.Answer) != 1 { + t.Fatalf("empty lookup: %#v", in) + } + edns := in.IsEdns0() + if edns == nil { + t.Fatalf("empty edns: %#v", in) + } + if edns.UDPSize() != 12345 { + t.Fatalf("bad edns size: %d", edns.UDPSize()) + } +} - m = new(dns.Msg) - m.SetQuestion("foo.node.consul.", dns.TypeA) +func TestDNS_EDNS0_ECS(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } - c = new(dns.Client) - in, _, err = c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Answer, 1) - require.Len(t, in.Extra, 1) + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - aRec, ok = in.Answer[0].(*dns.A) - require.True(t, ok, "Answer is not an A record") - require.Equal(t, "127.0.0.1", aRec.A.String()) - require.Equal(t, uint32(0), aRec.Hdr.Ttl) + // 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, + }, + } - txt, ok = in.Extra[0].(*dns.TXT) - require.True(t, ok, "Extra record is not a TXT record") - require.Len(t, txt.Txt, 1) - require.Equal(t, "key=value", txt.Txt[0]) + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) + } - // Re-do the query, but specify the DC - m = new(dns.Msg) - m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) + // 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", + }, + }, + } + require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) + } - c = new(dns.Client) - in, _, err = c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Answer, 2) - require.Len(t, in.Extra, 0) + cases := []struct { + Name string + Question string + SubnetAddr string + SourceNetmask uint8 + ExpectedScope uint8 + }{ + {"global", "db.service.consul.", "198.18.0.1", 32, 0}, + {"query", "test.query.consul.", "198.18.0.1", 32, 32}, + {"query-subnet", "test.query.consul.", "198.18.0.0", 21, 21}, + } - aRec, ok = in.Answer[0].(*dns.A) - require.True(t, ok, "First answer is not an A record") - require.Equal(t, "127.0.0.1", aRec.A.String()) - require.Equal(t, uint32(0), aRec.Hdr.Ttl) + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + c := new(dns.Client) + // Query the service directly - should have a globally valid scope (0) + m := new(dns.Msg) + edns := new(dns.OPT) + edns.Hdr.Name = "." + edns.Hdr.Rrtype = dns.TypeOPT + edns.SetUDPSize(12345) + edns.SetDo(true) + subnetOp := new(dns.EDNS0_SUBNET) + subnetOp.Code = dns.EDNS0SUBNET + subnetOp.Family = 1 + subnetOp.SourceNetmask = tc.SourceNetmask + subnetOp.Address = net.ParseIP(tc.SubnetAddr) + edns.Option = append(edns.Option, subnetOp) + m.Extra = append(m.Extra, edns) + m.SetQuestion(tc.Question, dns.TypeA) - _, ok = in.Answer[1].(*dns.TXT) - require.True(t, ok, "Second answer is not a TXT record") + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok) + require.Equal(t, "127.0.0.1", aRec.A.String()) - // lookup a non-existing node, we should receive a SOA - m = new(dns.Msg) - m.SetQuestion("nofoo.node.dc1.consul.", dns.TypeANY) + optRR := in.IsEdns0() + require.NotNil(t, optRR) + require.Len(t, optRR.Option, 1) - c = new(dns.Client) - in, _, err = c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Ns, 1) - soaRec, ok := in.Ns[0].(*dns.SOA) - require.True(t, ok, "NS RR is not a SOA record") - require.Equal(t, uint32(0), soaRec.Hdr.Ttl) + subnet, ok := optRR.Option[0].(*dns.EDNS0_SUBNET) + require.True(t, ok) + require.Equal(t, uint16(1), subnet.Family) + require.Equal(t, tc.SourceNetmask, subnet.SourceNetmask) + require.Equal(t, tc.ExpectedScope, subnet.SourceScope) + require.Equal(t, net.ParseIP(tc.SubnetAddr), subnet.Address) + }) + } } -func TestDNS_CaseInsensitiveNodeLookup(t *testing.T) { +func TestDNS_ReverseLookup(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } @@ -332,8 +451,8 @@ func TestDNS_CaseInsensitiveNodeLookup(t *testing.T) { // Register node args := &structs.RegisterRequest{ Datacenter: "dc1", - Node: "Foo", - Address: "127.0.0.1", + Node: "foo2", + Address: "127.0.0.2", } var out struct{} @@ -342,7 +461,7 @@ func TestDNS_CaseInsensitiveNodeLookup(t *testing.T) { } m := new(dns.Msg) - m.SetQuestion("fOO.node.dc1.consul.", dns.TypeANY) + m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) c := new(dns.Client) in, _, err := c.Exchange(m, a.DNSAddr()) @@ -351,25 +470,35 @@ func TestDNS_CaseInsensitiveNodeLookup(t *testing.T) { } if len(in.Answer) != 1 { - t.Fatalf("empty lookup: %#v", in) + t.Fatalf("Bad: %#v", in) + } + + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != "foo2.node.dc1.consul." { + t.Fatalf("Bad: %#v", ptrRec) } } -func TestDNS_NodeLookup_PeriodName(t *testing.T) { +func TestDNS_ReverseLookup_CustomDomain(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - a := NewTestAgent(t, "") + a := NewTestAgent(t, ` + domain = "custom" + `) defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node with period in name + // Register node args := &structs.RegisterRequest{ Datacenter: "dc1", - Node: "foo.bar", - Address: "127.0.0.1", + Node: "foo2", + Address: "127.0.0.2", } var out struct{} @@ -378,7 +507,7 @@ func TestDNS_NodeLookup_PeriodName(t *testing.T) { } m := new(dns.Msg) - m.SetQuestion("foo.bar.node.consul.", dns.TypeANY) + m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) c := new(dns.Client) in, _, err := c.Exchange(m, a.DNSAddr()) @@ -390,16 +519,16 @@ func TestDNS_NodeLookup_PeriodName(t *testing.T) { t.Fatalf("Bad: %#v", in) } - aRec, ok := in.Answer[0].(*dns.A) + ptrRec, ok := in.Answer[0].(*dns.PTR) if !ok { t.Fatalf("Bad: %#v", in.Answer[0]) } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Answer[0]) + if ptrRec.Ptr != "foo2.node.dc1.custom." { + t.Fatalf("Bad: %#v", ptrRec) } } -func TestDNS_NodeLookup_AAAA(t *testing.T) { +func TestDNS_ReverseLookup_IPV6(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } @@ -422,7 +551,7 @@ func TestDNS_NodeLookup_AAAA(t *testing.T) { } m := new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeAAAA) + m.SetQuestion("2.4.2.4.2.4.2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", dns.TypeANY) c := new(dns.Client) in, _, err := c.Exchange(m, a.DNSAddr()) @@ -434,229 +563,220 @@ func TestDNS_NodeLookup_AAAA(t *testing.T) { t.Fatalf("Bad: %#v", in) } - aRec, ok := in.Answer[0].(*dns.AAAA) + ptrRec, ok := in.Answer[0].(*dns.PTR) if !ok { t.Fatalf("Bad: %#v", in.Answer[0]) } - if aRec.AAAA.String() != "::4242:4242" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) + if ptrRec.Ptr != "bar.node.dc1.consul." { + t.Fatalf("Bad: %#v", ptrRec) } } -func TestDNSCycleRecursorCheck(t *testing.T) { +func TestDNS_SOA_Settings(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - // Start a DNS recursor that returns a SERVFAIL - server1 := makeRecursor(t, dns.Msg{ - MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}, - }) - // Start a DNS recursor that returns the result - defer server1.Shutdown() - server2 := makeRecursor(t, dns.Msg{ - MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, - Answer: []dns.RR{ - dnsA("www.google.com", "172.21.45.67"), - }, - }) - defer server2.Shutdown() - // Mock the agent startup with the necessary configs - agent := NewTestAgent(t, - `recursors = ["`+server1.Addr+`", "`+server2.Addr+`"] - `) - defer agent.Shutdown() - // DNS Message init - m := new(dns.Msg) - m.SetQuestion("google.com.", dns.TypeA) - // Agent request - client := new(dns.Client) - in, _, _ := client.Exchange(m, agent.DNSAddr()) - wantAnswer := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "www.google.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0xAC, 0x15, 0x2D, 0x43}, // 172 , 21, 45, 67 - }, - } - require.Equal(t, wantAnswer, in.Answer) -} -func TestDNSCycleRecursorCheckAllFail(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } + testSoaWithConfig := func(config string, ttl, expire, refresh, retry uint) { + a := NewTestAgent(t, config) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - t.Parallel() - // Start 3 DNS recursors that returns a REFUSED status - server1 := makeRecursor(t, dns.Msg{ - MsgHdr: dns.MsgHdr{Rcode: dns.RcodeRefused}, - }) - defer server1.Shutdown() - server2 := makeRecursor(t, dns.Msg{ - MsgHdr: dns.MsgHdr{Rcode: dns.RcodeRefused}, - }) - defer server2.Shutdown() - server3 := makeRecursor(t, dns.Msg{ - MsgHdr: dns.MsgHdr{Rcode: dns.RcodeRefused}, - }) - defer server3.Shutdown() - // Mock the agent startup with the necessary configs - agent := NewTestAgent(t, - `recursors = ["`+server1.Addr+`", "`+server2.Addr+`","`+server3.Addr+`"] - `) - defer agent.Shutdown() - // DNS dummy message initialization - m := new(dns.Msg) - m.SetQuestion("google.com.", dns.TypeA) - // Agent request - client := new(dns.Client) - in, _, _ := client.Exchange(m, agent.DNSAddr()) - // Verify if we hit SERVFAIL from Consul - require.Equal(t, dns.RcodeServerFailure, in.Rcode) + // lookup a non-existing node, we should receive a SOA + m := new(dns.Msg) + m.SetQuestion("nofoo.node.dc1.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Ns, 1) + soaRec, ok := in.Ns[0].(*dns.SOA) + require.True(t, ok, "NS RR is not a SOA record") + require.Equal(t, uint32(ttl), soaRec.Minttl) + require.Equal(t, uint32(expire), soaRec.Expire) + require.Equal(t, uint32(refresh), soaRec.Refresh) + require.Equal(t, uint32(retry), soaRec.Retry) + require.Equal(t, uint32(ttl), soaRec.Hdr.Ttl) + } + // Default configuration + testSoaWithConfig("", 0, 86400, 3600, 600) + // Override all settings + testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200,refresh=1800,retry=300}}", 60, 43200, 1800, 300) + // Override partial settings + testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200}}", 60, 43200, 3600, 600) + // Override partial settings, part II + testSoaWithConfig("dns_config={soa={refresh=1800,retry=300}}", 0, 86400, 1800, 300) } -func TestDNS_NodeLookup_CNAME(t *testing.T) { + +func TestDNS_VirtualIPLookup(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - recursor := makeRecursor(t, dns.Msg{ - Answer: []dns.RR{ - dnsCNAME("www.google.com", "google.com"), - dnsA("google.com", "1.2.3.4"), - dnsTXT("google.com", []string{"my_txt_value"}), - }, - }) - defer recursor.Shutdown() - a := NewTestAgent(t, ` - recursors = ["`+recursor.Addr+`"] - `) + a := StartTestAgent(t, TestAgent{HCL: ``, Overrides: `peering = { test_allow_peer_registrations = true }`}) defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "www.google.com", - } + server, ok := a.delegate.(*consul.Server) + require.True(t, ok) - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // The proxy service will not receive a virtual IP if the server is not assigning virtual IPs yet. + retry.Run(t, func(r *retry.R) { + _, entry, err := server.FSM().State().SystemMetadataGet(nil, structs.SystemMetadataVirtualIPsEnabled) + require.NoError(r, err) + require.NotNil(r, entry) + }) + + type testCase struct { + name string + reg *structs.RegisterRequest + question string + expect string } - m := new(dns.Msg) - m.SetQuestion("google.node.consul.", dns.TypeANY) - m.SetEdns0(8192, true) + run := func(t *testing.T, tc testCase) { + var out struct{} + require.Nil(t, a.RPC(context.Background(), "Catalog.Register", tc.reg, &out)) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) + m := new(dns.Msg) + m.SetQuestion(tc.question, dns.TypeA) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.Nil(t, err) + require.Len(t, in.Answer, 1) + + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok) + require.Equal(t, tc.expect, aRec.A.String()) } - wantAnswer := []dns.RR{ - &dns.CNAME{ - Hdr: dns.RR_Header{Name: "google.node.consul.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x10}, - Target: "www.google.com.", - }, - &dns.CNAME{ - Hdr: dns.RR_Header{Name: "www.google.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Rdlength: 0x2}, - Target: "google.com.", - }, - &dns.A{ - Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0x1, 0x2, 0x3, 0x4}, // 1.2.3.4 + tt := []testCase{ + { + name: "local query", + reg: &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.55", + Service: &structs.NodeService{ + Kind: structs.ServiceKindConnectProxy, + Service: "web-proxy", + Port: 12345, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "db", + }, + }, + }, + question: "db.virtual.consul.", + expect: "240.0.0.1", }, - &dns.TXT{ - Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xd}, - Txt: []string{"my_txt_value"}, + { + name: "query for imported service", + reg: &structs.RegisterRequest{ + PeerName: "frontend", + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.55", + Service: &structs.NodeService{ + PeerName: "frontend", + Kind: structs.ServiceKindConnectProxy, + Service: "web-proxy", + Port: 12345, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "db", + }, + }, + }, + question: "db.virtual.frontend.consul.", + expect: "240.0.0.2", }, } - require.Equal(t, wantAnswer, in.Answer) + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } } -func TestDNS_NodeLookup_TXT(t *testing.T) { +func TestDNS_InifiniteRecursion(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } - a := NewTestAgent(t, ``) + // This test should not create an infinite recursion + t.Parallel() + a := NewTestAgent(t, ` + domain = "CONSUL." + node_name = "test node" + `) defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "rfc1035-00": "value0", - "key0": "value1", - }, - } - - 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("google.node.consul.", dns.TypeTXT) + // Register the initial node with a service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "web", + Address: "web.service.consul.", + Service: &structs.NodeService{ + Service: "web", + Port: 12345, + Address: "web.service.consul.", + }, + } - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } } - // Should have the 1 TXT record reply - if len(in.Answer) != 2 { - t.Fatalf("Bad: %#v", in) + // Look up the service directly + questions := []string{ + "web.service.consul.", } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - txtRec, ok := in.Answer[0].(*dns.TXT) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if len(txtRec.Txt) != 1 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" { - t.Fatalf("Bad: %#v", in.Answer[0]) + if len(in.Answer) < 1 { + t.Fatalf("Bad: %#v", in) + } + aRec, ok := in.Answer[0].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.Target != "web.service.consul." { + t.Fatalf("Bad: %#v, target:=%s", aRec, aRec.Target) + } } } -func TestDNS_NodeLookup_TXT_DontSuppress(t *testing.T) { +func TestDNS_NSRecords(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false }`) + t.Parallel() + a := NewTestAgent(t, ` + domain = "CONSUL." + node_name = "server1" + `) defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "rfc1035-00": "value0", - "key0": "value1", - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + testrpc.WaitForTestAgent(t, a.RPC, "dc1") m := new(dns.Msg) - m.SetQuestion("google.node.consul.", dns.TypeTXT) + m.SetQuestion("something.node.consul.", dns.TypeNS) c := new(dns.Client) in, _, err := c.Exchange(m, a.DNSAddr()) @@ -664,93 +784,91 @@ func TestDNS_NodeLookup_TXT_DontSuppress(t *testing.T) { t.Fatalf("err: %v", err) } - // Should have the 1 TXT record reply - if len(in.Answer) != 2 { - t.Fatalf("Bad: %#v", in) + wantAnswer := []dns.RR{ + &dns.NS{ + Hdr: dns.RR_Header{Name: "consul.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x13}, + Ns: "server1.node.dc1.consul.", + }, } - - txtRec, ok := in.Answer[0].(*dns.TXT) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if len(txtRec.Txt) != 1 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" { - t.Fatalf("Bad: %#v", in.Answer[0]) + require.Equal(t, wantAnswer, in.Answer, "answer") + wantExtra := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "server1.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4, Ttl: 0}, + A: net.ParseIP("127.0.0.1").To4(), + }, } + + require.Equal(t, wantExtra, in.Extra, "extra") } -func TestDNS_NodeLookup_ANY(t *testing.T) { +func TestDNS_AltDomain_NSRecords(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } - a := NewTestAgent(t, ``) + t.Parallel() + a := NewTestAgent(t, ` + domain = "CONSUL." + node_name = "server1" + alt_domain = "test-domain." + `) defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + testrpc.WaitForTestAgent(t, a.RPC, "dc1") - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "key": "value", - }, + questions := []struct { + ask string + domain string + wantDomain string + }{ + {"something.node.consul.", "consul.", "server1.node.dc1.consul."}, + {"something.node.test-domain.", "test-domain.", "server1.node.dc1.test-domain."}, } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question.ask, dns.TypeNS) - m := new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeANY) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + wantAnswer := []dns.RR{ + &dns.NS{ + Hdr: dns.RR_Header{Name: question.domain, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x13}, + Ns: question.wantDomain, + }, + } + require.Equal(t, wantAnswer, in.Answer, "answer") + wantExtra := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: question.wantDomain, Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4, Ttl: 0}, + A: net.ParseIP("127.0.0.1").To4(), + }, + } - wantAnswer := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "bar.node.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.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, - Txt: []string{"key=value"}, - }, + require.Equal(t, wantExtra, in.Extra, "extra") } - require.Equal(t, wantAnswer, in.Answer) + } -func TestDNS_NodeLookup_ANY_DontSuppressTXT(t *testing.T) { +func TestDNS_NSRecords_IPV6(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false }`) + t.Parallel() + a := NewTestAgent(t, ` + domain = "CONSUL." + node_name = "server1" + advertise_addr = "::1" + `) 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", - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + testrpc.WaitForTestAgent(t, a.RPC, "dc1") m := new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeANY) + m.SetQuestion("server1.node.dc1.consul.", dns.TypeNS) c := new(dns.Client) in, _, err := c.Exchange(m, a.DNSAddr()) @@ -759,103 +877,77 @@ func TestDNS_NodeLookup_ANY_DontSuppressTXT(t *testing.T) { } wantAnswer := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "bar.node.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.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, - Txt: []string{"key=value"}, + &dns.NS{ + Hdr: dns.RR_Header{Name: "consul.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x2}, + Ns: "server1.node.dc1.consul.", }, } - require.Equal(t, wantAnswer, in.Answer) -} - -func TestDNS_NodeLookup_A_SuppressTXT(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false }`) - 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", + require.Equal(t, wantAnswer, in.Answer, "answer") + wantExtra := []dns.RR{ + &dns.AAAA{ + Hdr: dns.RR_Header{Name: "server1.node.dc1.consul.", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Rdlength: 0x10, Ttl: 0}, + AAAA: net.ParseIP("::1"), }, } - var out struct{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - - m := new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeA) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - - wantAnswer := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 - }, - } - require.Equal(t, wantAnswer, in.Answer) + require.Equal(t, wantExtra, in.Extra, "extra") - // ensure TXT RR suppression - require.Len(t, in.Extra, 0) } -func TestDNS_EDNS0(t *testing.T) { +func TestDNS_AltDomain_NSRecords_IPV6(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - a := NewTestAgent(t, "") + a := NewTestAgent(t, ` + domain = "CONSUL." + node_name = "server1" + advertise_addr = "::1" + alt_domain = "test-domain." + `) defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + testrpc.WaitForTestAgent(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.2", + questions := []struct { + ask string + domain string + wantDomain string + }{ + {"server1.node.dc1.consul.", "consul.", "server1.node.dc1.consul."}, + {"server1.node.dc1.test-domain.", "test-domain.", "server1.node.dc1.test-domain."}, } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question.ask, dns.TypeNS) - m := new(dns.Msg) - m.SetEdns0(12345, true) - m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + wantAnswer := []dns.RR{ + &dns.NS{ + Hdr: dns.RR_Header{Name: question.domain, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x2}, + Ns: question.wantDomain, + }, + } + require.Equal(t, wantAnswer, in.Answer, "answer") + wantExtra := []dns.RR{ + &dns.AAAA{ + Hdr: dns.RR_Header{Name: question.wantDomain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Rdlength: 0x10, Ttl: 0}, + AAAA: net.ParseIP("::1"), + }, + } - if len(in.Answer) != 1 { - t.Fatalf("empty lookup: %#v", in) - } - edns := in.IsEdns0() - if edns == nil { - t.Fatalf("empty edns: %#v", in) - } - if edns.UDPSize() != 12345 { - t.Fatalf("bad edns size: %d", edns.UDPSize()) + require.Equal(t, wantExtra, in.Extra, "extra") } + } -func TestDNS_EDNS0_ECS(t *testing.T) { +func TestDNS_Lookup_TaggedIPAddresses(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } @@ -865,23 +957,6 @@ func TestDNS_EDNS0_ECS(t *testing.T) { 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{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - } - // Register an equivalent prepared query. var id string { @@ -898,5615 +973,707 @@ func TestDNS_EDNS0_ECS(t *testing.T) { require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) } - cases := []struct { - Name string - Question string - SubnetAddr string - SourceNetmask uint8 - ExpectedScope uint8 - }{ - {"global", "db.service.consul.", "198.18.0.1", 32, 0}, - {"query", "test.query.consul.", "198.18.0.1", 32, 32}, - {"query-subnet", "test.query.consul.", "198.18.0.0", 21, 21}, + type testCase struct { + nodeAddress string + nodeTaggedAddresses map[string]string + serviceAddress string + serviceTaggedAddresses map[string]structs.ServiceAddress + + expectedServiceIPv4Address string + expectedServiceIPv6Address string + expectedNodeIPv4Address string + expectedNodeIPv6Address string } - for _, tc := range cases { - t.Run(tc.Name, func(t *testing.T) { - c := new(dns.Client) - // Query the service directly - should have a globally valid scope (0) - m := new(dns.Msg) - edns := new(dns.OPT) - edns.Hdr.Name = "." - edns.Hdr.Rrtype = dns.TypeOPT - edns.SetUDPSize(12345) - edns.SetDo(true) - subnetOp := new(dns.EDNS0_SUBNET) - subnetOp.Code = dns.EDNS0SUBNET - subnetOp.Family = 1 - subnetOp.SourceNetmask = tc.SourceNetmask - subnetOp.Address = net.ParseIP(tc.SubnetAddr) - edns.Option = append(edns.Option, subnetOp) - m.Extra = append(m.Extra, edns) - m.SetQuestion(tc.Question, dns.TypeA) - - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok) - require.Equal(t, "127.0.0.1", aRec.A.String()) - - optRR := in.IsEdns0() - require.NotNil(t, optRR) - require.Len(t, optRR.Option, 1) - - subnet, ok := optRR.Option[0].(*dns.EDNS0_SUBNET) - require.True(t, ok) - require.Equal(t, uint16(1), subnet.Family) - require.Equal(t, tc.SourceNetmask, subnet.SourceNetmask) - require.Equal(t, tc.ExpectedScope, subnet.SourceScope) - require.Equal(t, net.ParseIP(tc.SubnetAddr), subnet.Address) - }) - } -} - -func TestDNS_ReverseLookup(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo2", - Address: "127.0.0.2", - } - - 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("2.0.0.127.in-addr.arpa.", 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) - } - - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != "foo2.node.dc1.consul." { - t.Fatalf("Bad: %#v", ptrRec) - } -} - -func TestDNS_ReverseLookup_CustomDomain(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - domain = "custom" - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo2", - Address: "127.0.0.2", - } - - 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("2.0.0.127.in-addr.arpa.", 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) - } - - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != "foo2.node.dc1.custom." { - t.Fatalf("Bad: %#v", ptrRec) - } -} - -func TestDNS_ReverseLookup_IPV6(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "::4242:4242", - } - - 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("2.4.2.4.2.4.2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", 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) - } - - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != "bar.node.dc1.consul." { - t.Fatalf("Bad: %#v", ptrRec) - } -} - -func TestDNS_ServiceReverseLookup(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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, - Address: "127.0.0.2", - }, - } - - 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("2.0.0.127.in-addr.arpa.", 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) - } - - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "consul", nil)+"." { - t.Fatalf("Bad: %#v", ptrRec) - } -} - -func TestDNS_ServiceReverseLookup_IPV6(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "2001:db8::1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - Address: "2001:db8::ff00:42:8329", - }, - } - - 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("9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", 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) - } - - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "consul", nil)+"." { - t.Fatalf("Bad: %#v", ptrRec) - } -} - -func TestDNS_ServiceReverseLookup_CustomDomain(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - domain = "custom" - `) - 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, - Address: "127.0.0.2", - }, - } - - 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("2.0.0.127.in-addr.arpa.", 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) - } - - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "custom", nil)+"." { - t.Fatalf("Bad: %#v", ptrRec) - } -} - -func TestDNS_SOA_Settings(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - testSoaWithConfig := func(config string, ttl, expire, refresh, retry uint) { - a := NewTestAgent(t, config) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // lookup a non-existing node, we should receive a SOA - m := new(dns.Msg) - m.SetQuestion("nofoo.node.dc1.consul.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Ns, 1) - soaRec, ok := in.Ns[0].(*dns.SOA) - require.True(t, ok, "NS RR is not a SOA record") - require.Equal(t, uint32(ttl), soaRec.Minttl) - require.Equal(t, uint32(expire), soaRec.Expire) - require.Equal(t, uint32(refresh), soaRec.Refresh) - require.Equal(t, uint32(retry), soaRec.Retry) - require.Equal(t, uint32(ttl), soaRec.Hdr.Ttl) - } - // Default configuration - testSoaWithConfig("", 0, 86400, 3600, 600) - // Override all settings - testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200,refresh=1800,retry=300}}", 60, 43200, 1800, 300) - // Override partial settings - testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200}}", 60, 43200, 3600, 600) - // Override partial settings, part II - testSoaWithConfig("dns_config={soa={refresh=1800,retry=300}}", 0, 86400, 1800, 300) -} - -func TestDNS_ServiceReverseLookupNodeAddress(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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, - Address: "127.0.0.1", - }, - } - - 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("1.0.0.127.in-addr.arpa.", 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) - } - - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", ptrRec) - } -} - -func TestDNS_ServiceLookupNoMultiCNAME(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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 a CNAME and 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") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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 a CNAME and an A RR - require.Len(t, in.Answer, 2) - require.IsType(t, &dns.A{}, in.Answer[0]) - require.IsType(t, &dns.A{}, in.Answer[1]) -} - -func TestDNS_ServiceLookup(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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", - }, - }, - } - 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) - } - 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]) - } - } - - // 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]) - } - - } -} - -func TestDNS_ServiceLookupWithInternalServiceAddress(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - node_name = "my.test-node" - `) - 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") - } - - t.Parallel() - - a := NewTestAgent(t, "") - 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_VirtualIPLookup(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - - a := StartTestAgent(t, TestAgent{HCL: ``, Overrides: `peering = { test_allow_peer_registrations = true }`}) - defer a.Shutdown() - - testrpc.WaitForLeader(t, a.RPC, "dc1") - - server, ok := a.delegate.(*consul.Server) - require.True(t, ok) - - // The proxy service will not receive a virtual IP if the server is not assigning virtual IPs yet. - retry.Run(t, func(r *retry.R) { - _, entry, err := server.FSM().State().SystemMetadataGet(nil, structs.SystemMetadataVirtualIPsEnabled) - require.NoError(r, err) - require.NotNil(r, entry) - }) - - type testCase struct { - name string - reg *structs.RegisterRequest - question string - expect string - } - - run := func(t *testing.T, tc testCase) { - var out struct{} - require.Nil(t, a.RPC(context.Background(), "Catalog.Register", tc.reg, &out)) - - m := new(dns.Msg) - m.SetQuestion(tc.question, dns.TypeA) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.Nil(t, err) - require.Len(t, in.Answer, 1) - - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok) - require.Equal(t, tc.expect, aRec.A.String()) - } - - tt := []testCase{ - { - name: "local query", - reg: &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.55", - Service: &structs.NodeService{ - Kind: structs.ServiceKindConnectProxy, - Service: "web-proxy", - Port: 12345, - Proxy: structs.ConnectProxyConfig{ - DestinationServiceName: "db", - }, - }, - }, - question: "db.virtual.consul.", - expect: "240.0.0.1", - }, - { - name: "query for imported service", - reg: &structs.RegisterRequest{ - PeerName: "frontend", - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.55", - Service: &structs.NodeService{ - PeerName: "frontend", - Kind: structs.ServiceKindConnectProxy, - Service: "web-proxy", - Port: 12345, - Proxy: structs.ConnectProxyConfig{ - DestinationServiceName: "db", - }, - }, - }, - question: "db.virtual.frontend.consul.", - expect: "240.0.0.2", - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestDNS_IngressServiceLookup(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - - a := NewTestAgent(t, "") - 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") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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 { - 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_InifiniteRecursion(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - // This test should not create an infinite recursion - t.Parallel() - a := NewTestAgent(t, ` - domain = "CONSUL." - node_name = "test node" - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // Register the initial node with a service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "web", - Address: "web.service.consul.", - Service: &structs.NodeService{ - Service: "web", - Port: 12345, - Address: "web.service.consul.", - }, - } - - 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{ - "web.service.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) - 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.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.Target != "web.service.consul." { - t.Fatalf("Bad: %#v, target:=%s", aRec, aRec.Target) - } - } -} - -func TestDNS_ExternalServiceToConsulCNAMELookup(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - domain = "CONSUL." - node_name = "test node" - `) - 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_NSRecords(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - domain = "CONSUL." - node_name = "server1" - `) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") - - m := new(dns.Msg) - m.SetQuestion("something.node.consul.", dns.TypeNS) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{Name: "consul.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x13}, - Ns: "server1.node.dc1.consul.", - }, - } - require.Equal(t, wantAnswer, in.Answer, "answer") - wantExtra := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "server1.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4, Ttl: 0}, - A: net.ParseIP("127.0.0.1").To4(), - }, - } - - require.Equal(t, wantExtra, in.Extra, "extra") -} - -func TestDNS_AltDomain_NSRecords(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - domain = "CONSUL." - node_name = "server1" - alt_domain = "test-domain." - `) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") - - questions := []struct { - ask string - domain string - wantDomain string - }{ - {"something.node.consul.", "consul.", "server1.node.dc1.consul."}, - {"something.node.test-domain.", "test-domain.", "server1.node.dc1.test-domain."}, - } - - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question.ask, dns.TypeNS) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{Name: question.domain, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x13}, - Ns: question.wantDomain, - }, - } - require.Equal(t, wantAnswer, in.Answer, "answer") - wantExtra := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: question.wantDomain, Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4, Ttl: 0}, - A: net.ParseIP("127.0.0.1").To4(), - }, - } - - require.Equal(t, wantExtra, in.Extra, "extra") - } - -} - -func TestDNS_NSRecords_IPV6(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - domain = "CONSUL." - node_name = "server1" - advertise_addr = "::1" - `) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") - - m := new(dns.Msg) - m.SetQuestion("server1.node.dc1.consul.", dns.TypeNS) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{Name: "consul.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x2}, - Ns: "server1.node.dc1.consul.", - }, - } - require.Equal(t, wantAnswer, in.Answer, "answer") - wantExtra := []dns.RR{ - &dns.AAAA{ - Hdr: dns.RR_Header{Name: "server1.node.dc1.consul.", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Rdlength: 0x10, Ttl: 0}, - AAAA: net.ParseIP("::1"), - }, - } - - require.Equal(t, wantExtra, in.Extra, "extra") - -} - -func TestDNS_AltDomain_NSRecords_IPV6(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - domain = "CONSUL." - node_name = "server1" - advertise_addr = "::1" - alt_domain = "test-domain." - `) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") - - questions := []struct { - ask string - domain string - wantDomain string - }{ - {"server1.node.dc1.consul.", "consul.", "server1.node.dc1.consul."}, - {"server1.node.dc1.test-domain.", "test-domain.", "server1.node.dc1.test-domain."}, - } - - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question.ask, dns.TypeNS) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{Name: question.domain, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x2}, - Ns: question.wantDomain, - }, - } - require.Equal(t, wantAnswer, in.Answer, "answer") - wantExtra := []dns.RR{ - &dns.AAAA{ - Hdr: dns.RR_Header{Name: question.wantDomain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Rdlength: 0x10, Ttl: 0}, - AAAA: net.ParseIP("::1"), - }, - } - - require.Equal(t, wantExtra, in.Extra, "extra") - } - -} - -func TestDNS_ExternalServiceToConsulCNAMENestedLookup(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - node_name = "test-node" - `) - 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") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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") - } - - t.Parallel() - a := NewTestAgent(t, ` - alt_domain = "test-domain" - `) - 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") - } - - t.Parallel() - recursor := makeRecursor(t, dns.Msg{ - Answer: []dns.RR{ - dnsCNAME("www.google.com", "google.com"), - dnsA("google.com", "1.2.3.4"), - }, - }) - defer recursor.Shutdown() - - a := NewTestAgent(t, ` - recursors = ["`+recursor.Addr+`"] - `) - 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") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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") - } - - t.Parallel() - a := NewTestAgent(t, ` - alt_domain = "test-domain" - `) - 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") - } - - t.Parallel() - a1 := NewTestAgent(t, ` - datacenter = "dc1" - translate_wan_addrs = true - acl_datacenter = "" - `) - defer a1.Shutdown() - - a2 := NewTestAgent(t, ` - datacenter = "dc2" - translate_wan_addrs = true - acl_datacenter = "" - `) - 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_Lookup_TaggedIPAddresses(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // 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", - }, - }, - } - require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) - } - - type testCase struct { - nodeAddress string - nodeTaggedAddresses map[string]string - serviceAddress string - serviceTaggedAddresses map[string]structs.ServiceAddress - - expectedServiceIPv4Address string - expectedServiceIPv6Address string - expectedNodeIPv4Address string - expectedNodeIPv6Address string - } - - cases := map[string]testCase{ - "simple-ipv4": { - serviceAddress: "127.0.0.2", - nodeAddress: "127.0.0.1", - - expectedServiceIPv4Address: "127.0.0.2", - expectedServiceIPv6Address: "", - expectedNodeIPv4Address: "127.0.0.1", - expectedNodeIPv6Address: "", - }, - "simple-ipv6": { - serviceAddress: "::2", - nodeAddress: "::1", - - expectedServiceIPv6Address: "::2", - expectedServiceIPv4Address: "", - expectedNodeIPv6Address: "::1", - expectedNodeIPv4Address: "", - }, - "ipv4-with-tagged-ipv6": { - serviceAddress: "127.0.0.2", - nodeAddress: "127.0.0.1", - - serviceTaggedAddresses: map[string]structs.ServiceAddress{ - structs.TaggedAddressLANIPv6: {Address: "::2"}, - }, - nodeTaggedAddresses: map[string]string{ - structs.TaggedAddressLANIPv6: "::1", - }, - - expectedServiceIPv4Address: "127.0.0.2", - expectedServiceIPv6Address: "::2", - expectedNodeIPv4Address: "127.0.0.1", - expectedNodeIPv6Address: "::1", - }, - "ipv6-with-tagged-ipv4": { - serviceAddress: "::2", - nodeAddress: "::1", - - serviceTaggedAddresses: map[string]structs.ServiceAddress{ - structs.TaggedAddressLANIPv4: {Address: "127.0.0.2"}, - }, - nodeTaggedAddresses: map[string]string{ - structs.TaggedAddressLANIPv4: "127.0.0.1", - }, - - expectedServiceIPv4Address: "127.0.0.2", - expectedServiceIPv6Address: "::2", - expectedNodeIPv4Address: "127.0.0.1", - expectedNodeIPv6Address: "::1", - }, - } - - for name, tc := range cases { - name := name - tc := tc - t.Run(name, func(t *testing.T) { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: tc.nodeAddress, - TaggedAddresses: tc.nodeTaggedAddresses, - Service: &structs.NodeService{ - Service: "db", - Address: tc.serviceAddress, - Port: 8080, - TaggedAddresses: tc.serviceTaggedAddresses, - }, - } - - var out struct{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - - // Look up the SRV record via service and prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) - - c := new(dns.Client) - addr := a.config.DNSAddrs[0].String() - in, _, err := c.Exchange(m, addr) - require.NoError(t, err) - - if tc.expectedServiceIPv4Address != "" { - 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.expectedServiceIPv4Address, aRec.A.String()) - } else { - require.Len(t, in.Answer, 0) - } - - m = new(dns.Msg) - m.SetQuestion(question, dns.TypeAAAA) - - c = new(dns.Client) - addr = a.config.DNSAddrs[0].String() - in, _, err = c.Exchange(m, addr) - require.NoError(t, err) - - if tc.expectedServiceIPv6Address != "" { - require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.AAAA) - require.True(t, ok, "Bad: %#v", in.Answer[0]) - require.Equal(t, question, aRec.Hdr.Name) - require.Equal(t, tc.expectedServiceIPv6Address, aRec.AAAA.String()) - } else { - require.Len(t, in.Answer, 0) - } - } - - // Look up node - m := new(dns.Msg) - m.SetQuestion("foo.node.consul.", dns.TypeA) - - c := new(dns.Client) - addr := a.config.DNSAddrs[0].String() - in, _, err := c.Exchange(m, addr) - require.NoError(t, err) - - if tc.expectedNodeIPv4Address != "" { - 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, "foo.node.consul.", aRec.Hdr.Name) - require.Equal(t, tc.expectedNodeIPv4Address, aRec.A.String()) - } else { - require.Len(t, in.Answer, 0) - } - - m = new(dns.Msg) - m.SetQuestion("foo.node.consul.", dns.TypeAAAA) - - c = new(dns.Client) - addr = a.config.DNSAddrs[0].String() - in, _, err = c.Exchange(m, addr) - require.NoError(t, err) - - if tc.expectedNodeIPv6Address != "" { - require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.AAAA) - require.True(t, ok, "Bad: %#v", in.Answer[0]) - require.Equal(t, "foo.node.consul.", aRec.Hdr.Name) - require.Equal(t, tc.expectedNodeIPv6Address, aRec.AAAA.String()) - } else { - require.Len(t, in.Answer, 0) - } - }) - } -} - -func TestDNS_CaseInsensitiveServiceLookup(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - 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) { - a := NewTestAgent(t, tst.config) - 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) - } - }) - } - }) - } -} - -func TestDNS_ServiceLookup_TagPeriod(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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]) - } -} - -func TestDNS_PreparedQueryNearIPEDNS(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - ipCoord := lib.GenerateCoordinate(1 * time.Millisecond) - serviceNodes := []struct { - name string - address string - coord *coordinate.Coordinate - }{ - {"foo1", "198.18.0.1", lib.GenerateCoordinate(1 * time.Millisecond)}, - {"foo2", "198.18.0.2", lib.GenerateCoordinate(10 * time.Millisecond)}, - {"foo3", "198.18.0.3", lib.GenerateCoordinate(30 * time.Millisecond)}, - } - - t.Parallel() - a := NewTestAgent(t, "") - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - added := 0 - - // Register nodes with a service - for _, cfg := range serviceNodes { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: cfg.name, - Address: cfg.address, - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - }, - } - - var out struct{} - err := a.RPC(context.Background(), "Catalog.Register", args, &out) - require.NoError(t, err) - - // Send coordinate updates - coordArgs := structs.CoordinateUpdateRequest{ - Datacenter: "dc1", - Node: cfg.name, - Coord: cfg.coord, - } - err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) - require.NoError(t, err) - - added += 1 - } - - fmt.Printf("Added %d service nodes\n", added) - - // Register a node without a service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "198.18.0.9", - } - - var out struct{} - err := a.RPC(context.Background(), "Catalog.Register", args, &out) - require.NoError(t, err) - - // Send coordinate updates for a few nodes. - coordArgs := structs.CoordinateUpdateRequest{ - Datacenter: "dc1", - Node: "bar", - Coord: ipCoord, - } - err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) - require.NoError(t, err) - } - - // Register a prepared query Near = _ip - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "some.query.we.like", - Service: structs.ServiceQuery{ - Service: "db", - Near: "_ip", - }, - }, - } - - var id string - err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id) - require.NoError(t, err) - } - retry.Run(t, func(r *retry.R) { - m := new(dns.Msg) - m.SetQuestion("some.query.we.like.query.consul.", dns.TypeA) - m.SetEdns0(4096, false) - o := new(dns.OPT) - o.Hdr.Name = "." - o.Hdr.Rrtype = dns.TypeOPT - e := new(dns.EDNS0_SUBNET) - e.Code = dns.EDNS0SUBNET - e.Family = 1 - e.SourceNetmask = 32 - e.SourceScope = 0 - e.Address = net.ParseIP("198.18.0.9").To4() - o.Option = append(o.Option, e) - m.Extra = append(m.Extra, o) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - r.Fatalf("Error with call to dns.Client.Exchange: %s", err) - } - - if len(serviceNodes) != len(in.Answer) { - r.Fatalf("Expecting %d A RRs in response, Actual found was %d", len(serviceNodes), len(in.Answer)) - } - - for i, rr := range in.Answer { - if aRec, ok := rr.(*dns.A); ok { - if actual := aRec.A.String(); serviceNodes[i].address != actual { - r.Fatalf("Expecting A RR #%d = %s, Actual RR was %s", i, serviceNodes[i].address, actual) - } - } else { - r.Fatalf("DNS Answer contained a non-A RR") - } - } - }) -} - -func TestDNS_PreparedQueryNearIP(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - ipCoord := lib.GenerateCoordinate(1 * time.Millisecond) - serviceNodes := []struct { - name string - address string - coord *coordinate.Coordinate - }{ - {"foo1", "198.18.0.1", lib.GenerateCoordinate(1 * time.Millisecond)}, - {"foo2", "198.18.0.2", lib.GenerateCoordinate(10 * time.Millisecond)}, - {"foo3", "198.18.0.3", lib.GenerateCoordinate(30 * time.Millisecond)}, - } - - t.Parallel() - a := NewTestAgent(t, "") - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - added := 0 - - // Register nodes with a service - for _, cfg := range serviceNodes { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: cfg.name, - Address: cfg.address, - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - }, - } - - var out struct{} - err := a.RPC(context.Background(), "Catalog.Register", args, &out) - require.NoError(t, err) - - // Send coordinate updates - coordArgs := structs.CoordinateUpdateRequest{ - Datacenter: "dc1", - Node: cfg.name, - Coord: cfg.coord, - } - err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) - require.NoError(t, err) - - added += 1 - } - - fmt.Printf("Added %d service nodes\n", added) - - // Register a node without a service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "198.18.0.9", - } - - var out struct{} - err := a.RPC(context.Background(), "Catalog.Register", args, &out) - require.NoError(t, err) - - // Send coordinate updates for a few nodes. - coordArgs := structs.CoordinateUpdateRequest{ - Datacenter: "dc1", - Node: "bar", - Coord: ipCoord, - } - err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) - require.NoError(t, err) - } - - // Register a prepared query Near = _ip - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "some.query.we.like", - Service: structs.ServiceQuery{ - Service: "db", - Near: "_ip", - }, - }, - } - - var id string - err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id) - require.NoError(t, err) - } - - retry.Run(t, func(r *retry.R) { - m := new(dns.Msg) - m.SetQuestion("some.query.we.like.query.consul.", dns.TypeA) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - r.Fatalf("Error with call to dns.Client.Exchange: %s", err) - } - - if len(serviceNodes) != len(in.Answer) { - r.Fatalf("Expecting %d A RRs in response, Actual found was %d", len(serviceNodes), len(in.Answer)) - } - - for i, rr := range in.Answer { - if aRec, ok := rr.(*dns.A); ok { - if actual := aRec.A.String(); serviceNodes[i].address != actual { - r.Fatalf("Expecting A RR #%d = %s, Actual RR was %s", i, serviceNodes[i].address, actual) - } - } else { - r.Fatalf("DNS Answer contained a non-A RR") - } - } - }) -} - -func TestDNS_ServiceLookup_PreparedQueryNamePeriod(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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_Recurse(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - recursor := makeRecursor(t, dns.Msg{ - Answer: []dns.RR{dnsA("apple.com", "1.2.3.4")}, - }) - defer recursor.Shutdown() - - a := NewTestAgent(t, ` - recursors = ["`+recursor.Addr+`"] - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - m := new(dns.Msg) - m.SetQuestion("apple.com.", 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) == 0 { - t.Fatalf("Bad: %#v", in) - } - if in.Rcode != dns.RcodeSuccess { - t.Fatalf("Bad: %#v", in) - } -} - -func TestDNS_Recurse_Truncation(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - - recursor := makeRecursor(t, dns.Msg{ - MsgHdr: dns.MsgHdr{Truncated: true}, - Answer: []dns.RR{dnsA("apple.com", "1.2.3.4")}, - }) - defer recursor.Shutdown() - - a := NewTestAgent(t, ` - recursors = ["`+recursor.Addr+`"] - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - m := new(dns.Msg) - m.SetQuestion("apple.com.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - if in.Truncated != true { - t.Fatalf("err: message should have been truncated %v", in) - } - if len(in.Answer) == 0 { - t.Fatalf("Bad: Truncated message ignored, expected some reply %#v", in) - } - if in.Rcode != dns.RcodeSuccess { - t.Fatalf("Bad: %#v", in) - } -} - -func TestDNS_RecursorTimeout(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - serverClientTimeout := 3 * time.Second - testClientTimeout := serverClientTimeout + 5*time.Second - - resolverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") - if err != nil { - t.Error(err) - } - - resolver, err := net.ListenUDP("udp", resolverAddr) - if err != nil { - t.Error(err) - } - defer resolver.Close() - - a := NewTestAgent(t, ` - recursors = ["`+resolver.LocalAddr().String()+`"] // host must cause a connection|read|write timeout - dns_config { - recursor_timeout = "`+serverClientTimeout.String()+`" - } - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - m := new(dns.Msg) - m.SetQuestion("apple.com.", dns.TypeANY) - - // This client calling the server under test must have a longer timeout than the one we set internally - c := &dns.Client{Timeout: testClientTimeout} - - start := time.Now() - in, _, err := c.Exchange(m, a.DNSAddr()) - - duration := time.Since(start) - - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) != 0 { - t.Fatalf("Bad: %#v", in) - } - if in.Rcode != dns.RcodeServerFailure { - t.Fatalf("Bad: %#v", in) - } - - if duration < serverClientTimeout { - t.Fatalf("Expected the call to return after at least %f seconds but lasted only %f", serverClientTimeout.Seconds(), duration.Seconds()) - } - -} - -func TestDNS_ServiceLookup_FilterCritical(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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") - } - - t.Parallel() - a := NewTestAgent(t, ` - dns_config { - only_passing = true - } - `) - 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") - } - - t.Parallel() - a := NewTestAgent(t, "") - 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 TestBinarySearch(t *testing.T) { - t.Parallel() - msgSrc := new(dns.Msg) - msgSrc.Compress = true - msgSrc.SetQuestion("redis.service.consul.", dns.TypeSRV) - - for i := 0; i < 5000; i++ { - target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256) - msgSrc.Answer = append(msgSrc.Answer, &dns.SRV{Hdr: dns.RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: dns.TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) - msgSrc.Extra = append(msgSrc.Extra, &dns.CNAME{Hdr: dns.RR_Header{Name: target, Class: 1, Rrtype: dns.TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)}) - } - for _, compress := range []bool{true, false} { - for idx, maxSize := range []int{12, 256, 512, 8192, 65535} { - t.Run(fmt.Sprintf("binarySearch %d", maxSize), func(t *testing.T) { - msg := new(dns.Msg) - msgSrc.Compress = compress - msgSrc.SetQuestion("redis.service.consul.", dns.TypeSRV) - msg.Answer = msgSrc.Answer - msg.Extra = msgSrc.Extra - msg.Ns = msgSrc.Ns - index := make(map[string]dns.RR, len(msg.Extra)) - indexRRs(msg.Extra, index) - blen := dnsBinaryTruncate(msg, maxSize, index, true) - msg.Answer = msg.Answer[:blen] - syncExtra(index, msg) - predicted := msg.Len() - buf, err := msg.Pack() - if err != nil { - t.Error(err) - } - if predicted < len(buf) { - t.Fatalf("Bug in DNS library: %d != %d", predicted, len(buf)) - } - if len(buf) > maxSize || (idx != 0 && len(buf) < 16) { - t.Fatalf("bad[%d]: %d > %d", idx, len(buf), maxSize) - } - }) - } - } -} - -func TestDNS_TCP_and_UDP_Truncate(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - dns_config { - enable_truncate = true - } - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - services := []string{"normal", "truncated"} - for index, service := range services { - numServices := (index * 5000) + 2 - var eg errgroup.Group - for i := 1; i < numServices; i++ { - j := i - eg.Go(func() error { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: fmt.Sprintf("%s-%d.acme.com", service, j), - Address: fmt.Sprintf("127.%d.%d.%d", 0, (j / 255), j%255), - Service: &structs.NodeService{ - Service: service, - Port: 8000, - }, - } - - var out struct{} - return a.RPC(context.Background(), "Catalog.Register", args, &out) - }) - } - if err := eg.Wait(); err != nil { - t.Fatalf("error registering: %v", err) - } - - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: service, - Service: structs.ServiceQuery{ - Service: service, - }, - }, - } - 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{ - fmt.Sprintf("%s.service.consul.", service), - id + ".query.consul.", - } - protocols := []string{ - "tcp", - "udp", - } - for _, maxSize := range []uint16{8192, 65535} { - for _, qType := range []uint16{dns.TypeANY, dns.TypeA, dns.TypeSRV} { - for _, question := range questions { - for _, protocol := range protocols { - for _, compress := range []bool{true, false} { - t.Run(fmt.Sprintf("lookup %s %s (qType:=%d) compressed=%v", question, protocol, qType, compress), func(t *testing.T) { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) - maxSz := maxSize - if protocol == "udp" { - maxSz = 8192 - } - m.SetEdns0(maxSz, true) - c := new(dns.Client) - c.Net = protocol - m.Compress = compress - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - // actually check if we need to have the truncate bit - resbuf, err := in.Pack() - if err != nil { - t.Fatalf("Error while packing answer: %s", err) - } - if !in.Truncated && len(resbuf) > int(maxSz) { - t.Fatalf("should have truncate bit %#v %#v", in, len(in.Answer)) - } - // Check for the truncate bit - buf, err := m.Pack() - info := fmt.Sprintf("service %s question:=%s (%s) (%d total records) sz:= %d in %v", - service, question, protocol, numServices, len(in.Answer), in) - if err != nil { - t.Fatalf("Error while packing: %v ; info:=%s", err, info) - } - if len(buf) > int(maxSz) { - t.Fatalf("len(buf) := %d > maxSz=%d for %v", len(buf), maxSz, info) - } - }) - } - } - } - } - } - } -} - -func TestDNS_ServiceLookup_Truncate(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - dns_config { - enable_truncate = true - } - `) - 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") - } - - t.Parallel() - a := NewTestAgent(t, ` - dns_config { - enable_truncate = true - } - `) - 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) (bool, error) { - a := NewTestAgent(t, ` - node_name = "test-node" - dns_config { - udp_answer_limit = `+fmt.Sprintf("%d", answerLimit)+` - } - `) - 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, -) { - a := NewTestAgent(t, ` - node_name = "test-node" - dns_config { - a_record_limit = `+fmt.Sprintf("%d", aRecordLimit)+` - udp_answer_limit = `+fmt.Sprintf("%d", aRecordLimit)+` - } - `) - 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") - } - - t.Parallel() - 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) { - t.Parallel() - - // All those queries should have at max queriesLimited elements - - t.Run("A", func(t *testing.T) { - t.Parallel() - checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeA, test.expectedAResults, test.udpSize) - }) - - t.Run("AAAA", func(t *testing.T) { - t.Parallel() - checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeAAAA, test.expectedAAAAResults, test.udpSize) - }) - - t.Run("ANY", func(t *testing.T) { - t.Parallel() - 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) { - t.Parallel() - 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") - } - - t.Parallel() - // 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) { - t.Parallel() - ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeA, test.expectedAService, test.expectedAQuery, test.expectedAQueryID) - 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) { - t.Parallel() - ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeAAAA, test.expectedAAAAService, test.expectedAAAAQuery, test.expectedAAAAQueryID) - 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) { - t.Parallel() - ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeANY, test.expectedANYService, test.expectedANYQuery, test.expectedANYQueryID) - 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") - } - - t.Parallel() - recursor := makeRecursor(t, dns.Msg{ - Answer: []dns.RR{ - dnsCNAME("www.google.com", "google.com"), - dnsA("google.com", "1.2.3.4"), - }, - }) - defer recursor.Shutdown() - - a := NewTestAgent(t, ` - recursors = ["`+recursor.Addr+`"] - `) - 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") - } + cases := map[string]testCase{ + "simple-ipv4": { + serviceAddress: "127.0.0.2", + nodeAddress: "127.0.0.1", - t.Parallel() - recursor := makeRecursor(t, dns.Msg{ - Answer: []dns.RR{ - dnsCNAME("www.google.com", "google.com"), - dnsA("google.com", "1.2.3.4"), + expectedServiceIPv4Address: "127.0.0.2", + expectedServiceIPv6Address: "", + expectedNodeIPv4Address: "127.0.0.1", + expectedNodeIPv6Address: "", }, - }) - defer recursor.Shutdown() + "simple-ipv6": { + serviceAddress: "::2", + nodeAddress: "::1", - a := NewTestAgent(t, ` - recursors = ["`+recursor.Addr+`"] - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + expectedServiceIPv6Address: "::2", + expectedServiceIPv4Address: "", + expectedNodeIPv6Address: "::1", + expectedNodeIPv4Address: "", + }, + "ipv4-with-tagged-ipv6": { + serviceAddress: "127.0.0.2", + nodeAddress: "127.0.0.1", - // 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", + serviceTaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressLANIPv6: {Address: "::2"}, }, - } - - 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", - }, + nodeTaggedAddresses: map[string]string{ + structs.TaggedAddressLANIPv6: "::1", }, - } - 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_NodeLookup_TTL(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - t.Parallel() - recursor := makeRecursor(t, dns.Msg{ - Answer: []dns.RR{ - dnsCNAME("www.google.com", "google.com"), - dnsA("google.com", "1.2.3.4"), + expectedServiceIPv4Address: "127.0.0.2", + expectedServiceIPv6Address: "::2", + expectedNodeIPv4Address: "127.0.0.1", + expectedNodeIPv6Address: "::1", }, - }) - defer recursor.Shutdown() - - a := NewTestAgent(t, ` - recursors = ["`+recursor.Addr+`"] - dns_config { - node_ttl = "10s" - allow_stale = true - max_stale = "1s" - } - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - } - - 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("foo.node.consul.", 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]) - } - if aRec.Hdr.Ttl != 10 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - - // Register node with IPv6 - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "::4242:4242", - } - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - // Check an IPv6 record - m = new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeANY) - - 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) - } - - aaaaRec, ok := in.Answer[0].(*dns.AAAA) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aaaaRec.AAAA.String() != "::4242:4242" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aaaaRec.Hdr.Ttl != 10 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - - // Register node with CNAME - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "www.google.com", - } - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - m = new(dns.Msg) - m.SetQuestion("google.node.consul.", dns.TypeANY) - - in, _, err = c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Should have the CNAME record + a few A records - if len(in.Answer) < 2 { - t.Fatalf("Bad: %#v", in) - } - - 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]) - } - if cnRec.Hdr.Ttl != 10 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } -} - -func TestDNS_ServiceLookup_TTL(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - a := NewTestAgent(t, ` - dns_config { - service_ttl = { - "d*" = "42s" - "db" = "10s" - "db*" = "66s" - "*" = "5s" - } - allow_stale = true - max_stale = "1s" - } - `) - defer a.Shutdown() + "ipv6-with-tagged-ipv4": { + serviceAddress: "::2", + nodeAddress: "::1", - 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, + serviceTaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressLANIPv4: {Address: "127.0.0.2"}, + }, + nodeTaggedAddresses: map[string]string{ + structs.TaggedAddressLANIPv4: "127.0.0.1", }, - } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + expectedServiceIPv4Address: "127.0.0.2", + expectedServiceIPv6Address: "::2", + expectedNodeIPv4Address: "127.0.0.1", + expectedNodeIPv6Address: "::1", + }, } - 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)) + for name, tc := range cases { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: tc.nodeAddress, + TaggedAddresses: tc.nodeTaggedAddresses, + Service: &structs.NodeService{ + Service: "db", + Address: tc.serviceAddress, + Port: 8080, + TaggedAddresses: tc.serviceTaggedAddresses, + }, } - 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]) - } + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - 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]) + // Look up the SRV record via service and prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", } - }) - } - // 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_PreparedQuery_TTL(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) - t.Parallel() - a := NewTestAgent(t, ` - dns_config { - service_ttl = { - "d*" = "42s" - "db" = "10s" - "db*" = "66s" - "*" = "5s" - } - allow_stale = true - max_stale = "1s" - } - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + c := new(dns.Client) + addr := a.config.DNSAddrs[0].String() + in, _, err := c.Exchange(m, addr) + require.NoError(t, err) - 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, - }, - } + if tc.expectedServiceIPv4Address != "" { + 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.expectedServiceIPv4Address, aRec.A.String()) + } else { + require.Len(t, in.Answer, 0) + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - // Register prepared query without TTL and with TTL - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: service, - Service: structs.ServiceQuery{ - Service: service, - }, - }, - } + m = new(dns.Msg) + m.SetQuestion(question, dns.TypeAAAA) - var id string - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - queryTTL := fmt.Sprintf("%s-ttl", service) - args = &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: queryTTL, - Service: structs.ServiceQuery{ - Service: service, - }, - DNS: structs.QueryDNSOptions{ - TTL: "18s", - }, - }, - } + c = new(dns.Client) + addr = a.config.DNSAddrs[0].String() + in, _, err = c.Exchange(m, addr) + require.NoError(t, err) - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) + if tc.expectedServiceIPv6Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.AAAA) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, question, aRec.Hdr.Name) + require.Equal(t, tc.expectedServiceIPv6Address, aRec.AAAA.String()) + } else { + require.Len(t, in.Answer, 0) + } } - } - } - c := new(dns.Client) - expectResult := func(dnsQuery string, expectedTTL uint32) { - t.Run(dnsQuery, func(t *testing.T) { + // Look up node m := new(dns.Msg) - m.SetQuestion(dnsQuery, dns.TypeSRV) + m.SetQuestion("foo.node.consul.", dns.TypeA) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + addr := a.config.DNSAddrs[0].String() + in, _, err := c.Exchange(m, addr) + require.NoError(t, err) - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v, len is %d", in, len(in.Answer)) + if tc.expectedNodeIPv4Address != "" { + 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, "foo.node.consul.", aRec.Hdr.Name) + require.Equal(t, tc.expectedNodeIPv4Address, aRec.A.String()) + } else { + require.Len(t, in.Answer, 0) } - 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]) - } + m = new(dns.Msg) + m.SetQuestion("foo.node.consul.", dns.TypeAAAA) - 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]) + c = new(dns.Client) + addr = a.config.DNSAddrs[0].String() + in, _, err = c.Exchange(m, addr) + require.NoError(t, err) + + if tc.expectedNodeIPv6Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.AAAA) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, "foo.node.consul.", aRec.Hdr.Name) + require.Equal(t, tc.expectedNodeIPv6Address, aRec.AAAA.String()) + } else { + require.Len(t, in.Answer, 0) } }) } - - // Should have its exact TTL - expectResult("db.query.consul.", 10) - expectResult("db-ttl.query.consul.", 18) - // Should match db* - expectResult("dblb.query.consul.", 66) - expectResult("dblb-ttl.query.consul.", 18) - // Should match d* - expectResult("dk.query.consul.", 42) - expectResult("dk-ttl.query.consul.", 18) - // Should be the default value - expectResult("api.query.consul.", 5) - expectResult("api-ttl.query.consul.", 18) } -func TestDNS_PreparedQuery_Failover(t *testing.T) { +func TestDNS_PreparedQueryNearIPEDNS(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } - t.Parallel() - a1 := NewTestAgent(t, ` - datacenter = "dc1" - translate_wan_addrs = true - acl_datacenter = "" - `) - defer a1.Shutdown() + ipCoord := lib.GenerateCoordinate(1 * time.Millisecond) + serviceNodes := []struct { + name string + address string + coord *coordinate.Coordinate + }{ + {"foo1", "198.18.0.1", lib.GenerateCoordinate(1 * time.Millisecond)}, + {"foo2", "198.18.0.2", lib.GenerateCoordinate(10 * time.Millisecond)}, + {"foo3", "198.18.0.3", lib.GenerateCoordinate(30 * time.Millisecond)}, + } - a2 := NewTestAgent(t, ` - datacenter = "dc2" - translate_wan_addrs = true - acl_datacenter = "" - `) - defer a2.Shutdown() + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Join WAN cluster. - addr := fmt.Sprintf("127.0.0.1:%d", a1.Config.SerfPortWAN) - if _, err := a2.JoinWAN([]string{addr}); err != nil { - t.Fatalf("err: %v", err) - } - retry.Run(t, func(r *retry.R) { - if got, want := len(a1.WANMembers()), 2; got < want { - r.Fatalf("got %d WAN members want at least %d", got, want) - } - if got, want := len(a2.WANMembers()), 2; got < want { - r.Fatalf("got %d WAN members want at least %d", got, want) - } - }) + added := 0 - // 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) { + // Register nodes with a service + for _, cfg := range serviceNodes { args := &structs.RegisterRequest{ - Datacenter: "dc2", - Node: "foo", - Address: "127.0.0.1", - TaggedAddresses: map[string]string{ - "wan": "127.0.0.2", - }, + Datacenter: "dc1", + Node: cfg.name, + Address: cfg.address, Service: &structs.NodeService{ Service: "db", + Port: 12345, }, } var out struct{} - if err := a2.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - r.Fatalf("err: %v", err) - } - }) + err := a.RPC(context.Background(), "Catalog.Register", args, &out) + require.NoError(t, err) - // Register a local prepared query. - { - args := &structs.PreparedQueryRequest{ + // Send coordinate updates + coordArgs := structs.CoordinateUpdateRequest{ Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "my-query", - Service: structs.ServiceQuery{ - Service: "db", - Failover: structs.QueryFailoverOptions{ - Datacenters: []string{"dc2"}, - }, - }, - }, - } - var id string - if err := a1.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) + Node: cfg.name, + Coord: cfg.coord, } - } - - // Look up the SRV record via the query. - m := new(dns.Msg) - m.SetQuestion("my-query.query.consul.", dns.TypeSRV) - - c := new(dns.Client) - clAddr := a1.config.DNSAddrs[0] - in, _, err := c.Exchange(m, clAddr.String()) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Make sure we see the remote DC and that the address gets - // translated. - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } - if in.Answer[0].Header().Name != "my-query.query.consul." { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - srv, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srv.Target != "7f000002.addr.dc2.consul." { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) + require.NoError(t, err) - a, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if a.Hdr.Name != "7f000002.addr.dc2.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if a.A.String() != "127.0.0.2" { - t.Fatalf("Bad: %#v", in.Extra[0]) + added += 1 } -} -func TestDNS_ServiceLookup_SRV_RFC(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } + fmt.Printf("Added %d service nodes\n", added) - t.Parallel() - a := NewTestAgent(t, "") - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + // Register a node without a service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "198.18.0.9", + } - // 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{} + err := a.RPC(context.Background(), "Catalog.Register", args, &out) + require.NoError(t, err) - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Send coordinate updates for a few nodes. + coordArgs := structs.CoordinateUpdateRequest{ + Datacenter: "dc1", + Node: "bar", + Coord: ipCoord, + } + err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) + require.NoError(t, err) } - questions := []string{ - "_db._primary.service.dc1.consul.", - "_db._primary.service.consul.", - "_db._primary.dc1.consul.", - "_db._primary.consul.", - } + // Register a prepared query Near = _ip + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "some.query.we.like", + Service: structs.ServiceQuery{ + Service: "db", + Near: "_ip", + }, + }, + } - for _, question := range questions { + var id string + err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id) + require.NoError(t, err) + } + retry.Run(t, func(r *retry.R) { m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + m.SetQuestion("some.query.we.like.query.consul.", dns.TypeA) + m.SetEdns0(4096, false) + o := new(dns.OPT) + o.Hdr.Name = "." + o.Hdr.Rrtype = dns.TypeOPT + e := new(dns.EDNS0_SUBNET) + e.Code = dns.EDNS0SUBNET + e.Family = 1 + e.SourceNetmask = 32 + e.SourceScope = 0 + e.Address = net.ParseIP("198.18.0.9").To4() + o.Option = append(o.Option, e) + m.Extra = append(m.Extra, o) 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) + r.Fatalf("Error with call to dns.Client.Exchange: %s", err) } - 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]) + if len(serviceNodes) != len(in.Answer) { + r.Fatalf("Expecting %d A RRs in response, Actual found was %d", len(serviceNodes), len(in.Answer)) } - 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]) + for i, rr := range in.Answer { + if aRec, ok := rr.(*dns.A); ok { + if actual := aRec.A.String(); serviceNodes[i].address != actual { + r.Fatalf("Expecting A RR #%d = %s, Actual RR was %s", i, serviceNodes[i].address, actual) + } + } else { + r.Fatalf("DNS Answer contained a non-A RR") + } } - } - + }) } -func TestDNS_ServiceLookup_SRV_RFC_TCP_Default(t *testing.T) { +func TestDNS_PreparedQueryNearIP(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } + ipCoord := lib.GenerateCoordinate(1 * time.Millisecond) + serviceNodes := []struct { + name string + address string + coord *coordinate.Coordinate + }{ + {"foo1", "198.18.0.1", lib.GenerateCoordinate(1 * time.Millisecond)}, + {"foo2", "198.18.0.2", lib.GenerateCoordinate(10 * time.Millisecond)}, + {"foo3", "198.18.0.3", lib.GenerateCoordinate(30 * time.Millisecond)}, + } + t.Parallel() a := NewTestAgent(t, "") 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, - }, + added := 0 + + // Register nodes with a service + for _, cfg := range serviceNodes { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: cfg.name, + Address: cfg.address, + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + }, + } + + var out struct{} + err := a.RPC(context.Background(), "Catalog.Register", args, &out) + require.NoError(t, err) + + // Send coordinate updates + coordArgs := structs.CoordinateUpdateRequest{ + Datacenter: "dc1", + Node: cfg.name, + Coord: cfg.coord, + } + err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) + require.NoError(t, err) + + added += 1 } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + fmt.Printf("Added %d service nodes\n", added) + + // Register a node without a service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "198.18.0.9", + } + + var out struct{} + err := a.RPC(context.Background(), "Catalog.Register", args, &out) + require.NoError(t, err) + + // Send coordinate updates for a few nodes. + coordArgs := structs.CoordinateUpdateRequest{ + Datacenter: "dc1", + Node: "bar", + Coord: ipCoord, + } + err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) + require.NoError(t, err) } - questions := []string{ - "_db._tcp.service.dc1.consul.", - "_db._tcp.service.consul.", - "_db._tcp.dc1.consul.", - "_db._tcp.consul.", + // Register a prepared query Near = _ip + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "some.query.we.like", + Service: structs.ServiceQuery{ + Service: "db", + Near: "_ip", + }, + }, + } + + var id string + err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id) + require.NoError(t, err) } - for _, question := range questions { + retry.Run(t, func(r *retry.R) { m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + m.SetQuestion("some.query.we.like.query.consul.", dns.TypeA) 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) + r.Fatalf("Error with call to dns.Client.Exchange: %s", err) } - 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]) + if len(serviceNodes) != len(in.Answer) { + r.Fatalf("Expecting %d A RRs in response, Actual found was %d", len(serviceNodes), len(in.Answer)) } - 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]) + for i, rr := range in.Answer { + if aRec, ok := rr.(*dns.A); ok { + if actual := aRec.A.String(); serviceNodes[i].address != actual { + r.Fatalf("Expecting A RR #%d = %s, Actual RR was %s", i, serviceNodes[i].address, actual) + } + } else { + r.Fatalf("DNS Answer contained a non-A RR") + } } - } - -} - -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) { +func TestDNS_Recurse(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - 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 + ` - } - } - ` - - a := NewTestAgent(t, hcl) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - if tt.token == "dns" { - initDNSToken(t, a) - } + recursor := makeRecursor(t, dns.Msg{ + Answer: []dns.RR{dnsA("apple.com", "1.2.3.4")}, + }) + defer recursor.Shutdown() - // 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) - } + a := NewTestAgent(t, ` + recursors = ["`+recursor.Addr+`"] + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Set up the DNS query - c := new(dns.Client) - m := new(dns.Msg) - m.SetQuestion("foo.service.consul.", dns.TypeA) + m := new(dns.Msg) + m.SetQuestion("apple.com.", dns.TypeANY) - 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) - } - }) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) == 0 { + t.Fatalf("Bad: %#v", in) + } + if in.Rcode != dns.RcodeSuccess { + t.Fatalf("Bad: %#v", in) } } -func TestDNS_ServiceLookup_MetaTXT(t *testing.T) { +func TestDNS_Recurse_Truncation(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = true }`) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + t.Parallel() - 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, - }, - } + recursor := makeRecursor(t, dns.Msg{ + MsgHdr: dns.MsgHdr{Truncated: true}, + Answer: []dns.RR{dnsA("apple.com", "1.2.3.4")}, + }) + defer recursor.Shutdown() - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + a := NewTestAgent(t, ` + recursors = ["`+recursor.Addr+`"] + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeSRV) + m.SetQuestion("apple.com.", dns.TypeANY) 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"}, - }, + if in.Truncated != true { + t.Fatalf("err: message should have been truncated %v", in) + } + if len(in.Answer) == 0 { + t.Fatalf("Bad: Truncated message ignored, expected some reply %#v", in) + } + if in.Rcode != dns.RcodeSuccess { + t.Fatalf("Bad: %#v", in) } - require.Equal(t, wantAdditional, in.Extra) } -func TestDNS_ServiceLookup_SuppressTXT(t *testing.T) { +func TestDNS_RecursorTimeout(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false }`) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + t.Parallel() + serverClientTimeout := 3 * time.Second + testClientTimeout := serverClientTimeout + 5*time.Second - // 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, - }, + resolverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") + if err != nil { + t.Error(err) } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + resolver, err := net.ListenUDP("udp", resolverAddr) + if err != nil { + t.Error(err) } + defer resolver.Close() + + a := NewTestAgent(t, ` + recursors = ["`+resolver.LocalAddr().String()+`"] // host must cause a connection|read|write timeout + dns_config { + recursor_timeout = "`+serverClientTimeout.String()+`" + } + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeSRV) + m.SetQuestion("apple.com.", dns.TypeANY) - c := new(dns.Client) + // This client calling the server under test must have a longer timeout than the one we set internally + c := &dns.Client{Timeout: testClientTimeout} + + start := time.Now() in, _, err := c.Exchange(m, a.DNSAddr()) + + duration := time.Since(start) + 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 - }, + if len(in.Answer) != 0 { + t.Fatalf("Bad: %#v", in) + } + if in.Rcode != dns.RcodeServerFailure { + t.Fatalf("Bad: %#v", in) + } + + if duration < serverClientTimeout { + t.Fatalf("Expected the call to return after at least %f seconds but lasted only %f", serverClientTimeout.Seconds(), duration.Seconds()) + } + +} + +func TestBinarySearch(t *testing.T) { + t.Parallel() + msgSrc := new(dns.Msg) + msgSrc.Compress = true + msgSrc.SetQuestion("redis.service.consul.", dns.TypeSRV) + + for i := 0; i < 5000; i++ { + target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256) + msgSrc.Answer = append(msgSrc.Answer, &dns.SRV{Hdr: dns.RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: dns.TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) + msgSrc.Extra = append(msgSrc.Extra, &dns.CNAME{Hdr: dns.RR_Header{Name: target, Class: 1, Rrtype: dns.TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)}) + } + for _, compress := range []bool{true, false} { + for idx, maxSize := range []int{12, 256, 512, 8192, 65535} { + t.Run(fmt.Sprintf("binarySearch %d", maxSize), func(t *testing.T) { + msg := new(dns.Msg) + msgSrc.Compress = compress + msgSrc.SetQuestion("redis.service.consul.", dns.TypeSRV) + msg.Answer = msgSrc.Answer + msg.Extra = msgSrc.Extra + msg.Ns = msgSrc.Ns + index := make(map[string]dns.RR, len(msg.Extra)) + indexRRs(msg.Extra, index) + blen := dnsBinaryTruncate(msg, maxSize, index, true) + msg.Answer = msg.Answer[:blen] + syncExtra(index, msg) + predicted := msg.Len() + buf, err := msg.Pack() + if err != nil { + t.Error(err) + } + if predicted < len(buf) { + t.Fatalf("Bug in DNS library: %d != %d", predicted, len(buf)) + } + if len(buf) > maxSize || (idx != 0 && len(buf) < 16) { + t.Fatalf("bad[%d]: %d > %d", idx, len(buf), maxSize) + } + }) + } + } +} + +func TestDNS_TCP_and_UDP_Truncate(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, ` + dns_config { + enable_truncate = true + } + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + services := []string{"normal", "truncated"} + for index, service := range services { + numServices := (index * 5000) + 2 + var eg errgroup.Group + for i := 1; i < numServices; i++ { + j := i + eg.Go(func() error { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: fmt.Sprintf("%s-%d.acme.com", service, j), + Address: fmt.Sprintf("127.%d.%d.%d", 0, (j / 255), j%255), + Service: &structs.NodeService{ + Service: service, + Port: 8000, + }, + } + + var out struct{} + return a.RPC(context.Background(), "Catalog.Register", args, &out) + }) + } + if err := eg.Wait(); err != nil { + t.Fatalf("error registering: %v", err) + } + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: service, + Service: structs.ServiceQuery{ + Service: service, + }, + }, + } + 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{ + fmt.Sprintf("%s.service.consul.", service), + id + ".query.consul.", + } + protocols := []string{ + "tcp", + "udp", + } + for _, maxSize := range []uint16{8192, 65535} { + for _, qType := range []uint16{dns.TypeANY, dns.TypeA, dns.TypeSRV} { + for _, question := range questions { + for _, protocol := range protocols { + for _, compress := range []bool{true, false} { + t.Run(fmt.Sprintf("lookup %s %s (qType:=%d) compressed=%v", question, protocol, qType, compress), func(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) + maxSz := maxSize + if protocol == "udp" { + maxSz = 8192 + } + m.SetEdns0(maxSz, true) + c := new(dns.Client) + c.Net = protocol + m.Compress = compress + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + // actually check if we need to have the truncate bit + resbuf, err := in.Pack() + if err != nil { + t.Fatalf("Error while packing answer: %s", err) + } + if !in.Truncated && len(resbuf) > int(maxSz) { + t.Fatalf("should have truncate bit %#v %#v", in, len(in.Answer)) + } + // Check for the truncate bit + buf, err := m.Pack() + info := fmt.Sprintf("service %s question:=%s (%s) (%d total records) sz:= %d in %v", + service, question, protocol, numServices, len(in.Answer), in) + if err != nil { + t.Fatalf("Error while packing: %v ; info:=%s", err, info) + } + if len(buf) > int(maxSz) { + t.Fatalf("len(buf) := %d > maxSz=%d for %v", len(buf), maxSz, info) + } + }) + } + } + } + } + } } - require.Equal(t, wantAdditional, in.Extra) } func TestDNS_AddressLookup(t *testing.T) {