| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | package grpc
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							|  |  |  | 	"context"
 | 
					
						
							|  |  |  | 	"crypto/tls"
 | 
					
						
							| 
									
										
										
										
											2020-07-15 14:59:45 -03:00
										 |  |  | 	"errors"
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 	"time"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/debug"
 | 
					
						
							| 
									
										
										
										
											2025-06-06 04:58:17 -07:00
										 |  |  | 	"github.com/coredns/coredns/plugin/pkg/fall"
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 	"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
 | 
					
						
							| 
									
										
										
										
											2020-05-29 12:30:26 -04:00
										 |  |  | 	p       Policy
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	from    string
 | 
					
						
							|  |  |  | 	ignored []string
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tlsConfig     *tls.Config
 | 
					
						
							|  |  |  | 	tlsServerName string
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-06 04:58:17 -07:00
										 |  |  | 	Fall fall.F
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 	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) {
 | 
					
						
							| 
									
										
										
										
											2025-06-06 04:58:17 -07:00
										 |  |  | 		if g.Next != nil {
 | 
					
						
							|  |  |  | 			return plugin.NextOrFailure(g.Name(), g.Next, ctx, w, r)
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		// No next plugin, return SERVFAIL
 | 
					
						
							|  |  |  | 		return dns.RcodeServerFailure, nil
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var (
 | 
					
						
							| 
									
										
										
										
											2025-09-02 04:09:51 +03:00
										 |  |  | 		span ot.Span
 | 
					
						
							|  |  |  | 		ret  *dns.Msg
 | 
					
						
							|  |  |  | 		err  error
 | 
					
						
							|  |  |  | 		i    int
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 	)
 | 
					
						
							|  |  |  | 	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++
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 04:09:51 +03:00
										 |  |  | 		callCtx := ctx
 | 
					
						
							|  |  |  | 		var child ot.Span
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 		if span != nil {
 | 
					
						
							| 
									
										
										
										
											2025-09-02 04:09:51 +03:00
										 |  |  | 			child, callCtx = ot.StartSpanFromContext(callCtx, "query")
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-02 04:09:51 +03:00
										 |  |  | 		var cancel context.CancelFunc
 | 
					
						
							|  |  |  | 		callCtx, cancel = context.WithDeadline(callCtx, deadline)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret, err = proxy.query(callCtx, r)
 | 
					
						
							|  |  |  | 		cancel()
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if child != nil {
 | 
					
						
							|  |  |  | 			child.Finish()
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2025-09-02 04:09:51 +03:00
										 |  |  | 		if err != nil {
 | 
					
						
							|  |  |  | 			// Continue with the next proxy
 | 
					
						
							|  |  |  | 			continue
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// 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())
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-24 13:26:15 +00:00
										 |  |  | 			formerr := new(dns.Msg)
 | 
					
						
							|  |  |  | 			formerr.SetRcode(state.Req, dns.RcodeFormatError)
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 			w.WriteMsg(formerr)
 | 
					
						
							|  |  |  | 			return 0, nil
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-06 04:58:17 -07:00
										 |  |  | 		// Check if we should fallthrough on NXDOMAIN responses
 | 
					
						
							|  |  |  | 		if ret.Rcode == dns.RcodeNameError && g.Fall.Through(state.Name()) {
 | 
					
						
							|  |  |  | 			if g.Next != nil {
 | 
					
						
							|  |  |  | 				return plugin.NextOrFailure(g.Name(), g.Next, ctx, w, r)
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 			// No next plugin to fallthrough to, return the NXDOMAIN response
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 		w.WriteMsg(ret)
 | 
					
						
							|  |  |  | 		return 0, nil
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-10 08:58:46 -05:00
										 |  |  | 	// SERVFAIL if all healthy proxys returned errors.
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							| 
									
										
										
										
											2025-06-06 04:58:17 -07:00
										 |  |  | 		// If fallthrough is enabled, try the next plugin instead of returning SERVFAIL
 | 
					
						
							|  |  |  | 		if g.Fall.Through(state.Name()) && g.Next != nil {
 | 
					
						
							|  |  |  | 			return plugin.NextOrFailure(g.Name(), g.Next, ctx, w, r)
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2022-02-10 08:58:46 -05:00
										 |  |  | 		// just return the last error received
 | 
					
						
							|  |  |  | 		return dns.RcodeServerFailure, err
 | 
					
						
							| 
									
										
										
										
											2020-07-15 14:59:45 -03:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-06 04:58:17 -07:00
										 |  |  | 	// If fallthrough is enabled, try the next plugin instead of returning SERVFAIL
 | 
					
						
							|  |  |  | 	if g.Fall.Through(state.Name()) && g.Next != nil {
 | 
					
						
							|  |  |  | 		return plugin.NextOrFailure(g.Name(), g.Next, ctx, w, r)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-15 14:59:45 -03:00
										 |  |  | 	return dns.RcodeServerFailure, ErrNoHealthy
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewGRPC returns a new GRPC.
 | 
					
						
							|  |  |  | func newGRPC() *GRPC {
 | 
					
						
							|  |  |  | 	g := &GRPC{
 | 
					
						
							| 
									
										
										
										
											2020-05-29 12:30:26 -04:00
										 |  |  | 		p: new(random),
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	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.
 | 
					
						
							| 
									
										
										
										
											2020-05-29 12:30:26 -04:00
										 |  |  | func (g *GRPC) list() []*Proxy { return g.p.List(g.proxies) }
 | 
					
						
							| 
									
										
										
										
											2019-03-14 08:12:28 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | const defaultTimeout = 5 * time.Second
 | 
					
						
							| 
									
										
										
										
											2020-07-15 14:59:45 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | var (
 | 
					
						
							|  |  |  | 	// ErrNoHealthy means no healthy proxies left.
 | 
					
						
							|  |  |  | 	ErrNoHealthy = errors.New("no healthy gRPC proxies")
 | 
					
						
							|  |  |  | )
 |