* 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>
88 lines
2.3 KiB
Go
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
|
|
}
|