Add alternate option to forward plugin (#6681)
Allows the forward plugin to execute the next plugin based on the return code. Similar to the externally mainted alternate plugin https://github.com/coredns/alternate Based on the idea of chrisohaver@ in #6549 (comment) Also incoperated the request to rename `alternate` to `next` as an option I am having issues adding a proper test for functionality. Primarily, I do not know the code base enough and having multiple `dnstest.NewServer` with ResponseWriter does not work. From my testing these are "Singletons'' and only the last defined response writer is used for all servers Signed-off-by: Jasper Bernhardt <jasper.bernhardt@live.de>
This commit is contained in:
parent
3f388442cc
commit
2e9986c622
4 changed files with 84 additions and 0 deletions
|
@ -50,6 +50,7 @@ forward FROM TO... {
|
|||
policy random|round_robin|sequential
|
||||
health_check DURATION [no_rec] [domain FQDN]
|
||||
max_concurrent MAX
|
||||
next RCODE_1 [RCODE_2] [RCODE_3...]
|
||||
}
|
||||
~~~
|
||||
|
||||
|
@ -95,6 +96,7 @@ forward FROM TO... {
|
|||
response does not count as a health failure. When choosing a value for **MAX**, pick a number
|
||||
at least greater than the expected *upstream query rate* * *latency* of the upstream servers.
|
||||
As an upper bound for **MAX**, consider that each concurrent query will use about 2kb of memory.
|
||||
* `next` If the `RCODE` (i.e. `NXDOMAIN`) is returned by the remote then execute the next plugin. If no next plugin is defined, or the next plugin is not a `forward` plugin, this setting is ignored
|
||||
|
||||
Also note the TLS config is "global" for the whole forwarding proxy if you need a different
|
||||
`tls_servername` for different upstreams you're out of luck.
|
||||
|
@ -268,6 +270,21 @@ Or when you have multiple DoT upstreams with different `tls_servername`s, you ca
|
|||
}
|
||||
~~~
|
||||
|
||||
The following would try 1.2.3.4 first. If the response is `NXDOMAIN`, try 5.6.7.8. If the response from 5.6.7.8 is `NXDOMAIN`, try 9.0.1.2.
|
||||
|
||||
~~~ corefile
|
||||
. {
|
||||
forward . 1.2.3.4 {
|
||||
next NXDOMAIN
|
||||
}
|
||||
forward . 5.6.7.8 {
|
||||
next NXDOMAIN
|
||||
}
|
||||
forward . 9.0.1.2 {
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
## See Also
|
||||
|
||||
[RFC 7858](https://tools.ietf.org/html/rfc7858) for DNS over TLS.
|
||||
|
|
|
@ -43,6 +43,8 @@ type Forward struct {
|
|||
from string
|
||||
ignored []string
|
||||
|
||||
nextAlternateRcodes []int
|
||||
|
||||
tlsConfig *tls.Config
|
||||
tlsServerName string
|
||||
maxfails uint32
|
||||
|
@ -194,6 +196,15 @@ func (f *Forward) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
// Check if we have an alternate Rcode defined, check if we match on the code
|
||||
for _, alternateRcode := range f.nextAlternateRcodes {
|
||||
if alternateRcode == ret.Rcode && f.Next != nil { // In case we do not have a Next handler, just continue normally
|
||||
if _, ok := f.Next.(*Forward); ok { // Only continue if the next forwarder is also a Forworder
|
||||
return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteMsg(ret)
|
||||
return 0, nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coredns/caddy"
|
||||
|
@ -289,7 +290,22 @@ func parseBlock(c *caddy.Controller, f *Forward) error {
|
|||
}
|
||||
f.ErrLimitExceeded = errors.New("concurrent queries exceeded maximum " + c.Val())
|
||||
f.maxConcurrent = int64(n)
|
||||
case "next":
|
||||
args := c.RemainingArgs()
|
||||
if len(args) == 0 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
|
||||
for _, rcode := range args {
|
||||
var rc int
|
||||
var ok bool
|
||||
|
||||
if rc, ok = dns.StringToRcode[strings.ToUpper(rcode)]; !ok {
|
||||
return fmt.Errorf("%s is not a valid rcode", rcode)
|
||||
}
|
||||
|
||||
f.nextAlternateRcodes = append(f.nextAlternateRcodes, rc)
|
||||
}
|
||||
default:
|
||||
return c.Errf("unknown property '%s'", c.Val())
|
||||
}
|
||||
|
|
|
@ -342,3 +342,43 @@ func TestMultiForward(t *testing.T) {
|
|||
t.Error("expected third plugin to be last, but Next is not nil")
|
||||
}
|
||||
}
|
||||
func TestNextAlternate(t *testing.T) {
|
||||
testsValid := []struct {
|
||||
input string
|
||||
expected []int
|
||||
}{
|
||||
{"forward . 127.0.0.1 {\nnext NXDOMAIN\n}\n", []int{dns.RcodeNameError}},
|
||||
{"forward . 127.0.0.1 {\nnext SERVFAIL\n}\n", []int{dns.RcodeServerFailure}},
|
||||
{"forward . 127.0.0.1 {\nnext NXDOMAIN SERVFAIL\n}\n", []int{dns.RcodeNameError, dns.RcodeServerFailure}},
|
||||
{"forward . 127.0.0.1 {\nnext NXDOMAIN SERVFAIL REFUSED\n}\n", []int{dns.RcodeNameError, dns.RcodeServerFailure, dns.RcodeRefused}},
|
||||
}
|
||||
for i, test := range testsValid {
|
||||
c := caddy.NewTestController("dns", test.input)
|
||||
f, err := parseForward(c)
|
||||
forward := f[0]
|
||||
if err != nil {
|
||||
t.Errorf("Test %d: %v", i, err)
|
||||
}
|
||||
if len(forward.nextAlternateRcodes) != len(test.expected) {
|
||||
t.Errorf("Test %d: expected %d next rcodes, got %d", i, len(test.expected), len(forward.nextAlternateRcodes))
|
||||
}
|
||||
for j, rcode := range forward.nextAlternateRcodes {
|
||||
if rcode != test.expected[j] {
|
||||
t.Errorf("Test %d: expected next rcode %d, got %d", i, test.expected[j], rcode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testsInvalid := []string{
|
||||
"forward . 127.0.0.1 {\nnext\n}\n",
|
||||
"forward . 127.0.0.1 {\nnext INVALID\n}\n",
|
||||
"forward . 127.0.0.1 {\nnext NXDOMAIN INVALID\n}\n",
|
||||
}
|
||||
for i, test := range testsInvalid {
|
||||
c := caddy.NewTestController("dns", test)
|
||||
_, err := parseForward(c)
|
||||
if err == nil {
|
||||
t.Errorf("Test %d: expected error, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue