1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
package proxy
import (
"crypto/tls"
"sync/atomic"
"time"
"github.com/coredns/coredns/plugin/pkg/log"
"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)
GetTLSConfig() *tls.Config
SetRecursionDesired(bool)
GetRecursionDesired() bool
SetDomain(domain string)
GetDomain() string
SetTCPTransport()
GetReadTimeout() time.Duration
SetReadTimeout(time.Duration)
GetWriteTimeout() time.Duration
SetWriteTimeout(time.Duration)
}
// dnsHc is a health checker for a DNS endpoint (DNS, and DoT).
type dnsHc struct {
c *dns.Client
recursionDesired bool
domain string
}
// 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 = 1 * time.Second
c.WriteTimeout = 1 * time.Second
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) GetTLSConfig() *tls.Config {
return h.c.TLSConfig
}
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"
}
func (h *dnsHc) GetReadTimeout() time.Duration {
return h.c.ReadTimeout
}
func (h *dnsHc) SetReadTimeout(t time.Duration) {
h.c.ReadTimeout = t
}
func (h *dnsHc) GetWriteTimeout() time.Duration {
return h.c.WriteTimeout
}
func (h *dnsHc) SetWriteTimeout(t time.Duration) {
h.c.WriteTimeout = t
}
// 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)
p.incrementFails()
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
}
|