2016-03-18 20:57:35 +00:00
|
|
|
package proxy
|
|
|
|
|
|
|
|
|
|
import (
|
2017-01-15 08:12:58 +00:00
|
|
|
"fmt"
|
2016-04-07 17:42:35 +01:00
|
|
|
"net"
|
2016-03-18 20:57:35 +00:00
|
|
|
"strconv"
|
|
|
|
|
"time"
|
|
|
|
|
|
2017-09-14 09:36:06 +01:00
|
|
|
"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"
|
2016-08-19 17:14:17 -07:00
|
|
|
"github.com/mholt/caddy/caddyfile"
|
2016-03-19 16:11:30 +00:00
|
|
|
"github.com/miekg/dns"
|
2016-03-18 20:57:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type staticUpstream struct {
|
2017-04-26 10:58:14 +01:00
|
|
|
from string
|
|
|
|
|
|
2017-08-09 09:21:33 -07:00
|
|
|
healthcheck.HealthCheck
|
2016-03-18 20:57:35 +00:00
|
|
|
|
2016-03-19 16:11:30 +00:00
|
|
|
IgnoredSubDomains []string
|
2017-02-06 19:32:48 +00:00
|
|
|
ex Exchanger
|
2016-03-18 20:57:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewStaticUpstreams parses the configuration input and sets up
|
2017-09-14 09:36:06 +01:00
|
|
|
// static upstreams for the proxy plugin.
|
2016-09-16 23:49:35 -07:00
|
|
|
func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
|
2016-03-18 20:57:35 +00:00
|
|
|
var upstreams []Upstream
|
|
|
|
|
for c.Next() {
|
2018-01-27 17:25:39 -05:00
|
|
|
u, err := NewStaticUpstream(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return upstreams, err
|
2016-03-18 20:57:35 +00:00
|
|
|
}
|
2018-01-27 17:25:39 -05:00
|
|
|
upstreams = append(upstreams, u)
|
|
|
|
|
}
|
|
|
|
|
return upstreams, nil
|
|
|
|
|
}
|
2016-03-18 20:57:35 +00:00
|
|
|
|
2018-01-27 17:25:39 -05:00
|
|
|
// NewStaticUpstream parses the configuration of a single upstream
|
|
|
|
|
// starting from the FROM
|
|
|
|
|
func NewStaticUpstream(c *caddyfile.Dispenser) (Upstream, error) {
|
|
|
|
|
upstream := &staticUpstream{
|
|
|
|
|
from: ".",
|
|
|
|
|
HealthCheck: healthcheck.HealthCheck{
|
|
|
|
|
FailTimeout: 5 * time.Second,
|
|
|
|
|
MaxFails: 3,
|
|
|
|
|
},
|
|
|
|
|
ex: newDNSEx(),
|
|
|
|
|
}
|
2017-08-13 18:16:25 +01:00
|
|
|
|
2018-01-27 17:25:39 -05:00
|
|
|
if !c.Args(&upstream.from) {
|
|
|
|
|
return upstream, c.ArgErr()
|
|
|
|
|
}
|
|
|
|
|
upstream.from = plugin.Host(upstream.from).Normalize()
|
2016-10-22 10:52:10 -04:00
|
|
|
|
2018-01-27 17:25:39 -05:00
|
|
|
to := c.RemainingArgs()
|
|
|
|
|
if len(to) == 0 {
|
|
|
|
|
return upstream, c.ArgErr()
|
|
|
|
|
}
|
2016-03-18 20:57:35 +00:00
|
|
|
|
2018-01-27 17:25:39 -05:00
|
|
|
// process the host list, substituting in any nameservers in files
|
|
|
|
|
toHosts, err := dnsutil.ParseHostPortOrFile(to...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return upstream, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(toHosts) > max {
|
|
|
|
|
return upstream, fmt.Errorf("more than %d TOs configured: %d", max, len(toHosts))
|
|
|
|
|
}
|
2018-01-08 15:03:42 +00:00
|
|
|
|
2018-01-27 17:25:39 -05:00
|
|
|
for c.NextBlock() {
|
|
|
|
|
if err := parseBlock(c, upstream); err != nil {
|
|
|
|
|
return upstream, err
|
2016-03-18 20:57:35 +00:00
|
|
|
}
|
2018-01-27 17:25:39 -05:00
|
|
|
}
|
2016-03-18 20:57:35 +00:00
|
|
|
|
2018-01-27 17:25:39 -05:00
|
|
|
upstream.Hosts = make([]*healthcheck.UpstreamHost, len(toHosts))
|
2017-10-15 19:38:39 +02:00
|
|
|
|
2018-01-27 17:25:39 -05:00
|
|
|
for i, host := range toHosts {
|
|
|
|
|
uh := &healthcheck.UpstreamHost{
|
|
|
|
|
Name: host,
|
|
|
|
|
FailTimeout: upstream.FailTimeout,
|
|
|
|
|
CheckDown: checkDownFunc(upstream),
|
2016-03-18 20:57:35 +00:00
|
|
|
}
|
2018-01-27 17:25:39 -05:00
|
|
|
upstream.Hosts[i] = uh
|
2016-03-18 20:57:35 +00:00
|
|
|
}
|
2018-01-27 17:25:39 -05:00
|
|
|
upstream.Start()
|
|
|
|
|
|
|
|
|
|
return upstream, nil
|
2016-03-18 20:57:35 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-16 23:49:35 -07:00
|
|
|
func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
|
2016-03-18 20:57:35 +00:00
|
|
|
switch c.Val() {
|
|
|
|
|
case "policy":
|
|
|
|
|
if !c.NextArg() {
|
|
|
|
|
return c.ArgErr()
|
|
|
|
|
}
|
2017-08-09 09:21:33 -07:00
|
|
|
policyCreateFunc, ok := healthcheck.SupportedPolicies[c.Val()]
|
2016-03-18 20:57:35 +00:00
|
|
|
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()
|
|
|
|
|
}
|
2016-04-07 17:42:35 +01:00
|
|
|
var err error
|
|
|
|
|
u.HealthCheck.Path, u.HealthCheck.Port, err = net.SplitHostPort(c.Val())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
plugin/proxy: decrease health timeouts (#1107)
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
2017-09-24 20:05:36 +01:00
|
|
|
u.HealthCheck.Interval = 4 * time.Second
|
2016-03-18 20:57:35 +00:00
|
|
|
if c.NextArg() {
|
|
|
|
|
dur, err := time.ParseDuration(c.Val())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
u.HealthCheck.Interval = dur
|
|
|
|
|
}
|
|
|
|
|
case "except":
|
2016-03-19 16:11:30 +00:00
|
|
|
ignoredDomains := c.RemainingArgs()
|
|
|
|
|
if len(ignoredDomains) == 0 {
|
2016-03-18 20:57:35 +00:00
|
|
|
return c.ArgErr()
|
|
|
|
|
}
|
2016-03-19 16:11:30 +00:00
|
|
|
for i := 0; i < len(ignoredDomains); i++ {
|
2017-09-14 09:36:06 +01:00
|
|
|
ignoredDomains[i] = plugin.Host(ignoredDomains[i]).Normalize()
|
2016-03-19 16:11:30 +00:00
|
|
|
}
|
|
|
|
|
u.IgnoredSubDomains = ignoredDomains
|
2016-07-04 21:13:28 +01:00
|
|
|
case "spray":
|
2017-08-09 09:21:33 -07:00
|
|
|
u.Spray = &healthcheck.Spray{}
|
2017-01-15 08:12:58 +00:00
|
|
|
case "protocol":
|
|
|
|
|
encArgs := c.RemainingArgs()
|
|
|
|
|
if len(encArgs) == 0 {
|
|
|
|
|
return c.ArgErr()
|
|
|
|
|
}
|
|
|
|
|
switch encArgs[0] {
|
|
|
|
|
case "dns":
|
2017-03-14 21:32:21 +00:00
|
|
|
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()
|
|
|
|
|
}
|
2017-01-15 08:12:58 +00:00
|
|
|
case "https_google":
|
2017-02-06 19:32:48 +00:00
|
|
|
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
|
2017-02-14 22:20:20 -05:00
|
|
|
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)
|
2017-01-15 08:12:58 +00:00
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("%s: %s", errInvalidProtocol, encArgs[0])
|
|
|
|
|
}
|
2016-04-30 15:54:41 +01:00
|
|
|
|
2016-03-18 20:57:35 +00:00
|
|
|
default:
|
|
|
|
|
return c.Errf("unknown property '%s'", c.Val())
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-07 18:01:16 +00:00
|
|
|
func (u *staticUpstream) IsAllowedDomain(name string) bool {
|
2017-02-07 21:03:17 +00:00
|
|
|
if dns.Name(name) == dns.Name(u.From()) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2017-02-07 18:01:16 +00:00
|
|
|
|
2017-02-07 21:03:17 +00:00
|
|
|
for _, ignoredSubDomain := range u.IgnoredSubDomains {
|
2017-09-14 09:36:06 +01:00
|
|
|
if plugin.Name(ignoredSubDomain).Matches(name) {
|
2016-03-19 16:11:30 +00:00
|
|
|
return false
|
2016-03-18 20:57:35 +00:00
|
|
|
}
|
2016-03-19 16:11:30 +00:00
|
|
|
}
|
2016-03-18 20:57:35 +00:00
|
|
|
return true
|
|
|
|
|
}
|
2017-02-06 19:32:48 +00:00
|
|
|
|
|
|
|
|
func (u *staticUpstream) Exchanger() Exchanger { return u.ex }
|
2017-10-15 19:38:39 +02:00
|
|
|
func (u *staticUpstream) From() string { return u.from }
|
2018-01-08 15:03:42 +00:00
|
|
|
|
|
|
|
|
const max = 15
|