mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
739 lines
16 KiB
739 lines
16 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package dns |
|
|
|
import ( |
|
"errors" |
|
"net" |
|
"testing" |
|
"time" |
|
|
|
"github.com/miekg/dns" |
|
"github.com/stretchr/testify/require" |
|
|
|
"github.com/hashicorp/consul/agent/discovery" |
|
"github.com/hashicorp/consul/sdk/testutil" |
|
) |
|
|
|
func TestDNSResponseGenerator_generateResponseFromError(t *testing.T) { |
|
testCases := []struct { |
|
name string |
|
opts *generateResponseFromErrorOpts |
|
expectedResponse *dns.Msg |
|
}{ |
|
{ |
|
name: "error is nil returns server failure", |
|
opts: &generateResponseFromErrorOpts{ |
|
req: &dns.Msg{}, |
|
logger: testutil.Logger(t), |
|
configCtx: &RouterDynamicConfig{ |
|
DisableCompression: true, |
|
}, |
|
err: nil, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: false, |
|
Rcode: dns.RcodeServerFailure, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "error is invalid question returns name error", |
|
opts: &generateResponseFromErrorOpts{ |
|
req: &dns.Msg{ |
|
Question: []dns.Question{ |
|
{ |
|
Name: "invalid-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
}, |
|
qName: "invalid-question", |
|
responseDomain: "testdomain.", |
|
logger: testutil.Logger(t), |
|
configCtx: &RouterDynamicConfig{ |
|
DisableCompression: true, |
|
}, |
|
err: errInvalidQuestion, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: true, |
|
Rcode: dns.RcodeNameError, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "invalid-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
Ns: []dns.RR{ |
|
&dns.SOA{ |
|
Hdr: dns.RR_Header{ |
|
Name: "testdomain.", |
|
Rrtype: dns.TypeSOA, |
|
Class: dns.ClassINET, |
|
Ttl: 0, |
|
}, |
|
Ns: "ns.testdomain.", |
|
Mbox: "hostmaster.testdomain.", |
|
Serial: uint32(time.Now().Unix()), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "error is name not found returns name error", |
|
opts: &generateResponseFromErrorOpts{ |
|
req: &dns.Msg{ |
|
Question: []dns.Question{ |
|
{ |
|
Name: "invalid-name", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
}, |
|
qName: "invalid-name", |
|
responseDomain: "testdomain.", |
|
logger: testutil.Logger(t), |
|
configCtx: &RouterDynamicConfig{ |
|
DisableCompression: true, |
|
}, |
|
err: errNameNotFound, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: true, |
|
Rcode: dns.RcodeNameError, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "invalid-name", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
Ns: []dns.RR{ |
|
&dns.SOA{ |
|
Hdr: dns.RR_Header{ |
|
Name: "testdomain.", |
|
Rrtype: dns.TypeSOA, |
|
Class: dns.ClassINET, |
|
Ttl: 0, |
|
}, |
|
Ns: "ns.testdomain.", |
|
Mbox: "hostmaster.testdomain.", |
|
Serial: uint32(time.Now().Unix()), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "error is not implemented returns not implemented error", |
|
opts: &generateResponseFromErrorOpts{ |
|
req: &dns.Msg{ |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
}, |
|
qName: "some-question", |
|
responseDomain: "testdomain.", |
|
logger: testutil.Logger(t), |
|
configCtx: &RouterDynamicConfig{ |
|
DisableCompression: true, |
|
}, |
|
err: errNotImplemented, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: true, |
|
Rcode: dns.RcodeNotImplemented, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
Ns: []dns.RR{ |
|
&dns.SOA{ |
|
Hdr: dns.RR_Header{ |
|
Name: "testdomain.", |
|
Rrtype: dns.TypeSOA, |
|
Class: dns.ClassINET, |
|
Ttl: 0, |
|
}, |
|
Ns: "ns.testdomain.", |
|
Mbox: "hostmaster.testdomain.", |
|
Serial: uint32(time.Now().Unix()), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "error is not supported returns name error", |
|
opts: &generateResponseFromErrorOpts{ |
|
req: &dns.Msg{ |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
}, |
|
qName: "some-question", |
|
responseDomain: "testdomain.", |
|
logger: testutil.Logger(t), |
|
configCtx: &RouterDynamicConfig{ |
|
DisableCompression: true, |
|
}, |
|
err: discovery.ErrNotSupported, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: true, |
|
Rcode: dns.RcodeNameError, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
Ns: []dns.RR{ |
|
&dns.SOA{ |
|
Hdr: dns.RR_Header{ |
|
Name: "testdomain.", |
|
Rrtype: dns.TypeSOA, |
|
Class: dns.ClassINET, |
|
Ttl: 0, |
|
}, |
|
Ns: "ns.testdomain.", |
|
Mbox: "hostmaster.testdomain.", |
|
Serial: uint32(time.Now().Unix()), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "error is not found returns name error", |
|
opts: &generateResponseFromErrorOpts{ |
|
req: &dns.Msg{ |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
}, |
|
qName: "some-question", |
|
responseDomain: "testdomain.", |
|
logger: testutil.Logger(t), |
|
configCtx: &RouterDynamicConfig{ |
|
DisableCompression: true, |
|
}, |
|
err: discovery.ErrNotFound, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: true, |
|
Rcode: dns.RcodeNameError, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
Ns: []dns.RR{ |
|
&dns.SOA{ |
|
Hdr: dns.RR_Header{ |
|
Name: "testdomain.", |
|
Rrtype: dns.TypeSOA, |
|
Class: dns.ClassINET, |
|
Ttl: 0, |
|
}, |
|
Ns: "ns.testdomain.", |
|
Mbox: "hostmaster.testdomain.", |
|
Serial: uint32(time.Now().Unix()), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "error is no data returns success with soa", |
|
opts: &generateResponseFromErrorOpts{ |
|
req: &dns.Msg{ |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
}, |
|
qName: "some-question", |
|
responseDomain: "testdomain.", |
|
logger: testutil.Logger(t), |
|
configCtx: &RouterDynamicConfig{ |
|
DisableCompression: true, |
|
}, |
|
err: discovery.ErrNoData, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: true, |
|
Rcode: dns.RcodeSuccess, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
Ns: []dns.RR{ |
|
&dns.SOA{ |
|
Hdr: dns.RR_Header{ |
|
Name: "testdomain.", |
|
Rrtype: dns.TypeSOA, |
|
Class: dns.ClassINET, |
|
Ttl: 0, |
|
}, |
|
Ns: "ns.testdomain.", |
|
Mbox: "hostmaster.testdomain.", |
|
Serial: uint32(time.Now().Unix()), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "error is no path to datacenter returns name error", |
|
opts: &generateResponseFromErrorOpts{ |
|
req: &dns.Msg{ |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
}, |
|
qName: "some-question", |
|
responseDomain: "testdomain.", |
|
logger: testutil.Logger(t), |
|
configCtx: &RouterDynamicConfig{ |
|
DisableCompression: true, |
|
}, |
|
err: discovery.ErrNoPathToDatacenter, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: true, |
|
Rcode: dns.RcodeNameError, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
Ns: []dns.RR{ |
|
&dns.SOA{ |
|
Hdr: dns.RR_Header{ |
|
Name: "testdomain.", |
|
Rrtype: dns.TypeSOA, |
|
Class: dns.ClassINET, |
|
Ttl: 0, |
|
}, |
|
Ns: "ns.testdomain.", |
|
Mbox: "hostmaster.testdomain.", |
|
Serial: uint32(time.Now().Unix()), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "error is something else returns server failure error", |
|
opts: &generateResponseFromErrorOpts{ |
|
req: &dns.Msg{ |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
}, |
|
qName: "some-question", |
|
responseDomain: "testdomain.", |
|
logger: testutil.Logger(t), |
|
configCtx: &RouterDynamicConfig{ |
|
DisableCompression: true, |
|
}, |
|
err: errors.New("KABOOM"), |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: false, |
|
Rcode: dns.RcodeServerFailure, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "some-question", |
|
Qtype: dns.TypeSRV, |
|
Qclass: dns.ClassANY, |
|
}, |
|
}, |
|
Ns: nil, |
|
}, |
|
}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.name, func(t *testing.T) { |
|
tc.opts.req.IsEdns0() |
|
actualResponse := dnsResponseGenerator{}.generateResponseFromError(tc.opts) |
|
require.Equal(t, tc.expectedResponse, actualResponse) |
|
}) |
|
} |
|
} |
|
|
|
func TestDNSResponseGenerator_setEDNS(t *testing.T) { |
|
testCases := []struct { |
|
name string |
|
req *dns.Msg |
|
response *dns.Msg |
|
ecsGlobal bool |
|
expectedResponse *dns.Msg |
|
}{ |
|
{ |
|
name: "request is not edns0, response is not edns0", |
|
req: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
}, |
|
Extra: []dns.RR{ |
|
&dns.OPT{ |
|
Hdr: dns.RR_Header{ |
|
Name: ".", |
|
Rrtype: dns.TypeOPT, |
|
Class: 4096, |
|
Ttl: 0, |
|
}, |
|
Option: []dns.EDNS0{ |
|
&dns.EDNS0_SUBNET{ |
|
Code: 1, |
|
Family: 2, |
|
SourceNetmask: 3, |
|
SourceScope: 4, |
|
Address: net.ParseIP("255.255.255.255"), |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
response: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
}, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
}, |
|
Extra: []dns.RR{ |
|
&dns.OPT{ |
|
Hdr: dns.RR_Header{ |
|
Name: ".", |
|
Rrtype: dns.TypeOPT, |
|
Class: 4096, |
|
Ttl: 0, |
|
}, |
|
Option: []dns.EDNS0{ |
|
&dns.EDNS0_SUBNET{ |
|
Code: 8, |
|
Family: 2, |
|
SourceNetmask: 3, |
|
SourceScope: 3, |
|
Address: net.ParseIP("255.255.255.255"), |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.name, func(t *testing.T) { |
|
dnsResponseGenerator{}.setEDNS(tc.req, tc.response, tc.ecsGlobal) |
|
require.Equal(t, tc.expectedResponse, tc.response) |
|
}) |
|
} |
|
} |
|
|
|
func TestDNSResponseGenerator_trimDNSResponse(t *testing.T) { |
|
testCases := []struct { |
|
name string |
|
req *dns.Msg |
|
response *dns.Msg |
|
cfg *RouterDynamicConfig |
|
remoteAddress net.Addr |
|
expectedResponse *dns.Msg |
|
}{ |
|
{ |
|
name: "network is udp, enable truncate is true, answer count of 1 is less/equal than configured max f 1, response is not trimmed", |
|
req: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "foo.query.consul.", |
|
Qtype: dns.TypeA, |
|
Qclass: dns.ClassINET, |
|
}, |
|
}, |
|
}, |
|
cfg: &RouterDynamicConfig{ |
|
UDPAnswerLimit: 1, |
|
}, |
|
remoteAddress: &net.UDPAddr{ |
|
IP: net.ParseIP("127.0.0.1"), |
|
}, |
|
response: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Rcode: dns.RcodeSuccess, |
|
}, |
|
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: 123, |
|
}, |
|
A: net.ParseIP("1.2.3.4"), |
|
}, |
|
}, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Rcode: dns.RcodeSuccess, |
|
}, |
|
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: 123, |
|
}, |
|
A: net.ParseIP("1.2.3.4"), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "network is udp, enable truncate is true, answer count of 2 is greater than configure UDP max f 2, response is trimmed", |
|
req: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "foo.query.consul.", |
|
Qtype: dns.TypeA, |
|
Qclass: dns.ClassINET, |
|
}, |
|
}, |
|
}, |
|
cfg: &RouterDynamicConfig{ |
|
UDPAnswerLimit: 1, |
|
}, |
|
remoteAddress: &net.UDPAddr{ |
|
IP: net.ParseIP("127.0.0.1"), |
|
}, |
|
response: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Rcode: dns.RcodeSuccess, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "foo.query.consul.", |
|
Qtype: dns.TypeA, |
|
Qclass: dns.ClassINET, |
|
}, |
|
}, |
|
Answer: []dns.RR{ |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "foo1.query.consul.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
Ttl: 123, |
|
}, |
|
A: net.ParseIP("1.2.3.4"), |
|
}, |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "foo2.query.consul.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
Ttl: 123, |
|
}, |
|
A: net.ParseIP("2.2.3.4"), |
|
}, |
|
}, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Rcode: dns.RcodeSuccess, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "foo.query.consul.", |
|
Qtype: dns.TypeA, |
|
Qclass: dns.ClassINET, |
|
}, |
|
}, |
|
Answer: []dns.RR{ |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "foo1.query.consul.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
Ttl: 123, |
|
}, |
|
A: net.ParseIP("1.2.3.4"), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "network is tcp, enable truncate is true, answer is less than 64k limit, response is not trimmed", |
|
req: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "foo.query.consul.", |
|
Qtype: dns.TypeA, |
|
Qclass: dns.ClassINET, |
|
}, |
|
}, |
|
}, |
|
cfg: &RouterDynamicConfig{}, |
|
remoteAddress: &net.TCPAddr{ |
|
IP: net.ParseIP("127.0.0.1"), |
|
}, |
|
response: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Rcode: dns.RcodeSuccess, |
|
}, |
|
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: 123, |
|
}, |
|
A: net.ParseIP("1.2.3.4"), |
|
}, |
|
}, |
|
}, |
|
expectedResponse: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Rcode: dns.RcodeSuccess, |
|
}, |
|
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: 123, |
|
}, |
|
A: net.ParseIP("1.2.3.4"), |
|
}, |
|
}, |
|
}, |
|
}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.name, func(t *testing.T) { |
|
logger := testutil.Logger(t) |
|
dnsResponseGenerator{}.trimDNSResponse(tc.cfg, tc.remoteAddress, tc.req, tc.response, logger) |
|
require.Equal(t, tc.expectedResponse, tc.response) |
|
}) |
|
|
|
} |
|
}
|
|
|