coredns/middleware/proxy/proxy.go
Miek Gieben c4ab98c6e3 Add middleware.NextOrFailure (#462)
This checks if the next middleware to be called is nil, and if so returns
ServerFailure and an error. This makes the next calling more robust and
saves some lines of code.

Also prefix the error with the name of the middleware to aid in
debugging.
2016-12-20 18:58:05 +00:00

118 lines
3.4 KiB
Go

// Package proxy is middleware that proxies requests.
package proxy
import (
"errors"
"sync/atomic"
"time"
"github.com/miekg/coredns/middleware"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
var errUnreachable = errors.New("unreachable backend")
// Proxy represents a middleware instance that can proxy requests to another DNS server.
type Proxy struct {
Next middleware.Handler
Client *client
Upstreams []Upstream
}
// Upstream manages a pool of proxy upstream hosts. Select should return a
// suitable upstream host, or nil if no such hosts are available.
type Upstream interface {
// The domain name this upstream host should be routed on.
From() string
// Selects an upstream host to be routed to.
Select() *UpstreamHost
// Checks if subpdomain is not an ignored.
IsAllowedPath(string) bool
// Options returns the options set for this upstream
Options() Options
}
// UpstreamHostDownFunc can be used to customize how Down behaves.
type UpstreamHostDownFunc func(*UpstreamHost) bool
// UpstreamHost represents a single proxy upstream
type UpstreamHost struct {
Conns int64 // must be first field to be 64-bit aligned on 32-bit systems
Name string // IP address (and port) of this upstream host
Fails int32
FailTimeout time.Duration
Unhealthy bool
CheckDown UpstreamHostDownFunc
WithoutPathPrefix string
}
// Down checks whether the upstream host is down or not.
// Down will try to use uh.CheckDown first, and will fall
// back to some default criteria if necessary.
func (uh *UpstreamHost) Down() bool {
if uh.CheckDown == nil {
// Default settings
fails := atomic.LoadInt32(&uh.Fails)
return uh.Unhealthy || fails > 0
}
return uh.CheckDown(uh)
}
// tryDuration is how long to try upstream hosts; failures result in
// immediate retries until this duration ends or we get a nil host.
var tryDuration = 60 * time.Second
// ServeDNS satisfies the middleware.Handler interface.
func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
for _, upstream := range p.Upstreams {
start := time.Now()
// Since Select() should give us "up" hosts, keep retrying
// hosts until timeout (or until we get a nil host).
for time.Now().Sub(start) < tryDuration {
host := upstream.Select()
if host == nil {
RequestDuration.WithLabelValues(upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
return dns.RcodeServerFailure, errUnreachable
}
atomic.AddInt64(&host.Conns, 1)
reply, backendErr := p.Client.ServeDNS(w, r, host)
atomic.AddInt64(&host.Conns, -1)
if backendErr == nil {
w.WriteMsg(reply)
RequestDuration.WithLabelValues(upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
return 0, nil
}
timeout := host.FailTimeout
if timeout == 0 {
timeout = 10 * time.Second
}
atomic.AddInt32(&host.Fails, 1)
go func(host *UpstreamHost, timeout time.Duration) {
time.Sleep(timeout)
atomic.AddInt32(&host.Fails, -1)
}(host, timeout)
}
RequestDuration.WithLabelValues(upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
return dns.RcodeServerFailure, errUnreachable
}
return middleware.NextOrFailure(p.Name(), p.Next, ctx, w, r)
}
// Name implements the Handler interface.
func (p Proxy) Name() string { return "proxy" }
// defaultTimeout is the default networking timeout for DNS requests.
const defaultTimeout = 5 * time.Second