mirror of https://github.com/status-im/consul.git
Added SOA configuration for DNS settings. (#4714)
This will allow to fine TUNE SOA settings sent by Consul in DNS responses, for instance to be able to control negative ttl. Will fix: https://github.com/hashicorp/consul/issues/4713 # Example Override all settings: * min_ttl: 0 => 60s * retry: 600 (10m) => 300s (5 minutes), * expire: 86400 (24h) => 43200 (12h) * refresh: 3600 (1h) => 1800 (30 minutes) ``` consul agent -dev -hcl 'dns_config={soa={min_ttl=60,retry=300,expire=43200,refresh=1800}}' ``` Result: ``` dig +multiline @localhost -p 8600 service.consul ; <<>> DiG 9.12.1 <<>> +multiline @localhost -p 8600 service.consul ; (2 servers found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 36557 ;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;service.consul. IN A ;; AUTHORITY SECTION: consul. 0 IN SOA ns.consul. hostmaster.consul. ( 1537959133 ; serial 1800 ; refresh (30 minutes) 300 ; retry (5 minutes) 43200 ; expire (12 hours) 60 ; minimum (1 minute) ) ;; Query time: 4 msec ;; SERVER: 127.0.0.1#8600(127.0.0.1) ;; WHEN: Wed Sep 26 12:52:13 CEST 2018 ;; MSG SIZE rcvd: 93 ```
This commit is contained in:
parent
391dbcf8dd
commit
251156eb68
|
@ -301,6 +301,22 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
|
||||||
dnsServiceTTL[k] = b.durationVal(fmt.Sprintf("dns_config.service_ttl[%q]", k), &v)
|
dnsServiceTTL[k] = b.durationVal(fmt.Sprintf("dns_config.service_ttl[%q]", k), &v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
soa := RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0}
|
||||||
|
if c.DNS.SOA != nil {
|
||||||
|
if c.DNS.SOA.Expire != nil {
|
||||||
|
soa.Expire = *c.DNS.SOA.Expire
|
||||||
|
}
|
||||||
|
if c.DNS.SOA.Minttl != nil {
|
||||||
|
soa.Minttl = *c.DNS.SOA.Minttl
|
||||||
|
}
|
||||||
|
if c.DNS.SOA.Refresh != nil {
|
||||||
|
soa.Refresh = *c.DNS.SOA.Refresh
|
||||||
|
}
|
||||||
|
if c.DNS.SOA.Retry != nil {
|
||||||
|
soa.Retry = *c.DNS.SOA.Retry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
leaveOnTerm := !b.boolVal(c.ServerMode)
|
leaveOnTerm := !b.boolVal(c.ServerMode)
|
||||||
if c.LeaveOnTerm != nil {
|
if c.LeaveOnTerm != nil {
|
||||||
leaveOnTerm = b.boolVal(c.LeaveOnTerm)
|
leaveOnTerm = b.boolVal(c.LeaveOnTerm)
|
||||||
|
@ -649,6 +665,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
|
||||||
DNSRecursorTimeout: b.durationVal("recursor_timeout", c.DNS.RecursorTimeout),
|
DNSRecursorTimeout: b.durationVal("recursor_timeout", c.DNS.RecursorTimeout),
|
||||||
DNSRecursors: dnsRecursors,
|
DNSRecursors: dnsRecursors,
|
||||||
DNSServiceTTL: dnsServiceTTL,
|
DNSServiceTTL: dnsServiceTTL,
|
||||||
|
DNSSOA: soa,
|
||||||
DNSUDPAnswerLimit: b.intVal(c.DNS.UDPAnswerLimit),
|
DNSUDPAnswerLimit: b.intVal(c.DNS.UDPAnswerLimit),
|
||||||
DNSNodeMetaTXT: b.boolValWithDefault(c.DNS.NodeMetaTXT, true),
|
DNSNodeMetaTXT: b.boolValWithDefault(c.DNS.NodeMetaTXT, true),
|
||||||
|
|
||||||
|
|
|
@ -521,6 +521,14 @@ type ConnectProxyDefaults struct {
|
||||||
Config map[string]interface{} `json:"config,omitempty" hcl:"config" mapstructure:"config"`
|
Config map[string]interface{} `json:"config,omitempty" hcl:"config" mapstructure:"config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SOA is the configuration of SOA for DNS
|
||||||
|
type SOA struct {
|
||||||
|
Refresh *uint32 `json:"refresh,omitempty" hcl:"refresh" mapstructure:"refresh"`
|
||||||
|
Retry *uint32 `json:"retry,omitempty" hcl:"retry" mapstructure:"retry"`
|
||||||
|
Expire *uint32 `json:"expire,omitempty" hcl:"expire" mapstructure:"expire"`
|
||||||
|
Minttl *uint32 `json:"min_ttl,omitempty" hcl:"min_ttl" mapstructure:"min_ttl"`
|
||||||
|
}
|
||||||
|
|
||||||
type DNS struct {
|
type DNS struct {
|
||||||
AllowStale *bool `json:"allow_stale,omitempty" hcl:"allow_stale" mapstructure:"allow_stale"`
|
AllowStale *bool `json:"allow_stale,omitempty" hcl:"allow_stale" mapstructure:"allow_stale"`
|
||||||
ARecordLimit *int `json:"a_record_limit,omitempty" hcl:"a_record_limit" mapstructure:"a_record_limit"`
|
ARecordLimit *int `json:"a_record_limit,omitempty" hcl:"a_record_limit" mapstructure:"a_record_limit"`
|
||||||
|
@ -533,6 +541,7 @@ type DNS struct {
|
||||||
ServiceTTL map[string]string `json:"service_ttl,omitempty" hcl:"service_ttl" mapstructure:"service_ttl"`
|
ServiceTTL map[string]string `json:"service_ttl,omitempty" hcl:"service_ttl" mapstructure:"service_ttl"`
|
||||||
UDPAnswerLimit *int `json:"udp_answer_limit,omitempty" hcl:"udp_answer_limit" mapstructure:"udp_answer_limit"`
|
UDPAnswerLimit *int `json:"udp_answer_limit,omitempty" hcl:"udp_answer_limit" mapstructure:"udp_answer_limit"`
|
||||||
NodeMetaTXT *bool `json:"enable_additional_node_meta_txt,omitempty" hcl:"enable_additional_node_meta_txt" mapstructure:"enable_additional_node_meta_txt"`
|
NodeMetaTXT *bool `json:"enable_additional_node_meta_txt,omitempty" hcl:"enable_additional_node_meta_txt" mapstructure:"enable_additional_node_meta_txt"`
|
||||||
|
SOA *SOA `json:"soa,omitempty" hcl:"soa" mapstructure:"soa"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
|
|
|
@ -16,6 +16,13 @@ import (
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RuntimeSOAConfig struct {
|
||||||
|
Refresh uint32 // 3600 by default
|
||||||
|
Retry uint32 // 600
|
||||||
|
Expire uint32 // 86400
|
||||||
|
Minttl uint32 // 0,
|
||||||
|
}
|
||||||
|
|
||||||
// RuntimeConfig specifies the configuration the consul agent actually
|
// RuntimeConfig specifies the configuration the consul agent actually
|
||||||
// uses. Is is derived from one or more Config structures which can come
|
// uses. Is is derived from one or more Config structures which can come
|
||||||
// from files, flags and/or environment variables.
|
// from files, flags and/or environment variables.
|
||||||
|
@ -538,6 +545,10 @@ type RuntimeConfig struct {
|
||||||
// flags: -dns-port int
|
// flags: -dns-port int
|
||||||
DNSPort int
|
DNSPort int
|
||||||
|
|
||||||
|
// DNSSOA is the settings applied for DNS SOA
|
||||||
|
// hcl: soa {}
|
||||||
|
DNSSOA RuntimeSOAConfig
|
||||||
|
|
||||||
// DataDir is the path to the directory where the local state is stored.
|
// DataDir is the path to the directory where the local state is stored.
|
||||||
//
|
//
|
||||||
// hcl: data_dir = string
|
// hcl: data_dir = string
|
||||||
|
|
|
@ -4110,6 +4110,7 @@ func TestFullConfig(t *testing.T) {
|
||||||
DNSPort: 7001,
|
DNSPort: 7001,
|
||||||
DNSRecursorTimeout: 4427 * time.Second,
|
DNSRecursorTimeout: 4427 * time.Second,
|
||||||
DNSRecursors: []string{"63.38.39.58", "92.49.18.18"},
|
DNSRecursors: []string{"63.38.39.58", "92.49.18.18"},
|
||||||
|
DNSSOA: RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0},
|
||||||
DNSServiceTTL: map[string]time.Duration{"*": 32030 * time.Second},
|
DNSServiceTTL: map[string]time.Duration{"*": 32030 * time.Second},
|
||||||
DNSUDPAnswerLimit: 29909,
|
DNSUDPAnswerLimit: 29909,
|
||||||
DNSNodeMetaTXT: true,
|
DNSNodeMetaTXT: true,
|
||||||
|
@ -4754,6 +4755,7 @@ func TestSanitize(t *testing.T) {
|
||||||
&net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
|
&net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
|
||||||
&net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
|
&net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
|
||||||
},
|
},
|
||||||
|
DNSSOA: RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0},
|
||||||
HTTPAddrs: []net.Addr{
|
HTTPAddrs: []net.Addr{
|
||||||
&net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
|
&net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
|
||||||
&net.UnixAddr{Name: "/var/run/foo"},
|
&net.UnixAddr{Name: "/var/run/foo"},
|
||||||
|
@ -4894,6 +4896,12 @@ func TestSanitize(t *testing.T) {
|
||||||
"DNSRecursorTimeout": "0s",
|
"DNSRecursorTimeout": "0s",
|
||||||
"DNSRecursors": [],
|
"DNSRecursors": [],
|
||||||
"DNSServiceTTL": {},
|
"DNSServiceTTL": {},
|
||||||
|
"DNSSOA": {
|
||||||
|
"Refresh": 3600,
|
||||||
|
"Retry": 600,
|
||||||
|
"Expire": 86400,
|
||||||
|
"Minttl": 0
|
||||||
|
},
|
||||||
"DNSUDPAnswerLimit": 0,
|
"DNSUDPAnswerLimit": 0,
|
||||||
"DataDir": "",
|
"DataDir": "",
|
||||||
"Datacenter": "",
|
"Datacenter": "",
|
||||||
|
|
|
@ -940,8 +940,8 @@ func TestCatalog_ListNodes_StaleRead(t *testing.T) {
|
||||||
// Try to join
|
// Try to join
|
||||||
joinLAN(t, s2, s1)
|
joinLAN(t, s2, s1)
|
||||||
|
|
||||||
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
|
||||||
testrpc.WaitForLeader(t, s2.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, s2.RPC, "dc1")
|
||||||
|
|
||||||
// Use the follower as the client
|
// Use the follower as the client
|
||||||
var codec rpc.ClientCodec
|
var codec rpc.ClientCodec
|
||||||
|
|
32
agent/dns.go
32
agent/dns.go
|
@ -39,6 +39,13 @@ const (
|
||||||
|
|
||||||
var InvalidDnsRe = regexp.MustCompile(`[^A-Za-z0-9\\-]+`)
|
var InvalidDnsRe = regexp.MustCompile(`[^A-Za-z0-9\\-]+`)
|
||||||
|
|
||||||
|
type dnsSOAConfig struct {
|
||||||
|
Refresh uint32 // 3600 by default
|
||||||
|
Retry uint32 // 600
|
||||||
|
Expire uint32 // 86400
|
||||||
|
Minttl uint32 // 0,
|
||||||
|
}
|
||||||
|
|
||||||
type dnsConfig struct {
|
type dnsConfig struct {
|
||||||
AllowStale bool
|
AllowStale bool
|
||||||
Datacenter string
|
Datacenter string
|
||||||
|
@ -53,6 +60,7 @@ type dnsConfig struct {
|
||||||
UDPAnswerLimit int
|
UDPAnswerLimit int
|
||||||
ARecordLimit int
|
ARecordLimit int
|
||||||
NodeMetaTXT bool
|
NodeMetaTXT bool
|
||||||
|
dnsSOAConfig dnsSOAConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSServer is used to wrap an Agent and expose various
|
// DNSServer is used to wrap an Agent and expose various
|
||||||
|
@ -97,6 +105,7 @@ func NewDNSServer(a *Agent) (*DNSServer, error) {
|
||||||
return srv, nil
|
return srv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDNSConfig takes global config and creates the config used by DNS server
|
||||||
func GetDNSConfig(conf *config.RuntimeConfig) *dnsConfig {
|
func GetDNSConfig(conf *config.RuntimeConfig) *dnsConfig {
|
||||||
return &dnsConfig{
|
return &dnsConfig{
|
||||||
AllowStale: conf.DNSAllowStale,
|
AllowStale: conf.DNSAllowStale,
|
||||||
|
@ -112,6 +121,12 @@ func GetDNSConfig(conf *config.RuntimeConfig) *dnsConfig {
|
||||||
ServiceTTL: conf.DNSServiceTTL,
|
ServiceTTL: conf.DNSServiceTTL,
|
||||||
UDPAnswerLimit: conf.DNSUDPAnswerLimit,
|
UDPAnswerLimit: conf.DNSUDPAnswerLimit,
|
||||||
NodeMetaTXT: conf.DNSNodeMetaTXT,
|
NodeMetaTXT: conf.DNSNodeMetaTXT,
|
||||||
|
dnsSOAConfig: dnsSOAConfig{
|
||||||
|
Expire: conf.DNSSOA.Expire,
|
||||||
|
Minttl: conf.DNSSOA.Minttl,
|
||||||
|
Refresh: conf.DNSSOA.Refresh,
|
||||||
|
Retry: conf.DNSSOA.Retry,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,17 +364,16 @@ func (d *DNSServer) soa() *dns.SOA {
|
||||||
Name: d.domain,
|
Name: d.domain,
|
||||||
Rrtype: dns.TypeSOA,
|
Rrtype: dns.TypeSOA,
|
||||||
Class: dns.ClassINET,
|
Class: dns.ClassINET,
|
||||||
Ttl: 0,
|
// Has to be consistent with MinTTL to avoid invalidation
|
||||||
|
Ttl: d.config.dnsSOAConfig.Minttl,
|
||||||
},
|
},
|
||||||
Ns: "ns." + d.domain,
|
Ns: "ns." + d.domain,
|
||||||
Serial: uint32(time.Now().Unix()),
|
Serial: uint32(time.Now().Unix()),
|
||||||
|
|
||||||
// todo(fs): make these configurable
|
|
||||||
Mbox: "hostmaster." + d.domain,
|
Mbox: "hostmaster." + d.domain,
|
||||||
Refresh: 3600,
|
Refresh: d.config.dnsSOAConfig.Refresh,
|
||||||
Retry: 600,
|
Retry: d.config.dnsSOAConfig.Retry,
|
||||||
Expire: 86400,
|
Expire: d.config.dnsSOAConfig.Expire,
|
||||||
Minttl: 0,
|
Minttl: d.config.dnsSOAConfig.Minttl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1115,6 +1115,39 @@ func TestDNS_ServiceReverseLookup_CustomDomain(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDNS_SOA_Settings(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testSoaWithConfig := func(config string, ttl, expire, refresh, retry uint) {
|
||||||
|
a := NewTestAgent(t.Name(), config)
|
||||||
|
defer a.Shutdown()
|
||||||
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
// lookup a non-existing node, we should receive a SOA
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion("nofoo.node.dc1.consul.", dns.TypeANY)
|
||||||
|
|
||||||
|
c := new(dns.Client)
|
||||||
|
in, _, err := c.Exchange(m, a.DNSAddr())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, in.Ns, 1)
|
||||||
|
soaRec, ok := in.Ns[0].(*dns.SOA)
|
||||||
|
require.True(t, ok, "NS RR is not a SOA record")
|
||||||
|
require.Equal(t, uint32(ttl), soaRec.Minttl)
|
||||||
|
require.Equal(t, uint32(expire), soaRec.Expire)
|
||||||
|
require.Equal(t, uint32(refresh), soaRec.Refresh)
|
||||||
|
require.Equal(t, uint32(retry), soaRec.Retry)
|
||||||
|
require.Equal(t, uint32(ttl), soaRec.Hdr.Ttl)
|
||||||
|
}
|
||||||
|
// Default configuration
|
||||||
|
testSoaWithConfig("", 0, 86400, 3600, 600)
|
||||||
|
// Override all settings
|
||||||
|
testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200,refresh=1800,retry=300}}", 60, 43200, 1800, 300)
|
||||||
|
// Override partial settings
|
||||||
|
testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200}}", 60, 43200, 3600, 600)
|
||||||
|
// Override partial settings, part II
|
||||||
|
testSoaWithConfig("dns_config={soa={refresh=1800,retry=300}}", 0, 86400, 1800, 300)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDNS_ServiceReverseLookupNodeAddress(t *testing.T) {
|
func TestDNS_ServiceReverseLookupNodeAddress(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
a := NewTestAgent(t.Name(), "")
|
a := NewTestAgent(t.Name(), "")
|
||||||
|
|
|
@ -68,7 +68,7 @@ func TestUiNodes(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
a := NewTestAgent(t.Name(), "")
|
a := NewTestAgent(t.Name(), "")
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||||
|
|
||||||
args := &structs.RegisterRequest{
|
args := &structs.RegisterRequest{
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
|
|
|
@ -1242,6 +1242,7 @@ func TestAPI_AgentConnectCARoots_list(t *testing.T) {
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
|
|
||||||
agent := c.Agent()
|
agent := c.Agent()
|
||||||
|
s.WaitForSerfCheck(t)
|
||||||
list, meta, err := agent.ConnectCARoots(nil)
|
list, meta, err := agent.ConnectCARoots(nil)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.True(meta.LastIndex > 0)
|
require.True(meta.LastIndex > 0)
|
||||||
|
@ -1286,6 +1287,7 @@ func TestAPI_AgentConnectAuthorize(t *testing.T) {
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
|
|
||||||
agent := c.Agent()
|
agent := c.Agent()
|
||||||
|
s.WaitForSerfCheck(t)
|
||||||
params := &AgentAuthorizeParams{
|
params := &AgentAuthorizeParams{
|
||||||
Target: "foo",
|
Target: "foo",
|
||||||
ClientCertSerial: "fake",
|
ClientCertSerial: "fake",
|
||||||
|
|
|
@ -884,6 +884,28 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
|
||||||
same TXT records when they would be added to the Answer section of the response like when querying with type TXT or ANY. This
|
same TXT records when they would be added to the Answer section of the response like when querying with type TXT or ANY. This
|
||||||
defaults to true.
|
defaults to true.
|
||||||
|
|
||||||
|
* <a name="soa"></a><a href="#soa">`soa`</a> Allow to tune the setting set up in SOA.
|
||||||
|
Non specified values fallback to their default values, all values are integers and
|
||||||
|
expressed as seconds.
|
||||||
|
|
||||||
|
The following settings are available:
|
||||||
|
|
||||||
|
* <a name="soa_expire"></a><a href="soa_expire">expire</a> -
|
||||||
|
Configure SOA Expire duration in seconds, default value is 86400, ie: 24 hours.
|
||||||
|
|
||||||
|
* <a name="soa_min_ttl"></a><a href="soa_min_ttl">`min_ttl`</a> -
|
||||||
|
Configure SOA DNS minimum TTL.
|
||||||
|
As explained in [RFC-2308](https://tools.ietf.org/html/rfc2308) this also controls
|
||||||
|
negative cache TTL in most implementations. Default value is 0, ie: no minimum
|
||||||
|
delay or negative TTL.
|
||||||
|
|
||||||
|
* <a name="soa_refresh"></a><a href="soa_refresh">refresh</a> -
|
||||||
|
Configure SOA Refresh duration in seconds, default value is `3600`, ie: 1 hour.
|
||||||
|
|
||||||
|
* <a name="soa_retry"></a><a href="soa_retry">retry</a> -
|
||||||
|
Configures the Retry duration expressed in seconds, default value is
|
||||||
|
600, ie: 10 minutes.
|
||||||
|
|
||||||
* <a name="domain"></a><a href="#domain">`domain`</a> Equivalent to the
|
* <a name="domain"></a><a href="#domain">`domain`</a> Equivalent to the
|
||||||
[`-domain` command-line flag](#_domain).
|
[`-domain` command-line flag](#_domain).
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,11 @@ client and Consul and set the cache values appropriately. In many cases
|
||||||
"appropriately" simply is turning negative response caching off to get the best
|
"appropriately" simply is turning negative response caching off to get the best
|
||||||
recovery time when a service becomes available again.
|
recovery time when a service becomes available again.
|
||||||
|
|
||||||
|
With versions of Consul greater than 1.3.0, it is now possible to tune SOA
|
||||||
|
responses and modify the negative TTL cache for some resolvers. It can
|
||||||
|
be achieved using the [`soa.min_ttl`](/docs/agent/options.html#soa_min_ttl)
|
||||||
|
configuration within the [`soa`](/docs/agent/options.html#soa) configuration.
|
||||||
|
|
||||||
<a name="ttl"></a>
|
<a name="ttl"></a>
|
||||||
## TTL Values
|
## TTL Values
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue