mirror of https://github.com/hashicorp/consul
Browse Source
* feat(v2dns): recursor support * test: fix leaking test agent in dns svc testpull/20277/head
Dan Stough
10 months ago
committed by
GitHub
9 changed files with 570 additions and 73 deletions
@ -0,0 +1,56 @@
|
||||
// Code generated by mockery v2.20.0. DO NOT EDIT.
|
||||
|
||||
package dns |
||||
|
||||
import ( |
||||
miekgdns "github.com/miekg/dns" |
||||
mock "github.com/stretchr/testify/mock" |
||||
|
||||
net "net" |
||||
) |
||||
|
||||
// mockDnsRecursor is an autogenerated mock type for the dnsRecursor type
|
||||
type mockDnsRecursor struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
// handle provides a mock function with given fields: req, cfgCtx, remoteAddr
|
||||
func (_m *mockDnsRecursor) handle(req *miekgdns.Msg, cfgCtx *RouterDynamicConfig, remoteAddr net.Addr) (*miekgdns.Msg, error) { |
||||
ret := _m.Called(req, cfgCtx, remoteAddr) |
||||
|
||||
var r0 *miekgdns.Msg |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(*miekgdns.Msg, *RouterDynamicConfig, net.Addr) (*miekgdns.Msg, error)); ok { |
||||
return rf(req, cfgCtx, remoteAddr) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(*miekgdns.Msg, *RouterDynamicConfig, net.Addr) *miekgdns.Msg); ok { |
||||
r0 = rf(req, cfgCtx, remoteAddr) |
||||
} else { |
||||
if ret.Get(0) != nil { |
||||
r0 = ret.Get(0).(*miekgdns.Msg) |
||||
} |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(*miekgdns.Msg, *RouterDynamicConfig, net.Addr) error); ok { |
||||
r1 = rf(req, cfgCtx, remoteAddr) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
type mockConstructorTestingTnewMockDnsRecursor interface { |
||||
mock.TestingT |
||||
Cleanup(func()) |
||||
} |
||||
|
||||
// newMockDnsRecursor creates a new instance of mockDnsRecursor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func newMockDnsRecursor(t mockConstructorTestingTnewMockDnsRecursor) *mockDnsRecursor { |
||||
mock := &mockDnsRecursor{} |
||||
mock.Mock.Test(t) |
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||
|
||||
return mock |
||||
} |
@ -0,0 +1,123 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns |
||||
|
||||
import ( |
||||
"errors" |
||||
"net" |
||||
"time" |
||||
|
||||
"github.com/hashicorp/go-hclog" |
||||
"github.com/miekg/dns" |
||||
|
||||
"github.com/hashicorp/consul/ipaddr" |
||||
"github.com/hashicorp/consul/logging" |
||||
) |
||||
|
||||
type recursor struct { |
||||
logger hclog.Logger |
||||
} |
||||
|
||||
func newRecursor(logger hclog.Logger) *recursor { |
||||
return &recursor{ |
||||
logger: logger.Named(logging.DNS), |
||||
} |
||||
} |
||||
|
||||
// handle is used to process DNS queries for externally configured servers
|
||||
func (r *recursor) handle(req *dns.Msg, cfgCtx *RouterDynamicConfig, remoteAddr net.Addr) (*dns.Msg, error) { |
||||
q := req.Question[0] |
||||
|
||||
network := "udp" |
||||
defer func(s time.Time) { |
||||
r.logger.Debug("request served from client", |
||||
"question", q, |
||||
"network", network, |
||||
"latency", time.Since(s).String(), |
||||
"client", remoteAddr.String(), |
||||
"client_network", remoteAddr.Network(), |
||||
) |
||||
}(time.Now()) |
||||
|
||||
// Switch to TCP if the client is
|
||||
if _, ok := remoteAddr.(*net.TCPAddr); ok { |
||||
network = "tcp" |
||||
} |
||||
|
||||
// Recursively resolve
|
||||
c := &dns.Client{Net: network, Timeout: cfgCtx.RecursorTimeout} |
||||
var resp *dns.Msg |
||||
var rtt time.Duration |
||||
var err error |
||||
for _, idx := range cfgCtx.RecursorStrategy.Indexes(len(cfgCtx.Recursors)) { |
||||
recurseAddr := cfgCtx.Recursors[idx] |
||||
resp, rtt, err = c.Exchange(req, recurseAddr) |
||||
// Check if the response is valid and has the desired Response code
|
||||
if resp != nil && (resp.Rcode != dns.RcodeSuccess && resp.Rcode != dns.RcodeNameError) { |
||||
r.logger.Debug("recurse failed for question", |
||||
"question", q, |
||||
"rtt", rtt, |
||||
"recursor", recurseAddr, |
||||
"rcode", dns.RcodeToString[resp.Rcode], |
||||
) |
||||
// If we still have recursors to forward the query to,
|
||||
// we move forward onto the next one else the loop ends
|
||||
continue |
||||
} else if err == nil || (resp != nil && resp.Truncated) { |
||||
// Compress the response; we don't know if the incoming
|
||||
// response was compressed or not, so by not compressing
|
||||
// we might generate an invalid packet on the way out.
|
||||
resp.Compress = !cfgCtx.DisableCompression |
||||
|
||||
// Forward the response
|
||||
r.logger.Debug("recurse succeeded for question", |
||||
"question", q, |
||||
"rtt", rtt, |
||||
"recursor", recurseAddr, |
||||
) |
||||
return resp, nil |
||||
} |
||||
r.logger.Error("recurse failed", "error", err) |
||||
} |
||||
|
||||
// If all resolvers fail, return a SERVFAIL message
|
||||
r.logger.Error("all resolvers failed for question from client", |
||||
"question", q, |
||||
"client", remoteAddr.String(), |
||||
"client_network", remoteAddr.Network(), |
||||
) |
||||
|
||||
return nil, errRecursionFailed |
||||
} |
||||
|
||||
// formatRecursorAddress is used to add a port to the recursor if omitted.
|
||||
func formatRecursorAddress(recursor string) (string, error) { |
||||
_, _, err := net.SplitHostPort(recursor) |
||||
var ae *net.AddrError |
||||
if errors.As(err, &ae) { |
||||
switch ae.Err { |
||||
case "missing port in address": |
||||
recursor = ipaddr.FormatAddressPort(recursor, 53) |
||||
case "too many colons in address": |
||||
if ip := net.ParseIP(recursor); ip != nil && ip.To4() == nil { |
||||
recursor = ipaddr.FormatAddressPort(recursor, 53) |
||||
break |
||||
} |
||||
fallthrough |
||||
default: |
||||
return "", err |
||||
} |
||||
} else if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
// Get the address
|
||||
addr, err := net.ResolveTCPAddr("tcp", recursor) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
// Return string
|
||||
return addr.String(), nil |
||||
} |
@ -0,0 +1,39 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package dns |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
// Test_handle cases are covered by the integration tests in agent/dns_test.go.
|
||||
// They should be moved here when the V1 DNS server is deprecated.
|
||||
//func Test_handle(t *testing.T) {
|
||||
|
||||
func Test_formatRecursorAddress(t *testing.T) { |
||||
t.Parallel() |
||||
addr, err := formatRecursorAddress("8.8.8.8") |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
if addr != "8.8.8.8:53" { |
||||
t.Fatalf("bad: %v", addr) |
||||
} |
||||
addr, err = formatRecursorAddress("2001:4860:4860::8888") |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
if addr != "[2001:4860:4860::8888]:53" { |
||||
t.Fatalf("bad: %v", addr) |
||||
} |
||||
_, err = formatRecursorAddress("1.2.3.4::53") |
||||
if err == nil || !strings.Contains(err.Error(), "too many colons in address") { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
_, err = formatRecursorAddress("2001:4860:4860::8888:::53") |
||||
if err == nil || !strings.Contains(err.Error(), "too many colons in address") { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
} |
Loading…
Reference in new issue