diff options
author | 2018-02-05 22:00:47 +0000 | |
---|---|---|
committer | 2018-02-05 22:00:47 +0000 | |
commit | 5b844b5017f004fffa83157041e8ffd3ac085c92 (patch) | |
tree | cbf86bb06cd42f720037a0e473ce2d1cba4036af /plugin/forward/forward.go | |
parent | fb1cafe5fa54935361a5cc9a7e3308a738225126 (diff) | |
download | coredns-5b844b5017f004fffa83157041e8ffd3ac085c92.tar.gz coredns-5b844b5017f004fffa83157041e8ffd3ac085c92.tar.zst coredns-5b844b5017f004fffa83157041e8ffd3ac085c92.zip |
plugin/forward: add it (#1447)
* plugin/forward: add it
This moves coredns/forward into CoreDNS. Fixes as a few bugs, adds a
policy option and more tests to the plugin.
Update the documentation, test IPv6 address and add persistent tests.
* Always use random policy when spraying
* include scrub fix here as well
* use correct var name
* Code review
* go vet
* Move logging to metrcs
* Small readme updates
* Fix readme
Diffstat (limited to 'plugin/forward/forward.go')
-rw-r--r-- | plugin/forward/forward.go | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/plugin/forward/forward.go b/plugin/forward/forward.go new file mode 100644 index 000000000..35885008e --- /dev/null +++ b/plugin/forward/forward.go @@ -0,0 +1,154 @@ +// 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 ( + "crypto/tls" + "errors" + "time" + + "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/request" + + "github.com/miekg/dns" + ot "github.com/opentracing/opentracing-go" + "golang.org/x/net/context" +) + +// Forward represents a plugin instance that can proxy requests to another (DNS) server. It has a list +// of proxies each representing one upstream proxy. +type Forward struct { + proxies []*Proxy + p Policy + + from string + ignored []string + + tlsConfig *tls.Config + tlsServerName string + maxfails uint32 + expire time.Duration + + forceTCP bool // also here for testing + hcInterval time.Duration // also here for testing + + Next plugin.Handler +} + +// New returns a new Forward. +func New() *Forward { + f := &Forward{maxfails: 2, tlsConfig: new(tls.Config), expire: defaultExpire, hcInterval: hcDuration, p: new(random)} + return f +} + +// SetProxy appends p to the proxy list and starts healthchecking. +func (f *Forward) SetProxy(p *Proxy) { + f.proxies = append(f.proxies, p) + go p.healthCheck() +} + +// Len returns the number of configured proxies. +func (f *Forward) Len() int { return len(f.proxies) } + +// Name implements plugin.Handler. +func (f *Forward) Name() string { return "forward" } + +// ServeDNS implements plugin.Handler. +func (f *Forward) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + + state := request.Request{W: w, Req: r} + if !f.match(state) { + return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, r) + } + + fails := 0 + var span, child ot.Span + span = ot.SpanFromContext(ctx) + + 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 completely broken and randomly + // select an upstream to connect to. + r := new(random) + proxy = r.List(f.proxies)[0] + + HealthcheckBrokenCount.Add(1) + } + + if span != nil { + child = span.Tracer().StartSpan("connect", ot.ChildOf(span.Context())) + ctx = ot.ContextWithSpan(ctx, child) + } + + ret, err := proxy.connect(ctx, state, f.forceTCP, true) + + if child != nil { + child.Finish() + } + + if err != nil { + if fails < len(f.proxies) { + continue + } + break + } + + ret.Compress = true + // When using force_tcp the upstream can send a message that is too big for + // the udp buffer, hence we need to truncate the message to at least make it + // fit the udp buffer. + ret, _ = state.Scrub(ret) + + w.WriteMsg(ret) + + return 0, nil + } + + return dns.RcodeServerFailure, errNoHealthy +} + +func (f *Forward) match(state request.Request) bool { + from := f.from + + if !plugin.Name(from).Matches(state.Name()) || !f.isAllowedDomain(state.Name()) { + return false + } + + return true +} + +func (f *Forward) isAllowedDomain(name string) bool { + if dns.Name(name) == dns.Name(f.from) { + return true + } + + for _, ignore := range f.ignored { + if plugin.Name(ignore).Matches(name) { + return false + } + } + return true +} + +// List returns a set of proxies to be used for this client depending on the policy in f. +func (f *Forward) list() []*Proxy { return f.p.List(f.proxies) } + +var ( + errInvalidDomain = errors.New("invalid domain for proxy") + errNoHealthy = errors.New("no healthy proxies") + errNoForward = errors.New("no forwarder defined") +) + +// policy tells forward what policy for selecting upstream it uses. +type policy int + +const ( + randomPolicy policy = iota + roundRobinPolicy +) |