143 lines
3 KiB
Go
143 lines
3 KiB
Go
package grpc
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/coredns/coredns/plugin"
|
|
"github.com/coredns/coredns/plugin/debug"
|
|
"github.com/coredns/coredns/request"
|
|
|
|
"github.com/miekg/dns"
|
|
ot "github.com/opentracing/opentracing-go"
|
|
)
|
|
|
|
// GRPC represents a plugin instance that can proxy requests to another (DNS) server via gRPC protocol.
|
|
// It has a list of proxies each representing one upstream proxy.
|
|
type GRPC struct {
|
|
proxies []*Proxy
|
|
p Policy
|
|
|
|
from string
|
|
ignored []string
|
|
|
|
tlsConfig *tls.Config
|
|
tlsServerName string
|
|
|
|
Next plugin.Handler
|
|
}
|
|
|
|
// ServeDNS implements the plugin.Handler interface.
|
|
func (g *GRPC) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
|
state := request.Request{W: w, Req: r}
|
|
if !g.match(state) {
|
|
return plugin.NextOrFailure(g.Name(), g.Next, ctx, w, r)
|
|
}
|
|
|
|
var (
|
|
span, child ot.Span
|
|
ret *dns.Msg
|
|
err error
|
|
i int
|
|
)
|
|
span = ot.SpanFromContext(ctx)
|
|
list := g.list()
|
|
deadline := time.Now().Add(defaultTimeout)
|
|
|
|
for time.Now().Before(deadline) {
|
|
if i >= len(list) {
|
|
// reached the end of list without any answer
|
|
if ret != nil {
|
|
// write empty response and finish
|
|
w.WriteMsg(ret)
|
|
}
|
|
break
|
|
}
|
|
|
|
proxy := list[i]
|
|
i++
|
|
|
|
if span != nil {
|
|
child = span.Tracer().StartSpan("query", ot.ChildOf(span.Context()))
|
|
ctx = ot.ContextWithSpan(ctx, child)
|
|
}
|
|
|
|
ret, err = proxy.query(ctx, r)
|
|
if err != nil {
|
|
// Continue with the next proxy
|
|
continue
|
|
}
|
|
|
|
if child != nil {
|
|
child.Finish()
|
|
}
|
|
|
|
// Check if the reply is correct; if not return FormErr.
|
|
if !state.Match(ret) {
|
|
debug.Hexdumpf(ret, "Wrong reply for id: %d, %s %d", ret.Id, state.QName(), state.QType())
|
|
|
|
formerr := new(dns.Msg)
|
|
formerr.SetRcode(state.Req, dns.RcodeFormatError)
|
|
w.WriteMsg(formerr)
|
|
return 0, nil
|
|
}
|
|
|
|
w.WriteMsg(ret)
|
|
return 0, nil
|
|
}
|
|
|
|
// SERVFAIL if all healthy proxys returned errors.
|
|
if err != nil {
|
|
// just return the last error received
|
|
return dns.RcodeServerFailure, err
|
|
}
|
|
|
|
return dns.RcodeServerFailure, ErrNoHealthy
|
|
}
|
|
|
|
// NewGRPC returns a new GRPC.
|
|
func newGRPC() *GRPC {
|
|
g := &GRPC{
|
|
p: new(random),
|
|
}
|
|
return g
|
|
}
|
|
|
|
// Name implements the Handler interface.
|
|
func (g *GRPC) Name() string { return "grpc" }
|
|
|
|
// Len returns the number of configured proxies.
|
|
func (g *GRPC) len() int { return len(g.proxies) }
|
|
|
|
func (g *GRPC) match(state request.Request) bool {
|
|
if !plugin.Name(g.from).Matches(state.Name()) || !g.isAllowedDomain(state.Name()) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (g *GRPC) isAllowedDomain(name string) bool {
|
|
if dns.Name(name) == dns.Name(g.from) {
|
|
return true
|
|
}
|
|
|
|
for _, ignore := range g.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 p.
|
|
func (g *GRPC) list() []*Proxy { return g.p.List(g.proxies) }
|
|
|
|
const defaultTimeout = 5 * time.Second
|
|
|
|
var (
|
|
// ErrNoHealthy means no healthy proxies left.
|
|
ErrNoHealthy = errors.New("no healthy gRPC proxies")
|
|
)
|