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 CERT KEY CA
|
||||||
tls_servername NAME
|
tls_servername NAME
|
||||||
policy random|round_robin|sequential
|
policy random|round_robin|sequential
|
||||||
health_check DURATION [no_rec]
|
health_check DURATION [no_rec] [domain DOMAIN]
|
||||||
max_concurrent MAX
|
max_concurrent MAX
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
@ -90,6 +90,8 @@ forward FROM TO... {
|
||||||
* `<duration>` - use a different duration for health checking, the default duration is 0.5s.
|
* `<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`.
|
* `no_rec` - optional argument that sets the RecursionDesired-flag of the dns-query used in health checking to `false`.
|
||||||
The flag is default `true`.
|
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
|
* `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
|
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
|
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
|
Or with multiple upstreams from the same provider
|
||||||
|
|
||||||
~~~ corefile
|
~~~ corefile
|
||||||
|
|
|
@ -56,7 +56,7 @@ type Forward struct {
|
||||||
|
|
||||||
// New returns a new Forward.
|
// New returns a new Forward.
|
||||||
func 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
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,6 +234,7 @@ type options struct {
|
||||||
forceTCP bool
|
forceTCP bool
|
||||||
preferUDP bool
|
preferUDP bool
|
||||||
hcRecursionDesired bool
|
hcRecursionDesired bool
|
||||||
|
hcDomain string
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultTimeout = 5 * time.Second
|
var defaultTimeout = 5 * time.Second
|
||||||
|
|
|
@ -16,6 +16,8 @@ type HealthChecker interface {
|
||||||
SetTLSConfig(*tls.Config)
|
SetTLSConfig(*tls.Config)
|
||||||
SetRecursionDesired(bool)
|
SetRecursionDesired(bool)
|
||||||
GetRecursionDesired() bool
|
GetRecursionDesired() bool
|
||||||
|
SetDomain(domain string)
|
||||||
|
GetDomain() string
|
||||||
SetTCPTransport()
|
SetTCPTransport()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +25,7 @@ type HealthChecker interface {
|
||||||
type dnsHc struct {
|
type dnsHc struct {
|
||||||
c *dns.Client
|
c *dns.Client
|
||||||
recursionDesired bool
|
recursionDesired bool
|
||||||
|
domain string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -31,7 +34,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewHealthChecker returns a new HealthChecker based on transport.
|
// 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 {
|
switch trans {
|
||||||
case transport.DNS, transport.TLS:
|
case transport.DNS, transport.TLS:
|
||||||
c := new(dns.Client)
|
c := new(dns.Client)
|
||||||
|
@ -39,7 +42,7 @@ func NewHealthChecker(trans string, recursionDesired bool) HealthChecker {
|
||||||
c.ReadTimeout = hcReadTimeout
|
c.ReadTimeout = hcReadTimeout
|
||||||
c.WriteTimeout = hcWriteTimeout
|
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)
|
log.Warningf("No healthchecker for transport %q", trans)
|
||||||
|
@ -58,6 +61,13 @@ func (h *dnsHc) GetRecursionDesired() bool {
|
||||||
return h.recursionDesired
|
return h.recursionDesired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *dnsHc) SetDomain(domain string) {
|
||||||
|
h.domain = domain
|
||||||
|
}
|
||||||
|
func (h *dnsHc) GetDomain() string {
|
||||||
|
return h.domain
|
||||||
|
}
|
||||||
|
|
||||||
func (h *dnsHc) SetTCPTransport() {
|
func (h *dnsHc) SetTCPTransport() {
|
||||||
h.c.Net = "tcp"
|
h.c.Net = "tcp"
|
||||||
}
|
}
|
||||||
|
@ -80,7 +90,7 @@ func (h *dnsHc) Check(p *Proxy) error {
|
||||||
|
|
||||||
func (h *dnsHc) send(addr string) error {
|
func (h *dnsHc) send(addr string) error {
|
||||||
ping := new(dns.Msg)
|
ping := new(dns.Msg)
|
||||||
ping.SetQuestion(".", dns.TypeNS)
|
ping.SetQuestion(h.domain, dns.TypeNS)
|
||||||
ping.MsgHdr.RecursionDesired = h.recursionDesired
|
ping.MsgHdr.RecursionDesired = h.recursionDesired
|
||||||
|
|
||||||
m, _, err := h.c.Exchange(ping, addr)
|
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)
|
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(),
|
probe: up.New(),
|
||||||
transport: newTransport(addr),
|
transport: newTransport(addr),
|
||||||
}
|
}
|
||||||
p.health = NewHealthChecker(trans, true)
|
p.health = NewHealthChecker(trans, true, ".")
|
||||||
runtime.SetFinalizer(p, (*Proxy).finalizer)
|
runtime.SetFinalizer(p, (*Proxy).finalizer)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,7 @@ func parseStanza(c *caddy.Controller) (*Forward, error) {
|
||||||
if f.opts.forceTCP && transports[i] != transport.TLS {
|
if f.opts.forceTCP && transports[i] != transport.TLS {
|
||||||
f.proxies[i].health.SetTCPTransport()
|
f.proxies[i].health.SetTCPTransport()
|
||||||
}
|
}
|
||||||
|
f.proxies[i].health.SetDomain(f.opts.hcDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
return f, nil
|
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)
|
return fmt.Errorf("health_check can't be negative: %d", dur)
|
||||||
}
|
}
|
||||||
f.hcInterval = dur
|
f.hcInterval = dur
|
||||||
|
f.opts.hcDomain = "."
|
||||||
|
|
||||||
for c.NextArg() {
|
for c.NextArg() {
|
||||||
switch hcOpts := c.Val(); hcOpts {
|
switch hcOpts := c.Val(); hcOpts {
|
||||||
case "no_rec":
|
case "no_rec":
|
||||||
f.opts.hcRecursionDesired = false
|
f.opts.hcRecursionDesired = false
|
||||||
|
case "domain":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return c.ArgErr()
|
||||||
|
}
|
||||||
|
f.opts.hcDomain = c.Val()
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("health_check: unknown option %s", hcOpts)
|
return fmt.Errorf("health_check: unknown option %s", hcOpts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,25 +20,27 @@ func TestSetup(t *testing.T) {
|
||||||
expectedErr string
|
expectedErr string
|
||||||
}{
|
}{
|
||||||
// positive
|
// positive
|
||||||
{"forward . 127.0.0.1", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
{"forward . 127.0.0.1", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||||
{"forward . 127.0.0.1 {\nexcept miek.nl\n}\n", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
{"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 {\nmax_fails 3\n}\n", false, ".", nil, 3, options{hcRecursionDesired: true}, ""},
|
{"forward . 127.0.0.1 {\nexcept miek.nl\n}\n", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||||
{"forward . 127.0.0.1 {\nforce_tcp\n}\n", false, ".", nil, 2, options{forceTCP: true, hcRecursionDesired: true}, ""},
|
{"forward . 127.0.0.1 {\nmax_fails 3\n}\n", false, ".", nil, 3, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||||
{"forward . 127.0.0.1 {\nprefer_udp\n}\n", false, ".", nil, 2, options{preferUDP: true, hcRecursionDesired: true}, ""},
|
{"forward . 127.0.0.1 {\nforce_tcp\n}\n", false, ".", nil, 2, options{forceTCP: 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}, ""},
|
{"forward . 127.0.0.1 {\nprefer_udp\n}\n", false, ".", nil, 2, options{preferUDP: true, hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||||
{"forward . 127.0.0.1:53", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
{"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:8080", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
{"forward . 127.0.0.1:53", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||||
{"forward . [::1]:53", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
{"forward . 127.0.0.1:8080", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||||
{"forward . [2003::1]:53", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
{"forward . [::1]:53", false, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, ""},
|
||||||
{"forward . 127.0.0.1 \n", false, ".", nil, 2, options{hcRecursionDesired: true}, ""},
|
{"forward . [2003::1]:53", 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}, ""},
|
{"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
|
// negative
|
||||||
{"forward . a27.0.0.1", true, "", nil, 0, options{hcRecursionDesired: true}, "not an IP"},
|
{"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}, "unknown property"},
|
{"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 . ::1
|
||||||
forward com ::2`, true, "", nil, 0, options{hcRecursionDesired: true}, "plugin"},
|
forward com ::2`, true, "", nil, 0, options{hcRecursionDesired: true, hcDomain: "."}, "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 . 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}, "unable to normalize 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'"},
|
{"forward xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 127.0.0.1 \n", true, ".", nil, 2, options{hcRecursionDesired: true, hcDomain: "."}, "unable to normalize 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
@ -223,16 +225,21 @@ func TestSetupHealthCheck(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
shouldErr bool
|
shouldErr bool
|
||||||
expectedVal bool
|
expectedRecVal bool
|
||||||
|
expectedDomain string
|
||||||
expectedErr string
|
expectedErr string
|
||||||
}{
|
}{
|
||||||
// positive
|
// positive
|
||||||
{"forward . 127.0.0.1\n", false, true, ""},
|
{"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\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\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
|
// negative
|
||||||
{"forward . 127.0.0.1 {\nhealth_check no_rec\n}\n", true, true, "time: invalid duration"},
|
{"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 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 {
|
for i, test := range tests {
|
||||||
|
@ -247,13 +254,13 @@ func TestSetupHealthCheck(t *testing.T) {
|
||||||
if !test.shouldErr {
|
if !test.shouldErr {
|
||||||
t.Errorf("Test %d: expected no error but found one for input %s, got: %v", i, test.input, err)
|
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) {
|
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)
|
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) {
|
if !test.shouldErr && (f.opts.hcRecursionDesired != test.expectedRecVal || f.proxies[0].health.GetRecursionDesired() != test.expectedRecVal ||
|
||||||
t.Errorf("Test %d: expected: %t, got: %d", i, test.expectedVal, f.maxConcurrent)
|
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