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

Add multiple recursor definition support #448

Merged
merged 1 commit into from
Nov 3, 2014
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion command/agent/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log
}

server, err := NewDNSServer(agent, &config.DNSConfig, logOutput,
config.Domain, dnsAddr.String(), config.DNSRecursor)
config.Domain, dnsAddr.String(), config.DNSRecursors)
if err != nil {
agent.Shutdown()
c.Ui.Error(fmt.Sprintf("Error starting dns server: %s", err))
Expand Down
13 changes: 8 additions & 5 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ type Config struct {
// DataDir is the directory to store our state in
DataDir string `mapstructure:"data_dir"`

// DNSRecursor can be set to allow the DNS server to recursively
// DNSRecursors can be set to allow the DNS servers to recursively
// resolve non-consul domains
DNSRecursor string `mapstructure:"recursor"`
DNSRecursors []string `mapstructure:"recursors"`

// DNS configuration
DNSConfig DNSConfig `mapstructure:"dns_config"`
Expand Down Expand Up @@ -623,9 +623,12 @@ func MergeConfig(a, b *Config) *Config {
if b.DataDir != "" {
result.DataDir = b.DataDir
}
if b.DNSRecursor != "" {
result.DNSRecursor = b.DNSRecursor
}

// Copy the dns recursors
result.DNSRecursors = make([]string, 0, len(a.DNSRecursors)+len(b.DNSRecursors))
result.DNSRecursors = append(result.DNSRecursors, a.DNSRecursors...)
result.DNSRecursors = append(result.DNSRecursors, b.DNSRecursors...)

if b.Domain != "" {
result.Domain = b.Domain
}
Expand Down
13 changes: 9 additions & 4 deletions command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestDecodeConfig(t *testing.T) {
}

// DNS setup
input = `{"ports": {"dns": 8500}, "recursor": "8.8.8.8", "domain": "foobar"}`
input = `{"ports": {"dns": 8500}, "recursor": ["8.8.8.8","8.8.4.4"], "domain": "foobar"}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
t.Fatalf("err: %s", err)
Expand All @@ -119,7 +119,13 @@ func TestDecodeConfig(t *testing.T) {
t.Fatalf("bad: %#v", config)
}

if config.DNSRecursor != "8.8.8.8" {
if len(config.DNSRecursors) != 2 {
t.Fatalf("bad: %#v", config)
}
if config.DNSRecursors[0] != "8.8.8.8" {
t.Fatalf("bad: %#v", config)
}
if config.DNSRecursors[1] != "8.8.4.4" {
t.Fatalf("bad: %#v", config)
}

Expand Down Expand Up @@ -791,7 +797,6 @@ func TestMergeConfig(t *testing.T) {
BootstrapExpect: 0,
Datacenter: "dc1",
DataDir: "/tmp/foo",
DNSRecursor: "127.0.0.1:1001",
Domain: "basic",
LogLevel: "debug",
NodeName: "foo",
Expand All @@ -811,7 +816,7 @@ func TestMergeConfig(t *testing.T) {
BootstrapExpect: 3,
Datacenter: "dc2",
DataDir: "/tmp/bar",
DNSRecursor: "127.0.0.2:1001",
DNSRecursors: []string{"127.0.0.2:1001"},
DNSConfig: DNSConfig{
NodeTTL: 10 * time.Second,
ServiceTTL: map[string]time.Duration{
Expand Down
82 changes: 50 additions & 32 deletions command/agent/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ type DNSServer struct {
dnsServer *dns.Server
dnsServerTCP *dns.Server
domain string
recursor string
recursors []string
logger *log.Logger
}

// NewDNSServer starts a new DNS server to provide an agent interface
func NewDNSServer(agent *Agent, config *DNSConfig, logOutput io.Writer, domain, bind, recursor string) (*DNSServer, error) {
func NewDNSServer(agent *Agent, config *DNSConfig, logOutput io.Writer, domain string, bind string, recursors []string) (*DNSServer, error) {
// Make sure domain is FQDN
domain = dns.Fqdn(domain)

Expand Down Expand Up @@ -61,7 +61,7 @@ func NewDNSServer(agent *Agent, config *DNSConfig, logOutput io.Writer, domain,
dnsServer: server,
dnsServerTCP: serverTCP,
domain: domain,
recursor: recursor,
recursors: recursors,
logger: log.New(logOutput, "", log.LstdFlags),
}

Expand All @@ -70,12 +70,19 @@ func NewDNSServer(agent *Agent, config *DNSConfig, logOutput io.Writer, domain,
if domain != consulDomain {
mux.HandleFunc(consulDomain, srv.handleTest)
}
if recursor != "" {
recursor, err := recursorAddr(recursor)
if err != nil {
return nil, fmt.Errorf("Invalid recursor address: %v", err)
if len(recursors) > 0 {
validatedRecursors := []string{}

for _, recursor := range recursors {
recursor, err := recursorAddr(recursor)
if err != nil {
return nil, fmt.Errorf("Invalid recursor address: %v", err)
}

validatedRecursors = append(validatedRecursors, recursor)
}
srv.recursor = recursor

srv.recursors = validatedRecursors
mux.HandleFunc(".", srv.handleRecurse)
}

Expand Down Expand Up @@ -178,7 +185,7 @@ func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
m.Authoritative = true
m.RecursionAvailable = (d.recursor != "")
m.RecursionAvailable = (len(d.recursors) > 0)

// Only add the SOA if requested
if req.Question[0].Qtype == dns.TypeSOA {
Expand Down Expand Up @@ -587,30 +594,34 @@ func (d *DNSServer) handleRecurse(resp dns.ResponseWriter, req *dns.Msg) {

// Recursively resolve
c := &dns.Client{Net: network}
r, rtt, err := c.Exchange(req, d.recursor)
for i,recursor := range d.recursors {
r, rtt, err := c.Exchange(req, recursor)

// On failure, return a SERVFAIL message
if err != nil {
d.logger.Printf("[ERR] dns: recurse failed: %v", err)
m := &dns.Msg{}
m.SetReply(req)
m.RecursionAvailable = true
m.SetRcode(req, dns.RcodeServerFailure)
resp.WriteMsg(m)
return
}
d.logger.Printf("[DEBUG] dns: recurse RTT for %v (%v)", q, rtt)
if i < len(d.recursors) && err != nil {
continue
} else if err != nil {
// On all of failure, return a SERVFAIL message
d.logger.Printf("[ERR] dns: recurse failed: %v", err)
m := &dns.Msg{}
m.SetReply(req)
m.RecursionAvailable = true
m.SetRcode(req, dns.RcodeServerFailure)
resp.WriteMsg(m)
return
}
d.logger.Printf("[DEBUG] dns: recurse RTT for %v (%v)", q, rtt)

// Forward the response
if err := resp.WriteMsg(r); err != nil {
d.logger.Printf("[WARN] dns: failed to respond: %v", err)
// Forward the response
if err := resp.WriteMsg(r); err != nil {
d.logger.Printf("[WARN] dns: failed to respond: %v", err)
}
}
}

// resolveCNAME is used to recursively resolve CNAME records
func (d *DNSServer) resolveCNAME(name string) []dns.RR {
// Do nothing if we don't have a recursor
if d.recursor == "" {
if len(d.recursors) > 0 {
return nil
}

Expand All @@ -620,13 +631,20 @@ func (d *DNSServer) resolveCNAME(name string) []dns.RR {

// Make a DNS lookup request
c := &dns.Client{Net: "udp"}
r, rtt, err := c.Exchange(m, d.recursor)
if err != nil {
d.logger.Printf("[ERR] dns: cname recurse failed: %v", err)
return nil
for i,recursor := range d.recursors {
r, rtt, err := c.Exchange(m, recursor)

if i < len(d.recursors) && err != nil {
continue
} else if err != nil {
d.logger.Printf("[ERR] dns: cname recurse failed: %v", err)
return nil
}
d.logger.Printf("[DEBUG] dns: cname recurse RTT for %v (%v)", name, rtt)

// Return all the answers
return r.Answer
}
d.logger.Printf("[DEBUG] dns: cname recurse RTT for %v (%v)", name, rtt)

// Return all the answers
return r.Answer
return nil
}
2 changes: 1 addition & 1 deletion command/agent/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func makeDNSServerConfig(t *testing.T, config *DNSConfig) (string, *DNSServer) {
addr, _ := conf.ClientListener(conf.Addresses.DNS, conf.Ports.DNS)
dir, agent := makeAgent(t, conf)
server, err := NewDNSServer(agent, config, agent.logOutput,
conf.Domain, addr.String(), "8.8.8.8:53")
conf.Domain, addr.String(), []string{"8.8.8.8:53"})
if err != nil {
t.Fatalf("err: %v", err)
}
Expand Down
4 changes: 2 additions & 2 deletions website/source/docs/agent/dns.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ provide the redis service, located in the "east-aws" datacenter,
with no failing health checks. It's that simple!

There are a number of [configuration options](/docs/agent/options.html) that
are important for the DNS interface. They are `client_addr`, `ports.dns`, `recursor`,
are important for the DNS interface. They are `client_addr`, `ports.dns`, `recursors`,
`domain`, and `dns_config`. By default Consul will listen on 127.0.0.1:8600 for DNS queries
in the "consul." domain, without support for DNS recursion. All queries are case-insensitive, a
name lookup for `PostgreSQL.node.dc1.consul` will find all nodes named `postgresql`, no matter of case.

There are a few ways to use the DNS interface. One option is to use a custom
DNS resolver library and point it at Consul. Another option is to set Consul
as the DNS server for a node, and provide a `recursor` so that non-Consul queries
as the DNS server for a node, and provide `recursors` so that non-Consul queries
can also be resolved. The last method is to forward all queries for the "consul."
domain to a Consul agent from the existing DNS server. To play with the DNS server
on the command line, dig can be used:
Expand Down
2 changes: 1 addition & 1 deletion website/source/docs/agent/http.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ It returns a JSON body like this:
"Server": true,
"Datacenter": "dc1",
"DataDir": "/tmp/consul",
"DNSRecursor": "",
"DNSRecursors": [],
"Domain": "consul.",
"LogLevel": "INFO",
"NodeName": "foobar",
Expand Down
4 changes: 2 additions & 2 deletions website/source/docs/agent/options.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,10 @@ definitions support being updated during a reload.

* `protocol` - Equivalent to the `-protocol` command-line flag.

* `recursor` - This flag provides an address of an upstream DNS server that is used to
* `recursors` - This flag provides addresses of upstream DNS servers that are used to
recursively resolve queries if they are not inside the service domain for consul. For example,
a node can use Consul directly as a DNS server, and if the record is outside of the "consul." domain,
the query will be resolved upstream using this server.
the query will be resolved upstream using their servers.

* `rejoin_after_leave` - Equivalent to the `-rejoin` command-line flag.

Expand Down