mirror of
https://github.com/coredns/coredns.git
synced 2025-11-21 11:22:20 -05:00
plugin/timeouts - Allow ability to configure listening server timeouts (#5784)
This commit is contained in:
76
plugin/timeouts/README.md
Normal file
76
plugin/timeouts/README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# timeouts
|
||||
|
||||
## Name
|
||||
|
||||
*timeouts* - allows you to configure the server read, write and idle timeouts for the TCP, TLS and DoH servers.
|
||||
|
||||
## Description
|
||||
|
||||
CoreDNS is configured with sensible timeouts for server connections by default.
|
||||
However in some cases for example where CoreDNS is serving over a slow mobile
|
||||
data connection the default timeouts are not optimal.
|
||||
|
||||
Additionally some routers hold open connections when using DNS over TLS or DNS
|
||||
over HTTPS. Allowing a longer idle timeout helps performance and reduces issues
|
||||
with such routers.
|
||||
|
||||
The *timeouts* "plugin" allows you to configure CoreDNS server read, write and
|
||||
idle timeouts.
|
||||
|
||||
## Syntax
|
||||
|
||||
~~~ txt
|
||||
timeouts {
|
||||
read DURATION
|
||||
write DURATION
|
||||
idle DURATION
|
||||
}
|
||||
~~~
|
||||
|
||||
For any timeouts that are not provided, default values are used which may vary
|
||||
depending on the server type. At least one timeout must be specified otherwise
|
||||
the entire timeouts block should be omitted.
|
||||
|
||||
## Examples
|
||||
|
||||
Start a DNS-over-TLS server that picks up incoming DNS-over-TLS queries on port
|
||||
5553 and uses the nameservers defined in `/etc/resolv.conf` to resolve the
|
||||
query. This proxy path uses plain old DNS. A 10 second read timeout, 20
|
||||
second write timeout and a 60 second idle timeout have been configured.
|
||||
|
||||
~~~
|
||||
tls://.:5553 {
|
||||
tls cert.pem key.pem ca.pem
|
||||
timeouts {
|
||||
read 10s
|
||||
write 20s
|
||||
idle 60s
|
||||
}
|
||||
forward . /etc/resolv.conf
|
||||
}
|
||||
~~~
|
||||
|
||||
Start a DNS-over-HTTPS server that is similar to the previous example. Only the
|
||||
read timeout has been configured for 1 minute.
|
||||
|
||||
~~~
|
||||
https://. {
|
||||
tls cert.pem key.pem ca.pem
|
||||
timeouts {
|
||||
read 1m
|
||||
}
|
||||
forward . /etc/resolv.conf
|
||||
}
|
||||
~~~
|
||||
|
||||
Start a standard TCP/UDP server on port 1053. A read and write timeout has been
|
||||
configured. The timeouts are only applied to the TCP side of the server.
|
||||
~~~
|
||||
.:1053 {
|
||||
timeouts {
|
||||
read 15s
|
||||
write 30s
|
||||
}
|
||||
forward . /etc/resolv.conf
|
||||
}
|
||||
~~~
|
||||
69
plugin/timeouts/timeouts.go
Normal file
69
plugin/timeouts/timeouts.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package timeouts
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/coredns/caddy"
|
||||
"github.com/coredns/coredns/core/dnsserver"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/pkg/durations"
|
||||
)
|
||||
|
||||
func init() { plugin.Register("timeouts", setup) }
|
||||
|
||||
func setup(c *caddy.Controller) error {
|
||||
err := parseTimeouts(c)
|
||||
if err != nil {
|
||||
return plugin.Error("timeouts", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseTimeouts(c *caddy.Controller) error {
|
||||
config := dnsserver.GetConfig(c)
|
||||
|
||||
for c.Next() {
|
||||
args := c.RemainingArgs()
|
||||
if len(args) > 0 {
|
||||
return plugin.Error("timeouts", c.ArgErr())
|
||||
}
|
||||
|
||||
b := 0
|
||||
for c.NextBlock() {
|
||||
block := c.Val()
|
||||
timeoutArgs := c.RemainingArgs()
|
||||
if len(timeoutArgs) != 1 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
|
||||
timeout, err := durations.NewDurationFromArg(timeoutArgs[0])
|
||||
if err != nil {
|
||||
return c.Err(err.Error())
|
||||
}
|
||||
|
||||
if timeout < (1*time.Second) || timeout > (24*time.Hour) {
|
||||
return c.Errf("timeout provided '%s' needs to be between 1 second and 24 hours", timeout)
|
||||
}
|
||||
|
||||
switch block {
|
||||
case "read":
|
||||
config.ReadTimeout = timeout
|
||||
|
||||
case "write":
|
||||
config.WriteTimeout = timeout
|
||||
|
||||
case "idle":
|
||||
config.IdleTimeout = timeout
|
||||
|
||||
default:
|
||||
return c.Errf("unknown option: '%s'", block)
|
||||
}
|
||||
b++
|
||||
}
|
||||
|
||||
if b == 0 {
|
||||
return plugin.Error("timeouts", c.Err("timeouts block with no timeouts specified"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
75
plugin/timeouts/timeouts_test.go
Normal file
75
plugin/timeouts/timeouts_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package timeouts
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/caddy"
|
||||
)
|
||||
|
||||
func TestTimeouts(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
shouldErr bool
|
||||
expectedRoot string // expected root, set to the controller. Empty for negative cases.
|
||||
expectedErrContent string // substring from the expected error. Empty for positive cases.
|
||||
}{
|
||||
// positive
|
||||
{`timeouts {
|
||||
read 30s
|
||||
}`, false, "", ""},
|
||||
{`timeouts {
|
||||
read 1m
|
||||
write 2m
|
||||
}`, false, "", ""},
|
||||
{` timeouts {
|
||||
idle 1h
|
||||
}`, false, "", ""},
|
||||
{`timeouts {
|
||||
read 10
|
||||
write 20
|
||||
idle 60
|
||||
}`, false, "", ""},
|
||||
// negative
|
||||
{`timeouts`, true, "", "block with no timeouts specified"},
|
||||
{`timeouts {
|
||||
}`, true, "", "block with no timeouts specified"},
|
||||
{`timeouts {
|
||||
read 10s
|
||||
giraffe 30s
|
||||
}`, true, "", "unknown option"},
|
||||
{`timeouts {
|
||||
read 10s 20s
|
||||
write 30s
|
||||
}`, true, "", "Wrong argument"},
|
||||
{`timeouts {
|
||||
write snake
|
||||
}`, true, "", "failed to parse duration"},
|
||||
{`timeouts {
|
||||
idle 0s
|
||||
}`, true, "", "needs to be between"},
|
||||
{`timeouts {
|
||||
read 48h
|
||||
}`, true, "", "needs to be between"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
c := caddy.NewTestController("dns", test.input)
|
||||
err := setup(c)
|
||||
//cfg := dnsserver.GetConfig(c)
|
||||
|
||||
if test.shouldErr && err == nil {
|
||||
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if !test.shouldErr {
|
||||
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), test.expectedErrContent) {
|
||||
t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user