diff --git a/agent/config/builder.go b/agent/config/builder.go
index 812b513e3b..da5f4fb2b6 100644
--- a/agent/config/builder.go
+++ b/agent/config/builder.go
@@ -387,6 +387,26 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
}
}
+ // expand dns recursors
+ uniq := map[string]bool{}
+ dnsRecursors := []string{}
+ for _, r := range c.DNSRecursors {
+ x, err := template.Parse(r)
+ if err != nil {
+ return RuntimeConfig{}, fmt.Errorf("Invalid DNS recursor template %q: %s", r, err)
+ }
+ for _, addr := range strings.Fields(x) {
+ if strings.HasPrefix(addr, "unix://") {
+ return RuntimeConfig{}, fmt.Errorf("DNS Recursors cannot be unix sockets: %s", addr)
+ }
+ if uniq[addr] {
+ continue
+ }
+ uniq[addr] = true
+ dnsRecursors = append(dnsRecursors, addr)
+ }
+ }
+
// Create the default set of tagged addresses.
if c.TaggedAddresses == nil {
c.TaggedAddresses = make(map[string]string)
@@ -525,7 +545,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
DNSOnlyPassing: b.boolVal(c.DNS.OnlyPassing),
DNSPort: dnsPort,
DNSRecursorTimeout: b.durationVal("recursor_timeout", c.DNS.RecursorTimeout),
- DNSRecursors: c.DNSRecursors,
+ DNSRecursors: dnsRecursors,
DNSServiceTTL: dnsServiceTTL,
DNSUDPAnswerLimit: b.intVal(c.DNS.UDPAnswerLimit),
@@ -706,6 +726,11 @@ func (b *Builder) Validate(rt RuntimeConfig) error {
return fmt.Errorf("DNS address cannot be a unix socket")
}
}
+ for _, a := range rt.DNSRecursors {
+ if ipaddr.IsAny(a) {
+ return fmt.Errorf("DNS recursor address cannot be 0.0.0.0, :: or [::]")
+ }
+ }
if rt.Bootstrap && !rt.ServerMode {
return fmt.Errorf("'bootstrap = true' requires 'server = true'")
}
diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go
index 3076d97fc6..0f5d4ca7f9 100644
--- a/agent/config/runtime_test.go
+++ b/agent/config/runtime_test.go
@@ -445,12 +445,12 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
{
desc: "-recursor",
flags: []string{
- `-recursor=a`,
- `-recursor=b`,
+ `-recursor=1.2.3.4`,
+ `-recursor=5.6.7.8`,
`-data-dir=` + dataDir,
},
patch: func(rt *RuntimeConfig) {
- rt.DNSRecursors = []string{"a", "b"}
+ rt.DNSRecursors = []string{"1.2.3.4", "5.6.7.8"}
rt.DataDir = dataDir
},
},
@@ -988,6 +988,16 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
rt.DataDir = dataDir
},
},
+ {
+ desc: "dns recursor templates with deduplication",
+ flags: []string{`-data-dir=` + dataDir},
+ json: []string{`{ "recursors": [ "{{ printf \"5.6.7.8:9999\" }}", "{{ printf \"1.2.3.4\" }}", "{{ printf \"5.6.7.8:9999\" }}" ] }`},
+ hcl: []string{`recursors = [ "{{ printf \"5.6.7.8:9999\" }}", "{{ printf \"1.2.3.4\" }}", "{{ printf \"5.6.7.8:9999\" }}" ] `},
+ patch: func(rt *RuntimeConfig) {
+ rt.DNSRecursors = []string{"5.6.7.8:9999", "1.2.3.4"}
+ rt.DataDir = dataDir
+ },
+ },
// ------------------------------------------------------------
// precedence rules
@@ -1047,7 +1057,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
"bootstrap_expect": 3,
"datacenter":"a",
"node_meta": {"a":"b"},
- "recursors":["a", "b"],
+ "recursors":["1.2.3.5", "5.6.7.9"],
"serf_lan": "a",
"serf_wan": "a",
"start_join":["a", "b"]
@@ -1061,7 +1071,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
bootstrap_expect = 3
datacenter = "a"
node_meta = { "a" = "b" }
- recursors = ["a", "b"]
+ recursors = ["1.2.3.5", "5.6.7.9"]
serf_lan = "a"
serf_wan = "a"
start_join = ["a", "b"]
@@ -1076,7 +1086,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
`-data-dir=` + dataDir,
`-join`, `c`, `-join=d`,
`-node-meta=c:d`,
- `-recursor`, `c`, `-recursor=d`,
+ `-recursor`, `1.2.3.6`, `-recursor=5.6.7.10`,
`-serf-lan-bind=3.3.3.3`,
`-serf-wan-bind=4.4.4.4`,
},
@@ -1087,7 +1097,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
rt.SerfAdvertiseAddrLAN = tcpAddr("1.1.1.1:8301")
rt.SerfAdvertiseAddrWAN = tcpAddr("2.2.2.2:8302")
rt.Datacenter = "b"
- rt.DNSRecursors = []string{"c", "d", "a", "b"}
+ rt.DNSRecursors = []string{"1.2.3.6", "5.6.7.10", "1.2.3.5", "5.6.7.9"}
rt.NodeMeta = map[string]string{"c": "d"}
rt.SerfBindAddrLAN = tcpAddr("3.3.3.3:8301")
rt.SerfBindAddrWAN = tcpAddr("4.4.4.4:8302")
@@ -1453,6 +1463,15 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
hcl: []string{`advertise_addr_wan = "::"`},
err: "Advertise WAN address cannot be 0.0.0.0, :: or [::]",
},
+ {
+ desc: "recursors any",
+ flags: []string{
+ `-data-dir=` + dataDir,
+ },
+ json: []string{`{ "recursors": ["::"] }`},
+ hcl: []string{`recursors = ["::"]`},
+ err: "DNS recursor address cannot be 0.0.0.0, :: or [::]",
+ },
{
desc: "dns_config.udp_answer_limit invalid",
flags: []string{
@@ -2175,7 +2194,7 @@ func TestFullConfig(t *testing.T) {
"raft_protocol": 19016,
"reconnect_timeout": "23739s",
"reconnect_timeout_wan": "26694s",
- "recursors": [ "FtFhoUHl", "UYkwck1k" ],
+ "recursors": [ "63.38.39.58", "92.49.18.18" ],
"rejoin_after_leave": true,
"retry_interval": "8067s",
"retry_interval_wan": "28866s",
@@ -2609,7 +2628,7 @@ func TestFullConfig(t *testing.T) {
raft_protocol = 19016
reconnect_timeout = "23739s"
reconnect_timeout_wan = "26694s"
- recursors = [ "FtFhoUHl", "UYkwck1k" ]
+ recursors = [ "63.38.39.58", "92.49.18.18" ]
rejoin_after_leave = true
retry_interval = "8067s"
retry_interval_wan = "28866s"
@@ -3122,7 +3141,7 @@ func TestFullConfig(t *testing.T) {
DNSOnlyPassing: true,
DNSPort: 7001,
DNSRecursorTimeout: 4427 * time.Second,
- DNSRecursors: []string{"FtFhoUHl", "UYkwck1k"},
+ DNSRecursors: []string{"63.38.39.58", "92.49.18.18"},
DNSServiceTTL: map[string]time.Duration{"*": 32030 * time.Second},
DNSUDPAnswerLimit: 29909,
DataDir: dataDir,
diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md
index 5b5bd88e95..7b8b5d56ca 100644
--- a/website/source/docs/agent/options.html.md
+++ b/website/source/docs/agent/options.html.md
@@ -1036,7 +1036,9 @@ 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
- outside of the "consul." domain, the query will be resolved upstream.
+ outside of the "consul." domain, the query will be resolved upstream. As of Consul 1.0.1 recursors
+ can be provided as ip addresses or as go-sockaddr templates. IP addresses are resolved in order,
+ and duplicates are ignored.
* `rejoin_after_leave` Equivalent
to the [`-rejoin` command-line flag](#_rejoin).