coredns/plugin/forward/health.go
hansedong 0622a6c66c
plugin/forward: configurable domain support for healthcheck (#5281)
* plugin/forward: configurable domain support for healthcheck

Signed-off-by: hansedong <admin@yinxiaoluo.com>
2022-04-12 12:39:48 -04:00

106 lines
2.4 KiB
Go

package forward
import (
"crypto/tls"
"sync/atomic"
"time"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/miekg/dns"
)
// HealthChecker checks the upstream health.
type HealthChecker interface {
Check(*Proxy) error
SetTLSConfig(*tls.Config)
SetRecursionDesired(bool)
GetRecursionDesired() bool
SetDomain(domain string)
GetDomain() string
SetTCPTransport()
}
// dnsHc is a health checker for a DNS endpoint (DNS, and DoT).
type dnsHc struct {
c *dns.Client
recursionDesired bool
domain string
}
var (
hcReadTimeout = 1 * time.Second
hcWriteTimeout = 1 * time.Second
)
// NewHealthChecker returns a new HealthChecker based on transport.
func NewHealthChecker(trans string, recursionDesired bool, domain string) HealthChecker {
switch trans {
case transport.DNS, transport.TLS:
c := new(dns.Client)
c.Net = "udp"
c.ReadTimeout = hcReadTimeout
c.WriteTimeout = hcWriteTimeout
return &dnsHc{c: c, recursionDesired: recursionDesired, domain: domain}
}
log.Warningf("No healthchecker for transport %q", trans)
return nil
}
func (h *dnsHc) SetTLSConfig(cfg *tls.Config) {
h.c.Net = "tcp-tls"
h.c.TLSConfig = cfg
}
func (h *dnsHc) SetRecursionDesired(recursionDesired bool) {
h.recursionDesired = recursionDesired
}
func (h *dnsHc) GetRecursionDesired() bool {
return h.recursionDesired
}
func (h *dnsHc) SetDomain(domain string) {
h.domain = domain
}
func (h *dnsHc) GetDomain() string {
return h.domain
}
func (h *dnsHc) SetTCPTransport() {
h.c.Net = "tcp"
}
// For HC we send to . IN NS +[no]rec message to the upstream. Dial timeouts and empty
// replies are considered fails, basically anything else constitutes a healthy upstream.
// Check is used as the up.Func in the up.Probe.
func (h *dnsHc) Check(p *Proxy) error {
err := h.send(p.addr)
if err != nil {
HealthcheckFailureCount.WithLabelValues(p.addr).Add(1)
atomic.AddUint32(&p.fails, 1)
return err
}
atomic.StoreUint32(&p.fails, 0)
return nil
}
func (h *dnsHc) send(addr string) error {
ping := new(dns.Msg)
ping.SetQuestion(h.domain, dns.TypeNS)
ping.MsgHdr.RecursionDesired = h.recursionDesired
m, _, err := h.c.Exchange(ping, addr)
// If we got a header, we're alright, basically only care about I/O errors 'n stuff.
if err != nil && m != nil {
// Silly check, something sane came back.
if m.Response || m.Opcode == dns.OpcodeQuery {
err = nil
}
}
return err
}