mirror of https://github.com/hashicorp/consul
split tests out into multiple files.
parent
abdf8c8205
commit
a0176356f7
|
@ -0,0 +1,405 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func Test_HandleRequest_ADDR(t *testing.T) {
|
||||
testCases := []HandleTestCase{
|
||||
{
|
||||
name: "test A 'addr.' query, ipv4 response",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.addr.dc1.consul", // "intentionally missing the trailing dot"
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.addr.dc1.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "c000020a.addr.dc1.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("192.0.2.10"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test AAAA 'addr.' query, ipv4 response",
|
||||
// Since we asked for an AAAA record, the A record that resolves from the address is attached as an extra
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.addr.dc1.consul",
|
||||
Qtype: dns.TypeAAAA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.addr.dc1.consul.",
|
||||
Qtype: dns.TypeAAAA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "c000020a.addr.dc1.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("192.0.2.10"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test SRV 'addr.' query, ipv4 response",
|
||||
// Since we asked for a SRV record, the A record that resolves from the address is attached as an extra
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.addr.dc1.consul",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.addr.dc1.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "c000020a.addr.dc1.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("192.0.2.10"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test ANY 'addr.' query, ipv4 response",
|
||||
// The response to ANY should look the same as the A response
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.addr.dc1.consul",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.addr.dc1.consul.",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "c000020a.addr.dc1.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("192.0.2.10"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test AAAA 'addr.' query, ipv6 response",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul",
|
||||
Qtype: dns.TypeAAAA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul.",
|
||||
Qtype: dns.TypeAAAA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.AAAA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul.",
|
||||
Rrtype: dns.TypeAAAA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test A 'addr.' query, ipv6 response",
|
||||
// Since we asked for an A record, the AAAA record that resolves from the address is attached as an extra
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.AAAA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul.",
|
||||
Rrtype: dns.TypeAAAA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test SRV 'addr.' query, ipv6 response",
|
||||
// Since we asked for an SRV record, the AAAA record that resolves from the address is attached as an extra
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.AAAA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul.",
|
||||
Rrtype: dns.TypeAAAA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test ANY 'addr.' query, ipv6 response",
|
||||
// The response to ANY should look the same as the AAAA response
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul.",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.AAAA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "20010db800010002cafe000000001337.addr.dc1.consul.",
|
||||
Rrtype: dns.TypeAAAA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test malformed 'addr.' query",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000.addr.dc1.consul", // too short
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Rcode: dns.RcodeNameError, // NXDOMAIN
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000.addr.dc1.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Ns: []dns.RR{
|
||||
&dns.SOA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "consul.",
|
||||
Rrtype: dns.TypeSOA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 4,
|
||||
},
|
||||
Ns: "ns.consul.",
|
||||
Serial: uint32(time.Now().Unix()),
|
||||
Mbox: "hostmaster.consul.",
|
||||
Refresh: 1,
|
||||
Expire: 3,
|
||||
Retry: 2,
|
||||
Minttl: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runHandleTestCases(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/consul/agent/config"
|
||||
"github.com/hashicorp/consul/agent/discovery"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/internal/resource"
|
||||
)
|
||||
|
||||
func Test_HandleRequest_NS(t *testing.T) {
|
||||
testCases := []HandleTestCase{
|
||||
{
|
||||
name: "vanilla NS query",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "consul.",
|
||||
Qtype: dns.TypeNS,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return([]*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "server-one", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
},
|
||||
{
|
||||
Node: &discovery.Location{Name: "server-two", Address: "4.5.6.7"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
},
|
||||
}, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
reqType := args.Get(2).(discovery.LookupType)
|
||||
|
||||
require.Equal(t, discovery.LookupTypeService, reqType)
|
||||
require.Equal(t, structs.ConsulServiceName, req.Name)
|
||||
require.Equal(t, 3, req.Limit)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "consul.",
|
||||
Qtype: dns.TypeNS,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "consul.",
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Ns: "server-one.workload.default.ns.default.ap.consul.",
|
||||
},
|
||||
&dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "consul.",
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Ns: "server-two.workload.default.ns.default.ap.consul.",
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "server-one.workload.default.ns.default.ap.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "server-two.workload.default.ns.default.ap.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("4.5.6.7"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NS query against alternate domain",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "testdomain.",
|
||||
Qtype: dns.TypeNS,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSDomain: "consul",
|
||||
DNSAltDomain: "testdomain",
|
||||
DNSNodeTTL: 123 * time.Second,
|
||||
DNSSOA: config.RuntimeSOAConfig{
|
||||
Refresh: 1,
|
||||
Retry: 2,
|
||||
Expire: 3,
|
||||
Minttl: 4,
|
||||
},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return([]*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "server-one", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
},
|
||||
{
|
||||
Node: &discovery.Location{Name: "server-two", Address: "4.5.6.7"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
},
|
||||
}, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
reqType := args.Get(2).(discovery.LookupType)
|
||||
|
||||
require.Equal(t, discovery.LookupTypeService, reqType)
|
||||
require.Equal(t, structs.ConsulServiceName, req.Name)
|
||||
require.Equal(t, 3, req.Limit)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "testdomain.",
|
||||
Qtype: dns.TypeNS,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "testdomain.",
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Ns: "server-one.workload.default.ns.default.ap.testdomain.",
|
||||
},
|
||||
&dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "testdomain.",
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Ns: "server-two.workload.default.ns.default.ap.testdomain.",
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "server-one.workload.default.ns.default.ap.testdomain.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "server-two.workload.default.ns.default.ap.testdomain.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("4.5.6.7"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runHandleTestCases(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/consul/agent/config"
|
||||
"github.com/hashicorp/consul/agent/discovery"
|
||||
)
|
||||
|
||||
func Test_HandleRequest_PreparedQuery(t *testing.T) {
|
||||
testCases := []HandleTestCase{
|
||||
{
|
||||
name: "v1 prepared query w/ TTL override, ANY query, returns A record",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.query.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSDomain: "consul",
|
||||
DNSNodeTTL: 123 * time.Second,
|
||||
DNSSOA: config.RuntimeSOAConfig{
|
||||
Refresh: 1,
|
||||
Retry: 2,
|
||||
Expire: 3,
|
||||
Minttl: 4,
|
||||
},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
// We shouldn't use this if we have the override defined
|
||||
DNSServiceTTL: map[string]time.Duration{
|
||||
"foo": 1 * time.Second,
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchPreparedQuery", mock.Anything, mock.Anything).
|
||||
Return([]*discovery.Result{
|
||||
{
|
||||
Service: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Node: &discovery.Location{Name: "bar", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeService,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Datacenter: "dc1",
|
||||
},
|
||||
DNS: discovery.DNSConfig{
|
||||
TTL: getUint32Ptr(3),
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
}, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
require.Equal(t, "foo", req.Name)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.query.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.query.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 3,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "v1 prepared query w/ matching service TTL, ANY query, returns A record",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.query.dc1.cluster.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSDomain: "consul",
|
||||
DNSNodeTTL: 123 * time.Second,
|
||||
DNSSOA: config.RuntimeSOAConfig{
|
||||
Refresh: 1,
|
||||
Retry: 2,
|
||||
Expire: 3,
|
||||
Minttl: 4,
|
||||
},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
// Results should use this as the TTL
|
||||
DNSServiceTTL: map[string]time.Duration{
|
||||
"foo": 1 * time.Second,
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchPreparedQuery", mock.Anything, mock.Anything).
|
||||
Return([]*discovery.Result{
|
||||
{
|
||||
Service: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Node: &discovery.Location{Name: "bar", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeService,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Datacenter: "dc1",
|
||||
},
|
||||
DNS: discovery.DNSConfig{
|
||||
// Intentionally no TTL here.
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
}, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Equal(t, "dc1", req.Tenancy.Datacenter)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.query.dc1.cluster.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.query.dc1.cluster.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 1,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runHandleTestCases(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,563 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/consul/agent/discovery"
|
||||
)
|
||||
|
||||
func Test_HandleRequest_PTR(t *testing.T) {
|
||||
testCases := []HandleTestCase{
|
||||
{
|
||||
name: "PTR lookup for node, query type is ANY",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Service: &discovery.Location{Name: "bar", Address: "foo"},
|
||||
Type: discovery.ResultTypeNode,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Datacenter: "dc2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchRecordsByIp", mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(net.IP)
|
||||
|
||||
require.NotNil(t, req)
|
||||
require.Equal(t, "1.2.3.4", req.String())
|
||||
})
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
Ptr: "foo.node.dc2.consul.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PTR lookup for IPV6 node",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa",
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo", Address: "2001:db8::567:89ab"},
|
||||
Service: &discovery.Location{Name: "web", Address: "foo"},
|
||||
Type: discovery.ResultTypeNode,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Datacenter: "dc2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchRecordsByIp", mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(net.IP)
|
||||
|
||||
require.NotNil(t, req)
|
||||
require.Equal(t, "2001:db8::567:89ab", req.String())
|
||||
})
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.",
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.",
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
Ptr: "foo.node.dc2.consul.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PTR lookup for invalid IP address",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "257.3.2.1.in-addr.arpa",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
Rcode: dns.RcodeNameError,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "257.3.2.1.in-addr.arpa.",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Ns: []dns.RR{
|
||||
&dns.SOA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "consul.",
|
||||
Rrtype: dns.TypeSOA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 4,
|
||||
},
|
||||
Ns: "ns.consul.",
|
||||
Serial: uint32(time.Now().Unix()),
|
||||
Mbox: "hostmaster.consul.",
|
||||
Refresh: 1,
|
||||
Expire: 3,
|
||||
Retry: 2,
|
||||
Minttl: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PTR lookup for invalid subdomain",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.blah.arpa",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
Rcode: dns.RcodeNameError,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.blah.arpa.",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Ns: []dns.RR{
|
||||
&dns.SOA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "consul.",
|
||||
Rrtype: dns.TypeSOA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 4,
|
||||
},
|
||||
Ns: "ns.consul.",
|
||||
Serial: uint32(time.Now().Unix()),
|
||||
Mbox: "hostmaster.consul.",
|
||||
Refresh: 1,
|
||||
Expire: 3,
|
||||
Retry: 2,
|
||||
Minttl: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "[ENT] PTR Lookup for node w/ peer name in default partition, query type is ANY",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeNode,
|
||||
Service: &discovery.Location{Name: "foo-web", Address: "foo"},
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Datacenter: "dc2",
|
||||
PeerName: "peer1",
|
||||
Partition: "default",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchRecordsByIp", mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(net.IP)
|
||||
|
||||
require.NotNil(t, req)
|
||||
require.Equal(t, "1.2.3.4", req.String())
|
||||
})
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
Ptr: "foo.node.peer1.peer.default.ap.consul.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "[ENT] PTR Lookup for service in default namespace, query type is PTR",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa",
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeService,
|
||||
Service: &discovery.Location{Name: "foo", Address: "foo"},
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Datacenter: "dc2",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchRecordsByIp", mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(net.IP)
|
||||
|
||||
require.NotNil(t, req)
|
||||
require.Equal(t, "1.2.3.4", req.String())
|
||||
})
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
Ptr: "foo.service.default.dc2.consul.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "[ENT] PTR Lookup for service in a non-default namespace, query type is PTR",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa",
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo-node", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeService,
|
||||
Service: &discovery.Location{Name: "foo", Address: "foo"},
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Datacenter: "dc2",
|
||||
Namespace: "bar",
|
||||
Partition: "baz",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchRecordsByIp", mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(net.IP)
|
||||
|
||||
require.NotNil(t, req)
|
||||
require.Equal(t, "1.2.3.4", req.String())
|
||||
})
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
Ptr: "foo.service.bar.dc2.consul.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "[CE] PTR Lookup for node w/ peer name, query type is ANY",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeNode,
|
||||
Service: &discovery.Location{Name: "foo", Address: "foo"},
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Datacenter: "dc2",
|
||||
PeerName: "peer1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchRecordsByIp", mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(net.IP)
|
||||
|
||||
require.NotNil(t, req)
|
||||
require.Equal(t, "1.2.3.4", req.String())
|
||||
})
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
Ptr: "foo.node.peer1.peer.consul.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "[CE] PTR Lookup for service, query type is PTR",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa",
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Service: &discovery.Location{Name: "foo", Address: "foo"},
|
||||
Type: discovery.ResultTypeService,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Datacenter: "dc2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchRecordsByIp", mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(net.IP)
|
||||
|
||||
require.NotNil(t, req)
|
||||
require.Equal(t, "1.2.3.4", req.String())
|
||||
})
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "4.3.2.1.in-addr.arpa.",
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
Ptr: "foo.service.dc2.consul.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runHandleTestCases(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,296 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/hashicorp/consul/agent/config"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_HandleRequest_recursor(t *testing.T) {
|
||||
testCases := []HandleTestCase{
|
||||
{
|
||||
name: "recursors not configured, non-matching domain",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "google.com",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
// configureRecursor: call not expected.
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Rcode: dns.RcodeRefused,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "google.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recursors configured, matching domain",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "google.com",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSRecursors: []string{"8.8.8.8"},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
},
|
||||
configureRecursor: func(recursor dnsRecursor) {
|
||||
resp := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "google.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "google.com.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
}
|
||||
recursor.(*mockDnsRecursor).On("handle",
|
||||
mock.Anything, mock.Anything, mock.Anything).Return(resp, nil)
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "google.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "google.com.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recursors configured, no matching domain",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "google.com",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSRecursors: []string{"8.8.8.8"},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
},
|
||||
configureRecursor: func(recursor dnsRecursor) {
|
||||
recursor.(*mockDnsRecursor).On("handle", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(nil, errRecursionFailed)
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: false,
|
||||
Rcode: dns.RcodeServerFailure,
|
||||
RecursionAvailable: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "google.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recursors configured, unhandled error calling recursors",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "google.com",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSRecursors: []string{"8.8.8.8"},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
},
|
||||
configureRecursor: func(recursor dnsRecursor) {
|
||||
err := errors.New("ahhhhh!!!!")
|
||||
recursor.(*mockDnsRecursor).On("handle", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(nil, err)
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: false,
|
||||
Rcode: dns.RcodeServerFailure,
|
||||
RecursionAvailable: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "google.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recursors configured, the root domain is handled by the recursor",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: ".",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSRecursors: []string{"8.8.8.8"},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
},
|
||||
configureRecursor: func(recursor dnsRecursor) {
|
||||
// this response is modeled after `dig .`
|
||||
resp := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: ".",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.SOA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: ".",
|
||||
Rrtype: dns.TypeSOA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 86391,
|
||||
},
|
||||
Ns: "a.root-servers.net.",
|
||||
Serial: 2024012200,
|
||||
Mbox: "nstld.verisign-grs.com.",
|
||||
Refresh: 1800,
|
||||
Retry: 900,
|
||||
Expire: 604800,
|
||||
Minttl: 86400,
|
||||
},
|
||||
},
|
||||
}
|
||||
recursor.(*mockDnsRecursor).On("handle",
|
||||
mock.Anything, mock.Anything, mock.Anything).Return(resp, nil)
|
||||
},
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: ".",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.SOA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: ".",
|
||||
Rrtype: dns.TypeSOA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 86391,
|
||||
},
|
||||
Ns: "a.root-servers.net.",
|
||||
Serial: 2024012200,
|
||||
Mbox: "nstld.verisign-grs.com.",
|
||||
Refresh: 1800,
|
||||
Retry: 900,
|
||||
Expire: 604800,
|
||||
Minttl: 86400,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runHandleTestCases(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/consul/agent/config"
|
||||
"github.com/hashicorp/consul/agent/discovery"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/internal/resource"
|
||||
)
|
||||
|
||||
func Test_HandleRequest_SOA(t *testing.T) {
|
||||
testCases := []HandleTestCase{
|
||||
{
|
||||
name: "vanilla SOA query",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "consul.",
|
||||
Qtype: dns.TypeSOA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return([]*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "server-one", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
},
|
||||
{
|
||||
Node: &discovery.Location{Name: "server-two", Address: "4.5.6.7"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
},
|
||||
}, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
reqType := args.Get(2).(discovery.LookupType)
|
||||
|
||||
require.Equal(t, discovery.LookupTypeService, reqType)
|
||||
require.Equal(t, structs.ConsulServiceName, req.Name)
|
||||
require.Equal(t, 3, req.Limit)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "consul.",
|
||||
Qtype: dns.TypeSOA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.SOA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "consul.",
|
||||
Rrtype: dns.TypeSOA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 4,
|
||||
},
|
||||
Ns: "ns.consul.",
|
||||
Serial: uint32(time.Now().Unix()),
|
||||
Mbox: "hostmaster.consul.",
|
||||
Refresh: 1,
|
||||
Expire: 3,
|
||||
Retry: 2,
|
||||
Minttl: 4,
|
||||
},
|
||||
},
|
||||
Ns: []dns.RR{
|
||||
&dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "consul.",
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Ns: "server-one.workload.default.ns.default.ap.consul.",
|
||||
},
|
||||
&dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "consul.",
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Ns: "server-two.workload.default.ns.default.ap.consul.",
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "server-one.workload.default.ns.default.ap.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "server-two.workload.default.ns.default.ap.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("4.5.6.7"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SOA query against alternate domain",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "testdomain.",
|
||||
Qtype: dns.TypeSOA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSDomain: "consul",
|
||||
DNSAltDomain: "testdomain",
|
||||
DNSNodeTTL: 123 * time.Second,
|
||||
DNSSOA: config.RuntimeSOAConfig{
|
||||
Refresh: 1,
|
||||
Retry: 2,
|
||||
Expire: 3,
|
||||
Minttl: 4,
|
||||
},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return([]*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "server-one", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
},
|
||||
{
|
||||
Node: &discovery.Location{Name: "server-two", Address: "4.5.6.7"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
}},
|
||||
}, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
reqType := args.Get(2).(discovery.LookupType)
|
||||
|
||||
require.Equal(t, discovery.LookupTypeService, reqType)
|
||||
require.Equal(t, structs.ConsulServiceName, req.Name)
|
||||
require.Equal(t, 3, req.Limit)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "testdomain.",
|
||||
Qtype: dns.TypeSOA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.SOA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "testdomain.",
|
||||
Rrtype: dns.TypeSOA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 4,
|
||||
},
|
||||
Ns: "ns.testdomain.",
|
||||
Serial: uint32(time.Now().Unix()),
|
||||
Mbox: "hostmaster.testdomain.",
|
||||
Refresh: 1,
|
||||
Expire: 3,
|
||||
Retry: 2,
|
||||
Minttl: 4,
|
||||
},
|
||||
},
|
||||
Ns: []dns.RR{
|
||||
&dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "testdomain.",
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Ns: "server-one.workload.default.ns.default.ap.testdomain.",
|
||||
},
|
||||
&dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "testdomain.",
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Ns: "server-two.workload.default.ns.default.ap.testdomain.",
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "server-one.workload.default.ns.default.ap.testdomain.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "server-two.workload.default.ns.default.ap.testdomain.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("4.5.6.7"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runHandleTestCases(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,628 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/consul/agent/config"
|
||||
"github.com/hashicorp/consul/agent/discovery"
|
||||
"github.com/hashicorp/consul/internal/resource"
|
||||
)
|
||||
|
||||
func Test_HandleRequest_V2Services(t *testing.T) {
|
||||
testCases := []HandleTestCase{
|
||||
{
|
||||
name: "A/AAAA Query a service and return multiple A records",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.service.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo-1", Address: "10.0.0.1"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
// Intentionally not in the mesh
|
||||
},
|
||||
DNS: discovery.DNSConfig{
|
||||
Weight: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo-2", Address: "10.0.0.2"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
{
|
||||
Name: "mesh",
|
||||
Number: 21000,
|
||||
},
|
||||
},
|
||||
DNS: discovery.DNSConfig{
|
||||
Weight: 3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
reqType := args.Get(2).(discovery.LookupType)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Empty(t, req.PortName)
|
||||
require.Equal(t, discovery.LookupTypeService, reqType)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.service.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.service.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
A: net.ParseIP("10.0.0.1"),
|
||||
},
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.service.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
A: net.ParseIP("10.0.0.2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SRV Query with a multi-port service return multiple SRV records",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.service.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo-1", Address: "10.0.0.1"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
// Intentionally not in the mesh
|
||||
},
|
||||
DNS: discovery.DNSConfig{
|
||||
Weight: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo-2", Address: "10.0.0.2"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
{
|
||||
Name: "mesh",
|
||||
Number: 21000,
|
||||
},
|
||||
},
|
||||
DNS: discovery.DNSConfig{
|
||||
Weight: 3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
reqType := args.Get(2).(discovery.LookupType)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Empty(t, req.PortName)
|
||||
require.Equal(t, discovery.LookupTypeService, reqType)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.service.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.SRV{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.service.consul.",
|
||||
Rrtype: dns.TypeSRV,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
Weight: 2,
|
||||
Priority: 1,
|
||||
Port: 5678,
|
||||
Target: "api.port.foo-1.workload.default.ns.default.ap.consul.",
|
||||
},
|
||||
&dns.SRV{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.service.consul.",
|
||||
Rrtype: dns.TypeSRV,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
Weight: 3,
|
||||
Priority: 1,
|
||||
Port: 5678,
|
||||
Target: "api.port.foo-2.workload.default.ns.default.ap.consul.",
|
||||
},
|
||||
&dns.SRV{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.service.consul.",
|
||||
Rrtype: dns.TypeSRV,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
Weight: 3,
|
||||
Priority: 1,
|
||||
Port: 21000,
|
||||
Target: "mesh.port.foo-2.workload.default.ns.default.ap.consul.",
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "api.port.foo-1.workload.default.ns.default.ap.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
A: net.ParseIP("10.0.0.1"),
|
||||
},
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "api.port.foo-2.workload.default.ns.default.ap.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
A: net.ParseIP("10.0.0.2"),
|
||||
},
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "mesh.port.foo-2.workload.default.ns.default.ap.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
A: net.ParseIP("10.0.0.2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SRV Query with a multi-port service where the client requests a specific port, returns SRV and A records",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "mesh.port.foo.service.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo-2", Address: "10.0.0.2"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "mesh",
|
||||
Number: 21000,
|
||||
},
|
||||
},
|
||||
DNS: discovery.DNSConfig{
|
||||
Weight: 3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
reqType := args.Get(2).(discovery.LookupType)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Equal(t, "mesh", req.PortName)
|
||||
require.Equal(t, discovery.LookupTypeService, reqType)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "mesh.port.foo.service.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.SRV{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "mesh.port.foo.service.consul.",
|
||||
Rrtype: dns.TypeSRV,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
Weight: 3,
|
||||
Priority: 1,
|
||||
Port: 21000,
|
||||
Target: "mesh.port.foo-2.workload.default.ns.default.ap.consul.",
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "mesh.port.foo-2.workload.default.ns.default.ap.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
A: net.ParseIP("10.0.0.2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SRV Query with a multi-port service that has workloads w/ hostnames (no recursors)",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.service.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo-1", Address: "foo-1.example.com"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
{
|
||||
Name: "web",
|
||||
Number: 8080,
|
||||
},
|
||||
},
|
||||
DNS: discovery.DNSConfig{
|
||||
Weight: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
reqType := args.Get(2).(discovery.LookupType)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Empty(t, req.PortName)
|
||||
require.Equal(t, discovery.LookupTypeService, reqType)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.service.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.SRV{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.service.consul.",
|
||||
Rrtype: dns.TypeSRV,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
Weight: 2,
|
||||
Priority: 1,
|
||||
Port: 5678,
|
||||
Target: "foo-1.example.com.",
|
||||
},
|
||||
&dns.SRV{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.service.consul.",
|
||||
Rrtype: dns.TypeSRV,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
Weight: 2,
|
||||
Priority: 1,
|
||||
Port: 8080,
|
||||
Target: "foo-1.example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SRV Query with a multi-port service that has workloads w/ hostnames (no recursor)",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.service.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
results := []*discovery.Result{
|
||||
{
|
||||
Node: &discovery.Location{Name: "foo-1", Address: "foo-1.example.com"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: resource.DefaultNamespaceName,
|
||||
Partition: resource.DefaultPartitionName,
|
||||
},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
{
|
||||
Name: "web",
|
||||
Number: 8080,
|
||||
},
|
||||
},
|
||||
DNS: discovery.DNSConfig{
|
||||
Weight: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(results, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
reqType := args.Get(2).(discovery.LookupType)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Empty(t, req.PortName)
|
||||
require.Equal(t, discovery.LookupTypeService, reqType)
|
||||
})
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSRecursors: []string{"8.8.8.8"},
|
||||
DNSDomain: "consul",
|
||||
DNSNodeTTL: 123 * time.Second,
|
||||
DNSSOA: config.RuntimeSOAConfig{
|
||||
Refresh: 1,
|
||||
Retry: 2,
|
||||
Expire: 3,
|
||||
Minttl: 4,
|
||||
},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
},
|
||||
configureRecursor: func(recursor dnsRecursor) {
|
||||
resp := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo-1.example.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo-1.example.com.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
}
|
||||
recursor.(*mockDnsRecursor).On("handle",
|
||||
mock.Anything, mock.Anything, mock.Anything).Return(resp, nil)
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionAvailable: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.service.consul.",
|
||||
Qtype: dns.TypeSRV,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.SRV{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.service.consul.",
|
||||
Rrtype: dns.TypeSRV,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
Weight: 2,
|
||||
Priority: 1,
|
||||
Port: 5678,
|
||||
Target: "foo-1.example.com.",
|
||||
},
|
||||
&dns.SRV{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.service.consul.",
|
||||
Rrtype: dns.TypeSRV,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
Weight: 2,
|
||||
Priority: 1,
|
||||
Port: 8080,
|
||||
Target: "foo-1.example.com.",
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo-1.example.com.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
// TODO (v2-dns): This needs to be de-duplicated (NET-8064)
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo-1.example.com.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(123),
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runHandleTestCases(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/consul/agent/discovery"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_HandleRequest_Virtual(t *testing.T) {
|
||||
testCases := []HandleTestCase{
|
||||
{
|
||||
name: "test A 'virtual.' query, ipv4 response",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.virtual.dc1.consul", // "intentionally missing the trailing dot"
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).On("FetchVirtualIP",
|
||||
mock.Anything, mock.Anything).Return(&discovery.Result{
|
||||
Node: &discovery.Location{Address: "240.0.0.2"},
|
||||
Type: discovery.ResultTypeVirtual,
|
||||
}, nil)
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "c000020a.virtual.dc1.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "c000020a.virtual.dc1.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("240.0.0.2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test A 'virtual.' query, ipv6 response",
|
||||
// Since we asked for an A record, the AAAA record that resolves from the address is attached as an extra
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.virtual.dc1.consul",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).On("FetchVirtualIP",
|
||||
mock.Anything, mock.Anything).Return(&discovery.Result{
|
||||
Node: &discovery.Location{Address: "2001:db8:1:2:cafe::1337"},
|
||||
Type: discovery.ResultTypeVirtual,
|
||||
}, nil)
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "20010db800010002cafe000000001337.virtual.dc1.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
&dns.AAAA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "20010db800010002cafe000000001337.virtual.dc1.consul.",
|
||||
Rrtype: dns.TypeAAAA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runHandleTestCases(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,515 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/consul/agent/config"
|
||||
"github.com/hashicorp/consul/agent/discovery"
|
||||
)
|
||||
|
||||
func Test_HandleRequest_workloads(t *testing.T) {
|
||||
testCases := []HandleTestCase{
|
||||
{
|
||||
name: "workload A query w/ port, returns A record",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
result := &discovery.Result{
|
||||
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchWorkload", mock.Anything, mock.Anything).
|
||||
Return(result, nil). //TODO
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Equal(t, "api", req.PortName)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "workload ANY query w/o port, returns A record",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.workload.consul.",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
result := &discovery.Result{
|
||||
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchWorkload", mock.Anything, mock.Anything).
|
||||
Return(result, nil). //TODO
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Empty(t, req.PortName)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.workload.consul.",
|
||||
Qtype: dns.TypeANY,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.workload.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "workload A query with namespace, partition, and cluster id; IPV4 address; returns A record",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.workload.bar.ns.baz.ap.dc3.dc.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
result := &discovery.Result{
|
||||
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{
|
||||
Namespace: "bar",
|
||||
Partition: "baz",
|
||||
// We currently don't set the datacenter in any of the V2 results.
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchWorkload", mock.Anything, mock.Anything).
|
||||
Return(result, nil).
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Empty(t, req.PortName)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.workload.bar.ns.baz.ap.dc3.dc.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.workload.bar.ns.baz.ap.dc3.dc.consul.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "workload w/hostname address, ANY query (no recursor)",
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
result := &discovery.Result{
|
||||
Node: &discovery.Location{Name: "foo", Address: "foo.example.com"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchWorkload", mock.Anything, mock.Anything).
|
||||
Return(result, nil). //TODO
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Equal(t, "api", req.PortName)
|
||||
})
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.CNAME{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Rrtype: dns.TypeCNAME,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Target: "foo.example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "workload w/hostname address, ANY query (w/ recursor)",
|
||||
// https://datatracker.ietf.org/doc/html/rfc1034#section-3.6.2 both the CNAME and the A record should be in the answer
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
result := &discovery.Result{
|
||||
Node: &discovery.Location{Name: "foo", Address: "foo.example.com"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchWorkload", mock.Anything, mock.Anything).
|
||||
Return(result, nil). //TODO
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Equal(t, "api", req.PortName)
|
||||
})
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSRecursors: []string{"8.8.8.8"},
|
||||
DNSDomain: "consul",
|
||||
DNSNodeTTL: 123 * time.Second,
|
||||
DNSSOA: config.RuntimeSOAConfig{
|
||||
Refresh: 1,
|
||||
Retry: 2,
|
||||
Expire: 3,
|
||||
Minttl: 4,
|
||||
},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
},
|
||||
configureRecursor: func(recursor dnsRecursor) {
|
||||
resp := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.example.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.example.com.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
}
|
||||
recursor.(*mockDnsRecursor).On("handle",
|
||||
mock.Anything, mock.Anything, mock.Anything).Return(resp, nil)
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionAvailable: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.CNAME{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Rrtype: dns.TypeCNAME,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Target: "foo.example.com.",
|
||||
},
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.example.com.",
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "workload w/hostname address, CNAME query (w/ recursor)",
|
||||
// https://datatracker.ietf.org/doc/html/rfc1034#section-3.6.2 only the CNAME should be in the answer
|
||||
request: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Qtype: dns.TypeCNAME,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) {
|
||||
result := &discovery.Result{
|
||||
Node: &discovery.Location{Name: "foo", Address: "foo.example.com"},
|
||||
Type: discovery.ResultTypeWorkload,
|
||||
Tenancy: discovery.ResultTenancy{},
|
||||
Ports: []discovery.Port{
|
||||
{
|
||||
Name: "api",
|
||||
Number: 5678,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fetcher.(*discovery.MockCatalogDataFetcher).
|
||||
On("FetchWorkload", mock.Anything, mock.Anything).
|
||||
Return(result, nil). //TODO
|
||||
Run(func(args mock.Arguments) {
|
||||
req := args.Get(1).(*discovery.QueryPayload)
|
||||
|
||||
require.Equal(t, "foo", req.Name)
|
||||
require.Equal(t, "api", req.PortName)
|
||||
})
|
||||
},
|
||||
agentConfig: &config.RuntimeConfig{
|
||||
DNSRecursors: []string{"8.8.8.8"},
|
||||
DNSDomain: "consul",
|
||||
DNSNodeTTL: 123 * time.Second,
|
||||
DNSSOA: config.RuntimeSOAConfig{
|
||||
Refresh: 1,
|
||||
Retry: 2,
|
||||
Expire: 3,
|
||||
Minttl: 4,
|
||||
},
|
||||
DNSUDPAnswerLimit: maxUDPAnswerLimit,
|
||||
},
|
||||
configureRecursor: func(recursor dnsRecursor) {
|
||||
resp := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "foo.example.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.example.com.",
|
||||
Rrtype: dns.TypeCNAME,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
}
|
||||
recursor.(*mockDnsRecursor).On("handle",
|
||||
mock.Anything, mock.Anything, mock.Anything).Return(resp, nil)
|
||||
},
|
||||
validateAndNormalizeExpected: true,
|
||||
response: &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionAvailable: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{
|
||||
{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Qtype: dns.TypeCNAME,
|
||||
Qclass: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.CNAME{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "api.port.foo.workload.consul.",
|
||||
Rrtype: dns.TypeCNAME,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
Target: "foo.example.com.",
|
||||
},
|
||||
// TODO (v2-dns): this next record is wrong per the RFC-1034 mentioned in the comment above (NET-8060)
|
||||
&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: "foo.example.com.",
|
||||
Rrtype: dns.TypeCNAME,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 123,
|
||||
},
|
||||
A: net.ParseIP("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runHandleTestCases(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue