Rewrite SRV targets and additional names in response (#4287)

* Rewrite plugin - rewrite SRV targets and names in response answer and additional records

Signed-off-by: Nic Colledge <nic@njcolledge.net>

* Added README content to describe new behaviour

Signed-off-by: Nic Colledge <nic@njcolledge.net>

* Added more record types to rewrite handling based on PR/Issue feedback

Signed-off-by: Nic Colledge <nic@njcolledge.net>

* Updated README.md for plugin

Signed-off-by: Nic Colledge <nic@njcolledge.net>

* Updated unit tests.
Small refactor of getTarget... function.

Signed-off-by: Nic Colledge <nic@njcolledge.net>

* Refactor to add response value rewrite as answer value option

Signed-off-by: Nic Colledge <nic@njcolledge.net>

* Removed TODO comment, added test for NAPTR record.

Signed-off-by: Nic Colledge <nic@njcolledge.net>
This commit is contained in:
slick-nic
2021-02-23 09:12:40 +00:00
committed by GitHub
parent fe2b5f630d
commit 0103931263
10 changed files with 322 additions and 130 deletions

View File

@@ -151,26 +151,37 @@ ftp-us-west-1.coredns.rocks. 0 IN A 10.20.20.20
ftp-us-west-1.coredns.rocks. 0 IN A 10.30.30.30 ftp-us-west-1.coredns.rocks. 0 IN A 10.30.30.30
``` ```
It is also possible to rewrite other values returned in the DNS response records
(e.g. the server names returned in `SRV` and `MX` records). This can be enabled by adding
the `answer value` to a name regex rule as specified below. `answer value` takes a
regular expression and a rewrite name as parameters and works in the same way as the
`answer name` rule.
Note that names in the `AUTHORITY SECTION` and `ADDITIONAL SECTION` will also be
rewritten following the specified rules. The names returned by the following
record types: `CNAME`, `DNAME`, `SOA`, `SRV`, `MX`, `NAPTR`, `NS` will be rewritten
if the `answer value` rule is specified.
The syntax for the rewrite of DNS request and response is as follows: The syntax for the rewrite of DNS request and response is as follows:
``` ```
rewrite [continue|stop] { rewrite [continue|stop] {
name regex STRING STRING name regex STRING STRING
answer name STRING STRING answer name STRING STRING
[answer value STRING STRING]
} }
``` ```
Note that the above syntax is strict. For response rewrites, only `name` Note that the above syntax is strict. For response rewrites, only `name`
rules are allowed to match the question section, and only by match type rules are allowed to match the question section, and only by match type
`regex`. The answer rewrite must be after the name, as in the `regex`. The answer rewrite must be after the name, as in the
syntax example. There must only be two lines (a `name` followed by an syntax example.
`answer`) in the brackets; additional rules are not supported.
An alternate syntax for rewriting a DNS request and response is as An alternate syntax for rewriting a DNS request and response is as
follows: follows:
``` ```
rewrite [continue|stop] name regex STRING STRING answer name STRING STRING rewrite [continue|stop] name regex STRING STRING answer name STRING STRING [answer value STRING STRING]
``` ```
When using `exact` name rewrite rules, the answer gets rewritten automatically, When using `exact` name rewrite rules, the answer gets rewritten automatically,

View File

@@ -43,5 +43,5 @@ func (rule *classRule) Rewrite(ctx context.Context, state request.Request) Resul
// Mode returns the processing mode. // Mode returns the processing mode.
func (rule *classRule) Mode() string { return rule.NextAction } func (rule *classRule) Mode() string { return rule.NextAction }
// GetResponseRule return a rule to rewrite the response with. Currently not implemented. // GetResponseRules return rules to rewrite the response with. Currently not implemented.
func (rule *classRule) GetResponseRule() ResponseRule { return ResponseRule{} } func (rule *classRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }

View File

@@ -73,8 +73,8 @@ func (rule *edns0NsidRule) Rewrite(ctx context.Context, state request.Request) R
// Mode returns the processing mode. // Mode returns the processing mode.
func (rule *edns0NsidRule) Mode() string { return rule.mode } func (rule *edns0NsidRule) Mode() string { return rule.mode }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *edns0NsidRule) GetResponseRule() ResponseRule { return ResponseRule{} } func (rule *edns0NsidRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
// Rewrite will alter the request EDNS0 local options. // Rewrite will alter the request EDNS0 local options.
func (rule *edns0LocalRule) Rewrite(ctx context.Context, state request.Request) Result { func (rule *edns0LocalRule) Rewrite(ctx context.Context, state request.Request) Result {
@@ -103,8 +103,8 @@ func (rule *edns0LocalRule) Rewrite(ctx context.Context, state request.Request)
// Mode returns the processing mode. // Mode returns the processing mode.
func (rule *edns0LocalRule) Mode() string { return rule.mode } func (rule *edns0LocalRule) Mode() string { return rule.mode }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns a rule to rewrite the response with. Currently not implemented.
func (rule *edns0LocalRule) GetResponseRule() ResponseRule { return ResponseRule{} } func (rule *edns0LocalRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
// newEdns0Rule creates an EDNS0 rule of the appropriate type based on the args // newEdns0Rule creates an EDNS0 rule of the appropriate type based on the args
func newEdns0Rule(mode string, args ...string) (Rule, error) { func newEdns0Rule(mode string, args ...string) (Rule, error) {
@@ -253,8 +253,8 @@ func (rule *edns0VariableRule) Rewrite(ctx context.Context, state request.Reques
// Mode returns the processing mode. // Mode returns the processing mode.
func (rule *edns0VariableRule) Mode() string { return rule.mode } func (rule *edns0VariableRule) Mode() string { return rule.mode }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *edns0VariableRule) GetResponseRule() ResponseRule { return ResponseRule{} } func (rule *edns0VariableRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
func isValidVariable(variable string) bool { func isValidVariable(variable string) bool {
switch variable { switch variable {
@@ -362,8 +362,8 @@ func (rule *edns0SubnetRule) Rewrite(ctx context.Context, state request.Request)
// Mode returns the processing mode // Mode returns the processing mode
func (rule *edns0SubnetRule) Mode() string { return rule.mode } func (rule *edns0SubnetRule) Mode() string { return rule.mode }
// GetResponseRule return a rule to rewrite the response with. Currently not implemented. // GetResponseRules return rules to rewrite the response with. Currently not implemented.
func (rule *edns0SubnetRule) GetResponseRule() ResponseRule { return ResponseRule{} } func (rule *edns0SubnetRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
// These are all defined actions. // These are all defined actions.
const ( const (

View File

@@ -40,7 +40,7 @@ type regexNameRule struct {
NextAction string NextAction string
Pattern *regexp.Regexp Pattern *regexp.Regexp
Replacement string Replacement string
ResponseRule ResponseRules []ResponseRule
} }
const ( const (
@@ -113,7 +113,6 @@ func (rule *regexNameRule) Rewrite(ctx context.Context, state request.Request) R
// newNameRule creates a name matching rule based on exact, partial, or regex match // newNameRule creates a name matching rule based on exact, partial, or regex match
func newNameRule(nextAction string, args ...string) (Rule, error) { func newNameRule(nextAction string, args ...string) (Rule, error) {
var matchType, rewriteQuestionFrom, rewriteQuestionTo string var matchType, rewriteQuestionFrom, rewriteQuestionTo string
var rewriteAnswerField, rewriteAnswerFrom, rewriteAnswerTo string
if len(args) < 2 { if len(args) < 2 {
return nil, fmt.Errorf("too few arguments for a name rule") return nil, fmt.Errorf("too few arguments for a name rule")
} }
@@ -140,8 +139,9 @@ func newNameRule(nextAction string, args ...string) (Rule, error) {
} }
} }
if len(args) > 3 && len(args) != 7 { //if len(args) > 3 && len(args) != 7 {
return nil, fmt.Errorf("response rewrites must consist only of a name rule with 3 arguments and an answer rule with 3 arguments") if len(args) > 3 && (len(args) - 3) % 4 != 0 {
return nil, fmt.Errorf("response rewrites must consist only of a name rule with 3 arguments and one or more answer rules with 3 arguments each")
} }
if len(args) < 7 { if len(args) < 7 {
@@ -190,47 +190,39 @@ func newNameRule(nextAction string, args ...string) (Rule, error) {
nextAction, nextAction,
rewriteQuestionFromPattern, rewriteQuestionFromPattern,
rewriteQuestionTo, rewriteQuestionTo,
ResponseRule{ []ResponseRule{{
Type: "name", Type: "name",
}, }},
}, nil }, nil
default: default:
return nil, fmt.Errorf("name rule supports only exact, prefix, suffix, substring, and regex name matching, received: %s", matchType) return nil, fmt.Errorf("name rule supports only exact, prefix, suffix, substring, and regex name matching, received: %s", matchType)
} }
} }
if len(args) == 7 { //if len(args) == 7 {
if (len(args) - 3) % 4 == 0 {
if matchType == RegexMatch { if matchType == RegexMatch {
if args[3] != "answer" {
return nil, fmt.Errorf("exceeded the number of arguments for a regex name rule")
}
rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo) rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rewriteAnswerField = strings.ToLower(args[4]) rewriteQuestionTo = plugin.Name(args[2]).Normalize()
switch rewriteAnswerField {
case "name": responseRuleCount := (len(args) - 3) / 4
default: responseRules := make([]ResponseRule, responseRuleCount)
return nil, fmt.Errorf("exceeded the number of arguments for a regex name rule") for i := 0; i < responseRuleCount; i ++ {
} startIdx := 3 + (i * 4)
rewriteAnswerFrom = args[5] responseRule, err := newResponseRule(args[startIdx:startIdx + 4])
rewriteAnswerTo = args[6]
rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rewriteQuestionTo = plugin.Name(args[2]).Normalize() responseRules[i] = *responseRule
rewriteAnswerTo = plugin.Name(args[6]).Normalize() }
return &regexNameRule{ return &regexNameRule{
nextAction, nextAction,
rewriteQuestionFromPattern, rewriteQuestionFromPattern,
rewriteQuestionTo, rewriteQuestionTo,
ResponseRule{ responseRules,
Active: true,
Type: "name",
Pattern: rewriteAnswerFromPattern,
Replacement: rewriteAnswerTo,
},
}, nil }, nil
} }
return nil, fmt.Errorf("the rewrite of response is supported only for name regex rule") return nil, fmt.Errorf("the rewrite of response is supported only for name regex rule")
@@ -238,6 +230,34 @@ func newNameRule(nextAction string, args ...string) (Rule, error) {
return nil, fmt.Errorf("the rewrite rule is invalid: %s", args) return nil, fmt.Errorf("the rewrite rule is invalid: %s", args)
} }
// newResponseRule creates a new "answer name" or "answer value" response rule.
func newResponseRule(args []string) (responseRule *ResponseRule, err error){
if args[0] != "answer" {
return nil, fmt.Errorf("exceeded the number of arguments for a regex name rule")
}
rewriteAnswerField := strings.ToLower(args[1])
switch rewriteAnswerField {
case "name":
case "value":
default:
return nil, fmt.Errorf("exceeded the number of arguments for a regex name rule")
}
rewriteAnswerFrom := args[2]
rewriteAnswerTo := args[3]
rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo)
if err != nil {
return nil, err
}
rewriteAnswerTo = plugin.Name(args[3]).Normalize()
return &ResponseRule{
Active: true,
Type: rewriteAnswerField,
Pattern: rewriteAnswerFromPattern,
Replacement: rewriteAnswerTo,
}, nil
}
// Mode returns the processing nextAction // Mode returns the processing nextAction
func (rule *exactNameRule) Mode() string { return rule.NextAction } func (rule *exactNameRule) Mode() string { return rule.NextAction }
func (rule *prefixNameRule) Mode() string { return rule.NextAction } func (rule *prefixNameRule) Mode() string { return rule.NextAction }
@@ -245,20 +265,20 @@ func (rule *suffixNameRule) Mode() string { return rule.NextAction }
func (rule *substringNameRule) Mode() string { return rule.NextAction } func (rule *substringNameRule) Mode() string { return rule.NextAction }
func (rule *regexNameRule) Mode() string { return rule.NextAction } func (rule *regexNameRule) Mode() string { return rule.NextAction }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *exactNameRule) GetResponseRule() ResponseRule { return rule.ResponseRule } func (rule *exactNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{rule.ResponseRule} }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *prefixNameRule) GetResponseRule() ResponseRule { return ResponseRule{} } func (rule *prefixNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *suffixNameRule) GetResponseRule() ResponseRule { return ResponseRule{} } func (rule *suffixNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *substringNameRule) GetResponseRule() ResponseRule { return ResponseRule{} } func (rule *substringNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
// GetResponseRule returns a rule to rewrite the response with. // GetResponseRules returns rules to rewrite the response with.
func (rule *regexNameRule) GetResponseRule() ResponseRule { return rule.ResponseRule } func (rule *regexNameRule) GetResponseRules() []ResponseRule { return rule.ResponseRules }
// hasClosingDot returns true if s has a closing dot at the end. // hasClosingDot returns true if s has a closing dot at the end.
func hasClosingDot(s string) bool { func hasClosingDot(s string) bool {

View File

@@ -42,44 +42,114 @@ func (r *ResponseReverter) WriteMsg(res1 *dns.Msg) error {
res.Question[0] = r.originalQuestion res.Question[0] = r.originalQuestion
if r.ResponseRewrite { if r.ResponseRewrite {
for _, rr := range res.Ns {
rewriteResourceRecord(res, rr, r)
}
for _, rr := range res.Answer { for _, rr := range res.Answer {
rewriteResourceRecord(res, rr, r)
}
for _, rr := range res.Extra {
rewriteResourceRecord(res, rr, r)
}
}
return r.ResponseWriter.WriteMsg(res)
}
func rewriteResourceRecord(res *dns.Msg, rr dns.RR, r *ResponseReverter) {
var ( var (
isNameRewritten bool isNameRewritten bool
isTTLRewritten bool isTTLRewritten bool
isValueRewritten bool
name = rr.Header().Name name = rr.Header().Name
ttl = rr.Header().Ttl ttl = rr.Header().Ttl
value string
) )
for _, rule := range r.ResponseRules { for _, rule := range r.ResponseRules {
if rule.Type == "" { if rule.Type == "" {
rule.Type = "name" rule.Type = "name"
} }
switch rule.Type { switch rule.Type {
case "name": case "name":
regexGroups := rule.Pattern.FindStringSubmatch(name) rewriteString(rule, &name, &isNameRewritten)
if len(regexGroups) == 0 { case "value":
continue value = getRecordValueForRewrite(rr)
if value != "" {
rewriteString(rule, &value, &isValueRewritten)
} }
s := rule.Replacement
for groupIndex, groupValue := range regexGroups {
groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
s = strings.Replace(s, groupIndexStr, groupValue, -1)
}
name = s
isNameRewritten = true
case "ttl": case "ttl":
ttl = rule.TTL ttl = rule.TTL
isTTLRewritten = true isTTLRewritten = true
} }
} }
if isNameRewritten { if isNameRewritten {
rr.Header().Name = name rr.Header().Name = name
} }
if isTTLRewritten { if isTTLRewritten {
rr.Header().Ttl = ttl rr.Header().Ttl = ttl
} }
if isValueRewritten {
setRewrittenRecordValue(rr, value)
} }
}
func getRecordValueForRewrite(rr dns.RR) (name string) {
switch rr.Header().Rrtype {
case dns.TypeSRV:
return rr.(*dns.SRV).Target
case dns.TypeMX:
return rr.(*dns.MX).Mx
case dns.TypeCNAME:
return rr.(*dns.CNAME).Target
case dns.TypeNS:
return rr.(*dns.NS).Ns
case dns.TypeDNAME:
return rr.(*dns.DNAME).Target
case dns.TypeNAPTR:
return rr.(*dns.NAPTR).Replacement
case dns.TypeSOA:
return rr.(*dns.SOA).Ns
default:
return ""
} }
return r.ResponseWriter.WriteMsg(res) }
func setRewrittenRecordValue(rr dns.RR, value string) {
switch rr.Header().Rrtype {
case dns.TypeSRV:
rr.(*dns.SRV).Target = value
case dns.TypeMX:
rr.(*dns.MX).Mx = value
case dns.TypeCNAME:
rr.(*dns.CNAME).Target = value
case dns.TypeNS:
rr.(*dns.NS).Ns = value
case dns.TypeDNAME:
rr.(*dns.DNAME).Target = value
case dns.TypeNAPTR:
rr.(*dns.NAPTR).Replacement = value
case dns.TypeSOA:
rr.(*dns.SOA).Ns = value
}
}
func rewriteString(rule ResponseRule, str *string, isStringRewritten *bool) {
regexGroups := rule.Pattern.FindStringSubmatch(*str)
if len(regexGroups) == 0 {
return
}
s := rule.Replacement
for groupIndex, groupValue := range regexGroups {
groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
s = strings.Replace(s, groupIndexStr, groupValue, -1)
}
*isStringRewritten = true
*str = s
} }
// Write is a wrapper that records the size of the message that gets written. // Write is a wrapper that records the size of the message that gets written.

View File

@@ -68,3 +68,90 @@ func doReverterTests(rules []Rule, t *testing.T) {
} }
} }
} }
var valueTests = []struct {
from string
fromType uint16
answer []dns.RR
extra []dns.RR
to string
toType uint16
noRevert bool
expectValue string
expectAnswerType uint16
expectAddlName string
}{
{"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."},
}
func TestValueResponseReverter(t *testing.T) {
rules := []Rule{}
r, _ := newNameRule("stop", "regex", `(.*)\.domain\.uk`, "{1}.cluster.local", "answer", "name", `(.*)\.cluster\.local`, "{1}.domain.uk", "answer", "value", `(.*)\.cluster\.local`, "{1}.domain.uk")
rules = append(rules, r)
doValueReverterTests(rules, t)
rules = []Rule{}
r, _ = newNameRule("continue", "regex", `(.*)\.domain\.uk`, "{1}.cluster.local", "answer", "name", `(.*)\.cluster\.local`, "{1}.domain.uk", "answer", "value", `(.*)\.cluster\.local`, "{1}.domain.uk")
rules = append(rules, r)
doValueReverterTests(rules, t)
}
func doValueReverterTests(rules []Rule, t *testing.T) {
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{
Next: plugin.HandlerFunc(msgPrinter),
Rules: rules,
noRevert: tc.noRevert,
}
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)
}
if len(resp.Answer) <= 0 || resp.Answer[0].Header().Rrtype != tc.expectAnswerType {
t.Error("Unexpected Answer Record Type / No Answers")
return
}
value := getRecordValueForRewrite(resp.Answer[0])
if value != tc.expectValue {
t.Errorf("Test %d: Expected Target to be '%s' but was '%s'", i, tc.expectValue, value)
}
if len(resp.Extra) <= 0 || resp.Extra[0].Header().Rrtype != dns.TypeA {
t.Error("Unexpected Additional Record Type / No Additional Records")
return
}
if resp.Extra[0].Header().Name != tc.expectAddlName {
t.Errorf("Test %d: Expected Extra Name to be %q but was %q", i, tc.expectAddlName, resp.Extra[0].Header().Name)
}
}
}

View File

@@ -49,11 +49,12 @@ func (rw Rewrite) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
state.Req.Question[0] = wr.originalQuestion state.Req.Question[0] = wr.originalQuestion
return dns.RcodeServerFailure, err return dns.RcodeServerFailure, err
} }
respRule := rule.GetResponseRule() for _, respRule := range rule.GetResponseRules() {
if respRule.Active { if respRule.Active {
wr.ResponseRewrite = true wr.ResponseRewrite = true
wr.ResponseRules = append(wr.ResponseRules, respRule) wr.ResponseRules = append(wr.ResponseRules, respRule)
} }
}
if rule.Mode() == Stop { if rule.Mode() == Stop {
if rw.noRevert { if rw.noRevert {
return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, w, r) return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, w, r)
@@ -78,8 +79,8 @@ type Rule interface {
Rewrite(ctx context.Context, state request.Request) Result Rewrite(ctx context.Context, state request.Request) Result
// Mode returns the processing mode stop or continue. // Mode returns the processing mode stop or continue.
Mode() string Mode() string
// GetResponseRule returns the rule to rewrite response with, if any. // GetResponseRules returns rules to rewrite response with, if any.
GetResponseRule() ResponseRule GetResponseRules() []ResponseRule
} }
func newRule(args ...string) (Rule, error) { func newRule(args ...string) (Rule, error) {

View File

@@ -15,31 +15,31 @@ import (
type exactTTLRule struct { type exactTTLRule struct {
NextAction string NextAction string
From string From string
ResponseRule ResponseRules []ResponseRule
} }
type prefixTTLRule struct { type prefixTTLRule struct {
NextAction string NextAction string
Prefix string Prefix string
ResponseRule ResponseRules []ResponseRule
} }
type suffixTTLRule struct { type suffixTTLRule struct {
NextAction string NextAction string
Suffix string Suffix string
ResponseRule ResponseRules []ResponseRule
} }
type substringTTLRule struct { type substringTTLRule struct {
NextAction string NextAction string
Substring string Substring string
ResponseRule ResponseRules []ResponseRule
} }
type regexTTLRule struct { type regexTTLRule struct {
NextAction string NextAction string
Pattern *regexp.Regexp Pattern *regexp.Regexp
ResponseRule ResponseRules []ResponseRule
} }
// Rewrite rewrites the current request based upon exact match of the name // Rewrite rewrites the current request based upon exact match of the name
@@ -108,41 +108,41 @@ func newTTLRule(nextAction string, args ...string) (Rule, error) {
return &exactTTLRule{ return &exactTTLRule{
nextAction, nextAction,
plugin.Name(args[1]).Normalize(), plugin.Name(args[1]).Normalize(),
ResponseRule{ []ResponseRule{{
Active: true, Active: true,
Type: "ttl", Type: "ttl",
TTL: ttl, TTL: ttl,
}, }},
}, nil }, nil
case PrefixMatch: case PrefixMatch:
return &prefixTTLRule{ return &prefixTTLRule{
nextAction, nextAction,
plugin.Name(args[1]).Normalize(), plugin.Name(args[1]).Normalize(),
ResponseRule{ []ResponseRule{{
Active: true, Active: true,
Type: "ttl", Type: "ttl",
TTL: ttl, TTL: ttl,
}, }},
}, nil }, nil
case SuffixMatch: case SuffixMatch:
return &suffixTTLRule{ return &suffixTTLRule{
nextAction, nextAction,
plugin.Name(args[1]).Normalize(), plugin.Name(args[1]).Normalize(),
ResponseRule{ []ResponseRule{{
Active: true, Active: true,
Type: "ttl", Type: "ttl",
TTL: ttl, TTL: ttl,
}, }},
}, nil }, nil
case SubstringMatch: case SubstringMatch:
return &substringTTLRule{ return &substringTTLRule{
nextAction, nextAction,
plugin.Name(args[1]).Normalize(), plugin.Name(args[1]).Normalize(),
ResponseRule{ []ResponseRule{{
Active: true, Active: true,
Type: "ttl", Type: "ttl",
TTL: ttl, TTL: ttl,
}, }},
}, nil }, nil
case RegexMatch: case RegexMatch:
regexPattern, err := regexp.Compile(args[1]) regexPattern, err := regexp.Compile(args[1])
@@ -152,11 +152,11 @@ func newTTLRule(nextAction string, args ...string) (Rule, error) {
return &regexTTLRule{ return &regexTTLRule{
nextAction, nextAction,
regexPattern, regexPattern,
ResponseRule{ []ResponseRule{{
Active: true, Active: true,
Type: "ttl", Type: "ttl",
TTL: ttl, TTL: ttl,
}, }},
}, nil }, nil
default: default:
return nil, fmt.Errorf("ttl rule supports only exact, prefix, suffix, substring, and regex name matching") return nil, fmt.Errorf("ttl rule supports only exact, prefix, suffix, substring, and regex name matching")
@@ -168,11 +168,11 @@ func newTTLRule(nextAction string, args ...string) (Rule, error) {
return &exactTTLRule{ return &exactTTLRule{
nextAction, nextAction,
plugin.Name(args[0]).Normalize(), plugin.Name(args[0]).Normalize(),
ResponseRule{ []ResponseRule{{
Active: true, Active: true,
Type: "ttl", Type: "ttl",
TTL: ttl, TTL: ttl,
}, }},
}, nil }, nil
} }
@@ -183,29 +183,29 @@ func (rule *suffixTTLRule) Mode() string { return rule.NextAction }
func (rule *substringTTLRule) Mode() string { return rule.NextAction } func (rule *substringTTLRule) Mode() string { return rule.NextAction }
func (rule *regexTTLRule) Mode() string { return rule.NextAction } func (rule *regexTTLRule) Mode() string { return rule.NextAction }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *exactTTLRule) GetResponseRule() ResponseRule { func (rule *exactTTLRule) GetResponseRules() []ResponseRule {
return rule.ResponseRule return rule.ResponseRules
} }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *prefixTTLRule) GetResponseRule() ResponseRule { func (rule *prefixTTLRule) GetResponseRules() []ResponseRule {
return rule.ResponseRule return rule.ResponseRules
} }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *suffixTTLRule) GetResponseRule() ResponseRule { func (rule *suffixTTLRule) GetResponseRules() []ResponseRule {
return rule.ResponseRule return rule.ResponseRules
} }
// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. // GetResponseRules returns rules to rewrite the response with. Currently not implemented.
func (rule *substringTTLRule) GetResponseRule() ResponseRule { func (rule *substringTTLRule) GetResponseRules() []ResponseRule {
return rule.ResponseRule return rule.ResponseRules
} }
// GetResponseRule returns a rule to rewrite the response with. // GetResponseRules returns rules to rewrite the response with.
func (rule *regexTTLRule) GetResponseRule() ResponseRule { func (rule *regexTTLRule) GetResponseRules() []ResponseRule {
return rule.ResponseRule return rule.ResponseRules
} }
// validTTL returns true if v is valid TTL value. // validTTL returns true if v is valid TTL value.

View File

@@ -44,5 +44,5 @@ func (rule *typeRule) Rewrite(ctx context.Context, state request.Request) Result
// Mode returns the processing mode. // Mode returns the processing mode.
func (rule *typeRule) Mode() string { return rule.nextAction } func (rule *typeRule) Mode() string { return rule.nextAction }
// GetResponseRule return a rule to rewrite the response with. Currently not implemented. // GetResponseRules return rules to rewrite the response with. Currently not implemented.
func (rule *typeRule) GetResponseRule() ResponseRule { return ResponseRule{} } func (rule *typeRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }

View File

@@ -99,6 +99,9 @@ func DNSKEY(rr string) *dns.DNSKEY { r, _ := dns.NewRR(rr); return r.(*dns.DNSKE
// DS returns a DS record from rr. It panics on errors. // DS returns a DS record from rr. It panics on errors.
func DS(rr string) *dns.DS { r, _ := dns.NewRR(rr); return r.(*dns.DS) } func DS(rr string) *dns.DS { r, _ := dns.NewRR(rr); return r.(*dns.DS) }
// NAPTR returns a NAPTR record from rr. It panics on errors.
func NAPTR(rr string) *dns.NAPTR { r, _ := dns.NewRR(rr); return r.(*dns.NAPTR) }
// OPT returns an OPT record with UDP buffer size set to bufsize and the DO bit set to do. // OPT returns an OPT record with UDP buffer size set to bufsize and the DO bit set to do.
func OPT(bufsize int, do bool) *dns.OPT { func OPT(bufsize int, do bool) *dns.OPT {
o := new(dns.OPT) o := new(dns.OPT)