plugin/forward: configurable domain support for healthcheck (#5281)
* plugin/forward: configurable domain support for healthcheck Signed-off-by: hansedong <admin@yinxiaoluo.com>
This commit is contained in:
parent
e60c179194
commit
0622a6c66c
7 changed files with 113 additions and 35 deletions
|
@ -50,7 +50,7 @@ forward FROM TO... {
|
|||
tls CERT KEY CA
|
||||
tls_servername NAME
|
||||
policy random|round_robin|sequential
|
||||
health_check DURATION [no_rec]
|
||||
health_check DURATION [no_rec] [domain DOMAIN]
|
||||
max_concurrent MAX
|
||||
}
|
||||
~~~
|
||||
|
@ -90,6 +90,8 @@ forward FROM TO... {
|
|||
* `<duration>` - use a different duration for health checking, the default duration is 0.5s.
|
||||
* `no_rec` - optional argument that sets the RecursionDesired-flag of the dns-query used in health checking to `false`.
|
||||
The flag is default `true`.
|
||||
* `domain DOMAIN` - optional arguments that sets the domain of the dns-query used in health checking.
|
||||
If not configured, the requested domain name is `.`. `DOMAIN` is used to configure the domain name.
|
||||
* `max_concurrent` **MAX** will limit the number of concurrent queries to **MAX**. Any new query that would
|
||||
raise the number of concurrent queries above the **MAX** will result in a REFUSED response. This
|
||||
response does not count as a health failure. When choosing a value for **MAX**, pick a number
|
||||
|
@ -182,6 +184,18 @@ service with health checks.
|
|||
}
|
||||
~~~
|
||||
|
||||
Or configure other domain name for health check requests
|
||||
|
||||
~~~ corefile
|
||||
. {
|
||||
forward . tls://9.9.9.9 {
|
||||
tls_servername dns.quad9.net
|
||||
health_check 5s domain example.org
|
||||
}
|
||||
cache 30
|
||||
}
|
||||
~~~
|
||||
|
||||
Or with multiple upstreams from the same provider
|
||||
|
||||
~~~ corefile
|
||||
|
|
|
@ -56,7 +56,7 @@ type Forward struct {
|
|||
|
||||
// New returns a new Forward.
|
||||
func New() *Forward {
|
||||
f := &Forward{maxfails: 2, tlsConfig: new(tls.Config), expire: defaultExpire, p: new(random), from: ".", hcInterval: hcInterval, opts: options{forceTCP: false, preferUDP: false, hcRecursionDesired: true}}
|
||||
f := &Forward{maxfails: 2, tlsConfig: new(tls.Config), expire: defaultExpire, p: new(random), from: ".", hcInterval: hcInterval, opts: options{forceTCP: false, preferUDP: false, hcRecursionDesired: true, hcDomain: "."}}
|
||||
return f
|
||||
}
|
||||
|
||||
|
@ -234,6 +234,7 @@ type options struct {
|
|||
forceTCP bool
|
||||
preferUDP bool
|
||||
hcRecursionDesired bool
|
||||
hcDomain string
|
||||
}
|
||||
|
||||
var defaultTimeout = 5 * time.Second
|
||||
|
|
|
@ -16,6 +16,8 @@ type HealthChecker interface {
|
|||
SetTLSConfig(*tls.Config)
|
||||
SetRecursionDesired(bool)
|
||||
GetRecursionDesired() bool
|
||||
SetDomain(domain string)
|
||||
GetDomain() string
|
||||
SetTCPTransport()
|
||||
}
|
||||
|
||||
|
@ -23,6 +25,7 @@ type HealthChecker interface {
|
|||
type dnsHc struct {
|
||||
c *dns.Client
|
||||
recursionDesired bool
|
||||
domain string
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -31,7 +34,7 @@ var (
|
|||
)
|
||||
|
||||
// NewHealthChecker returns a new HealthChecker based on transport.
|
||||
func NewHealthChecker(trans string, recursionDesired bool) HealthChecker {
|
||||
func NewHealthChecker(trans string, recursionDesired bool, domain string) HealthChecker {
|
||||
switch trans {
|
||||
case transport.DNS, transport.TLS:
|
||||
c := new(dns.Client)
|
||||
|
@ -39,7 +42,7 @@ func NewHealthChecker(trans string, recursionDesired bool) HealthChecker {
|
|||
c.ReadTimeout = hcReadTimeout
|
||||
c.WriteTimeout = hcWriteTimeout
|
||||
|
||||
return &dnsHc{c: c, recursionDesired: recursionDesired}
|
||||
return &dnsHc{c: c, recursionDesired: recursionDesired, domain: domain}
|
||||
}
|
||||
|
||||
log.Warningf("No healthchecker for transport %q", trans)
|
||||
|
@ -58,6 +61,13 @@ 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"
|
||||
}
|
||||
|
@ -80,7 +90,7 @@ func (h *dnsHc) Check(p *Proxy) error {
|
|||
|
||||
func (h *dnsHc) send(addr string) error {
|
||||
ping := new(dns.Msg)
|
||||
ping.SetQuestion(".", dns.TypeNS)
|
||||
ping.SetQuestion(h.domain, dns.TypeNS)
|
||||
ping.MsgHdr.RecursionDesired = h.recursionDesired
|
||||
|
||||
m, _, err := h.c.Exchange(ping, addr)
|
||||
|
|
|
@ -242,3 +242,42 @@ func TestHealthNoMaxFails(t *testing.T) {
|
|||
t.Errorf("Expected number of health checks to be %d, got %d", 0, i1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthDomain(t *testing.T) {
|
||||
hcReadTimeout = 10 * time.Millisecond
|
||||
readTimeout = 10 * time.Millisecond
|
||||
defaultTimeout = 10 * time.Millisecond
|
||||
hcWriteTimeout = 10 * time.Millisecond
|
||||
hcDomain := "example.org."
|
||||
i := uint32(0)
|
||||
q := uint32(0)
|
||||
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
if atomic.LoadUint32(&q) == 0 { //drop the first query to trigger health-checking
|
||||
atomic.AddUint32(&q, 1)
|
||||
return
|
||||
}
|
||||
if r.Question[0].Name == hcDomain && r.RecursionDesired == true {
|
||||
atomic.AddUint32(&i, 1)
|
||||
}
|
||||
ret := new(dns.Msg)
|
||||
ret.SetReply(r)
|
||||
w.WriteMsg(ret)
|
||||
})
|
||||
defer s.Close()
|
||||
p := NewProxy(s.Addr, transport.DNS)
|
||||
p.health.SetDomain(hcDomain)
|
||||
f := New()
|
||||
f.SetProxy(p)
|
||||
defer f.OnShutdown()
|
||||
|
||||
req := new(dns.Msg)
|
||||
req.SetQuestion(".", dns.TypeNS)
|
||||
|
||||
f.ServeDNS(context.TODO(), &test.ResponseWriter{}, req)
|
||||
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
i1 := atomic.LoadUint32(&i)
|
||||
if i1 != 1 {
|
||||
t.Errorf("Expected number of health checks with Domain==%s to be %d, got %d", hcDomain, 1, i1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ func NewProxy(addr, trans string) *Proxy {
|
|||
probe: up.New(),
|
||||
transport: newTransport(addr),
|
||||
}
|
||||
p.health = NewHealthChecker(trans, true)
|
||||
p.health = NewHealthChecker(trans, true, ".")
|
||||
runtime.SetFinalizer(p, (*Proxy).finalizer)
|
||||
return p
|
||||
}
|
||||
|
|
|
@ -151,6 +151,7 @@ func parseStanza(c *caddy.Controller) (*Forward, error) {
|
|||
if f.opts.forceTCP && transports[i] != transport.TLS {
|
||||
f.proxies[i].health.SetTCPTransport()
|
||||
}
|
||||
f.proxies[i].health.SetDomain(f.opts.hcDomain)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
|
@ -187,11 +188,17 @@ func parseBlock(c *caddy.Controller, f *Forward) error {
|
|||
return fmt.Errorf("health_check can't be negative: %d", dur)
|
||||
}
|
||||
f.hcInterval = dur
|
||||
f.opts.hcDomain = "."
|
||||
|
||||
for c.NextArg() {
|
||||
switch hcOpts := c.Val(); hcOpts {
|
||||
case "no_rec":
|
||||
f.opts.hcRecursionDesired = false
|
||||
case "domain":
|
||||
if !c.NextArg() {
|
||||
return c.ArgErr()
|
||||
}
|
||||
f.opts.hcDomain = c.Val()
|
||||
default:
|
||||
return fmt.Errorf("health_check: unknown option %s", hcOpts)
|
||||
}
|
||||
|
|
|
@ -20,25 +20,27 @@ func TestSetup(t *testing.T) {
|
|||
expectedErr string
|
||||
}{
|
||||
// positive
|
||||
{"forward . 127.0.0.1", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
||||
{"forward . 127.0.0.1 {\nexcept miek.nl\n}\n", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
||||
{"forward . 127.0.0.1 {\nmax_fails 3\n}\n", false, ".", nil, 3, options{hcRecursionDesired: true}, ""},
|
||||
{"forward . 127.0.0.1 {\nforce_tcp\n}\n", false, ".", nil, 2, options{forceTCP: true, hcRecursionDesired: true}, ""},
|
||||
{"forward . 127.0.0.1 {\nprefer_udp\n}\n", false, ".", nil, 2, options{preferUDP: true, hcRecursionDesired: true}, ""},
|
||||
{"forward . 127.0.0.1 {\nforce_tcp\nprefer_udp\n}\n", false, ".", nil, 2, options{preferUDP: true, forceTCP: true, hcRecursionDesired: true}, ""},
|
||||
{"forward . 127.0.0.1:53", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
||||
{"forward . 127.0.0.1:8080", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
||||
{"forward . [::1]:53", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
||||
{"forward . [2003::1]:53", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
||||
{"forward . 127.0.0.1 \n", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
||||
{"forward 10.9.3.0/18 127.0.0.1", false, "0.9.10.in-addr.arpa.", nil, 2, options{hcRecursionDesired: true}, ""},
|
||||
{"forward . 127.0.0.1", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s domain example.org\n}\n", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "example.org"}, ""},
|
||||
{"forward . 127.0.0.1 {\nexcept miek.nl\n}\n", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . 127.0.0.1 {\nmax_fails 3\n}\n", false, ".", nil, 3, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . 127.0.0.1 {\nforce_tcp\n}\n", false, ".", nil, 2, options{forceTCP: true, hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . 127.0.0.1 {\nprefer_udp\n}\n", false, ".", nil, 2, options{preferUDP: true, hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . 127.0.0.1 {\nforce_tcp\nprefer_udp\n}\n", false, ".", nil, 2, options{preferUDP: true, forceTCP: true, hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . 127.0.0.1:53", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . 127.0.0.1:8080", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . [::1]:53", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . [2003::1]:53", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward . 127.0.0.1 \n", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
{"forward 10.9.3.0/18 127.0.0.1", false, "0.9.10.in-addr.arpa.", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||
// negative
|
||||
{"forward . a27.0.0.1", true, "", nil, 0, options{hcRecursionDesired: true}, "not an IP"},
|
||||
{"forward . 127.0.0.1 {\nblaatl\n}\n", true, "", nil, 0, options{hcRecursionDesired: true}, "unknown property"},
|
||||
{"forward . a27.0.0.1", true, "", nil, 0, options{hcRecursionDesired: true, hcDomain: "."}, "not an IP"},
|
||||
{"forward . 127.0.0.1 {\nblaatl\n}\n", true, "", nil, 0, options{hcRecursionDesired: true, hcDomain: "."}, "unknown property"},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s domain\n}\n", true, "", nil, 0, options{hcRecursionDesired: true, hcDomain: "."}, "Wrong argument count or unexpected line ending after 'domain'"},
|
||||
{`forward . ::1
|
||||
forward com ::2`, true, "", nil, 0, options{hcRecursionDesired: true}, "plugin"},
|
||||
{"forward . https://127.0.0.1 \n", true, ".", nil, 2, options{hcRecursionDesired: true}, "'https' is not supported as a destination protocol in forward: https://127.0.0.1"},
|
||||
{"forward xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 127.0.0.1 \n", true, ".", nil, 2, options{hcRecursionDesired: true}, "unable to normalize 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'"},
|
||||
forward com ::2`, true, "", nil, 0, options{hcRecursionDesired: true, hcDomain: "."}, "plugin"},
|
||||
{"forward . https://127.0.0.1 \n", true, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, "'https' is not supported as a destination protocol in forward: https://127.0.0.1"},
|
||||
{"forward xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 127.0.0.1 \n", true, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, "unable to normalize 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
|
@ -221,18 +223,23 @@ func TestSetupMaxConcurrent(t *testing.T) {
|
|||
|
||||
func TestSetupHealthCheck(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
shouldErr bool
|
||||
expectedVal bool
|
||||
expectedErr string
|
||||
input string
|
||||
shouldErr bool
|
||||
expectedRecVal bool
|
||||
expectedDomain string
|
||||
expectedErr string
|
||||
}{
|
||||
// positive
|
||||
{"forward . 127.0.0.1\n", false, true, ""},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s\n}\n", false, true, ""},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s no_rec\n}\n", false, false, ""},
|
||||
{"forward . 127.0.0.1\n", false, true, ".", ""},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s\n}\n", false, true, ".", ""},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s no_rec\n}\n", false, false, ".", ""},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s no_rec domain example.org\n}\n", false, false, "example.org", ""},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s domain example.org\n}\n", false, true, "example.org", ""},
|
||||
// negative
|
||||
{"forward . 127.0.0.1 {\nhealth_check no_rec\n}\n", true, true, "time: invalid duration"},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s rec\n}\n", true, true, "health_check: unknown option rec"},
|
||||
{"forward . 127.0.0.1 {\nhealth_check no_rec\n}\n", true, true, ".", "time: invalid duration"},
|
||||
{"forward . 127.0.0.1 {\nhealth_check domain example.org\n}\n", true, true, "example.org", "time: invalid duration"},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s rec\n}\n", true, true, ".", "health_check: unknown option rec"},
|
||||
{"forward . 127.0.0.1 {\nhealth_check 0.5s domain\n}\n", true, true, ".", "Wrong argument count or unexpected line ending after 'domain'"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
|
@ -247,13 +254,13 @@ func TestSetupHealthCheck(t *testing.T) {
|
|||
if !test.shouldErr {
|
||||
t.Errorf("Test %d: expected no error but found one for input %s, got: %v", i, test.input, err)
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), test.expectedErr) {
|
||||
t.Errorf("Test %d: expected error to contain: %v, found error: %v, input: %s", i, test.expectedErr, err, test.input)
|
||||
}
|
||||
}
|
||||
if !test.shouldErr && (f.opts.hcRecursionDesired != test.expectedVal || f.proxies[0].health.GetRecursionDesired() != test.expectedVal) {
|
||||
t.Errorf("Test %d: expected: %t, got: %d", i, test.expectedVal, f.maxConcurrent)
|
||||
if !test.shouldErr && (f.opts.hcRecursionDesired != test.expectedRecVal || f.proxies[0].health.GetRecursionDesired() != test.expectedRecVal ||
|
||||
f.opts.hcDomain != test.expectedDomain || f.proxies[0].health.GetDomain() != test.expectedDomain) {
|
||||
t.Errorf("Test %d: expectedRec: %v, got: %v. expectedDomain: %s, got: %s. ", i, test.expectedRecVal, f.opts.hcRecursionDesired, test.expectedDomain, f.opts.hcDomain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue