diff --git a/command/agent/config.go b/command/agent/config.go
index 31d546e4e..6dd65fa23 100644
--- a/command/agent/config.go
+++ b/command/agent/config.go
@@ -109,6 +109,13 @@ type DNSConfig struct {
// compressed. In Consul 0.7 this was turned on by default and this
// config was added as an opt-out.
DisableCompression bool `mapstructure:"disable_compression"`
+
+ // RecursorTimeout specifies the timeout in seconds
+ // for Consul's internal dns client used for recursion.
+ // This value is used for the connection, read and write timeout.
+ // Default: 2s
+ RecursorTimeout time.Duration `mapstructure:"-"`
+ RecursorTimeoutRaw string `mapstructure:"recursor_timeout" json:"-"`
}
// Performance is used to tune the performance of Consul's subsystems.
@@ -645,9 +652,10 @@ func DefaultConfig() *Config {
Server: 8300,
},
DNSConfig: DNSConfig{
- AllowStale: Bool(true),
- UDPAnswerLimit: 3,
- MaxStale: 5 * time.Second,
+ AllowStale: Bool(true),
+ UDPAnswerLimit: 3,
+ MaxStale: 5 * time.Second,
+ RecursorTimeout: 2 * time.Second,
},
Telemetry: Telemetry{
StatsitePrefix: "consul",
@@ -840,6 +848,14 @@ func DecodeConfig(r io.Reader) (*Config, error) {
result.DNSConfig.MaxStale = dur
}
+ if raw := result.DNSConfig.RecursorTimeoutRaw; raw != "" {
+ dur, err := time.ParseDuration(raw)
+ if err != nil {
+ return nil, fmt.Errorf("RecursorTimeout invalid: %v", err)
+ }
+ result.DNSConfig.RecursorTimeout = dur
+ }
+
if len(result.DNSConfig.ServiceTTLRaw) != 0 {
if result.DNSConfig.ServiceTTL == nil {
result.DNSConfig.ServiceTTL = make(map[string]time.Duration)
@@ -1355,6 +1371,9 @@ func MergeConfig(a, b *Config) *Config {
if b.DNSConfig.DisableCompression {
result.DNSConfig.DisableCompression = true
}
+ if b.DNSConfig.RecursorTimeout != 0 {
+ result.DNSConfig.RecursorTimeout = b.DNSConfig.RecursorTimeout
+ }
if b.CheckUpdateIntervalRaw != "" || b.CheckUpdateInterval != 0 {
result.CheckUpdateInterval = b.CheckUpdateInterval
}
diff --git a/command/agent/config_test.go b/command/agent/config_test.go
index 0937697bf..27b733ee3 100644
--- a/command/agent/config_test.go
+++ b/command/agent/config_test.go
@@ -544,7 +544,7 @@ func TestDecodeConfig(t *testing.T) {
}
// DNS node ttl, max stale
- input = `{"dns_config": {"allow_stale": false, "enable_truncate": false, "max_stale": "15s", "node_ttl": "5s", "only_passing": true, "udp_answer_limit": 6}}`
+ input = `{"dns_config": {"allow_stale": false, "enable_truncate": false, "max_stale": "15s", "node_ttl": "5s", "only_passing": true, "udp_answer_limit": 6, "recursor_timeout": "7s"}}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
t.Fatalf("err: %s", err)
@@ -568,6 +568,9 @@ func TestDecodeConfig(t *testing.T) {
if config.DNSConfig.UDPAnswerLimit != 6 {
t.Fatalf("bad: %#v", config)
}
+ if config.DNSConfig.RecursorTimeout != 7*time.Second {
+ t.Fatalf("bad: %#v", config)
+ }
// DNS service ttl
input = `{"dns_config": {"service_ttl": {"*": "1s", "api": "10s", "web": "30s"}}}`
@@ -1416,7 +1419,8 @@ func TestMergeConfig(t *testing.T) {
ServiceTTL: map[string]time.Duration{
"api": 10 * time.Second,
},
- UDPAnswerLimit: 4,
+ UDPAnswerLimit: 4,
+ RecursorTimeout: 30 * time.Second,
},
Domain: "other",
LogLevel: "info",
diff --git a/command/agent/dns.go b/command/agent/dns.go
index c859f9a27..a0705ad7d 100644
--- a/command/agent/dns.go
+++ b/command/agent/dns.go
@@ -842,7 +842,7 @@ func (d *DNSServer) handleRecurse(resp dns.ResponseWriter, req *dns.Msg) {
}
// Recursively resolve
- c := &dns.Client{Net: network}
+ c := &dns.Client{Net: network, Timeout: d.config.RecursorTimeout}
var r *dns.Msg
var rtt time.Duration
var err error
@@ -887,7 +887,7 @@ func (d *DNSServer) resolveCNAME(name string) []dns.RR {
m.SetQuestion(name, dns.TypeA)
// Make a DNS lookup request
- c := &dns.Client{Net: "udp"}
+ c := &dns.Client{Net: "udp", Timeout: d.config.RecursorTimeout}
var r *dns.Msg
var rtt time.Duration
var err error
diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go
index f686fc922..2c4011235 100644
--- a/command/agent/dns_test.go
+++ b/command/agent/dns_test.go
@@ -1400,6 +1400,47 @@ func TestDNS_Recurse(t *testing.T) {
}
}
+func TestDNS_RecursorTimeout(t *testing.T) {
+ serverClientTimeout := 3 * time.Second
+ testClientTimeout := serverClientTimeout + 5*time.Second
+
+ dir, srv := makeDNSServerConfig(t, func(c *Config) {
+ c.DNSRecursor = "10.255.255.1" // host must cause a connection|read|write timeout
+ }, func(c *DNSConfig) {
+ c.RecursorTimeout = serverClientTimeout
+ })
+ defer os.RemoveAll(dir)
+ defer srv.agent.Shutdown()
+
+ m := new(dns.Msg)
+ m.SetQuestion("apple.com.", dns.TypeANY)
+
+ // This client calling the server under test must have a longer timeout than the one we set internally
+ c := &dns.Client{Timeout: testClientTimeout}
+ addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS)
+
+ start := time.Now()
+ in, _, err := c.Exchange(m, addr.String())
+
+ duration := time.Now().Sub(start)
+
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ if len(in.Answer) != 0 {
+ t.Fatalf("Bad: %#v", in)
+ }
+ if in.Rcode != dns.RcodeServerFailure {
+ t.Fatalf("Bad: %#v", in)
+ }
+
+ if duration < serverClientTimeout {
+ t.Fatalf("Expected the call to return after at least %f seconds but lasted only %f", serverClientTimeout.Seconds(), duration.Seconds())
+ }
+
+}
+
func TestDNS_ServiceLookup_FilterCritical(t *testing.T) {
dir, srv := makeDNSServer(t)
defer os.RemoveAll(dir)
diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown
index 6289a1134..13e5b1072 100644
--- a/website/source/docs/agent/options.html.markdown
+++ b/website/source/docs/agent/options.html.markdown
@@ -517,6 +517,10 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
are considered. For example, if a node has a health check that is critical then all services on
that node will be excluded because they are also considered critical.
+ * `recursor_timeout` Timeout used
+ by Consul when recursively querying an upstream DNS server. See `recursors`
+ for more details. Default is 2s.
+
* `disable_compression` If
set to true, DNS responses will not be compressed. Compression was added and enabled by default
in Consul 0.7.
@@ -636,7 +640,7 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
* `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
+ 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.
* `rejoin_after_leave` Equivalent