diff options
author | 2021-02-23 09:12:40 +0000 | |
---|---|---|
committer | 2021-02-23 10:12:40 +0100 | |
commit | 01039312635d2577ec08f6f426fa395f0ccca392 (patch) | |
tree | add615081f63ba971230c8d86e517c1d108a08c0 /plugin | |
parent | fe2b5f630d969bac5eda7b72e28f740e65778469 (diff) | |
download | coredns-01039312635d2577ec08f6f426fa395f0ccca392.tar.gz coredns-01039312635d2577ec08f6f426fa395f0ccca392.tar.zst coredns-01039312635d2577ec08f6f426fa395f0ccca392.zip |
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>
Diffstat (limited to 'plugin')
-rw-r--r-- | plugin/rewrite/README.md | 17 | ||||
-rw-r--r-- | plugin/rewrite/class.go | 4 | ||||
-rw-r--r-- | plugin/rewrite/edns0.go | 16 | ||||
-rw-r--r-- | plugin/rewrite/name.go | 104 | ||||
-rw-r--r-- | plugin/rewrite/reverter.go | 138 | ||||
-rw-r--r-- | plugin/rewrite/reverter_test.go | 87 | ||||
-rw-r--r-- | plugin/rewrite/rewrite.go | 13 | ||||
-rw-r--r-- | plugin/rewrite/ttl.go | 64 | ||||
-rw-r--r-- | plugin/rewrite/type.go | 4 | ||||
-rw-r--r-- | plugin/test/helpers.go | 3 |
10 files changed, 321 insertions, 129 deletions
diff --git a/plugin/rewrite/README.md b/plugin/rewrite/README.md index ca48a6d17..2eecf748e 100644 --- a/plugin/rewrite/README.md +++ b/plugin/rewrite/README.md @@ -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 ``` +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: ``` rewrite [continue|stop] { name regex STRING STRING answer name STRING STRING + [answer value STRING STRING] } ``` 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 `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 -`answer`) in the brackets; additional rules are not supported. +syntax example. An alternate syntax for rewriting a DNS request and response is as 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, diff --git a/plugin/rewrite/class.go b/plugin/rewrite/class.go index 6906dcf6c..178cf8181 100644 --- a/plugin/rewrite/class.go +++ b/plugin/rewrite/class.go @@ -43,5 +43,5 @@ func (rule *classRule) Rewrite(ctx context.Context, state request.Request) Resul // Mode returns the processing mode. func (rule *classRule) Mode() string { return rule.NextAction } -// GetResponseRule return a rule to rewrite the response with. Currently not implemented. -func (rule *classRule) GetResponseRule() ResponseRule { return ResponseRule{} } +// GetResponseRules return rules to rewrite the response with. Currently not implemented. +func (rule *classRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } diff --git a/plugin/rewrite/edns0.go b/plugin/rewrite/edns0.go index c8d480ba9..fd355d678 100644 --- a/plugin/rewrite/edns0.go +++ b/plugin/rewrite/edns0.go @@ -73,8 +73,8 @@ func (rule *edns0NsidRule) Rewrite(ctx context.Context, state request.Request) R // Mode returns the processing mode. func (rule *edns0NsidRule) Mode() string { return rule.mode } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *edns0NsidRule) GetResponseRule() ResponseRule { return ResponseRule{} } +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *edns0NsidRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } // Rewrite will alter the request EDNS0 local options. 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. func (rule *edns0LocalRule) Mode() string { return rule.mode } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *edns0LocalRule) GetResponseRule() ResponseRule { return ResponseRule{} } +// GetResponseRules returns a rule to rewrite the response with. Currently not implemented. +func (rule *edns0LocalRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } // newEdns0Rule creates an EDNS0 rule of the appropriate type based on the args 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. func (rule *edns0VariableRule) Mode() string { return rule.mode } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *edns0VariableRule) GetResponseRule() ResponseRule { return ResponseRule{} } +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *edns0VariableRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } func isValidVariable(variable string) bool { switch variable { @@ -362,8 +362,8 @@ func (rule *edns0SubnetRule) Rewrite(ctx context.Context, state request.Request) // Mode returns the processing mode func (rule *edns0SubnetRule) Mode() string { return rule.mode } -// GetResponseRule return a rule to rewrite the response with. Currently not implemented. -func (rule *edns0SubnetRule) GetResponseRule() ResponseRule { return ResponseRule{} } +// GetResponseRules return rules to rewrite the response with. Currently not implemented. +func (rule *edns0SubnetRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } // These are all defined actions. const ( diff --git a/plugin/rewrite/name.go b/plugin/rewrite/name.go index c55c7d1ff..1f43d15b8 100644 --- a/plugin/rewrite/name.go +++ b/plugin/rewrite/name.go @@ -37,10 +37,10 @@ type substringNameRule struct { } type regexNameRule struct { - NextAction string - Pattern *regexp.Regexp - Replacement string - ResponseRule + NextAction string + Pattern *regexp.Regexp + Replacement string + ResponseRules []ResponseRule } 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 func newNameRule(nextAction string, args ...string) (Rule, error) { var matchType, rewriteQuestionFrom, rewriteQuestionTo string - var rewriteAnswerField, rewriteAnswerFrom, rewriteAnswerTo string if len(args) < 2 { 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 { - 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) != 7 { + 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 { @@ -190,47 +190,39 @@ func newNameRule(nextAction string, args ...string) (Rule, error) { nextAction, rewriteQuestionFromPattern, rewriteQuestionTo, - ResponseRule{ + []ResponseRule{{ Type: "name", - }, + }}, }, nil default: 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 args[3] != "answer" { - return nil, fmt.Errorf("exceeded the number of arguments for a regex name rule") - } rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo) if err != nil { return nil, err } - rewriteAnswerField = strings.ToLower(args[4]) - switch rewriteAnswerField { - case "name": - default: - return nil, fmt.Errorf("exceeded the number of arguments for a regex name rule") - } - rewriteAnswerFrom = args[5] - rewriteAnswerTo = args[6] - rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo) - if err != nil { - return nil, err - } rewriteQuestionTo = plugin.Name(args[2]).Normalize() - rewriteAnswerTo = plugin.Name(args[6]).Normalize() + + responseRuleCount := (len(args) - 3) / 4 + responseRules := make([]ResponseRule, responseRuleCount) + for i := 0; i < responseRuleCount; i ++ { + startIdx := 3 + (i * 4) + responseRule, err := newResponseRule(args[startIdx:startIdx + 4]) + if err != nil { + return nil, err + } + responseRules[i] = *responseRule + } + return ®exNameRule{ nextAction, rewriteQuestionFromPattern, rewriteQuestionTo, - ResponseRule{ - Active: true, - Type: "name", - Pattern: rewriteAnswerFromPattern, - Replacement: rewriteAnswerTo, - }, + responseRules, }, nil } 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) } +// 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 func (rule *exactNameRule) 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 *regexNameRule) Mode() string { return rule.NextAction } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *exactNameRule) GetResponseRule() ResponseRule { return rule.ResponseRule } +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *exactNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{rule.ResponseRule} } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *prefixNameRule) GetResponseRule() ResponseRule { return ResponseRule{} } +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *prefixNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *suffixNameRule) GetResponseRule() ResponseRule { return ResponseRule{} } +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *suffixNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *substringNameRule) GetResponseRule() ResponseRule { return ResponseRule{} } +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *substringNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } -// GetResponseRule returns a rule to rewrite the response with. -func (rule *regexNameRule) GetResponseRule() ResponseRule { return rule.ResponseRule } +// GetResponseRules returns rules to rewrite the response with. +func (rule *regexNameRule) GetResponseRules() []ResponseRule { return rule.ResponseRules } // hasClosingDot returns true if s has a closing dot at the end. func hasClosingDot(s string) bool { diff --git a/plugin/rewrite/reverter.go b/plugin/rewrite/reverter.go index 9b1de0563..846e9624b 100644 --- a/plugin/rewrite/reverter.go +++ b/plugin/rewrite/reverter.go @@ -42,46 +42,116 @@ func (r *ResponseReverter) WriteMsg(res1 *dns.Msg) error { res.Question[0] = r.originalQuestion if r.ResponseRewrite { + for _, rr := range res.Ns { + rewriteResourceRecord(res, rr, r) + } + for _, rr := range res.Answer { - var ( - isNameRewritten bool - isTTLRewritten bool - name = rr.Header().Name - ttl = rr.Header().Ttl - ) - for _, rule := range r.ResponseRules { - if rule.Type == "" { - rule.Type = "name" - } - switch rule.Type { - case "name": - regexGroups := rule.Pattern.FindStringSubmatch(name) - if len(regexGroups) == 0 { - continue - } - 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": - ttl = rule.TTL - isTTLRewritten = true - } - } - if isNameRewritten { - rr.Header().Name = name - } - if isTTLRewritten { - rr.Header().Ttl = ttl - } + 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 ( + isNameRewritten bool + isTTLRewritten bool + isValueRewritten bool + name = rr.Header().Name + ttl = rr.Header().Ttl + value string + ) + + for _, rule := range r.ResponseRules { + if rule.Type == "" { + rule.Type = "name" + } + switch rule.Type { + case "name": + rewriteString(rule, &name, &isNameRewritten) + case "value": + value = getRecordValueForRewrite(rr) + if value != "" { + rewriteString(rule, &value, &isValueRewritten) + } + case "ttl": + ttl = rule.TTL + isTTLRewritten = true + } + } + + if isNameRewritten { + rr.Header().Name = name + } + if isTTLRewritten { + 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 "" + } +} + +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. func (r *ResponseReverter) Write(buf []byte) (int, error) { n, err := r.ResponseWriter.Write(buf) diff --git a/plugin/rewrite/reverter_test.go b/plugin/rewrite/reverter_test.go index d46180af4..d0e0cfe4d 100644 --- a/plugin/rewrite/reverter_test.go +++ b/plugin/rewrite/reverter_test.go @@ -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) + } + } +} diff --git a/plugin/rewrite/rewrite.go b/plugin/rewrite/rewrite.go index 13e1d2092..f44cc62f1 100644 --- a/plugin/rewrite/rewrite.go +++ b/plugin/rewrite/rewrite.go @@ -49,10 +49,11 @@ func (rw Rewrite) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg state.Req.Question[0] = wr.originalQuestion return dns.RcodeServerFailure, err } - respRule := rule.GetResponseRule() - if respRule.Active { - wr.ResponseRewrite = true - wr.ResponseRules = append(wr.ResponseRules, respRule) + for _, respRule := range rule.GetResponseRules() { + if respRule.Active { + wr.ResponseRewrite = true + wr.ResponseRules = append(wr.ResponseRules, respRule) + } } if rule.Mode() == Stop { if rw.noRevert { @@ -78,8 +79,8 @@ type Rule interface { Rewrite(ctx context.Context, state request.Request) Result // Mode returns the processing mode stop or continue. Mode() string - // GetResponseRule returns the rule to rewrite response with, if any. - GetResponseRule() ResponseRule + // GetResponseRules returns rules to rewrite response with, if any. + GetResponseRules() []ResponseRule } func newRule(args ...string) (Rule, error) { diff --git a/plugin/rewrite/ttl.go b/plugin/rewrite/ttl.go index 50ace9c14..999f03547 100644 --- a/plugin/rewrite/ttl.go +++ b/plugin/rewrite/ttl.go @@ -15,31 +15,31 @@ import ( type exactTTLRule struct { NextAction string From string - ResponseRule + ResponseRules []ResponseRule } type prefixTTLRule struct { NextAction string Prefix string - ResponseRule + ResponseRules []ResponseRule } type suffixTTLRule struct { NextAction string Suffix string - ResponseRule + ResponseRules []ResponseRule } type substringTTLRule struct { NextAction string Substring string - ResponseRule + ResponseRules []ResponseRule } type regexTTLRule struct { NextAction string Pattern *regexp.Regexp - ResponseRule + ResponseRules []ResponseRule } // 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{ nextAction, plugin.Name(args[1]).Normalize(), - ResponseRule{ + []ResponseRule{{ Active: true, Type: "ttl", TTL: ttl, - }, + }}, }, nil case PrefixMatch: return &prefixTTLRule{ nextAction, plugin.Name(args[1]).Normalize(), - ResponseRule{ + []ResponseRule{{ Active: true, Type: "ttl", TTL: ttl, - }, + }}, }, nil case SuffixMatch: return &suffixTTLRule{ nextAction, plugin.Name(args[1]).Normalize(), - ResponseRule{ + []ResponseRule{{ Active: true, Type: "ttl", TTL: ttl, - }, + }}, }, nil case SubstringMatch: return &substringTTLRule{ nextAction, plugin.Name(args[1]).Normalize(), - ResponseRule{ + []ResponseRule{{ Active: true, Type: "ttl", TTL: ttl, - }, + }}, }, nil case RegexMatch: regexPattern, err := regexp.Compile(args[1]) @@ -152,11 +152,11 @@ func newTTLRule(nextAction string, args ...string) (Rule, error) { return ®exTTLRule{ nextAction, regexPattern, - ResponseRule{ + []ResponseRule{{ Active: true, Type: "ttl", TTL: ttl, - }, + }}, }, nil default: 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{ nextAction, plugin.Name(args[0]).Normalize(), - ResponseRule{ + []ResponseRule{{ Active: true, Type: "ttl", TTL: ttl, - }, + }}, }, nil } @@ -183,29 +183,29 @@ func (rule *suffixTTLRule) Mode() string { return rule.NextAction } func (rule *substringTTLRule) 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. -func (rule *exactTTLRule) GetResponseRule() ResponseRule { - return rule.ResponseRule +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *exactTTLRule) GetResponseRules() []ResponseRule { + return rule.ResponseRules } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *prefixTTLRule) GetResponseRule() ResponseRule { - return rule.ResponseRule +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *prefixTTLRule) GetResponseRules() []ResponseRule { + return rule.ResponseRules } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *suffixTTLRule) GetResponseRule() ResponseRule { - return rule.ResponseRule +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *suffixTTLRule) GetResponseRules() []ResponseRule { + return rule.ResponseRules } -// GetResponseRule returns a rule to rewrite the response with. Currently not implemented. -func (rule *substringTTLRule) GetResponseRule() ResponseRule { - return rule.ResponseRule +// GetResponseRules returns rules to rewrite the response with. Currently not implemented. +func (rule *substringTTLRule) GetResponseRules() []ResponseRule { + return rule.ResponseRules } -// GetResponseRule returns a rule to rewrite the response with. -func (rule *regexTTLRule) GetResponseRule() ResponseRule { - return rule.ResponseRule +// GetResponseRules returns rules to rewrite the response with. +func (rule *regexTTLRule) GetResponseRules() []ResponseRule { + return rule.ResponseRules } // validTTL returns true if v is valid TTL value. diff --git a/plugin/rewrite/type.go b/plugin/rewrite/type.go index 91fc593a3..44f5047b5 100644 --- a/plugin/rewrite/type.go +++ b/plugin/rewrite/type.go @@ -44,5 +44,5 @@ func (rule *typeRule) Rewrite(ctx context.Context, state request.Request) Result // Mode returns the processing mode. func (rule *typeRule) Mode() string { return rule.nextAction } -// GetResponseRule return a rule to rewrite the response with. Currently not implemented. -func (rule *typeRule) GetResponseRule() ResponseRule { return ResponseRule{} } +// GetResponseRules return rules to rewrite the response with. Currently not implemented. +func (rule *typeRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } diff --git a/plugin/test/helpers.go b/plugin/test/helpers.go index fa89f9218..0c7e85f2a 100644 --- a/plugin/test/helpers.go +++ b/plugin/test/helpers.go @@ -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. 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. func OPT(bufsize int, do bool) *dns.OPT { o := new(dns.OPT) |