coredns/plugin/forward/lookup.go
Miek Gieben a536833546
plugin/forward: add HealthChecker interface (#1950)
* plugin/forward: add HealthChecker interface

Make the HealthChecker interface and morph the current DNS health
checker into that interface.

Remove all whole bunch of method on Forward that didn't make sense.

This is done in preparation of adding a DoH client to forward - which
requires a completely different healthcheck implementation (and more,
but lets start here)

Signed-off-by: Miek Gieben <miek@miek.nl>

* Use protocol

Signed-off-by: Miek Gieben <miek@miek.nl>

* Dial doesnt need to be method an Forward either

Signed-off-by: Miek Gieben <miek@miek.nl>

* Address comments

Address various comments on the PR.

Signed-off-by: Miek Gieben <miek@miek.nl>
2018-07-09 15:14:55 +01:00

88 lines
2.3 KiB
Go

// Package forward implements a forwarding proxy. It caches an upstream net.Conn for some time, so if the same
// client returns the upstream's Conn will be precached. Depending on how you benchmark this looks to be
// 50% faster than just openening a new connection for every client. It works with UDP and TCP and uses
// inband healthchecking.
package forward
import (
"context"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// Forward forward the request in state as-is. Unlike Lookup that adds EDNS0 suffix to the message.
// Forward may be called with a nil f, an error is returned in that case.
func (f *Forward) Forward(state request.Request) (*dns.Msg, error) {
if f == nil {
return nil, ErrNoForward
}
fails := 0
var upstreamErr error
for _, proxy := range f.List() {
if proxy.Down(f.maxfails) {
fails++
if fails < len(f.proxies) {
continue
}
// All upstream proxies are dead, assume healtcheck is complete broken and randomly
// select an upstream to connect to.
proxy = f.List()[0]
}
ret, err := proxy.Connect(context.Background(), state, f.opts)
ret, err = truncated(state, ret, err)
upstreamErr = err
if err != nil {
if fails < len(f.proxies) {
continue
}
break
}
// Check if the reply is correct; if not return FormErr.
if !state.Match(ret) {
return state.ErrorMessage(dns.RcodeFormatError), nil
}
return ret, err
}
if upstreamErr != nil {
return nil, upstreamErr
}
return nil, ErrNoHealthy
}
// Lookup will use name and type to forge a new message and will send that upstream. It will
// set any EDNS0 options correctly so that downstream will be able to process the reply.
// Lookup may be called with a nil f, an error is returned in that case.
func (f *Forward) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) {
if f == nil {
return nil, ErrNoForward
}
req := new(dns.Msg)
req.SetQuestion(name, typ)
state.SizeAndDo(req)
state2 := request.Request{W: state.W, Req: req}
return f.Forward(state2)
}
// NewLookup returns a Forward that can be used for plugin that need an upstream to resolve external names.
// Note that the caller must run Close on the forward to stop the health checking goroutines.
func NewLookup(addr []string) *Forward {
f := New()
for i := range addr {
p := NewProxy(addr[i], DNS)
f.SetProxy(p)
}
return f
}