2018-02-06 13:40:46 -05:00
package rewrite
import (
2018-04-22 08:34:35 +01:00
"context"
2018-02-06 13:40:46 -05:00
"testing"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
)
2018-04-27 02:05:44 -04:00
var tests = [ ] struct {
from string
fromType uint16
answer [ ] dns . RR
to string
toType uint16
noRevert bool
} {
{ "core.dns.rocks" , dns . TypeA , [ ] dns . RR { test . A ( "dns.core.rocks. 5 IN A 10.0.0.1" ) } , "core.dns.rocks" , dns . TypeA , false } ,
{ "core.dns.rocks" , dns . TypeSRV , [ ] dns . RR { test . SRV ( "dns.core.rocks. 5 IN SRV 0 100 100 srv1.dns.core.rocks." ) } , "core.dns.rocks" , dns . TypeSRV , false } ,
{ "core.dns.rocks" , dns . TypeA , [ ] dns . RR { test . A ( "core.dns.rocks. 5 IN A 10.0.0.1" ) } , "dns.core.rocks." , dns . TypeA , true } ,
{ "core.dns.rocks" , dns . TypeSRV , [ ] dns . RR { test . SRV ( "core.dns.rocks. 5 IN SRV 0 100 100 srv1.dns.core.rocks." ) } , "dns.core.rocks." , dns . TypeSRV , true } ,
{ "core.dns.rocks" , dns . TypeHINFO , [ ] dns . RR { test . HINFO ( "core.dns.rocks. 5 HINFO INTEL-64 \"RHEL 7.4\"" ) } , "core.dns.rocks" , dns . TypeHINFO , false } ,
{ "core.dns.rocks" , dns . TypeA , [ ] dns . RR {
test . A ( "dns.core.rocks. 5 IN A 10.0.0.1" ) ,
test . A ( "dns.core.rocks. 5 IN A 10.0.0.2" ) ,
} , "core.dns.rocks" , dns . TypeA , false } ,
}
2018-02-06 13:40:46 -05:00
func TestResponseReverter ( t * testing . T ) {
2026-02-16 14:26:18 +02:00
rules := make ( [ ] Rule , 0 , 1 )
2018-02-06 13:40:46 -05:00
r , _ := newNameRule ( "stop" , "regex" , ` (core)\.(dns)\.(rocks) ` , "{2}.{1}.{3}" , "answer" , "name" , ` (dns)\.(core)\.(rocks) ` , "{2}.{1}.{3}" )
rules = append ( rules , r )
2025-06-05 00:36:04 +03:00
doReverterTests ( t , rules )
2018-04-27 02:05:44 -04:00
2026-02-16 14:26:18 +02:00
rules = make ( [ ] Rule , 0 , 1 )
2018-04-27 02:05:44 -04:00
r , _ = newNameRule ( "continue" , "regex" , ` (core)\.(dns)\.(rocks) ` , "{2}.{1}.{3}" , "answer" , "name" , ` (dns)\.(core)\.(rocks) ` , "{2}.{1}.{3}" )
rules = append ( rules , r )
2025-06-05 00:36:04 +03:00
doReverterTests ( t , rules )
2018-04-27 02:05:44 -04:00
}
2018-02-06 13:40:46 -05:00
2025-06-05 00:36:04 +03:00
func doReverterTests ( t * testing . T , rules [ ] Rule ) {
t . Helper ( )
2018-02-06 13:40:46 -05:00
ctx := context . TODO ( )
for i , tc := range tests {
m := new ( dns . Msg )
m . SetQuestion ( tc . from , tc . fromType )
m . Question [ 0 ] . Qclass = dns . ClassINET
m . Answer = tc . answer
rw := Rewrite {
2021-05-04 10:05:45 +02:00
Next : plugin . HandlerFunc ( msgPrinter ) ,
Rules : rules ,
RevertPolicy : NewRevertPolicy ( tc . noRevert , false ) ,
2018-02-06 13:40:46 -05:00
}
rec := dnstest . NewRecorder ( & test . ResponseWriter { } )
rw . ServeDNS ( ctx , rec , m )
resp := rec . Msg
if resp . Question [ 0 ] . Name != tc . to {
t . Errorf ( "Test %d: Expected Name to be %q but was %q" , i , tc . to , resp . Question [ 0 ] . Name )
}
if resp . Question [ 0 ] . Qtype != tc . toType {
t . Errorf ( "Test %d: Expected Type to be '%d' but was '%d'" , i , tc . toType , resp . Question [ 0 ] . Qtype )
}
}
}
2021-02-23 09:12:40 +00:00
var valueTests = [ ] struct {
from string
fromType uint16
answer [ ] dns . RR
extra [ ] dns . RR
to string
toType uint16
noRevert bool
2021-02-24 10:22:15 +00:00
expectValue string
2021-02-23 09:12:40 +00:00
expectAnswerType uint16
expectAddlName string
} {
2021-05-04 10:05:45 +02:00
{ "my.domain.uk." , dns . TypeSRV , [ ] dns . RR { test . SRV ( "my.cluster.local. 5 IN SRV 0 100 100 srv1.my.cluster.local." ) } , [ ] dns . RR { test . A ( "srv1.my.cluster.local. 5 IN A 10.0.0.1" ) } , "my.domain.uk." , dns . TypeSRV , false , "srv1.my.domain.uk." , dns . TypeSRV , "srv1.my.domain.uk." } ,
{ "my.domain.uk." , dns . TypeSRV , [ ] dns . RR { test . SRV ( "my.cluster.local. 5 IN SRV 0 100 100 srv1.my.cluster.local." ) } , [ ] dns . RR { test . A ( "srv1.my.cluster.local. 5 IN A 10.0.0.1" ) } , "my.cluster.local." , dns . TypeSRV , true , "srv1.my.cluster.local." , dns . TypeSRV , "srv1.my.cluster.local." } ,
{ "my.domain.uk." , dns . TypeANY , [ ] dns . RR { test . CNAME ( "my.cluster.local. 3600 IN CNAME cname.cluster.local." ) } , [ ] dns . RR { test . A ( "cname.cluster.local. 5 IN A 10.0.0.1" ) } , "my.domain.uk." , dns . TypeANY , false , "cname.domain.uk." , dns . TypeCNAME , "cname.domain.uk." } ,
{ "my.domain.uk." , dns . TypeANY , [ ] dns . RR { test . CNAME ( "my.cluster.local. 3600 IN CNAME cname.cluster.local." ) } , [ ] dns . RR { test . A ( "cname.cluster.local. 5 IN A 10.0.0.1" ) } , "my.cluster.local." , dns . TypeANY , true , "cname.cluster.local." , dns . TypeCNAME , "cname.cluster.local." } ,
{ "my.domain.uk." , dns . TypeANY , [ ] dns . RR { test . DNAME ( "my.cluster.local. 3600 IN DNAME dname.cluster.local." ) } , [ ] dns . RR { test . A ( "dname.cluster.local. 5 IN A 10.0.0.1" ) } , "my.domain.uk." , dns . TypeANY , false , "dname.domain.uk." , dns . TypeDNAME , "dname.domain.uk." } ,
{ "my.domain.uk." , dns . TypeANY , [ ] dns . RR { test . DNAME ( "my.cluster.local. 3600 IN DNAME dname.cluster.local." ) } , [ ] dns . RR { test . A ( "dname.cluster.local. 5 IN A 10.0.0.1" ) } , "my.cluster.local." , dns . TypeANY , true , "dname.cluster.local." , dns . TypeDNAME , "dname.cluster.local." } ,
{ "my.domain.uk." , dns . TypeMX , [ ] dns . RR { test . MX ( "my.cluster.local. 3600 IN MX 1 mx1.cluster.local." ) } , [ ] dns . RR { test . A ( "mx1.cluster.local. 5 IN A 10.0.0.1" ) } , "my.domain.uk." , dns . TypeMX , false , "mx1.domain.uk." , dns . TypeMX , "mx1.domain.uk." } ,
{ "my.domain.uk." , dns . TypeMX , [ ] dns . RR { test . MX ( "my.cluster.local. 3600 IN MX 1 mx1.cluster.local." ) } , [ ] dns . RR { test . A ( "mx1.cluster.local. 5 IN A 10.0.0.1" ) } , "my.cluster.local." , dns . TypeMX , true , "mx1.cluster.local." , dns . TypeMX , "mx1.cluster.local." } ,
{ "my.domain.uk." , dns . TypeANY , [ ] dns . RR { test . NS ( "my.cluster.local. 3600 IN NS ns1.cluster.local." ) } , [ ] dns . RR { test . A ( "ns1.cluster.local. 5 IN A 10.0.0.1" ) } , "my.domain.uk." , dns . TypeANY , false , "ns1.domain.uk." , dns . TypeNS , "ns1.domain.uk." } ,
{ "my.domain.uk." , dns . TypeANY , [ ] dns . RR { test . NS ( "my.cluster.local. 3600 IN NS ns1.cluster.local." ) } , [ ] dns . RR { test . A ( "ns1.cluster.local. 5 IN A 10.0.0.1" ) } , "my.cluster.local." , dns . TypeANY , true , "ns1.cluster.local." , dns . TypeNS , "ns1.cluster.local." } ,
{ "my.domain.uk." , dns . TypeSOA , [ ] dns . RR { test . SOA ( "my.cluster.local. 1800 IN SOA ns1.cluster.local. admin.cluster.local. 1502165581 14400 3600 604800 14400" ) } , [ ] dns . RR { test . A ( "ns1.cluster.local. 5 IN A 10.0.0.1" ) } , "my.domain.uk." , dns . TypeSOA , false , "ns1.domain.uk." , dns . TypeSOA , "ns1.domain.uk." } ,
{ "my.domain.uk." , dns . TypeSOA , [ ] dns . RR { test . SOA ( "my.cluster.local. 1800 IN SOA ns1.cluster.local. admin.cluster.local. 1502165581 14400 3600 604800 14400" ) } , [ ] dns . RR { test . A ( "ns1.cluster.local. 5 IN A 10.0.0.1" ) } , "my.cluster.local." , dns . TypeSOA , true , "ns1.cluster.local." , dns . TypeSOA , "ns1.cluster.local." } ,
{ "my.domain.uk." , dns . TypeNAPTR , [ ] dns . RR { test . NAPTR ( "my.cluster.local. 100 IN NAPTR 100 10 \"S\" \"SIP+D2U\" \"!^.*$!sip:customer-service@example.com!\" _sip._udp.cluster.local." ) } , [ ] dns . RR { test . A ( "ns1.cluster.local. 5 IN A 10.0.0.1" ) } , "my.domain.uk." , dns . TypeNAPTR , false , "_sip._udp.domain.uk." , dns . TypeNAPTR , "ns1.domain.uk." } ,
{ "my.domain.uk." , dns . TypeNAPTR , [ ] dns . RR { test . NAPTR ( "my.cluster.local. 100 IN NAPTR 100 10 \"S\" \"SIP+D2U\" \"!^.*$!sip:customer-service@example.com!\" _sip._udp.cluster.local." ) } , [ ] dns . RR { test . A ( "ns1.cluster.local. 5 IN A 10.0.0.1" ) } , "my.cluster.local." , dns . TypeNAPTR , true , "_sip._udp.cluster.local." , dns . TypeNAPTR , "ns1.cluster.local." } ,
2021-02-23 09:12:40 +00:00
}
func TestValueResponseReverter ( t * testing . T ) {
2026-02-16 14:26:18 +02:00
rules := make ( [ ] Rule , 0 , 1 )
2021-05-04 10:05:45 +02:00
r , err := newNameRule ( "stop" , "regex" , ` (.*)\.domain\.uk ` , "{1}.cluster.local" , "answer" , "name" , ` (.*)\.cluster\.local ` , "{1}.domain.uk" , "answer" , "value" , ` (.*)\.cluster\.local ` , "{1}.domain.uk" )
if err != nil {
t . Errorf ( "cannot parse rule: %s" , err )
return
}
2021-02-23 09:12:40 +00:00
rules = append ( rules , r )
2025-06-05 00:36:04 +03:00
doValueReverterTests ( t , "stop" , rules )
2021-02-23 09:12:40 +00:00
2026-02-16 14:26:18 +02:00
rules = make ( [ ] Rule , 0 , 1 )
2021-05-04 10:05:45 +02:00
r , err = newNameRule ( "continue" , "regex" , ` (.*)\.domain\.uk ` , "{1}.cluster.local" , "answer" , "name" , ` (.*)\.cluster\.local ` , "{1}.domain.uk" , "answer" , "value" , ` (.*)\.cluster\.local ` , "{1}.domain.uk" )
if err != nil {
t . Errorf ( "cannot parse rule: %s" , err )
return
}
2021-02-23 09:12:40 +00:00
rules = append ( rules , r )
2025-06-05 00:36:04 +03:00
doValueReverterTests ( t , "continue" , rules )
2021-05-04 10:05:45 +02:00
2026-02-16 14:26:18 +02:00
rules = make ( [ ] Rule , 0 , 1 )
2021-05-04 10:05:45 +02:00
r , err = newNameRule ( "stop" , "suffix" , ` .domain.uk ` , ".cluster.local" , "answer" , "auto" , "answer" , "value" , ` (.*)\.cluster\.local ` , "{1}.domain.uk" )
if err != nil {
t . Errorf ( "cannot parse rule: %s" , err )
return
}
rules = append ( rules , r )
2025-06-05 00:36:04 +03:00
doValueReverterTests ( t , "suffix" , rules )
2024-09-12 09:07:02 +08:00
// multiple rules
2026-02-16 14:26:18 +02:00
rules = make ( [ ] Rule , 0 , 1 )
2024-09-12 09:07:02 +08:00
r , err = newNameRule ( "continue" , "suffix" , ` .domain.uk ` , ".domain.us" , "answer" , "auto" )
if err != nil {
t . Errorf ( "cannot parse rule: %s" , err )
return
}
rules = append ( rules , r )
r , err = newNameRule ( "stop" , "suffix" , ` .domain.us ` , ".cluster.local" , "answer" , "auto" )
if err != nil {
t . Errorf ( "cannot parse rule: %s" , err )
return
}
rules = append ( rules , r )
2025-06-05 00:36:04 +03:00
doValueReverterTests ( t , "suffix_multiple" , rules )
2021-02-23 09:12:40 +00:00
}
2025-06-05 00:36:04 +03:00
func doValueReverterTests ( t * testing . T , name string , rules [ ] Rule ) {
t . Helper ( )
2021-02-23 09:12:40 +00:00
ctx := context . TODO ( )
for i , tc := range valueTests {
m := new ( dns . Msg )
m . SetQuestion ( tc . from , tc . fromType )
m . Question [ 0 ] . Qclass = dns . ClassINET
m . Answer = tc . answer
m . Extra = tc . extra
rw := Rewrite {
2021-05-04 10:05:45 +02:00
Next : plugin . HandlerFunc ( msgPrinter ) ,
Rules : rules ,
RevertPolicy : NewRevertPolicy ( tc . noRevert , false ) ,
2021-02-23 09:12:40 +00:00
}
rec := dnstest . NewRecorder ( & test . ResponseWriter { } )
rw . ServeDNS ( ctx , rec , m )
resp := rec . Msg
if resp . Question [ 0 ] . Name != tc . to {
2021-05-04 10:05:45 +02:00
t . Errorf ( "Test %s.%d: Expected Name to be %q but was %q" , name , i , tc . to , resp . Question [ 0 ] . Name )
2021-02-23 09:12:40 +00:00
}
if resp . Question [ 0 ] . Qtype != tc . toType {
2021-05-04 10:05:45 +02:00
t . Errorf ( "Test %s.%d: Expected Type to be '%d' but was '%d'" , name , i , tc . toType , resp . Question [ 0 ] . Qtype )
2021-02-23 09:12:40 +00:00
}
2021-05-04 10:05:45 +02:00
if len ( resp . Answer ) <= 0 {
t . Errorf ( "Test %s.%d: No Answers" , name , i )
return
}
if len ( resp . Answer ) > 0 && resp . Answer [ 0 ] . Header ( ) . Rrtype != tc . expectAnswerType {
t . Errorf ( "Test %s.%d: Unexpected Answer Record Type %d" , name , i , resp . Answer [ 0 ] . Header ( ) . Rrtype )
2021-02-23 09:12:40 +00:00
return
}
value := getRecordValueForRewrite ( resp . Answer [ 0 ] )
if value != tc . expectValue {
2021-05-04 10:05:45 +02:00
t . Errorf ( "Test %s.%d: Expected Target to be '%s' but was '%s'" , name , i , tc . expectValue , value )
2021-02-23 09:12:40 +00:00
}
if len ( resp . Extra ) <= 0 || resp . Extra [ 0 ] . Header ( ) . Rrtype != dns . TypeA {
2021-05-04 10:05:45 +02:00
t . Errorf ( "Test %s.%d: Unexpected Additional Record Type / No Additional Records" , name , i )
2021-02-23 09:12:40 +00:00
return
}
if resp . Extra [ 0 ] . Header ( ) . Name != tc . expectAddlName {
2021-05-04 10:05:45 +02:00
t . Errorf ( "Test %s.%d: Expected Extra Name to be %q but was %q" , name , i , tc . expectAddlName , resp . Extra [ 0 ] . Header ( ) . Name )
2021-02-23 09:12:40 +00:00
}
}
}
2026-06-23 16:02:35 -07:00
var edns0NoOPTRevertTests = [ ] struct {
name string
args [ ] string
req func ( ) * dns . Msg
} {
{
name : "local_set_revert" ,
args : [ ] string {
"edns0" , "local" , "set" ,
"0xffee" , "0xabcdef" ,
"revert" ,
} ,
req : func ( ) * dns . Msg {
m := new ( dns . Msg )
m . SetQuestion ( "example.org." , dns . TypeA )
m . Question [ 0 ] . Qclass = dns . ClassINET
return m
} ,
} ,
{
name : "local_replace_revert" ,
args : [ ] string {
"edns0" , "local" , "replace" ,
"0xffee" , "0x222222" ,
"revert" ,
} ,
req : func ( ) * dns . Msg {
m := new ( dns . Msg )
m . SetQuestion ( "example.org." , dns . TypeA )
m . Question [ 0 ] . Qclass = dns . ClassINET
opt := new ( dns . OPT )
opt . Hdr . Name = "."
opt . Hdr . Rrtype = dns . TypeOPT
opt . Option = append ( opt . Option ,
& dns . EDNS0_LOCAL {
Code : 0xffee ,
Data : [ ] byte { 0x11 , 0x11 , 0x11 } ,
} ,
)
m . Extra = append ( m . Extra , opt )
return m
} ,
} ,
}
func TestEDNS0ResponseReverterNoOPT ( t * testing . T ) {
ctx := context . TODO ( )
for _ , tc := range edns0NoOPTRevertTests {
t . Run ( tc . name , func ( t * testing . T ) {
r , err := newRule ( tc . args ... )
if err != nil {
t . Fatalf ( "cannot parse rule: %s" , err )
}
rw := Rewrite {
Next : plugin . HandlerFunc ( noOPTMsgPrinter ) ,
Rules : [ ] Rule { r } ,
RevertPolicy : NewRevertPolicy ( false , false ) ,
}
rec := dnstest . NewRecorder ( & test . ResponseWriter { } )
rw . ServeDNS ( ctx , rec , tc . req ( ) )
resp := rec . Msg
if resp == nil {
t . Fatal ( "expected response" )
}
if resp . Rcode != dns . RcodeSuccess {
t . Fatalf (
"expected rcode %d got %d" ,
dns . RcodeSuccess ,
resp . Rcode ,
)
}
if len ( resp . Answer ) != 1 {
t . Fatalf (
"expected 1 answer got %d" ,
len ( resp . Answer ) ,
)
}
if resp . IsEdns0 ( ) != nil {
t . Fatalf ( "expected response without OPT record" )
}
} )
}
}
2026-06-30 15:05:12 +04:00
func TestResponseReverterRestoresMissingQuestion ( t * testing . T ) {
ctx := context . TODO ( )
r , err := newNameRule ( "stop" , "suffix" , ".example.org" , ".example.net" , "answer" , "auto" )
if err != nil {
t . Fatalf ( "cannot parse rule: %s" , err )
}
req := new ( dns . Msg )
req . SetQuestion ( "service.example.org." , dns . TypeA )
req . Question [ 0 ] . Qclass = dns . ClassINET
rw := Rewrite {
Next : plugin . HandlerFunc ( noQuestionMsgPrinter ) ,
Rules : [ ] Rule { r } ,
RevertPolicy : NewRevertPolicy ( false , false ) ,
}
rec := dnstest . NewRecorder ( & test . ResponseWriter { } )
if _ , err := rw . ServeDNS ( ctx , rec , req ) ; err != nil {
t . Fatalf ( "ServeDNS returned error: %v" , err )
}
resp := rec . Msg
if resp == nil {
t . Fatal ( "expected response" )
}
if len ( resp . Question ) != 1 {
t . Fatalf ( "expected 1 question got %d" , len ( resp . Question ) )
}
if got := resp . Question [ 0 ] . Name ; got != "service.example.org." {
t . Fatalf ( "question name = %q, want %q" , got , "service.example.org." )
}
if got := resp . Question [ 0 ] . Qtype ; got != dns . TypeA {
t . Fatalf ( "question type = %d, want %d" , got , dns . TypeA )
}
if len ( resp . Answer ) != 1 {
t . Fatalf ( "expected 1 answer got %d" , len ( resp . Answer ) )
}
if got := resp . Answer [ 0 ] . Header ( ) . Name ; got != "service.example.org." {
t . Fatalf ( "answer name = %q, want %q" , got , "service.example.org." )
}
}
2026-06-23 16:02:35 -07:00
func noOPTMsgPrinter ( _ context . Context , w dns . ResponseWriter , r * dns . Msg ) ( int , error ) {
m := new ( dns . Msg )
m . SetReply ( r )
m . Answer = [ ] dns . RR {
test . A ( "example.org. 60 IN A 127.0.0.1" ) ,
}
// Deliberately do NOT add an OPT RR.
if err := w . WriteMsg ( m ) ; err != nil {
return dns . RcodeServerFailure , err
}
return dns . RcodeSuccess , nil
}
2026-06-30 15:05:12 +04:00
func noQuestionMsgPrinter ( _ context . Context , w dns . ResponseWriter , _ * dns . Msg ) ( int , error ) {
m := new ( dns . Msg )
m . Answer = [ ] dns . RR {
test . A ( "service.example.net. 60 IN A 127.0.0.1" ) ,
}
if err := w . WriteMsg ( m ) ; err != nil {
return dns . RcodeServerFailure , err
}
return dns . RcodeSuccess , nil
}
2026-09-05 09:02:52 +08:00
func TestResponseReverterWriteMsgNilResponse ( t * testing . T ) {
req := new ( dns . Msg )
req . SetQuestion ( "service.example.org." , dns . TypeA )
rec := dnstest . NewRecorder ( & test . ResponseWriter { } )
rw := NewResponseReverter ( rec , req , NewRevertPolicy ( false , false ) )
defer func ( ) {
if r := recover ( ) ; r != nil {
t . Fatalf ( "ResponseReverter.WriteMsg panicked on nil response: %v" , r )
}
} ( )
err := rw . WriteMsg ( nil )
if err == nil {
t . Error ( "Expected error when passing nil response to WriteMsg, got nil" )
}
}