Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dns): Support alt domains #5940

Merged
merged 15 commits into from
Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agent/config/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
DNSARecordLimit: b.intVal(c.DNS.ARecordLimit),
DNSDisableCompression: b.boolVal(c.DNS.DisableCompression),
DNSDomain: b.stringVal(c.DNSDomain),
DNSAltDomain: b.stringVal(c.DNSAltDomain),
DNSEnableTruncate: b.boolVal(c.DNS.EnableTruncate),
DNSMaxStale: b.durationVal("dns_config.max_stale", c.DNS.MaxStale),
DNSNodeTTL: b.durationVal("dns_config.node_ttl", c.DNS.NodeTTL),
Expand Down
1 change: 1 addition & 0 deletions agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ type Config struct {
Connect Connect `json:"connect,omitempty" hcl:"connect" mapstructure:"connect"`
DNS DNS `json:"dns_config,omitempty" hcl:"dns_config" mapstructure:"dns_config"`
DNSDomain *string `json:"domain,omitempty" hcl:"domain" mapstructure:"domain"`
DNSAltDomain *string `json:"alt_domain,omitempty" hcl:"alt_domain" mapstructure:"alt_domain"`
DNSRecursors []string `json:"recursors,omitempty" hcl:"recursors" mapstructure:"recursors"`
DataDir *string `json:"data_dir,omitempty" hcl:"data_dir" mapstructure:"data_dir"`
Datacenter *string `json:"datacenter,omitempty" hcl:"datacenter" mapstructure:"datacenter"`
Expand Down
1 change: 1 addition & 0 deletions agent/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func AddFlags(fs *flag.FlagSet, f *Flags) {
add(&f.Config.DisableKeyringFile, "disable-keyring-file", "Disables the backing up of the keyring to a file.")
add(&f.Config.Ports.DNS, "dns-port", "DNS port to use.")
add(&f.Config.DNSDomain, "domain", "Domain to use for DNS interface.")
add(&f.Config.DNSAltDomain, "alt-domain", "Alternate domain to use for DNS interface.")
add(&f.Config.EnableScriptChecks, "enable-script-checks", "Enables health check scripts.")
add(&f.Config.EnableLocalScriptChecks, "enable-local-script-checks", "Enables health check scripts from configuration file.")
add(&f.Config.HTTPConfig.AllowWriteHTTPFrom, "allow-write-http-from", "Only allow write endpoint calls from given network. CIDR format, can be specified multiple times.")
Expand Down
8 changes: 8 additions & 0 deletions agent/config/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,14 @@ type RuntimeConfig struct {
// flag: -domain string
DNSDomain string

// DNSAltDomain can be set to support resolution on an additional
// consul domain. Should end with a dot.
// If left blank, only the primary domain will be used.
//
// hcl: alt_domain = string
// flag: -alt-domain string
DNSAltDomain string

// DNSEnableTruncate is used to enable setting the truncate
// flag for UDP DNS queries. This allows unmodified
// clients to re-query the consul server using TCP
Expand Down
45 changes: 32 additions & 13 deletions agent/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ type dnsConfig struct {
// service discovery endpoints using a DNS interface.
type DNSServer struct {
*dns.Server
agent *Agent
mux *dns.ServeMux
domain string
logger *log.Logger
agent *Agent
mux *dns.ServeMux
domain string
altDomain string
logger *log.Logger

// config stores the config as an atomic value (for hot-reloading). It is always of type *dnsConfig
config atomic.Value
Expand All @@ -92,13 +93,15 @@ type DNSServer struct {
}

func NewDNSServer(a *Agent) (*DNSServer, error) {
// Make sure domain is FQDN, make it case insensitive for ServeMux
// Make sure domains are FQDN, make them case insensitive for ServeMux
domain := dns.Fqdn(strings.ToLower(a.config.DNSDomain))
altDomain := dns.Fqdn(strings.ToLower(a.config.DNSAltDomain))

srv := &DNSServer{
agent: a,
domain: domain,
logger: a.logger,
agent: a,
domain: domain,
altDomain: altDomain,
logger: a.logger,
}
cfg, err := GetDNSConfig(a.config)
if err != nil {
Expand Down Expand Up @@ -183,6 +186,9 @@ func (d *DNSServer) ListenAndServe(network, addr string, notif func()) error {
d.mux = dns.NewServeMux()
d.mux.HandleFunc("arpa.", d.handlePtr)
d.mux.HandleFunc(d.domain, d.handleQuery)
if d.altDomain != "" {
d.mux.HandleFunc(d.altDomain, d.handleQuery)
}
d.toggleRecursorHandlerFromConfig(cfg)

d.Server = &dns.Server{
Expand Down Expand Up @@ -529,8 +535,7 @@ func (d *DNSServer) doDispatch(network string, remoteAddr net.Addr, req, resp *d
datacenter := d.agent.config.Datacenter

// Get the QName without the domain suffix
qName := strings.ToLower(dns.Fqdn(req.Question[0].Name))
qName = strings.TrimSuffix(qName, d.domain)
akshayganeshen marked this conversation as resolved.
Show resolved Hide resolved
qName := d.trimDomain(dns.Fqdn(req.Question[0].Name))
akshayganeshen marked this conversation as resolved.
Show resolved Hide resolved

// Split into the label parts
labels := dns.SplitDomainName(qName)
Expand Down Expand Up @@ -684,6 +689,20 @@ INVALID:
return
}

func (d *DNSServer) trimDomain(query string) string {
longer := d.domain
shorter := d.altDomain

if len(shorter) > len(longer) {
longer, shorter = shorter, longer
}

if strings.HasSuffix(query, longer) {
return strings.TrimSuffix(query, longer)
}
return strings.TrimSuffix(query, shorter)
}

// nodeLookup is used to handle a node query
func (d *DNSServer) nodeLookup(cfg *dnsConfig, network, datacenter, node string, req, resp *dns.Msg, maxRecursionLevel int) {
// Only handle ANY, A, AAAA, and TXT type requests
Expand Down Expand Up @@ -1602,10 +1621,10 @@ func (d *DNSServer) handleRecurse(resp dns.ResponseWriter, req *dns.Msg) {
// resolveCNAME is used to recursively resolve CNAME records
func (d *DNSServer) resolveCNAME(cfg *dnsConfig, name string, maxRecursionLevel int) []dns.RR {
// If the CNAME record points to a Consul address, resolve it internally
// Convert query to lowercase because DNS is case insensitive; d.domain is
// already converted
// Convert query to lowercase because DNS is case insensitive; d.domain and
// d.altDomain are already converted

if strings.HasSuffix(strings.ToLower(name), "."+d.domain) {
akshayganeshen marked this conversation as resolved.
Show resolved Hide resolved
if ln := strings.ToLower(name); strings.HasSuffix(ln, "."+d.domain) || strings.HasSuffix(ln, "."+d.altDomain) {
if maxRecursionLevel < 1 {
d.logger.Printf("[ERR] dns: Infinite recursion detected for %s, won't perform any CNAME resolution.", name)
return nil
Expand Down
158 changes: 158 additions & 0 deletions agent/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5541,6 +5541,164 @@ func TestDNS_NonExistingLookupEmptyAorAAAA(t *testing.T) {
}
}

func TestDNS_AltDomains_Service(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), `
alt_domain = "test-domain."
`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")

// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "test-node",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"master"},
Port: 12345,
},
}

var out struct{}
if err := a.RPC("Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}

questions := []string{
"db.service.consul.",
"db.service.test-domain.",
"db.service.dc1.consul.",
"db.service.dc1.test-domain.",
}

for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)

c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}

if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}

srvRec, ok := in.Answer[0].(*dns.SRV)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}
if srvRec.Port != 12345 {
t.Fatalf("Bad: %#v", srvRec)
}
if srvRec.Target != "test-node.node.dc1.consul." {
t.Fatalf("Bad: %#v", srvRec)
}

aRec, ok := in.Extra[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.Hdr.Name != "test-node.node.dc1.consul." {
t.Fatalf("Bad: %#v", in.Extra[0])
}
if aRec.A.String() != "127.0.0.1" {
t.Fatalf("Bad: %#v", in.Extra[0])
}
}
}

func TestDNS_AltDomains_SOA(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), `
node_name = "test-node"
alt_domain = "test-domain."
`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")

questions := []string{
"test-node.node.consul.",
"test-node.node.test-domain.",
}

for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSOA)

c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}

if len(in.Answer) != 1 {
t.Fatalf("Bad: %#v", in)
}

soaRec, ok := in.Answer[0].(*dns.SOA)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}

if got, want := soaRec.Hdr.Name, "consul."; got != want {
t.Fatalf("SOA name invalid, got %q want %q", got, want)
}
if got, want := soaRec.Ns, "ns.consul."; got != want {
t.Fatalf("SOA ns invalid, got %q want %q", got, want)
}
}
}

func TestDNS_AltDomains_Overlap(t *testing.T) {
// this tests the domain matching logic in DNSServer when encountering more
// than one potential match (i.e. ambiguous match)
// it should select the longer matching domain when dispatching
t.Parallel()
a := NewTestAgent(t, t.Name(), `
node_name = "test-node"
alt_domain = "node.consul."
`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")

questions := []string{
"test-node.node.consul.",
"test-node.node.node.consul.",
"test-node.node.dc1.consul.",
"test-node.node.node.dc1.consul.",
}

for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeA)

c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
if err != nil {
t.Fatalf("err: %v", err)
}

if len(in.Answer) != 1 {
t.Fatalf("failed to resolve ambiguous alt domain %q: %#v", question, in)
}

aRec, ok := in.Answer[0].(*dns.A)
if !ok {
t.Fatalf("Bad: %#v", in.Answer[0])
}

if got, want := aRec.A.To4().String(), "127.0.0.1"; got != want {
t.Fatalf("A ip invalid, got %v want %v", got, want)
}
}
}

func TestDNS_PreparedQuery_AllowStale(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), `
Expand Down