mirror of
https://github.com/coredns/coredns.git
synced 2026-07-10 09:40:12 -04:00
* fix(dnssec): avoid caching empty signing results (#7996) Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * save only Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * do #8008 Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Fix Address used if tcp Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Fix bad using of dialer type Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * core: Add full TSIG verification in gRPC transport (#8006) * core: Add full TSIG verification in gRPC transport This PR add full TSIG verification in gRPC using dns.TsigVerify() so invalid signatures and timestamps are correctly detected instead of only checking key presence. Signed-off-by: Yong Tang <yong.tang.github@outlook.com> * Fix Signed-off-by: Yong Tang <yong.tang.github@outlook.com> * Fix Signed-off-by: Yong Tang <yong.tang.github@outlook.com> --------- Signed-off-by: Yong Tang <yong.tang.github@outlook.com> Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * core: Add full TSIG verification in QUIC transport (#8007) * core: Add full TSIG verification in QUIC transport This PR add full TSIG verification in QUIC using dns.TsigVerify() Signed-off-by: Yong Tang <yong.tang.github@outlook.com> * Fix Signed-off-by: Yong Tang <yong.tang.github@outlook.com> --------- Signed-off-by: Yong Tang <yong.tang.github@outlook.com> Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix(test): deduplicate TSIG test helpers (#8009) Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix(dnssec): return nil sigs on sign error (#7999) Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix(dnssec): return nil from ParseKeyFile on error (#8000) Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix(dnsserver): allow view server blocks in any declaration order (#8001) When using the view plugin, filtered and unfiltered server blocks can share the same zone and port. The zone overlap validation rejected this configuration when the unfiltered block was not declared last, because filtered configs treated an already-registered zone as an error. Skip the 'already defined' check for configs that have filter functions, since they are expected to coexist with an unfiltered catch-all block on the same zone/port. Fixes #7733 Signed-off-by: umut-polat <52835619+umut-polat@users.noreply.github.com> Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix(doh): use per-connection local address for PROXY protocol (#8005) Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix(transfer): batch AXFR records by message size instead of count (#8002) Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix(tls): use temp dir for keylog test path (#8010) Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Rename local_address option to source_address Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Rename local_address also in readme Add test Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Resolve change request in pr Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix ci lint Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Improve doc on source_address routing needs Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Remove added timeout Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * add use of source address also for health check query Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * remove untrailing newline from health_test.go Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix file format Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Update plugin/forward/setup_test.go Co-authored-by: Ville Vesilehto <ville@vesilehto.fi> Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Remove dead code in TestHealthLocalAddress Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Fix misspelling Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * Try to set default timeout Signed-off-by: Filippo <filippo.ferrazini@gmail.com> * fix format in health.go Signed-off-by: Filippo <filippo.ferrazini@gmail.com> --------- Signed-off-by: Filippo <filippo.ferrazini@gmail.com> Signed-off-by: Yong Tang <yong.tang.github@outlook.com> Signed-off-by: umut-polat <52835619+umut-polat@users.noreply.github.com> Co-authored-by: Ville Vesilehto <ville@vesilehto.fi> Co-authored-by: Yong Tang <yong.tang.github@outlook.com> Co-authored-by: Umut Polat <52835619+umut-polat@users.noreply.github.com> Co-authored-by: Cedric Wang <wangzongqi@msn.com>
909 lines
29 KiB
Go
909 lines
29 KiB
Go
package forward
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/coredns/caddy"
|
|
"github.com/coredns/coredns/core/dnsserver"
|
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
|
"github.com/coredns/coredns/plugin/pkg/proxy"
|
|
"github.com/coredns/coredns/plugin/test"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
func TestSetup(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
shouldErr bool
|
|
expectedFrom string
|
|
expectedIgnored []string
|
|
expectedFails uint32
|
|
expectedOpts proxy.Options
|
|
expectedErr string
|
|
}{
|
|
// positive
|
|
{"forward . 127.0.0.1", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . 127.0.0.1 {\nhealth_check 0.5s domain example.org\n}\n", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "example.org."}, ""},
|
|
{"forward . 127.0.0.1 {\nexcept miek.nl\n}\n", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . 127.0.0.1 {\nmax_fails 3\n}\n", false, ".", nil, 3, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . 127.0.0.1 {\nforce_tcp\n}\n", false, ".", nil, 2, proxy.Options{ForceTCP: true, HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . 127.0.0.1 {\nprefer_udp\n}\n", false, ".", nil, 2, proxy.Options{PreferUDP: true, HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . 127.0.0.1 {\nforce_tcp\nprefer_udp\n}\n", false, ".", nil, 2, proxy.Options{PreferUDP: true, ForceTCP: true, HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . 127.0.0.1:53", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . 127.0.0.1:8080", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . [::1]:53", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . [2003::1]:53", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . 127.0.0.1 \n", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward 10.9.3.0/18 127.0.0.1", false, "0.9.10.in-addr.arpa.", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{`forward . ::1
|
|
forward com ::2`, false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, "plugin"},
|
|
{"forward . tls://[2400:3200::1%dns.alidns.com]:853 {\ntls\n}\n", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
{"forward . https://127.0.0.1 \n", false, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, ""},
|
|
// negative
|
|
{"forward . https://1.1.1.1/ \n", true, "", nil, 0, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, "paths are not allowed in HTTPS upstream addresses"},
|
|
{"forward . a27.0.0.1", true, "", nil, 0, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, "failed to resolve"},
|
|
{"forward . 127.0.0.1 {\nblaatl\n}\n", true, "", nil, 0, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, "unknown property"},
|
|
{"forward . 127.0.0.1 {\nhealth_check 0.5s domain\n}\n", true, "", nil, 0, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, "Wrong argument count or unexpected line ending after 'domain'"},
|
|
{"forward xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 127.0.0.1 \n", true, ".", nil, 2, proxy.Options{HCRecursionDesired: true, HCDomain: "."}, "unable to normalize 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if test.shouldErr && err == nil {
|
|
t.Errorf("Test %d: expected error but found %s for input %s", i, err, test.input)
|
|
}
|
|
|
|
if err != nil {
|
|
if !test.shouldErr {
|
|
t.Fatalf("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 := fs[0]
|
|
if f.from != test.expectedFrom {
|
|
t.Errorf("Test %d: expected: %s, got: %s", i, test.expectedFrom, f.from)
|
|
}
|
|
if test.expectedIgnored != nil {
|
|
if !reflect.DeepEqual(f.ignored, test.expectedIgnored) {
|
|
t.Errorf("Test %d: expected: %q, actual: %q", i, test.expectedIgnored, f.ignored)
|
|
}
|
|
}
|
|
if f.maxfails != test.expectedFails {
|
|
t.Errorf("Test %d: expected: %d, got: %d", i, test.expectedFails, f.maxfails)
|
|
}
|
|
if f.opts != test.expectedOpts {
|
|
t.Errorf("Test %d: expected: %v, got: %v", i, test.expectedOpts, f.opts)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSourceAddress(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expectedSourceAddress net.IP
|
|
expectedErr string
|
|
}{
|
|
|
|
{"forward . 127.0.0.1 {\nsource_address 192.0.2.1\n}\n", net.ParseIP("192.0.2.1"), ""},
|
|
{"forward . 127.0.0.1 {\nsource_address not-an-ip\n}\n", nil, "invalid IP address"},
|
|
{"forward . 127.0.0.1 {\nsource_address 2001:0db8:85a3:0000:1319:8a2e:0370:7344\n}\n", net.ParseIP("2001:0db8:85a3:0000:1319:8a2e:0370:7344"), ""},
|
|
{"forward . 127.0.0.1 {\nsource_address ::ffff:192.0.2.1\n}\n", net.ParseIP("192.0.2.1"), ""},
|
|
{"forward . 127.0.0.1 {\nsource_address \n}\n", nil, "Error during parsing: Wrong argument count or unexpected line ending after 'source_address'"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if test.expectedErr != "" && err == nil {
|
|
t.Errorf("Test %d: expected error but found %s for input %s", i, err, test.input)
|
|
}
|
|
if err != nil {
|
|
if test.expectedErr == "" {
|
|
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.expectedErr == "" {
|
|
f := fs[0]
|
|
if !test.expectedSourceAddress.Equal(f.sourceAddress) {
|
|
t.Errorf("Test %d: expected: %v, got: %v", i, test.expectedSourceAddress, f.sourceAddress)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSplitZone(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expectedHost string
|
|
expectedZone string
|
|
}{
|
|
{
|
|
"tls://127.0.0.1%example.net:854", "tls://127.0.0.1:854", "example.net",
|
|
}, {
|
|
"tls://127.0.0.1%example.net", "tls://127.0.0.1", "example.net",
|
|
}, {
|
|
"tls://127.0.0.1:854", "tls://127.0.0.1:854", "",
|
|
}, {
|
|
"dns://127.0.0.1", "dns://127.0.0.1", "",
|
|
}, {
|
|
"foo%bar:baz", "foo:baz", "bar",
|
|
}, {
|
|
"tls://[::1%example.net]:853", "tls://[::1]:853", "example.net",
|
|
},
|
|
}
|
|
for i, test := range tests {
|
|
host, zone := splitZone(test.input)
|
|
|
|
if host != test.expectedHost {
|
|
t.Errorf("Test %d: expected host %q, actual: %q", i, test.expectedHost, host)
|
|
}
|
|
if zone != test.expectedZone {
|
|
t.Errorf("Test %d: expected zone %q, actual: %q", i, test.expectedZone, zone)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetupTLS(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
shouldErr bool
|
|
expectedServerName string
|
|
expectedErr string
|
|
}{
|
|
// positive
|
|
{`forward . tls://127.0.0.1 {
|
|
tls_servername dns
|
|
}`, false, "dns", ""},
|
|
{`forward . tls://127.0.0.1%example.net {
|
|
tls
|
|
}`, false, "example.net", ""},
|
|
{`forward . tls://127.0.0.1%example.net:854 tls://127.0.0.2%example.net tls://fe80::1%example.com {
|
|
tls
|
|
}`, false, "example.net", ""},
|
|
{`forward . tls://127.0.0.1%example.net:854 {
|
|
tls
|
|
}`, false, "example.net", ""},
|
|
// SNI specifications clash test
|
|
{`forward . tls://127.0.0.1%example.net:854 {
|
|
tls_servername foo
|
|
}`, true, "", "both forward ('foo') and proxy level ('example.net') TLS servernames are set for upstream proxy 'tls://127.0.0.1:854'"},
|
|
{`forward . 127.0.0.1 {
|
|
tls_servername dns
|
|
}`, false, "", ""},
|
|
{`forward . 127.0.0.1 {
|
|
tls
|
|
}`, false, "", ""},
|
|
{`forward . tls://127.0.0.1`, false, "", ""},
|
|
{`forward . tls://[2400:3200::1%dns.alidns.com]:853 {
|
|
tls
|
|
}`, false, "dns.alidns.com", ""},
|
|
{`forward . tls://[2400:3200::1]:853 {
|
|
tls
|
|
tls_servername dns.alidns.com
|
|
}`, false, "dns.alidns.com", ""},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if test.shouldErr && err == nil {
|
|
t.Errorf("Test %d: expected error but found %s for input %s", i, err, test.input)
|
|
}
|
|
|
|
if err != nil {
|
|
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)
|
|
}
|
|
continue
|
|
}
|
|
/*
|
|
if len(fs) == 0 {
|
|
continue
|
|
}
|
|
*/
|
|
|
|
f := fs[0]
|
|
|
|
if !test.shouldErr && test.expectedServerName != "" && test.expectedServerName != f.proxies[0].GetTransport().GetTLSConfig().ServerName {
|
|
t.Errorf("Test %d: expected server name: %q, actual: %q", i, test.expectedServerName, f.proxies[0].GetTransport().GetTLSConfig().ServerName)
|
|
}
|
|
|
|
if !test.shouldErr && test.expectedServerName != "" && test.expectedServerName != f.proxies[0].GetHealthchecker().GetTLSConfig().ServerName {
|
|
t.Errorf("Test %d: expected server name: %q, actual: %q", i, test.expectedServerName, f.proxies[0].GetHealthchecker().GetTLSConfig().ServerName)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetupTLSclientSessionCacheCount(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
}{
|
|
{`forward . tls://127.0.0.1%foo tls://127.0.0.2%foo tls://127.0.0.3%foo tls://127.0.0.4%bar tls://127.0.0.5%bar { }`},
|
|
{`forward . tls://127.0.0.1%foo tls://127.0.0.2%foo tls://127.0.0.3%bar tls://127.0.0.4%bar tls://127.0.0.5%bar { }`},
|
|
}
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
if err != nil {
|
|
t.Errorf("Test %d: expected no error but found one for input %s, got: %v", i, test.input, err)
|
|
}
|
|
|
|
if fs[0].proxies[0].GetTransport().GetTLSConfig() == fs[0].proxies[len(fs[0].proxies)-1].GetTransport().GetTLSConfig() {
|
|
t.Errorf("Test %d: tlsConfig is the same for both the first and last proxies", i)
|
|
}
|
|
if fs[0].proxies[0].GetTransport().GetTLSConfig() != fs[0].proxies[1].GetTransport().GetTLSConfig() {
|
|
t.Errorf("Test %d: tlsConfig differs for the first two proxies", i)
|
|
}
|
|
if fs[0].proxies[len(fs[0].proxies)-1].GetTransport().GetTLSConfig() != fs[0].proxies[len(fs[0].proxies)-2].GetTransport().GetTLSConfig() {
|
|
t.Errorf("Test %d: tlsConfig differs for the last two proxies", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetupResolvconf(t *testing.T) {
|
|
const resolv = "resolv.conf"
|
|
if err := os.WriteFile(resolv,
|
|
[]byte(`nameserver 10.10.255.252
|
|
nameserver 10.10.255.253`), 0666); err != nil {
|
|
t.Fatalf("Failed to write resolv.conf file: %s", err)
|
|
}
|
|
defer os.Remove(resolv)
|
|
|
|
const resolvIPV6 = "resolv-ipv6.conf"
|
|
if err := os.WriteFile(resolvIPV6,
|
|
[]byte(`nameserver 0388:d254:7aec:6892:9f7f:e93b:5806:1b0f%en0`), 0666); err != nil {
|
|
t.Fatalf("Failed to write %v file: %s", resolvIPV6, err)
|
|
}
|
|
defer os.Remove(resolvIPV6)
|
|
|
|
const emptyResolv = "empty.conf"
|
|
if err := os.WriteFile(emptyResolv,
|
|
[]byte(`# nameserver 1.1.1.1
|
|
# nameserver 1.0.0.1`), 0666); err != nil {
|
|
t.Fatalf("Failed to write empty.conf file: %s", err)
|
|
}
|
|
defer os.Remove(emptyResolv)
|
|
|
|
tests := []struct {
|
|
input string
|
|
shouldErr bool
|
|
expectedErr string
|
|
expectedNames []string
|
|
}{
|
|
// pass
|
|
{`forward . ` + resolv, false, "", []string{"10.10.255.252:53", "10.10.255.253:53"}},
|
|
// fail
|
|
{`forward . /dev/null`, true, "no valid upstream addresses found", nil},
|
|
// IPV6 with local zone
|
|
{`forward . ` + resolvIPV6, false, "", []string{"[0388:d254:7aec:6892:9f7f:e93b:5806:1b0f]:53"}},
|
|
// pass when empty forward file is found
|
|
{`forward . ` + emptyResolv + ` 127.0.0.1`, false, "", []string{"127.0.0.1:53"}},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if test.shouldErr && err == nil {
|
|
t.Errorf("Test %d: expected error but found %s for input %s", i, err, test.input)
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
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 {
|
|
continue
|
|
}
|
|
|
|
f := fs[0]
|
|
for j, n := range test.expectedNames {
|
|
addr := f.proxies[j].Addr()
|
|
if n != addr {
|
|
t.Errorf("Test %d, expected %q, got %q", j, n, addr)
|
|
}
|
|
}
|
|
|
|
for _, p := range f.proxies {
|
|
p.Healthcheck() // this should almost always err, we don't care it shouldn't crash
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetupMaxConcurrent(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
shouldErr bool
|
|
expectedVal int64
|
|
expectedErr string
|
|
}{
|
|
// positive
|
|
{"forward . 127.0.0.1 {\nmax_concurrent 1000\n}\n", false, 1000, ""},
|
|
// negative
|
|
{"forward . 127.0.0.1 {\nmax_concurrent many\n}\n", true, 0, "invalid"},
|
|
{"forward . 127.0.0.1 {\nmax_concurrent -4\n}\n", true, 0, "negative"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if test.shouldErr && err == nil {
|
|
t.Errorf("Test %d: expected error but found %s for input %s", i, err, test.input)
|
|
}
|
|
|
|
if err != nil {
|
|
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 {
|
|
continue
|
|
}
|
|
f := fs[0]
|
|
if f.maxConcurrent != test.expectedVal {
|
|
t.Errorf("Test %d: expected: %d, got: %d", i, test.expectedVal, f.maxConcurrent)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetupMaxConnectAttempts(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
shouldErr bool
|
|
expectedVal uint32
|
|
expectedErr string
|
|
}{
|
|
|
|
{"forward . 127.0.0.1 {\n}\n", false, 0, ""},
|
|
{"forward . 127.0.0.1 {\nmax_connect_attempts 5\n}\n", false, 5, ""},
|
|
{"forward . 127.0.0.1 {\nmax_connect_attempts many\n}\n", true, 0, "invalid"},
|
|
{"forward . 127.0.0.1 {\nmax_connect_attempts -4\n}\n", true, 0, "invalid"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if test.shouldErr && err == nil {
|
|
t.Errorf("Test %d: expected error but found %s for input %s", i, err, test.input)
|
|
}
|
|
|
|
if err != nil {
|
|
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 := fs[0]
|
|
if f.maxConnectAttempts != test.expectedVal {
|
|
t.Errorf("Test %d: expected: %d, got: %d", i, test.expectedVal, f.maxConnectAttempts)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetupMaxIdleConns(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
shouldErr bool
|
|
expectedVal int
|
|
expectedErr string
|
|
}{
|
|
{"forward . 127.0.0.1\n", false, 0, ""},
|
|
{"forward . 127.0.0.1 {\nmax_idle_conns 10\n}\n", false, 10, ""},
|
|
{"forward . 127.0.0.1 {\nmax_idle_conns 0\n}\n", false, 0, ""},
|
|
{"forward . 127.0.0.1 {\nmax_idle_conns many\n}\n", true, 0, "invalid"},
|
|
{"forward . 127.0.0.1 {\nmax_idle_conns -1\n}\n", true, 0, "negative"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if test.shouldErr && err == nil {
|
|
t.Errorf("Test %d: expected error but found none for input %s", i, test.input)
|
|
}
|
|
|
|
if err != nil {
|
|
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 {
|
|
continue
|
|
}
|
|
f := fs[0]
|
|
if f.maxIdleConns != test.expectedVal {
|
|
t.Errorf("Test %d: expected: %d, got: %d", i, test.expectedVal, f.maxIdleConns)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetupHealthCheck(t *testing.T) {
|
|
tests := []struct {
|
|
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 {\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.", ""},
|
|
{"forward . 127.0.0.1 {\nhealth_check 0.5s domain .\n}\n", false, true, ".", ""},
|
|
{"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 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'"},
|
|
{"forward . 127.0.0.1 {\nhealth_check 0.5s domain example..org\n}\n", true, true, ".", "health_check: invalid domain name"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if test.shouldErr && err == nil {
|
|
t.Errorf("Test %d: expected error but found %s for input %s", i, err, test.input)
|
|
}
|
|
|
|
if err != nil {
|
|
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 {
|
|
continue
|
|
}
|
|
|
|
f := fs[0]
|
|
if f.opts.HCRecursionDesired != test.expectedRecVal || f.proxies[0].GetHealthchecker().GetRecursionDesired() != test.expectedRecVal ||
|
|
f.opts.HCDomain != test.expectedDomain || f.proxies[0].GetHealthchecker().GetDomain() != test.expectedDomain || !dns.IsFqdn(f.proxies[0].GetHealthchecker().GetDomain()) {
|
|
t.Errorf("Test %d: expectedRec: %v, got: %v. expectedDomain: %s, got: %s. ", i, test.expectedRecVal, f.opts.HCRecursionDesired, test.expectedDomain, f.opts.HCDomain)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMultiForward(t *testing.T) {
|
|
input := `
|
|
forward 1st.example.org 10.0.0.1
|
|
forward 2nd.example.org 10.0.0.2
|
|
forward 3rd.example.org 10.0.0.3
|
|
`
|
|
|
|
c := caddy.NewTestController("dns", input)
|
|
setup(c)
|
|
dnsserver.NewServer("", []*dnsserver.Config{dnsserver.GetConfig(c)})
|
|
|
|
handlers := dnsserver.GetConfig(c).Handlers()
|
|
f1, ok := handlers[0].(*Forward)
|
|
if !ok {
|
|
t.Fatalf("expected first plugin to be Forward, got %v", reflect.TypeOf(handlers[0]))
|
|
}
|
|
|
|
if f1.from != "1st.example.org." {
|
|
t.Errorf("expected first forward from \"1st.example.org.\", got %q", f1.from)
|
|
}
|
|
if f1.Next == nil {
|
|
t.Fatal("expected first forward to point to next forward instance, not nil")
|
|
}
|
|
|
|
f2, ok := f1.Next.(*Forward)
|
|
if !ok {
|
|
t.Fatalf("expected second plugin to be Forward, got %v", reflect.TypeOf(f1.Next))
|
|
}
|
|
if f2.from != "2nd.example.org." {
|
|
t.Errorf("expected second forward from \"2nd.example.org.\", got %q", f2.from)
|
|
}
|
|
if f2.Next == nil {
|
|
t.Fatal("expected second forward to point to third forward instance, got nil")
|
|
}
|
|
|
|
f3, ok := f2.Next.(*Forward)
|
|
if !ok {
|
|
t.Fatalf("expected third plugin to be Forward, got %v", reflect.TypeOf(f2.Next))
|
|
}
|
|
if f3.from != "3rd.example.org." {
|
|
t.Errorf("expected third forward from \"3rd.example.org.\", got %q", f3.from)
|
|
}
|
|
if f3.Next != nil {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFailfastAllUnhealthyUpstreams(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expectedRecVal bool
|
|
expectedErr string
|
|
}{
|
|
// positive
|
|
{"forward . 127.0.0.1\n", false, ""},
|
|
{"forward . 127.0.0.1 {\nfailfast_all_unhealthy_upstreams\n}\n", true, ""},
|
|
// negative
|
|
{"forward . 127.0.0.1 {\nfailfast_all_unhealthy_upstreams false\n}\n", false, "Wrong argument count"},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if err != nil {
|
|
if test.expectedErr == "" {
|
|
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)
|
|
}
|
|
} else {
|
|
if test.expectedErr != "" {
|
|
t.Errorf("Test %d: expected error but found no error for input %s", i, test.input)
|
|
}
|
|
}
|
|
|
|
if test.expectedErr != "" {
|
|
continue
|
|
}
|
|
|
|
f := fs[0]
|
|
if f.failfastUnhealthyUpstreams != test.expectedRecVal {
|
|
t.Errorf("Test %d: Expected Rec:%v, got:%v", i, test.expectedRecVal, f.failfastUnhealthyUpstreams)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFailover(t *testing.T) {
|
|
server_fail_s := dnstest.NewMultipleServer(func(w dns.ResponseWriter, r *dns.Msg) {
|
|
ret := new(dns.Msg)
|
|
ret.SetRcode(r, dns.RcodeServerFailure)
|
|
w.WriteMsg(ret)
|
|
})
|
|
defer server_fail_s.Close()
|
|
|
|
server_refused_s := dnstest.NewMultipleServer(func(w dns.ResponseWriter, r *dns.Msg) {
|
|
ret := new(dns.Msg)
|
|
ret.SetRcode(r, dns.RcodeRefused)
|
|
w.WriteMsg(ret)
|
|
})
|
|
defer server_refused_s.Close()
|
|
|
|
s := dnstest.NewMultipleServer(func(w dns.ResponseWriter, r *dns.Msg) {
|
|
ret := new(dns.Msg)
|
|
ret.SetReply(r)
|
|
ret.Answer = append(ret.Answer, test.A("example.org. IN A 127.0.0.1"))
|
|
w.WriteMsg(ret)
|
|
})
|
|
defer s.Close()
|
|
|
|
tests := []struct {
|
|
input string
|
|
hasRecord bool
|
|
failMsg string
|
|
}{
|
|
{fmt.Sprintf(
|
|
`forward . %s %s %s {
|
|
policy sequential
|
|
failover ServFail Refused
|
|
}`, server_fail_s.Addr, server_refused_s.Addr, s.Addr), true, "If failover is set, records should be returned as long as one of the upstreams is work"},
|
|
{fmt.Sprintf(
|
|
`forward . %s %s %s {
|
|
policy sequential
|
|
}`, server_fail_s.Addr, server_refused_s.Addr, s.Addr), false, "If failover is not set and the first upstream is not work, no records should be returned"},
|
|
{fmt.Sprintf(
|
|
`forward . %s %s %s {
|
|
policy sequential
|
|
}`, s.Addr, server_fail_s.Addr, server_refused_s.Addr), true, "Although failover is not set, as long as the first upstream is work, there should be has a record return"},
|
|
}
|
|
|
|
for i, testCase := range tests {
|
|
c := caddy.NewTestController("dns", testCase.input)
|
|
fs, err := parseForward(c)
|
|
|
|
f := fs[0]
|
|
if err != nil {
|
|
t.Errorf("Test #%d: Failed to create forwarder: %s", i, err)
|
|
}
|
|
f.OnStartup()
|
|
defer f.OnShutdown()
|
|
|
|
// Reduce per-upstream read timeout to make the test fit within the
|
|
// per-query deadline defaultTimeout of 5 seconds.
|
|
for _, p := range f.proxies {
|
|
p.SetReadTimeout(500 * time.Millisecond)
|
|
}
|
|
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("example.org.", dns.TypeA)
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
|
|
if _, err := f.ServeDNS(context.TODO(), rec, m); err != nil {
|
|
t.Fatalf("Test #%d: Expected to receive reply, but didn't", i)
|
|
}
|
|
|
|
if (len(rec.Msg.Answer) > 0) != testCase.hasRecord {
|
|
t.Errorf("Test #%d: %s: \n %s", i, testCase.failMsg, testCase.input)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFailoverValidation(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
input string
|
|
wantError string
|
|
}{
|
|
{
|
|
name: "NoErrorDisallowed",
|
|
input: `forward . 127.0.0.1 {
|
|
failover NOERROR
|
|
}`,
|
|
wantError: "NoError cannot be used in failover",
|
|
},
|
|
{
|
|
name: "InvalidRcode",
|
|
input: `forward . 127.0.0.1 {
|
|
failover NOT_A_VALID_RCODE
|
|
}`,
|
|
wantError: "not a valid rcode",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := caddy.NewTestController("dns", tc.input)
|
|
_, err := parseForward(c)
|
|
if err == nil {
|
|
t.Fatalf("expected error for %s, got nil", tc.name)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantError) {
|
|
t.Fatalf("expected error to contain %q, got: %v", tc.wantError, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetupMaxAge(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
shouldErr bool
|
|
expectedVal time.Duration
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "default (no max_age)",
|
|
input: "forward . 127.0.0.1\n",
|
|
expectedVal: 0,
|
|
},
|
|
{
|
|
name: "valid max_age",
|
|
input: "forward . 127.0.0.1 {\nmax_age 30s\n}\n",
|
|
expectedVal: 30 * time.Second,
|
|
},
|
|
{
|
|
name: "max_age equal to expire",
|
|
input: "forward . 127.0.0.1 {\nexpire 10s\nmax_age 10s\n}\n",
|
|
expectedVal: 10 * time.Second,
|
|
},
|
|
{
|
|
name: "max_age zero (unlimited)",
|
|
input: "forward . 127.0.0.1 {\nmax_age 0s\n}\n",
|
|
expectedVal: 0,
|
|
},
|
|
{
|
|
name: "negative max_age",
|
|
input: "forward . 127.0.0.1 {\nmax_age -1s\n}\n",
|
|
shouldErr: true,
|
|
expectedErr: "negative",
|
|
},
|
|
{
|
|
name: "invalid max_age value",
|
|
input: "forward . 127.0.0.1 {\nmax_age invalid\n}\n",
|
|
shouldErr: true,
|
|
expectedErr: "invalid",
|
|
},
|
|
{
|
|
name: "max_age less than expire",
|
|
input: "forward . 127.0.0.1 {\nexpire 30s\nmax_age 10s\n}\n",
|
|
shouldErr: true,
|
|
expectedErr: "max_age",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
|
|
if test.shouldErr {
|
|
if err == nil {
|
|
t.Errorf("expected error but found none for input %s", test.input)
|
|
return
|
|
}
|
|
if !strings.Contains(err.Error(), test.expectedErr) {
|
|
t.Errorf("expected error to contain %q, got: %v", test.expectedErr, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("expected no error but found: %v", err)
|
|
return
|
|
}
|
|
if fs[0].maxAge != test.expectedVal {
|
|
t.Errorf("expected maxAge %v, got %v", test.expectedVal, fs[0].maxAge)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetupReadTimeout(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
shouldErr bool
|
|
expectedVal time.Duration
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "default (no read_timeout)",
|
|
input: "forward . 127.0.0.1\n",
|
|
expectedVal: defaultReadTimeout,
|
|
},
|
|
{
|
|
name: "valid read_timeout",
|
|
input: "forward . 127.0.0.1 {\nread_timeout 5s\n}\n",
|
|
expectedVal: 5 * time.Second,
|
|
},
|
|
{
|
|
name: "sub-second read_timeout",
|
|
input: "forward . 127.0.0.1 {\nread_timeout 500ms\n}\n",
|
|
expectedVal: 500 * time.Millisecond,
|
|
},
|
|
{
|
|
name: "zero read_timeout",
|
|
input: "forward . 127.0.0.1 {\nread_timeout 0s\n}\n",
|
|
shouldErr: true,
|
|
expectedErr: "positive",
|
|
},
|
|
{
|
|
name: "negative read_timeout",
|
|
input: "forward . 127.0.0.1 {\nread_timeout -1s\n}\n",
|
|
shouldErr: true,
|
|
expectedErr: "positive",
|
|
},
|
|
{
|
|
name: "invalid read_timeout value",
|
|
input: "forward . 127.0.0.1 {\nread_timeout invalid\n}\n",
|
|
shouldErr: true,
|
|
expectedErr: "invalid",
|
|
},
|
|
{
|
|
name: "missing read_timeout value",
|
|
input: "forward . 127.0.0.1 {\nread_timeout\n}\n",
|
|
shouldErr: true,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
fs, err := parseForward(c)
|
|
if test.shouldErr {
|
|
if err == nil {
|
|
t.Errorf("expected error but found none for input %s", test.input)
|
|
return
|
|
}
|
|
if test.expectedErr != "" && !strings.Contains(err.Error(), test.expectedErr) {
|
|
t.Errorf("expected error to contain %q, got: %v", test.expectedErr, err)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Errorf("expected no error but found: %v", err)
|
|
return
|
|
}
|
|
if fs[0].readTimeout != test.expectedVal {
|
|
t.Errorf("expected readTimeout %v, got %v", test.expectedVal, fs[0].readTimeout)
|
|
}
|
|
})
|
|
}
|
|
}
|