Turn down the timeouts and numbers a bit: FailTimeout 10s -> 5s Future 60s -> 12s TryDuration 60s -> 16s The timeout for decrementing the fails in a host: 10s -> 2s And the biggest change: don't set fails when the error is Timeout(), meaning we loop for a bit and may try the same server again, but we don't mark our upstream as bad, see comments in proxy.go. Testing this with "ANY isc.org" and "MX miek.nl" we see: ~~~ ::1 - [24/Sep/2017:08:06:17 +0100] "ANY IN isc.org. udp 37 false 4096" SERVFAIL qr,rd 37 10.001621221s 24/Sep/2017:08:06:17 +0100 [ERROR 0 isc.org. ANY] unreachable backend: read udp 192.168.1.148:37420->8.8.8.8:53: i/o timeout ::1 - [24/Sep/2017:08:06:17 +0100] "MX IN miek.nl. udp 37 false 4096" NOERROR qr,rd,ra,ad 170 35.957284ms 127.0.0.1 - [24/Sep/2017:08:06:18 +0100] "ANY IN isc.org. udp 37 false 4096" SERVFAIL qr,rd 37 10.002051726s 24/Sep/2017:08:06:18 +0100 [ERROR 0 isc.org. ANY] unreachable backend: read udp 192.168.1.148:54901->8.8.8.8:53: i/o timeout ::1 - [24/Sep/2017:08:06:19 +0100] "MX IN miek.nl. udp 37 false 4096" NOERROR qr,rd,ra,ad 170 56.848416ms 127.0.0.1 - [24/Sep/2017:08:06:21 +0100] "MX IN miek.nl. udp 37 false 4096" NOERROR qr,rd,ra,ad 170 48.118349ms ::1 - [24/Sep/2017:08:06:21 +0100] "MX IN miek.nl. udp 37 false 4096" NOERROR qr,rd,ra,ad 170 1.055172915s ~~~ So the ANY isc.org queries show up twice, because we retry internally - this is I think WAI. The `miek.nl MX` queries are just processed normally as no backend is marked as unreachable. May fix #1035 #486
206 lines
4.5 KiB
Go
206 lines
4.5 KiB
Go
package proxy
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/coredns/coredns/plugin"
|
|
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
|
"github.com/coredns/coredns/plugin/pkg/healthcheck"
|
|
"github.com/coredns/coredns/plugin/pkg/tls"
|
|
"github.com/mholt/caddy/caddyfile"
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
type staticUpstream struct {
|
|
from string
|
|
|
|
healthcheck.HealthCheck
|
|
|
|
IgnoredSubDomains []string
|
|
ex Exchanger
|
|
}
|
|
|
|
// NewStaticUpstreams parses the configuration input and sets up
|
|
// static upstreams for the proxy plugin.
|
|
func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
|
|
var upstreams []Upstream
|
|
for c.Next() {
|
|
upstream := &staticUpstream{
|
|
from: ".",
|
|
HealthCheck: healthcheck.HealthCheck{
|
|
FailTimeout: 5 * time.Second,
|
|
MaxFails: 3,
|
|
Future: 12 * time.Second,
|
|
},
|
|
ex: newDNSEx(),
|
|
}
|
|
|
|
if !c.Args(&upstream.from) {
|
|
return upstreams, c.ArgErr()
|
|
}
|
|
upstream.from = plugin.Host(upstream.from).Normalize()
|
|
|
|
to := c.RemainingArgs()
|
|
if len(to) == 0 {
|
|
return upstreams, c.ArgErr()
|
|
}
|
|
|
|
// process the host list, substituting in any nameservers in files
|
|
toHosts, err := dnsutil.ParseHostPortOrFile(to...)
|
|
if err != nil {
|
|
return upstreams, err
|
|
}
|
|
|
|
for c.NextBlock() {
|
|
if err := parseBlock(c, upstream); err != nil {
|
|
return upstreams, err
|
|
}
|
|
}
|
|
|
|
upstream.Hosts = make([]*healthcheck.UpstreamHost, len(toHosts))
|
|
for i, host := range toHosts {
|
|
uh := &healthcheck.UpstreamHost{
|
|
Name: host,
|
|
Conns: 0,
|
|
Fails: 0,
|
|
FailTimeout: upstream.FailTimeout,
|
|
CheckDown: checkDownFunc(upstream),
|
|
}
|
|
|
|
upstream.Hosts[i] = uh
|
|
}
|
|
upstream.Start()
|
|
|
|
upstreams = append(upstreams, upstream)
|
|
}
|
|
return upstreams, nil
|
|
}
|
|
|
|
func (u *staticUpstream) From() string {
|
|
return u.from
|
|
}
|
|
|
|
func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
|
|
switch c.Val() {
|
|
case "policy":
|
|
if !c.NextArg() {
|
|
return c.ArgErr()
|
|
}
|
|
policyCreateFunc, ok := healthcheck.SupportedPolicies[c.Val()]
|
|
if !ok {
|
|
return c.ArgErr()
|
|
}
|
|
u.Policy = policyCreateFunc()
|
|
case "fail_timeout":
|
|
if !c.NextArg() {
|
|
return c.ArgErr()
|
|
}
|
|
dur, err := time.ParseDuration(c.Val())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.FailTimeout = dur
|
|
case "max_fails":
|
|
if !c.NextArg() {
|
|
return c.ArgErr()
|
|
}
|
|
n, err := strconv.Atoi(c.Val())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.MaxFails = int32(n)
|
|
case "health_check":
|
|
if !c.NextArg() {
|
|
return c.ArgErr()
|
|
}
|
|
var err error
|
|
u.HealthCheck.Path, u.HealthCheck.Port, err = net.SplitHostPort(c.Val())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.HealthCheck.Interval = 4 * time.Second
|
|
if c.NextArg() {
|
|
dur, err := time.ParseDuration(c.Val())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.HealthCheck.Interval = dur
|
|
u.Future = 2 * dur
|
|
|
|
// set a minimum of 3 seconds
|
|
if u.Future < (3 * time.Second) {
|
|
u.Future = 3 * time.Second
|
|
}
|
|
}
|
|
case "except":
|
|
ignoredDomains := c.RemainingArgs()
|
|
if len(ignoredDomains) == 0 {
|
|
return c.ArgErr()
|
|
}
|
|
for i := 0; i < len(ignoredDomains); i++ {
|
|
ignoredDomains[i] = plugin.Host(ignoredDomains[i]).Normalize()
|
|
}
|
|
u.IgnoredSubDomains = ignoredDomains
|
|
case "spray":
|
|
u.Spray = &healthcheck.Spray{}
|
|
case "protocol":
|
|
encArgs := c.RemainingArgs()
|
|
if len(encArgs) == 0 {
|
|
return c.ArgErr()
|
|
}
|
|
switch encArgs[0] {
|
|
case "dns":
|
|
if len(encArgs) > 1 {
|
|
if encArgs[1] == "force_tcp" {
|
|
opts := Options{ForceTCP: true}
|
|
u.ex = newDNSExWithOption(opts)
|
|
} else {
|
|
return fmt.Errorf("only force_tcp allowed as parameter to dns")
|
|
}
|
|
} else {
|
|
u.ex = newDNSEx()
|
|
}
|
|
case "https_google":
|
|
boot := []string{"8.8.8.8:53", "8.8.4.4:53"}
|
|
if len(encArgs) > 2 && encArgs[1] == "bootstrap" {
|
|
boot = encArgs[2:]
|
|
}
|
|
|
|
u.ex = newGoogle("", boot) // "" for default in google.go
|
|
case "grpc":
|
|
if len(encArgs) == 2 && encArgs[1] == "insecure" {
|
|
u.ex = newGrpcClient(nil, u)
|
|
return nil
|
|
}
|
|
tls, err := tls.NewTLSConfigFromArgs(encArgs[1:]...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.ex = newGrpcClient(tls, u)
|
|
default:
|
|
return fmt.Errorf("%s: %s", errInvalidProtocol, encArgs[0])
|
|
}
|
|
|
|
default:
|
|
return c.Errf("unknown property '%s'", c.Val())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *staticUpstream) IsAllowedDomain(name string) bool {
|
|
if dns.Name(name) == dns.Name(u.From()) {
|
|
return true
|
|
}
|
|
|
|
for _, ignoredSubDomain := range u.IgnoredSubDomains {
|
|
if plugin.Name(ignoredSubDomain).Matches(name) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (u *staticUpstream) Exchanger() Exchanger { return u.ex }
|