aboutsummaryrefslogtreecommitdiff
path: root/plugin/forward/forward.go
diff options
context:
space:
mode:
authorGravatar Miek Gieben <miek@miek.nl> 2018-02-05 22:00:47 +0000
committerGravatar GitHub <noreply@github.com> 2018-02-05 22:00:47 +0000
commit5b844b5017f004fffa83157041e8ffd3ac085c92 (patch)
treecbf86bb06cd42f720037a0e473ce2d1cba4036af /plugin/forward/forward.go
parentfb1cafe5fa54935361a5cc9a7e3308a738225126 (diff)
downloadcoredns-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.go154
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
+)