2018-02-05 22:00:47 +00:00
package forward
import (
2018-09-22 13:25:31 +01:00
"os"
2018-02-05 22:00:47 +00:00
"reflect"
"strings"
"testing"
2020-09-24 18:14:41 +02:00
"github.com/coredns/caddy"
2022-07-20 10:35:04 -04:00
"github.com/coredns/coredns/core/dnsserver"
2023-03-24 12:55:51 +00:00
"github.com/coredns/coredns/plugin/pkg/proxy"
2022-08-15 22:16:15 +08:00
"github.com/miekg/dns"
2018-02-05 22:00:47 +00:00
)
func TestSetup ( t * testing . T ) {
tests := [ ] struct {
2018-07-07 10:14:21 +03:00
input string
shouldErr bool
expectedFrom string
expectedIgnored [ ] string
expectedFails uint32
2023-03-24 12:55:51 +00:00
expectedOpts proxy . Options
2018-07-07 10:14:21 +03:00
expectedErr string
2018-02-05 22:00:47 +00:00
} {
// positive
2023-03-24 12:55:51 +00:00
{ "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 : "." } , "" } ,
2022-07-20 10:35:04 -04:00
{ ` forward . : : 1
2023-03-24 12:55:51 +00:00
forward com : : 2 ` , false , "." , nil , 2 , proxy . Options { HCRecursionDesired : true , HCDomain : "." } , "plugin" } ,
2018-02-05 22:00:47 +00:00
// negative
2023-03-24 12:55:51 +00:00
{ "forward . a27.0.0.1" , true , "" , nil , 0 , proxy . Options { HCRecursionDesired : true , HCDomain : "." } , "not an IP" } ,
{ "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 . https://127.0.0.1 \n" , true , "." , nil , 2 , proxy . 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 , proxy . Options { HCRecursionDesired : true , HCDomain : "." } , "unable to normalize 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'" } ,
2018-02-05 22:00:47 +00:00
}
for i , test := range tests {
c := caddy . NewTestController ( "dns" , test . input )
2022-07-20 10:35:04 -04:00
fs , err := parseForward ( c )
2018-02-05 22:00:47 +00:00
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 {
2021-05-20 03:24:36 -04:00
t . Fatalf ( "Test %d: expected no error but found one for input %s, got: %v" , i , test . input , err )
2018-02-05 22:00:47 +00:00
}
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 )
}
}
2022-07-20 10:35:04 -04:00
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 )
2018-02-05 22:00:47 +00:00
}
}
}
}
2018-04-13 17:02:54 +01:00
func TestSetupTLS ( t * testing . T ) {
tests := [ ] struct {
input string
shouldErr bool
expectedServerName string
expectedErr string
} {
// positive
2018-04-24 18:18:26 +01:00
{ ` forward . tls : //127.0.0.1 {
tls_servername dns
} ` , false , "dns" , "" } ,
2018-04-13 17:02:54 +01:00
{ ` forward . 127.0 .0 .1 {
2018-04-24 18:18:26 +01:00
tls_servername dns
} ` , false , "" , "" } ,
{ ` forward . 127.0 .0 .1 {
tls
} ` , false , "" , "" } ,
{ ` forward . tls://127.0.0.1 ` , false , "" , "" } ,
2018-04-13 17:02:54 +01:00
}
for i , test := range tests {
c := caddy . NewTestController ( "dns" , test . input )
2022-07-20 10:35:04 -04:00
fs , err := parseForward ( c )
f := fs [ 0 ]
2018-04-13 17:02:54 +01:00
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 )
}
}
2018-04-24 18:18:26 +01:00
if ! test . shouldErr && test . expectedServerName != "" && test . expectedServerName != f . tlsConfig . ServerName {
2018-04-13 17:02:54 +01:00
t . Errorf ( "Test %d: expected: %q, actual: %q" , i , test . expectedServerName , f . tlsConfig . ServerName )
}
2018-04-24 18:18:26 +01:00
2023-03-24 12:55:51 +00:00
if ! test . shouldErr && test . expectedServerName != "" && test . expectedServerName != f . proxies [ 0 ] . GetHealthchecker ( ) . GetTLSConfig ( ) . ServerName {
t . Errorf ( "Test %d: expected: %q, actual: %q" , i , test . expectedServerName , f . proxies [ 0 ] . GetHealthchecker ( ) . GetTLSConfig ( ) . ServerName )
2018-04-24 18:18:26 +01:00
}
2018-04-13 17:02:54 +01:00
}
}
2018-09-22 13:25:31 +01:00
func TestSetupResolvconf ( t * testing . T ) {
const resolv = "resolv.conf"
2021-10-13 15:30:31 +08:00
if err := os . WriteFile ( resolv ,
2018-09-22 13:25:31 +01:00
[ ] 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 )
2024-04-26 13:12:25 -04:00
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 )
2018-09-22 13:25:31 +01:00
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" } } ,
2020-03-13 14:23:10 +01:00
// fail
{ ` forward . /dev/null ` , true , "no nameservers" , nil } ,
2024-04-26 13:12:25 -04:00
// IPV6 with local zone
{ ` forward . ` + resolvIPV6 , false , "" , [ ] string { "[0388:d254:7aec:6892:9f7f:e93b:5806:1b0f]:53" } } ,
2018-09-22 13:25:31 +01:00
}
for i , test := range tests {
c := caddy . NewTestController ( "dns" , test . input )
2022-07-20 10:35:04 -04:00
fs , err := parseForward ( c )
2018-09-22 13:25:31 +01:00
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 )
}
}
2020-03-13 14:23:10 +01:00
if test . shouldErr {
continue
}
2022-07-20 10:35:04 -04:00
f := fs [ 0 ]
for j , n := range test . expectedNames {
2023-03-24 12:55:51 +00:00
addr := f . proxies [ j ] . Addr ( )
2022-07-20 10:35:04 -04:00
if n != addr {
t . Errorf ( "Test %d, expected %q, got %q" , j , n , addr )
}
}
2018-09-22 13:25:31 +01:00
for _ , p := range f . proxies {
2023-03-24 12:55:51 +00:00
p . Healthcheck ( ) // this should almost always err, we don't care it shouldn't crash
2020-02-04 07:59:08 -05:00
}
}
}
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 )
2022-07-20 10:35:04 -04:00
fs , err := parseForward ( c )
2020-02-04 07:59:08 -05:00
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 )
}
}
2022-07-20 10:35:04 -04:00
if test . shouldErr {
continue
}
f := fs [ 0 ]
if f . maxConcurrent != test . expectedVal {
2020-02-04 07:59:08 -05:00
t . Errorf ( "Test %d: expected: %d, got: %d" , i , test . expectedVal , f . maxConcurrent )
2018-09-22 13:25:31 +01:00
}
}
}
2020-03-06 11:52:43 +01:00
func TestSetupHealthCheck ( t * testing . T ) {
tests := [ ] struct {
2022-04-13 00:39:48 +08:00
input string
shouldErr bool
expectedRecVal bool
expectedDomain string
expectedErr string
2020-03-06 11:52:43 +01:00
} {
// positive
2022-04-13 00:39:48 +08:00
{ "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 , "." , "" } ,
2022-08-15 22:16:15 +08:00
{ "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." , "" } ,
2020-03-06 11:52:43 +01:00
// negative
2022-04-13 00:39:48 +08:00
{ "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'" } ,
2022-08-15 22:16:15 +08:00
{ "forward . 127.0.0.1 {\nhealth_check 0.5s domain example..org\n}\n" , true , true , "." , "health_check: invalid domain name" } ,
2020-03-06 11:52:43 +01:00
}
for i , test := range tests {
c := caddy . NewTestController ( "dns" , test . input )
2022-07-20 10:35:04 -04:00
fs , err := parseForward ( c )
2020-03-06 11:52:43 +01:00
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 )
}
}
2022-07-20 10:35:04 -04:00
if test . shouldErr {
continue
}
f := fs [ 0 ]
2023-03-24 12:55:51 +00:00
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 )
2020-03-06 11:52:43 +01:00
}
}
}
2022-07-20 10:35:04 -04:00
func TestMultiForward ( t * testing . T ) {
input := `
forward 1 st . example . org 10.0 .0 .1
forward 2 nd . example . org 10.0 .0 .2
forward 3 rd . 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 ( f1 . Next ) )
}
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" )
}
}
2024-07-01 17:20:12 +02:00
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 )
}
}
}
2025-03-07 08:37:25 -08:00
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 )
}
}
}