aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugin/rewrite/README.md99
-rw-r--r--plugin/rewrite/class.go9
-rw-r--r--plugin/rewrite/edns0.go50
-rw-r--r--plugin/rewrite/name.go502
-rw-r--r--plugin/rewrite/name_test.go263
-rw-r--r--plugin/rewrite/reverter.go135
-rw-r--r--plugin/rewrite/reverter_test.go86
-rw-r--r--plugin/rewrite/rewrite.go34
-rw-r--r--plugin/rewrite/rewrite_test.go32
-rw-r--r--plugin/rewrite/setup_test.go4
-rw-r--r--plugin/rewrite/ttl.go168
-rw-r--r--plugin/rewrite/ttl_test.go5
-rw-r--r--plugin/rewrite/type.go9
13 files changed, 882 insertions, 514 deletions
diff --git a/plugin/rewrite/README.md b/plugin/rewrite/README.md
index 1665d658a..b0757f68b 100644
--- a/plugin/rewrite/README.md
+++ b/plugin/rewrite/README.md
@@ -13,23 +13,36 @@ Rewrites are invisible to the client. There are simple rewrites (fast) and compl
A simplified/easy-to-digest syntax for *rewrite* is...
~~~
-rewrite [continue|stop] FIELD [FROM TO|FROM TTL]
+rewrite [continue|stop] FIELD [TYPE] [(FROM TO)|TTL] [OPTIONS]
~~~
* **FIELD** indicates what part of the request/response is being re-written.
* `type` - the type field of the request will be rewritten. FROM/TO must be a DNS record type (`A`, `MX`, etc.);
e.g., to rewrite ANY queries to HINFO, use `rewrite type ANY HINFO`.
- * `class` - the class of the message will be rewritten. FROM/TO must be a DNS class type (`IN`, `CH`, or `HS`); e.g., to rewrite CH queries to IN use `rewrite class CH IN`.
* `name` - the query name in the _request_ is rewritten; by default this is a full match of the
name, e.g., `rewrite name example.net example.org`. Other match types are supported, see the **Name Field Rewrites** section below.
- * `answer name` - the query name in the _response_ is rewritten. This option has special restrictions and requirements, in particular it must always combined with a `name` rewrite. See below in the **Response Rewrites** section.
- * `edns0` - an EDNS0 option can be appended to the request as described below in the **EDNS0 Options** section.
+ * `class` - the class of the message will be rewritten. FROM/TO must be a DNS class type (`IN`, `CH`, or `HS`); e.g., to rewrite CH queries to IN use `rewrite class CH IN`.
+ * `edns0` - an EDNS0 option can be appended to the request as described below in the **EDNS0 Options** section.
* `ttl` - the TTL value in the _response_ is rewritten.
+* **TYPE** this optional element can be specified for a `name` or `ttl` field.
+ If not given type `exact` will be assumed. If options should be specified the
+ type must be given.
* **FROM** is the name (exact, suffix, prefix, substring, or regex) or type to match
* **TO** is the destination name or type to rewrite to
-* **TTL** is the number of seconds to set the TTL value to
+* **TTL** is the number of seconds to set the TTL value to (only for field `ttl`)
+
+* **OPTIONS**
+
+ for field `name` further options are possible controlling the response rewrites.
+ All name matching types support the following options
+
+ * `answer auto` - the names in the _response_ is rewritten in a best effort manner.
+ * `answer name FROM TO` - the query name in the _response_ is rewritten matching the from regex pattern.
+ * `answer value FROM TO` - the names in the _response_ is rewritten matching the from regex pattern.
+
+ See below in the **Response Rewrites** section for further details.
If you specify multiple rules and an incoming query matches multiple rules, the rewrite
will behave as follows:
@@ -49,7 +62,7 @@ client.
The syntax for name rewriting is as follows:
```
-rewrite [continue|stop] name [exact|prefix|suffix|substring|regex] STRING STRING
+rewrite [continue|stop] name [exact|prefix|suffix|substring|regex] STRING STRING [OPTIONS]
```
The match type, e.g., `exact`, `substring`, etc., triggers rewrite:
@@ -60,7 +73,8 @@ The match type, e.g., `exact`, `substring`, etc., triggers rewrite:
* **suffix**: when the name ends with the matching string
* **regex**: when the name in the question section of a request matches a regular expression
-If the match type is omitted, the `exact` match type is assumed.
+If the match type is omitted, the `exact` match type is assumed. If OPTIONS are
+given, the type must be specified.
The following instruction allows rewriting names in the query that
contain the substring `service.us-west-1.example.org`:
@@ -95,7 +109,8 @@ rewrite name suffix .schmoogle.com. .google.com.
### Response Rewrites
-When rewriting incoming DNS requests' names, CoreDNS re-writes the `QUESTION SECTION`
+When rewriting incoming DNS requests' names (field `name`), CoreDNS re-writes
+the `QUESTION SECTION`
section of the requests. It may be necessary to rewrite the `ANSWER SECTION` of the
requests, because some DNS resolvers treat mismatches between the `QUESTION SECTION`
and `ANSWER SECTION` as a man-in-the-middle attack (MITM).
@@ -127,6 +142,35 @@ ftp.service.us-west-1.consul. 0 IN A 10.30.30.30
The above is a mismatch between the question asked and the answer provided.
+There are three possibilities to specify an answer rewrite:
+- A rewrite can request a best effort answer rewrite by adding the option `answer auto`.
+- A rewrite may specify a dedicated regex based response name rewrite with the
+ `answer name FROM TO` option.
+- A regex based rewrite of record values like `CNAME`, `SRV`, etc, can be requested by
+ an `answer value FROM TO` option.
+
+Hereby FROM/TO follow the rules for the `regex` name rewrite syntax.
+
+#### Auto Response Name Rewrite
+
+The following configuration snippet allows for rewriting of the
+`ANSWER SECTION` according to the rewrite of the `QUESTION SECTION`:
+
+```
+ rewrite stop {
+ name suffix .coredns.rocks .service.consul answer auto
+ }
+```
+
+Any occurrence of the rewritten question in the answer is mapped
+back to the original value before the rewrite.
+
+Please note that answers for rewrites of type `exact` are always rewritten.
+For a `suffix` name rule `auto` leads to a reverse suffix response rewrite,
+exchanging FROM and TO from the rewrite request.
+
+#### Explicit Response Name Rewrite
+
The following configuration snippet allows for rewriting of the
`ANSWER SECTION`, provided that the `QUESTION SECTION` was rewritten:
@@ -151,9 +195,11 @@ 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
```
+#### Rewriting other Response Values
+
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
+the `answer value FROM TO` option to a name 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.
@@ -173,20 +219,29 @@ rewrite [continue|stop] {
```
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.
+rules are allowed to match the question section. The answer rewrite must be
+after the name, as in the syntax example.
+
+#### Multiple Response Rewrites
+
+`name` and `value` rewrites can be chained by appending multiple answer rewrite
+options. For all occurrences but the first one the keyword `answer` might be
+omitted.
-An alternate syntax for rewriting a DNS request and response is as
-follows:
+```options
+answer (auto | (name|value FROM TO)) { [answer] (auto | (name|value FROM TO)) }
+```
+For example:
```
-rewrite [continue|stop] name regex STRING STRING answer name STRING STRING [answer value STRING STRING]
+rewrite [continue|stop] name regex FROM TO answer name FROM TO [answer] value FROM TO
```
When using `exact` name rewrite rules, the answer gets rewritten automatically,
-and there is no need to define `answer name`. The rule below
-rewrites the name in a request from `RED` to `BLUE`, and subsequently
+and there is no need to define `answer name auto`. But it is still possible to define
+additional `answer value` and `answer value` options.
+
+The rule below rewrites the name in a request from `RED` to `BLUE`, and subsequently
rewrites the name in a corresponding response from `BLUE` to `RED`. The
client in the request would see only `RED` and no `BLUE`.
@@ -215,6 +270,7 @@ setting the TTL value really low.
The syntax for the TTL rewrite rule is as follows. The meaning of
`exact|prefix|suffix|substring|regex` is the same as with the name rewrite rules.
+An omitted type is defaulted to `exact`.
```
rewrite [continue|stop] ttl [exact|prefix|suffix|substring|regex] STRING SECONDS
@@ -290,12 +346,3 @@ rewrite edns0 subnet set 24 56
* If the query's source IP address is an IPv4 address, the first 24 bits in the IP will be the network subnet.
* If the query's source IP address is an IPv6 address, the first 56 bits in the IP will be the network subnet.
-
-## Full Syntax
-
-The full plugin usage syntax is harder to digest...
-~~~
-rewrite [continue|stop] {type|class|edns0|name [exact|prefix|suffix|substring|regex [FROM TO answer name]]} FROM TO
-~~~
-
-The syntax above doesn't cover the multi-line block option for specifying a name request+response rewrite rule described in the **Response Rewrite** section.
diff --git a/plugin/rewrite/class.go b/plugin/rewrite/class.go
index 178cf8181..243a86449 100644
--- a/plugin/rewrite/class.go
+++ b/plugin/rewrite/class.go
@@ -30,18 +30,15 @@ func newClassRule(nextAction string, args ...string) (Rule, error) {
}
// Rewrite rewrites the current request.
-func (rule *classRule) Rewrite(ctx context.Context, state request.Request) Result {
+func (rule *classRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
if rule.fromClass > 0 && rule.toClass > 0 {
if state.Req.Question[0].Qclass == rule.fromClass {
state.Req.Question[0].Qclass = rule.toClass
- return RewriteDone
+ return nil, RewriteDone
}
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
// Mode returns the processing mode.
func (rule *classRule) Mode() string { return rule.NextAction }
-
-// 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 fd355d678..5e3663ef6 100644
--- a/plugin/rewrite/edns0.go
+++ b/plugin/rewrite/edns0.go
@@ -49,14 +49,14 @@ func setupEdns0Opt(r *dns.Msg) *dns.OPT {
}
// Rewrite will alter the request EDNS0 NSID option
-func (rule *edns0NsidRule) Rewrite(ctx context.Context, state request.Request) Result {
+func (rule *edns0NsidRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
o := setupEdns0Opt(state.Req)
for _, s := range o.Option {
if e, ok := s.(*dns.EDNS0_NSID); ok {
if rule.action == Replace || rule.action == Set {
e.Nsid = "" // make sure it is empty for request
- return RewriteDone
+ return nil, RewriteDone
}
}
}
@@ -64,20 +64,17 @@ func (rule *edns0NsidRule) Rewrite(ctx context.Context, state request.Request) R
// add option if not found
if rule.action == Append || rule.action == Set {
o.Option = append(o.Option, &dns.EDNS0_NSID{Code: dns.EDNS0NSID, Nsid: ""})
- return RewriteDone
+ return nil, RewriteDone
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
// Mode returns the processing mode.
func (rule *edns0NsidRule) Mode() string { return rule.mode }
-// 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 {
+func (rule *edns0LocalRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
o := setupEdns0Opt(state.Req)
for _, s := range o.Option {
@@ -85,7 +82,7 @@ func (rule *edns0LocalRule) Rewrite(ctx context.Context, state request.Request)
if rule.code == e.Code {
if rule.action == Replace || rule.action == Set {
e.Data = rule.data
- return RewriteDone
+ return nil, RewriteDone
}
}
}
@@ -94,18 +91,15 @@ func (rule *edns0LocalRule) Rewrite(ctx context.Context, state request.Request)
// add option if not found
if rule.action == Append || rule.action == Set {
o.Option = append(o.Option, &dns.EDNS0_LOCAL{Code: rule.code, Data: rule.data})
- return RewriteDone
+ return nil, RewriteDone
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
// Mode returns the processing mode.
func (rule *edns0LocalRule) Mode() string { return rule.mode }
-// 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) {
if len(args) < 2 {
@@ -222,10 +216,10 @@ func (rule *edns0VariableRule) ruleData(ctx context.Context, state request.Reque
}
// Rewrite will alter the request EDNS0 local options with specified variables.
-func (rule *edns0VariableRule) Rewrite(ctx context.Context, state request.Request) Result {
+func (rule *edns0VariableRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
data, err := rule.ruleData(ctx, state)
if err != nil || data == nil {
- return RewriteIgnored
+ return nil, RewriteIgnored
}
o := setupEdns0Opt(state.Req)
@@ -234,9 +228,9 @@ func (rule *edns0VariableRule) Rewrite(ctx context.Context, state request.Reques
if rule.code == e.Code {
if rule.action == Replace || rule.action == Set {
e.Data = data
- return RewriteDone
+ return nil, RewriteDone
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
}
}
@@ -244,18 +238,15 @@ func (rule *edns0VariableRule) Rewrite(ctx context.Context, state request.Reques
// add option if not found
if rule.action == Append || rule.action == Set {
o.Option = append(o.Option, &dns.EDNS0_LOCAL{Code: rule.code, Data: data})
- return RewriteDone
+ return nil, RewriteDone
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
// Mode returns the processing mode.
func (rule *edns0VariableRule) Mode() string { return rule.mode }
-// 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 {
case
@@ -333,17 +324,17 @@ func (rule *edns0SubnetRule) fillEcsData(state request.Request, ecs *dns.EDNS0_S
}
// Rewrite will alter the request EDNS0 subnet option.
-func (rule *edns0SubnetRule) Rewrite(ctx context.Context, state request.Request) Result {
+func (rule *edns0SubnetRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
o := setupEdns0Opt(state.Req)
for _, s := range o.Option {
if e, ok := s.(*dns.EDNS0_SUBNET); ok {
if rule.action == Replace || rule.action == Set {
if rule.fillEcsData(state, e) == nil {
- return RewriteDone
+ return nil, RewriteDone
}
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
}
@@ -352,19 +343,16 @@ func (rule *edns0SubnetRule) Rewrite(ctx context.Context, state request.Request)
opt := &dns.EDNS0_SUBNET{Code: dns.EDNS0SUBNET}
if rule.fillEcsData(state, opt) == nil {
o.Option = append(o.Option, opt)
- return RewriteDone
+ return nil, RewriteDone
}
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
// Mode returns the processing mode
func (rule *edns0SubnetRule) Mode() string { return rule.mode }
-// GetResponseRules return rules to rewrite the response with. Currently not implemented.
-func (rule *edns0SubnetRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
-
// These are all defined actions.
const (
Replace = "replace"
diff --git a/plugin/rewrite/name.go b/plugin/rewrite/name.go
index 24c813983..e9612839f 100644
--- a/plugin/rewrite/name.go
+++ b/plugin/rewrite/name.go
@@ -9,38 +9,106 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/request"
+
+ "github.com/miekg/dns"
)
-type exactNameRule struct {
- NextAction string
- From string
- To string
- ResponseRule
+// stringRewriter rewrites a string
+type stringRewriter interface {
+ rewriteString(src string) string
}
-type prefixNameRule struct {
- NextAction string
- Prefix string
- Replacement string
+// regexStringRewriter can be used to rewrite strings by regex pattern.
+// it contains all the information required to detect and execute a rewrite
+// on a string.
+type regexStringRewriter struct {
+ pattern *regexp.Regexp
+ replacement string
}
-type suffixNameRule struct {
- NextAction string
- Suffix string
- Replacement string
+var _ stringRewriter = &regexStringRewriter{}
+
+func newStringRewriter(pattern *regexp.Regexp, replacement string) stringRewriter {
+ return &regexStringRewriter{pattern, replacement}
}
-type substringNameRule struct {
- NextAction string
- Substring string
- Replacement string
+func (r *regexStringRewriter) rewriteString(src string) string {
+ regexGroups := r.pattern.FindStringSubmatch(src)
+ if len(regexGroups) == 0 {
+ return src
+ }
+ s := r.replacement
+ for groupIndex, groupValue := range regexGroups {
+ groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
+ s = strings.Replace(s, groupIndexStr, groupValue, -1)
+ }
+ return s
}
-type regexNameRule struct {
- NextAction string
- Pattern *regexp.Regexp
- Replacement string
- ResponseRules []ResponseRule
+// remapStringRewriter maps a dedicated string to another string
+// it also maps a the domain of a sub domain.
+type remapStringRewriter struct {
+ orig string
+ replacement string
+}
+
+var _ stringRewriter = &remapStringRewriter{}
+
+func newRemapStringRewriter(orig, replacement string) stringRewriter {
+ return &remapStringRewriter{orig, replacement}
+}
+
+func (r *remapStringRewriter) rewriteString(src string) string {
+ if src == r.orig {
+ return r.replacement
+ }
+ if strings.HasSuffix(src, "."+r.orig) {
+ return src[0:len(src)-len(r.orig)] + r.replacement
+ }
+ return src
+}
+
+// suffixStringRewriter maps a dedicated suffix string to another string
+type suffixStringRewriter struct {
+ suffix string
+ replacement string
+}
+
+var _ stringRewriter = &suffixStringRewriter{}
+
+func newSuffixStringRewriter(orig, replacement string) stringRewriter {
+ return &suffixStringRewriter{orig, replacement}
+}
+
+func (r *suffixStringRewriter) rewriteString(src string) string {
+ if strings.HasSuffix(src, r.suffix) {
+ return strings.TrimSuffix(src, r.suffix) + r.replacement
+ }
+ return src
+}
+
+// nameRewriterResponseRule maps a record name according to a stringRewriter.
+type nameRewriterResponseRule struct {
+ stringRewriter
+}
+
+func (r *nameRewriterResponseRule) RewriteResponse(rr dns.RR) {
+ rr.Header().Name = r.rewriteString(rr.Header().Name)
+}
+
+// valueRewriterResponseRule maps a record value according to a stringRewriter.
+type valueRewriterResponseRule struct {
+ stringRewriter
+}
+
+func (r *valueRewriterResponseRule) RewriteResponse(rr dns.RR) {
+ value := getRecordValueForRewrite(rr)
+ if value != "" {
+ new := r.rewriteString(value)
+ if new != value {
+ setRewrittenRecordValue(rr, new)
+ }
+ }
}
const (
@@ -54,60 +122,173 @@ const (
SubstringMatch = "substring"
// RegexMatch matches when the name in the question section of a request matches a regular expression
RegexMatch = "regex"
+
+ // AnswerMatch matches an answer rewrite
+ AnswerMatch = "answer"
+ // AutoMatch matches the auto name answer rewrite
+ AutoMatch = "auto"
+ // NameMatch matches the name answer rewrite
+ NameMatch = "name"
+ // ValueMatch matches the value answer rewrite
+ ValueMatch = "value"
)
-// Rewrite rewrites the current request based upon exact match of the name
+type nameRuleBase struct {
+ nextAction string
+ auto bool
+ replacement string
+ static ResponseRules
+}
+
+func newNameRuleBase(nextAction string, auto bool, replacement string, staticResponses ResponseRules) nameRuleBase {
+ return nameRuleBase{
+ nextAction: nextAction,
+ auto: auto,
+ replacement: replacement,
+ static: staticResponses,
+ }
+}
+
+// responseRuleFor create for auto mode dynamically response rewriters for name and value
+// reverting the mapping done by the name rewrite rule, which can be found in the state.
+func (rule *nameRuleBase) responseRuleFor(state request.Request) (ResponseRules, Result) {
+ if !rule.auto {
+ return rule.static, RewriteDone
+ }
+
+ rewriter := newRemapStringRewriter(state.Req.Question[0].Name, state.Name())
+ rules := ResponseRules{
+ &nameRewriterResponseRule{rewriter},
+ &valueRewriterResponseRule{rewriter},
+ }
+ return append(rules, rule.static...), RewriteDone
+}
+
+// Mode returns the processing nextAction
+func (rule *nameRuleBase) Mode() string { return rule.nextAction }
+
+// exactNameRule rewrites the current request based upon exact match of the name
// in the question section of the request.
-func (rule *exactNameRule) Rewrite(ctx context.Context, state request.Request) Result {
- if rule.From == state.Name() {
- state.Req.Question[0].Name = rule.To
- return RewriteDone
+type exactNameRule struct {
+ nameRuleBase
+ from string
+}
+
+func newExactNameRule(nextAction string, orig, replacement string, answers ResponseRules) Rule {
+ return &exactNameRule{
+ newNameRuleBase(nextAction, true, replacement, answers),
+ orig,
+ }
+}
+
+func (rule *exactNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ if rule.from == state.Name() {
+ state.Req.Question[0].Name = rule.replacement
+ return rule.responseRuleFor(state)
}
- return RewriteIgnored
+ return nil, RewriteIgnored
+}
+
+// prefixNameRule rewrites the current request when the name begins with the matching string.
+type prefixNameRule struct {
+ nameRuleBase
+ prefix string
}
-// Rewrite rewrites the current request when the name begins with the matching string.
-func (rule *prefixNameRule) Rewrite(ctx context.Context, state request.Request) Result {
- if strings.HasPrefix(state.Name(), rule.Prefix) {
- state.Req.Question[0].Name = rule.Replacement + strings.TrimPrefix(state.Name(), rule.Prefix)
- return RewriteDone
+func newPrefixNameRule(nextAction string, auto bool, prefix, replacement string, answers ResponseRules) Rule {
+ return &prefixNameRule{
+ newNameRuleBase(nextAction, auto, replacement, answers),
+ prefix,
}
- return RewriteIgnored
}
-// Rewrite rewrites the current request when the name ends with the matching string.
-func (rule *suffixNameRule) Rewrite(ctx context.Context, state request.Request) Result {
- if strings.HasSuffix(state.Name(), rule.Suffix) {
- state.Req.Question[0].Name = strings.TrimSuffix(state.Name(), rule.Suffix) + rule.Replacement
- return RewriteDone
+func (rule *prefixNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ if strings.HasPrefix(state.Name(), rule.prefix) {
+ state.Req.Question[0].Name = rule.replacement + strings.TrimPrefix(state.Name(), rule.prefix)
+ return rule.responseRuleFor(state)
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
-// Rewrite rewrites the current request based upon partial match of the
+// suffixNameRule rewrites the current request when the name ends with the matching string.
+type suffixNameRule struct {
+ nameRuleBase
+ suffix string
+}
+
+func newSuffixNameRule(nextAction string, auto bool, suffix, replacement string, answers ResponseRules) Rule {
+ var rules ResponseRules
+ if auto {
+ // for a suffix rewriter better standard response rewrites can be done
+ // just by using the original suffix/replacement in the opposite order
+ rewriter := newSuffixStringRewriter(replacement, suffix)
+ rules = ResponseRules{
+ &nameRewriterResponseRule{rewriter},
+ &valueRewriterResponseRule{rewriter},
+ }
+ }
+ return &suffixNameRule{
+ newNameRuleBase(nextAction, false, replacement, append(rules, answers...)),
+ suffix,
+ }
+}
+
+func (rule *suffixNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ if strings.HasSuffix(state.Name(), rule.suffix) {
+ state.Req.Question[0].Name = strings.TrimSuffix(state.Name(), rule.suffix) + rule.replacement
+ return rule.responseRuleFor(state)
+ }
+ return nil, RewriteIgnored
+}
+
+// substringNameRule rewrites the current request based upon partial match of the
// name in the question section of the request.
-func (rule *substringNameRule) Rewrite(ctx context.Context, state request.Request) Result {
- if strings.Contains(state.Name(), rule.Substring) {
- state.Req.Question[0].Name = strings.Replace(state.Name(), rule.Substring, rule.Replacement, -1)
- return RewriteDone
+type substringNameRule struct {
+ nameRuleBase
+ substring string
+}
+
+func newSubstringNameRule(nextAction string, auto bool, substring, replacement string, answers ResponseRules) Rule {
+ return &substringNameRule{
+ newNameRuleBase(nextAction, auto, replacement, answers),
+ substring,
+ }
+}
+
+func (rule *substringNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ if strings.Contains(state.Name(), rule.substring) {
+ state.Req.Question[0].Name = strings.Replace(state.Name(), rule.substring, rule.replacement, -1)
+ return rule.responseRuleFor(state)
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
-// Rewrite rewrites the current request when the name in the question
+// regexNameRule rewrites the current request when the name in the question
// section of the request matches a regular expression.
-func (rule *regexNameRule) Rewrite(ctx context.Context, state request.Request) Result {
- regexGroups := rule.Pattern.FindStringSubmatch(state.Name())
+type regexNameRule struct {
+ nameRuleBase
+ pattern *regexp.Regexp
+}
+
+func newRegexNameRule(nextAction string, auto bool, pattern *regexp.Regexp, replacement string, answers ResponseRules) Rule {
+ return &regexNameRule{
+ newNameRuleBase(nextAction, auto, replacement, answers),
+ pattern,
+ }
+}
+
+func (rule *regexNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ regexGroups := rule.pattern.FindStringSubmatch(state.Name())
if len(regexGroups) == 0 {
- return RewriteIgnored
+ return nil, RewriteIgnored
}
- s := rule.Replacement
+ s := rule.replacement
for groupIndex, groupValue := range regexGroups {
groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
s = strings.Replace(s, groupIndexStr, groupValue, -1)
}
state.Req.Question[0].Name = s
- return RewriteDone
+ return rule.responseRuleFor(state)
}
// newNameRule creates a name matching rule based on exact, partial, or regex match
@@ -139,149 +320,106 @@ func newNameRule(nextAction string, args ...string) (Rule, error) {
}
}
- //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")
+ var err error
+ var answers ResponseRules
+ auto := false
+ if len(args) > 3 {
+ auto, answers, err = parseAnswerRules(matchType, args[3:])
+ if err != nil {
+ return nil, err
+ }
}
- if len(args) < 7 {
- switch matchType {
- case ExactMatch:
- rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteQuestionTo, rewriteQuestionFrom)
- if err != nil {
- return nil, err
- }
- return &exactNameRule{
- nextAction,
- rewriteQuestionFrom,
- rewriteQuestionTo,
- ResponseRule{
- Active: true,
- Type: "name",
- Pattern: rewriteAnswerFromPattern,
- Replacement: rewriteQuestionFrom,
- },
- }, nil
- case PrefixMatch:
- return &prefixNameRule{
- nextAction,
- rewriteQuestionFrom,
- rewriteQuestionTo,
- }, nil
- case SuffixMatch:
- return &suffixNameRule{
- nextAction,
- rewriteQuestionFrom,
- rewriteQuestionTo,
- }, nil
- case SubstringMatch:
- return &substringNameRule{
- nextAction,
- rewriteQuestionFrom,
- rewriteQuestionTo,
- }, nil
- case RegexMatch:
- rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo)
- if err != nil {
- return nil, err
- }
- rewriteQuestionTo := plugin.Name(args[2]).Normalize()
- return &regexNameRule{
- nextAction,
- rewriteQuestionFromPattern,
- rewriteQuestionTo,
- []ResponseRule{{
- Type: "name",
- }},
- }, nil
- default:
- return nil, fmt.Errorf("name rule supports only exact, prefix, suffix, substring, and regex name matching, received: %s", matchType)
+ switch matchType {
+ case ExactMatch:
+ if _, err := isValidRegexPattern(rewriteQuestionTo, rewriteQuestionFrom); err != nil {
+ return nil, err
+ }
+ return newExactNameRule(nextAction, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
+ case PrefixMatch:
+ return newPrefixNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
+ case SuffixMatch:
+ return newSuffixNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
+ case SubstringMatch:
+ return newSubstringNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
+ case RegexMatch:
+ rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo)
+ if err != nil {
+ return nil, err
}
+ rewriteQuestionTo := plugin.Name(args[2]).Normalize()
+ return newRegexNameRule(nextAction, auto, rewriteQuestionFromPattern, rewriteQuestionTo, answers), nil
+ default:
+ return nil, fmt.Errorf("name rule supports only exact, prefix, suffix, substring, and regex name matching, received: %s", matchType)
+ }
+}
+
+func parseAnswerRules(name string, args []string) (auto bool, rules ResponseRules, err error) {
+ auto = false
+ arg := 0
+ nameRules := 0
+ last := ""
+ if len(args) < 2 {
+ return false, nil, fmt.Errorf("invalid arguments for %s rule", name)
}
- //if len(args) == 7 {
- if (len(args)-3)%4 == 0 {
- if matchType == RegexMatch {
- rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo)
+ for arg < len(args) {
+ if last == "" && args[arg] != AnswerMatch {
+ if last == "" {
+ return false, nil, fmt.Errorf("exceeded the number of arguments for a non-answer rule argument for %s rule", name)
+
+ }
+ return false, nil, fmt.Errorf("exceeded the number of arguments for %s answer rule for %s rule", last, name)
+ }
+ if args[arg] == AnswerMatch {
+ arg++
+ }
+ if len(args)-arg == 0 {
+ return false, nil, fmt.Errorf("type missing for answer rule for %s rule", name)
+ }
+ last = args[arg]
+ arg++
+ switch last {
+ case AutoMatch:
+ auto = true
+ continue
+ case NameMatch:
+ if len(args)-arg < 2 {
+ return false, nil, fmt.Errorf("%s answer rule for %s rule: 2 arguments required", last, name)
+ }
+ rewriteAnswerFrom := args[arg]
+ rewriteAnswerTo := args[arg+1]
+ rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo)
+ rewriteAnswerTo = plugin.Name(rewriteAnswerTo).Normalize()
if err != nil {
- return nil, err
+ return false, nil, fmt.Errorf("%s answer rule for %s rule: %s", last, name, err)
}
- rewriteQuestionTo = plugin.Name(args[2]).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
+ rules = append(rules, &nameRewriterResponseRule{newStringRewriter(rewriteAnswerFromPattern, rewriteAnswerTo)})
+ arg += 2
+ nameRules++
+ case ValueMatch:
+ if len(args)-arg < 2 {
+ return false, nil, fmt.Errorf("%s answer rule for %s rule: 2 arguments required", last, name)
}
-
- return &regexNameRule{
- nextAction,
- rewriteQuestionFromPattern,
- rewriteQuestionTo,
- responseRules,
- }, nil
+ rewriteAnswerFrom := args[arg]
+ rewriteAnswerTo := args[arg+1]
+ rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo)
+ rewriteAnswerTo = plugin.Name(rewriteAnswerTo).Normalize()
+ if err != nil {
+ return false, nil, fmt.Errorf("%s answer rule for %s rule: %s", last, name, err)
+ }
+ rules = append(rules, &valueRewriterResponseRule{newStringRewriter(rewriteAnswerFromPattern, rewriteAnswerTo)})
+ arg += 2
+ default:
+ return false, nil, fmt.Errorf("invalid type %q for answer rule for %s rule", last, name)
}
- return nil, fmt.Errorf("the rewrite of response is supported only for name regex rule")
}
- 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")
+ if auto && nameRules > 0 {
+ return false, nil, fmt.Errorf("auto name answer rule cannot be combined with explicit name anwer rules")
}
- 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 }
-func (rule *suffixNameRule) Mode() string { return rule.NextAction }
-func (rule *substringNameRule) Mode() string { return rule.NextAction }
-func (rule *regexNameRule) Mode() string { return rule.NextAction }
-
-// GetResponseRules returns rules to rewrite the response with. Currently not implemented.
-func (rule *exactNameRule) GetResponseRules() []ResponseRule {
- return []ResponseRule{rule.ResponseRule}
+ return
}
-// GetResponseRules returns rules to rewrite the response with. Currently not implemented.
-func (rule *prefixNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
-
-// GetResponseRules returns rules to rewrite the response with. Currently not implemented.
-func (rule *suffixNameRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }
-
-// GetResponseRules returns rules to rewrite the response with. Currently not implemented.
-func (rule *substringNameRule) GetResponseRules() []ResponseRule { return []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 {
return strings.HasSuffix(s, ".")
diff --git a/plugin/rewrite/name_test.go b/plugin/rewrite/name_test.go
index 0d3c3dd28..bd0112e39 100644
--- a/plugin/rewrite/name_test.go
+++ b/plugin/rewrite/name_test.go
@@ -16,9 +16,9 @@ func TestRewriteIllegalName(t *testing.T) {
r, _ := newNameRule("stop", "example.org.", "example..org.")
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- Rules: []Rule{r},
- noRevert: true,
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: []Rule{r},
+ RevertPolicy: NoRevertPolicy(),
}
ctx := context.TODO()
@@ -55,9 +55,9 @@ func TestRewriteNamePrefixSuffix(t *testing.T) {
}
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- Rules: []Rule{r},
- noRevert: true,
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: []Rule{r},
+ RevertPolicy: NoRevertPolicy(),
}
m := new(dns.Msg)
@@ -75,6 +75,237 @@ func TestRewriteNamePrefixSuffix(t *testing.T) {
}
}
+func TestRewriteNameNoRewrite(t *testing.T) {
+
+ ctx, close := context.WithCancel(context.TODO())
+ defer close()
+
+ tests := []struct {
+ next string
+ args []string
+ question string
+ expected string
+ }{
+ {"stop", []string{"prefix", "foo", "bar"}, "coredns.foo.", "coredns.foo."},
+ {"stop", []string{"prefix", "foo", "bar."}, "coredns.foo.", "coredns.foo."},
+ {"stop", []string{"suffix", "com", "org"}, "com.coredns.", "com.coredns."},
+ {"stop", []string{"suffix", "com", "org."}, "com.coredns.", "com.coredns."},
+ {"stop", []string{"substring", "service", "svc"}, "com.coredns.", "com.coredns."},
+ }
+ for i, tc := range tests {
+ r, err := newNameRule(tc.next, tc.args...)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+
+ rw := Rewrite{
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: []Rule{r},
+ }
+
+ m := new(dns.Msg)
+ m.SetQuestion(tc.question, dns.TypeA)
+
+ rec := dnstest.NewRecorder(&test.ResponseWriter{})
+ _, err = rw.ServeDNS(ctx, rec, m)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+ actual := rec.Msg.Answer[0].Header().Name
+ if actual != tc.expected {
+ t.Fatalf("Test %d: Expected answer rewrite to %v, got %v", i, tc.expected, actual)
+ }
+ }
+}
+
+func TestRewriteNamePrefixSuffixNoAutoAnswer(t *testing.T) {
+
+ ctx, close := context.WithCancel(context.TODO())
+ defer close()
+
+ tests := []struct {
+ next string
+ args []string
+ question string
+ expected string
+ }{
+ {"stop", []string{"prefix", "foo", "bar"}, "foo.example.com.", "bar.example.com."},
+ {"stop", []string{"prefix", "foo.", "bar."}, "foo.example.com.", "bar.example.com."},
+ {"stop", []string{"suffix", "com", "org"}, "foo.example.com.", "foo.example.org."},
+ {"stop", []string{"suffix", ".com", ".org"}, "foo.example.com.", "foo.example.org."},
+ {"stop", []string{"suffix", ".ingress.coredns.rocks", "nginx.coredns.rocks"}, "coredns.ingress.coredns.rocks.", "corednsnginx.coredns.rocks."},
+ }
+ for i, tc := range tests {
+ r, err := newNameRule(tc.next, tc.args...)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+
+ rw := Rewrite{
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: []Rule{r},
+ }
+
+ m := new(dns.Msg)
+ m.SetQuestion(tc.question, dns.TypeA)
+
+ rec := dnstest.NewRecorder(&test.ResponseWriter{})
+ _, err = rw.ServeDNS(ctx, rec, m)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+ actual := rec.Msg.Answer[0].Header().Name
+ if actual != tc.expected {
+ t.Fatalf("Test %d: Expected answer rewrite to %v, got %v", i, tc.expected, actual)
+ }
+ }
+}
+
+func TestRewriteNamePrefixSuffixAutoAnswer(t *testing.T) {
+
+ ctx, close := context.WithCancel(context.TODO())
+ defer close()
+
+ tests := []struct {
+ next string
+ args []string
+ question string
+ rewrite string
+ expected string
+ }{
+ {"stop", []string{"prefix", "foo", "bar", "answer", "auto"}, "foo.example.com.", "bar.example.com.", "foo.example.com."},
+ {"stop", []string{"prefix", "foo.", "bar.", "answer", "auto"}, "foo.example.com.", "bar.example.com.", "foo.example.com."},
+ {"stop", []string{"suffix", "com", "org", "answer", "auto"}, "foo.example.com.", "foo.example.org.", "foo.example.com."},
+ {"stop", []string{"suffix", ".com", ".org", "answer", "auto"}, "foo.example.com.", "foo.example.org.", "foo.example.com."},
+ {"stop", []string{"suffix", ".ingress.coredns.rocks", "nginx.coredns.rocks", "answer", "auto"}, "coredns.ingress.coredns.rocks.", "corednsnginx.coredns.rocks.", "coredns.ingress.coredns.rocks."},
+ }
+ for i, tc := range tests {
+ r, err := newNameRule(tc.next, tc.args...)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+
+ rw := Rewrite{
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: []Rule{r},
+ RevertPolicy: NoRestorePolicy(),
+ }
+
+ m := new(dns.Msg)
+ m.SetQuestion(tc.question, dns.TypeA)
+
+ rec := dnstest.NewRecorder(&test.ResponseWriter{})
+ _, err = rw.ServeDNS(ctx, rec, m)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+ rewrite := rec.Msg.Question[0].Name
+ if rewrite != tc.rewrite {
+ t.Fatalf("Test %d: Expected question rewrite to %v, got %v", i, tc.rewrite, rewrite)
+ }
+ actual := rec.Msg.Answer[0].Header().Name
+ if actual != tc.expected {
+ t.Fatalf("Test %d: Expected answer rewrite to %v, got %v", i, tc.expected, actual)
+ }
+ }
+}
+
+func TestRewriteNameExactAnswer(t *testing.T) {
+
+ ctx, close := context.WithCancel(context.TODO())
+ defer close()
+
+ tests := []struct {
+ next string
+ args []string
+ question string
+ rewrite string
+ expected string
+ }{
+ {"stop", []string{"exact", "coredns.rocks", "service.consul", "answer", "auto"}, "coredns.rocks.", "service.consul.", "coredns.rocks."},
+ {"stop", []string{"exact", "coredns.rocks.", "service.consul.", "answer", "auto"}, "coredns.rocks.", "service.consul.", "coredns.rocks."},
+ {"stop", []string{"exact", "coredns.rocks", "service.consul"}, "coredns.rocks.", "service.consul.", "coredns.rocks."},
+ {"stop", []string{"exact", "coredns.rocks.", "service.consul."}, "coredns.rocks.", "service.consul.", "coredns.rocks."},
+ {"stop", []string{"exact", "coredns.org.", "service.consul."}, "coredns.rocks.", "coredns.rocks.", "coredns.rocks."},
+ }
+ for i, tc := range tests {
+ r, err := newNameRule(tc.next, tc.args...)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+
+ rw := Rewrite{
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: []Rule{r},
+ RevertPolicy: NoRestorePolicy(),
+ }
+
+ m := new(dns.Msg)
+ m.SetQuestion(tc.question, dns.TypeA)
+
+ rec := dnstest.NewRecorder(&test.ResponseWriter{})
+ _, err = rw.ServeDNS(ctx, rec, m)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+ rewrite := rec.Msg.Question[0].Name
+ if rewrite != tc.rewrite {
+ t.Fatalf("Test %d: Expected question rewrite to %v, got %v", i, tc.rewrite, rewrite)
+ }
+ actual := rec.Msg.Answer[0].Header().Name
+ if actual != tc.expected {
+ t.Fatalf("Test %d: Expected answer rewrite to %v, got %v", i, tc.expected, actual)
+ }
+ }
+}
+
+func TestRewriteNameRegexAnswer(t *testing.T) {
+
+ ctx, close := context.WithCancel(context.TODO())
+ defer close()
+
+ tests := []struct {
+ next string
+ args []string
+ question string
+ rewrite string
+ expected string
+ }{
+ {"stop", []string{"regex", "(.*).coredns.rocks", "{1}.coredns.maps", "answer", "auto"}, "foo.coredns.rocks.", "foo.coredns.maps.", "foo.coredns.rocks."},
+ {"stop", []string{"regex", "(.*).coredns.rocks", "{1}.coredns.maps", "answer", "name", "(.*).coredns.maps", "{1}.coredns.works"}, "foo.coredns.rocks.", "foo.coredns.maps.", "foo.coredns.works."},
+ {"stop", []string{"regex", "(.*).coredns.rocks", "{1}.coredns.maps"}, "foo.coredns.rocks.", "foo.coredns.maps.", "foo.coredns.maps."},
+ }
+ for i, tc := range tests {
+ r, err := newNameRule(tc.next, tc.args...)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+
+ rw := Rewrite{
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: []Rule{r},
+ RevertPolicy: NoRestorePolicy(),
+ }
+
+ m := new(dns.Msg)
+ m.SetQuestion(tc.question, dns.TypeA)
+
+ rec := dnstest.NewRecorder(&test.ResponseWriter{})
+ _, err = rw.ServeDNS(ctx, rec, m)
+ if err != nil {
+ t.Fatalf("Test %d: Expected no error, got %s", i, err)
+ }
+ rewrite := rec.Msg.Question[0].Name
+ if rewrite != tc.rewrite {
+ t.Fatalf("Test %d: Expected question rewrite to %v, got %v", i, tc.rewrite, rewrite)
+ }
+ actual := rec.Msg.Answer[0].Header().Name
+ if actual != tc.expected {
+ t.Fatalf("Test %d: Expected answer rewrite to %v, got %v", i, tc.expected, actual)
+ }
+ }
+}
+
func TestNewNameRule(t *testing.T) {
tests := []struct {
next string
@@ -91,6 +322,26 @@ func TestNewNameRule(t *testing.T) {
{"stop", []string{"regex", "(.*).coredns.rocks", "{1}.coredns.rocks"}, false},
{"stop", []string{"regex", "(.*).coredns.rocks", "{1}.{2}.coredns.rocks"}, true},
{"stop", []string{"regex", "staging.mydomain.com", "aws-loadbalancer-id.us-east-1.elb.amazonaws.com"}, false},
+ {"stop", []string{"suffix", "staging.mydomain.com", "coredns.rock", "answer"}, true},
+ {"stop", []string{"suffix", "staging.mydomain.com", "coredns.rock", "answer", "name"}, true},
+ {"stop", []string{"suffix", "staging.mydomain.com", "coredns.rock", "answer", "other"}, true},
+ {"stop", []string{"suffix", "staging.mydomain.com", "coredns.rock", "answer", "auto"}, false},
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "auto"}, false},
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "name"}, true},
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "name", "coredns.rock", "staging.mydomain.com"}, false},
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "name", "(.*).coredns.rock", "{1}.{2}.staging.mydomain.com"}, true},
+
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "name", "(.*).coredns.rock", "{1}.staging.mydomain.com", "name", "(.*).coredns.rock", "{1}.staging.mydomain.com"}, false},
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "name", "(.*).coredns.rock", "{1}.staging.mydomain.com", "answer", "name", "(.*).coredns.rock", "{1}.staging.mydomain.com"}, false},
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "name", "(.*).coredns.rock", "{1}.staging.mydomain.com", "name", "(.*).coredns.rock"}, true},
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "name", "(.*).coredns.rock", "{1}.staging.mydomain.com", "value", "(.*).coredns.rock", "{1}.staging.mydomain.com"}, false},
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "name", "(.*).coredns.rock", "{1}.staging.mydomain.com", "answer", "value", "(.*).coredns.rock", "{1}.staging.mydomain.com"}, false},
+ {"stop", []string{"regex", "staging.mydomain.com", "coredns.rock", "answer", "name", "(.*).coredns.rock", "{1}.staging.mydomain.com", "value", "(.*).coredns.rock"}, true},
+
+ {"stop", []string{"suffix", "staging.mydomain.com.", "coredns.rock.", "answer", "value", "(.*).coredns.rock", "{1}.staging.mydomain.com", "value", "(.*).coredns.rock", "{1}.staging.mydomain.com"}, false},
+ {"stop", []string{"suffix", "staging.mydomain.com.", "coredns.rock.", "answer", "value", "(.*).coredns.rock", "{1}.staging.mydomain.com", "answer", "value", "(.*).coredns.rock", "{1}.staging.mydomain.com"}, false},
+ {"stop", []string{"suffix", "staging.mydomain.com.", "coredns.rock.", "answer", "value", "(.*).coredns.rock", "{1}.staging.mydomain.com", "name", "(.*).coredns.rock", "{1}.staging.mydomain.com"}, false},
+ {"stop", []string{"suffix", "staging.mydomain.com.", "coredns.rock.", "answer", "value", "(.*).coredns.rock", "{1}.staging.mydomain.com", "value", "(.*).coredns.rock"}, true},
}
for i, tc := range tests {
failed := false
diff --git a/plugin/rewrite/reverter.go b/plugin/rewrite/reverter.go
index 49222ddfc..7d83e557b 100644
--- a/plugin/rewrite/reverter.go
+++ b/plugin/rewrite/reverter.go
@@ -1,37 +1,69 @@
package rewrite
import (
- "regexp"
- "strconv"
- "strings"
-
"github.com/miekg/dns"
)
+// RevertPolicy controls the overall reverting process
+type RevertPolicy interface {
+ DoRevert() bool
+ DoQuestionRestore() bool
+}
+
+type revertPolicy struct {
+ noRevert bool
+ noRestore bool
+}
+
+func (p revertPolicy) DoRevert() bool {
+ return !p.noRevert
+}
+
+func (p revertPolicy) DoQuestionRestore() bool {
+ return !p.noRestore
+}
+
+// NoRevertPolicy disables all response rewrite rules
+func NoRevertPolicy() RevertPolicy {
+ return revertPolicy{true, false}
+}
+
+// NoRestorePolicy disables the question restoration during the response rewrite
+func NoRestorePolicy() RevertPolicy {
+ return revertPolicy{false, true}
+}
+
+// NewRevertPolicy creates a new reverter policy by dynamically specifying all
+// options.
+func NewRevertPolicy(noRevert, noRestore bool) RevertPolicy {
+ return revertPolicy{noRestore: noRestore, noRevert: noRevert}
+}
+
// ResponseRule contains a rule to rewrite a response with.
-type ResponseRule struct {
- Active bool
- Type string
- Pattern *regexp.Regexp
- Replacement string
- TTL uint32
+type ResponseRule interface {
+ RewriteResponse(rr dns.RR)
}
+// ResponseRules describes an ordered list of response rules to apply
+// after a name rewrite
+type ResponseRules = []ResponseRule
+
// ResponseReverter reverses the operations done on the question section of a packet.
// This is need because the client will otherwise disregards the response, i.e.
// dig will complain with ';; Question section mismatch: got example.org/HINFO/IN'
type ResponseReverter struct {
dns.ResponseWriter
originalQuestion dns.Question
- ResponseRewrite bool
- ResponseRules []ResponseRule
+ ResponseRules ResponseRules
+ revertPolicy RevertPolicy
}
// NewResponseReverter returns a pointer to a new ResponseReverter.
-func NewResponseReverter(w dns.ResponseWriter, r *dns.Msg) *ResponseReverter {
+func NewResponseReverter(w dns.ResponseWriter, r *dns.Msg, policy RevertPolicy) *ResponseReverter {
return &ResponseReverter{
ResponseWriter: w,
originalQuestion: r.Question[0],
+ revertPolicy: policy,
}
}
@@ -40,61 +72,33 @@ func (r *ResponseReverter) WriteMsg(res1 *dns.Msg) error {
// Deep copy 'res' as to not (e.g). rewrite a message that's also stored in the cache.
res := res1.Copy()
- res.Question[0] = r.originalQuestion
- if r.ResponseRewrite {
+ if r.revertPolicy.DoQuestionRestore() {
+ res.Question[0] = r.originalQuestion
+ }
+ if len(r.ResponseRules) > 0 {
for _, rr := range res.Ns {
- rewriteResourceRecord(res, rr, r)
+ r.rewriteResourceRecord(res, rr)
}
-
for _, rr := range res.Answer {
- rewriteResourceRecord(res, rr, r)
+ r.rewriteResourceRecord(res, rr)
}
-
for _, rr := range res.Extra {
- rewriteResourceRecord(res, rr, r)
+ r.rewriteResourceRecord(res, rr)
}
-
}
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
- )
-
+func (r *ResponseReverter) rewriteResourceRecord(res *dns.Msg, rr dns.RR) {
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
- }
+ rule.RewriteResponse(rr)
}
+}
- if isNameRewritten {
- rr.Header().Name = name
- }
- if isTTLRewritten {
- rr.Header().Ttl = ttl
- }
- if isValueRewritten {
- setRewrittenRecordValue(rr, value)
- }
+// 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)
+ return n, err
}
func getRecordValueForRewrite(rr dns.RR) (name string) {
@@ -136,24 +140,3 @@ func setRewrittenRecordValue(rr dns.RR, value string) {
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)
- return n, err
-}
diff --git a/plugin/rewrite/reverter_test.go b/plugin/rewrite/reverter_test.go
index 7e31a707f..2c90d9bb6 100644
--- a/plugin/rewrite/reverter_test.go
+++ b/plugin/rewrite/reverter_test.go
@@ -53,9 +53,9 @@ func doReverterTests(rules []Rule, t *testing.T) {
m.Question[0].Qclass = dns.ClassINET
m.Answer = tc.answer
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- Rules: rules,
- noRevert: tc.noRevert,
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: rules,
+ RevertPolicy: NewRevertPolicy(tc.noRevert, false),
}
rec := dnstest.NewRecorder(&test.ResponseWriter{})
rw.ServeDNS(ctx, rec, m)
@@ -81,38 +81,56 @@ var valueTests = []struct {
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."},
+ {"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")
+ 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
+ }
rules = append(rules, r)
- doValueReverterTests(rules, t)
+ doValueReverterTests("stop", 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")
+ 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
+ }
rules = append(rules, r)
- doValueReverterTests(rules, t)
+ doValueReverterTests("continue", rules, t)
+
+ rules = []Rule{}
+ 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)
+
+ doValueReverterTests("suffix", rules, t)
}
-func doValueReverterTests(rules []Rule, t *testing.T) {
+func doValueReverterTests(name string, rules []Rule, t *testing.T) {
ctx := context.TODO()
for i, tc := range valueTests {
m := new(dns.Msg)
@@ -121,37 +139,41 @@ func doValueReverterTests(rules []Rule, t *testing.T) {
m.Answer = tc.answer
m.Extra = tc.extra
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- Rules: rules,
- noRevert: tc.noRevert,
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: rules,
+ RevertPolicy: NewRevertPolicy(tc.noRevert, false),
}
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)
+ t.Errorf("Test %s.%d: Expected Name to be %q but was %q", name, 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)
+ t.Errorf("Test %s.%d: Expected Type to be '%d' but was '%d'", name, 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")
+ 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)
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)
+ t.Errorf("Test %s.%d: Expected Target to be '%s' but was '%s'", name, 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")
+ t.Errorf("Test %s.%d: Unexpected Additional Record Type / No Additional Records", name, i)
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)
+ t.Errorf("Test %s.%d: Expected Extra Name to be %q but was %q", name, i, tc.expectAddlName, resp.Extra[0].Header().Name)
}
}
}
diff --git a/plugin/rewrite/rewrite.go b/plugin/rewrite/rewrite.go
index f44cc62f1..188418ca2 100644
--- a/plugin/rewrite/rewrite.go
+++ b/plugin/rewrite/rewrite.go
@@ -31,40 +31,34 @@ const (
// Rewrite is a plugin to rewrite requests internally before being handled.
type Rewrite struct {
- Next plugin.Handler
- Rules []Rule
- noRevert bool
+ Next plugin.Handler
+ Rules []Rule
+ RevertPolicy
}
// ServeDNS implements the plugin.Handler interface.
func (rw Rewrite) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
- wr := NewResponseReverter(w, r)
+ if rw.RevertPolicy == nil {
+ rw.RevertPolicy = NewRevertPolicy(false, false)
+ }
+ wr := NewResponseReverter(w, r, rw.RevertPolicy)
state := request.Request{W: w, Req: r}
for _, rule := range rw.Rules {
- switch result := rule.Rewrite(ctx, state); result {
- case RewriteDone:
+ respRules, result := rule.Rewrite(ctx, state)
+ if result == RewriteDone {
if _, ok := dns.IsDomainName(state.Req.Question[0].Name); !ok {
err := fmt.Errorf("invalid name after rewrite: %s", state.Req.Question[0].Name)
state.Req.Question[0] = wr.originalQuestion
return dns.RcodeServerFailure, err
}
- for _, respRule := range rule.GetResponseRules() {
- if respRule.Active {
- wr.ResponseRewrite = true
- wr.ResponseRules = append(wr.ResponseRules, respRule)
- }
- }
+ wr.ResponseRules = append(wr.ResponseRules, respRules...)
if rule.Mode() == Stop {
- if rw.noRevert {
- return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, w, r)
- }
- return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, wr, r)
+ break
}
- case RewriteIgnored:
}
}
- if rw.noRevert || len(wr.ResponseRules) == 0 {
+ if !rw.RevertPolicy.DoRevert() || len(wr.ResponseRules) == 0 {
return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, w, r)
}
return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, wr, r)
@@ -76,11 +70,9 @@ func (rw Rewrite) Name() string { return "rewrite" }
// Rule describes a rewrite rule.
type Rule interface {
// Rewrite rewrites the current request.
- Rewrite(ctx context.Context, state request.Request) Result
+ Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result)
// Mode returns the processing mode stop or continue.
Mode() string
- // GetResponseRules returns rules to rewrite response with, if any.
- GetResponseRules() []ResponseRule
}
func newRule(args ...string) (Rule, error) {
diff --git a/plugin/rewrite/rewrite_test.go b/plugin/rewrite/rewrite_test.go
index ecab07787..260b65c82 100644
--- a/plugin/rewrite/rewrite_test.go
+++ b/plugin/rewrite/rewrite_test.go
@@ -3,6 +3,7 @@ package rewrite
import (
"bytes"
"context"
+ "fmt"
"reflect"
"testing"
@@ -16,6 +17,11 @@ import (
)
func msgPrinter(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
+ if len(r.Answer) == 0 {
+ r.Answer = []dns.RR{
+ test.A(fmt.Sprintf("%s 5 IN A 10.0.0.1", r.Question[0].Name)),
+ }
+ }
w.WriteMsg(r)
return 0, nil
}
@@ -44,7 +50,7 @@ func TestNewRule(t *testing.T) {
{[]string{"name", "regex", "(cdns)\\.(core)\\.(rocks)", "{2}.{1}.{3}", "answer", "ttl", "(core)\\.(cdns)\\.(rocks)", "{2}.{1}.{3}"}, true, nil},
{[]string{"name", "regex", "(ddns)\\.(core)\\.(rocks)", "{2}.{1}.{3}", "answer", "name", "\xecore\\.(ddns)\\.(rocks)", "{2}.{1}.{3}"}, true, nil},
{[]string{"name", "regex", "\xedns\\.(core)\\.(rocks)", "{2}.{1}.{3}", "answer", "name", "(core)\\.(edns)\\.(rocks)", "{2}.{1}.{3}"}, true, nil},
- {[]string{"name", "substring", "fcore.dns.rocks", "dns.fcore.rocks", "answer", "name", "(fcore)\\.(dns)\\.(rocks)", "{2}.{1}.{3}"}, true, nil},
+ {[]string{"name", "substring", "fcore.dns.rocks", "dns.fcore.rocks", "answer", "name", "(fcore)\\.(dns)\\.(rocks)", "{2}.{1}.{3}"}, false, reflect.TypeOf(&substringNameRule{})},
{[]string{"name", "substring", "a.com", "b.com", "c.com"}, true, nil},
{[]string{"type"}, true, nil},
{[]string{"type", "a"}, true, nil},
@@ -185,9 +191,9 @@ func TestRewrite(t *testing.T) {
rules = append(rules, r)
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- Rules: rules,
- noRevert: true,
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: rules,
+ RevertPolicy: NoRevertPolicy(),
}
tests := []struct {
@@ -248,8 +254,8 @@ func TestRewrite(t *testing.T) {
func TestRewriteEDNS0Local(t *testing.T) {
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- noRevert: true,
+ Next: plugin.HandlerFunc(msgPrinter),
+ RevertPolicy: NoRevertPolicy(),
}
tests := []struct {
@@ -336,9 +342,9 @@ func TestEdns0LocalMultiRule(t *testing.T) {
rules = append(rules, r)
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- Rules: rules,
- noRevert: true,
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: rules,
+ RevertPolicy: NoRevertPolicy(),
}
tests := []struct {
@@ -447,8 +453,8 @@ func (tp testProvider) Metadata(ctx context.Context, state request.Request) cont
func TestRewriteEDNS0LocalVariable(t *testing.T) {
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- noRevert: true,
+ Next: plugin.HandlerFunc(msgPrinter),
+ RevertPolicy: NoRevertPolicy(),
}
expectedMetadata := []metadata.Provider{
@@ -569,8 +575,8 @@ func TestRewriteEDNS0LocalVariable(t *testing.T) {
func TestRewriteEDNS0Subnet(t *testing.T) {
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- noRevert: true,
+ Next: plugin.HandlerFunc(msgPrinter),
+ RevertPolicy: NoRevertPolicy(),
}
tests := []struct {
diff --git a/plugin/rewrite/setup_test.go b/plugin/rewrite/setup_test.go
index dcad03961..00808bd9c 100644
--- a/plugin/rewrite/setup_test.go
+++ b/plugin/rewrite/setup_test.go
@@ -44,12 +44,12 @@ func TestParse(t *testing.T) {
`rewrite stop {
name regex foo bar
answer name bar foo
- name baz qux
+ name baz
}`)
_, err = rewriteParse(c)
if err == nil {
t.Errorf("Expected error but got success for invalid response rewrite")
- } else if !strings.Contains(err.Error(), "must consist only of") {
+ } else if !strings.Contains(err.Error(), "2 arguments required") {
t.Errorf("Got wrong error for invalid response rewrite: %v", err.Error())
}
diff --git a/plugin/rewrite/ttl.go b/plugin/rewrite/ttl.go
index 6af9cdc40..364583dc7 100644
--- a/plugin/rewrite/ttl.go
+++ b/plugin/rewrite/ttl.go
@@ -9,81 +9,91 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/request"
- //"github.com/miekg/dns"
+
+ "github.com/miekg/dns"
)
+type ttlResponseRule struct {
+ TTL uint32
+}
+
+func (r *ttlResponseRule) RewriteResponse(rr dns.RR) {
+ rr.Header().Ttl = r.TTL
+}
+
+type ttlRuleBase struct {
+ nextAction string
+ response ttlResponseRule
+}
+
+func newTTLRuleBase(nextAction string, ttl uint32) ttlRuleBase {
+ return ttlRuleBase{
+ nextAction: nextAction,
+ response: ttlResponseRule{TTL: ttl},
+ }
+}
+
+func (rule *ttlRuleBase) responseRule(match bool) (ResponseRules, Result) {
+ if match {
+ return ResponseRules{&rule.response}, RewriteDone
+ }
+ return nil, RewriteIgnored
+}
+
+// Mode returns the processing nextAction
+func (rule *ttlRuleBase) Mode() string { return rule.nextAction }
+
type exactTTLRule struct {
- NextAction string
- From string
- ResponseRules []ResponseRule
+ ttlRuleBase
+ From string
}
type prefixTTLRule struct {
- NextAction string
- Prefix string
- ResponseRules []ResponseRule
+ ttlRuleBase
+ Prefix string
}
type suffixTTLRule struct {
- NextAction string
- Suffix string
- ResponseRules []ResponseRule
+ ttlRuleBase
+ Suffix string
}
type substringTTLRule struct {
- NextAction string
- Substring string
- ResponseRules []ResponseRule
+ ttlRuleBase
+ Substring string
}
type regexTTLRule struct {
- NextAction string
- Pattern *regexp.Regexp
- ResponseRules []ResponseRule
+ ttlRuleBase
+ Pattern *regexp.Regexp
}
// Rewrite rewrites the current request based upon exact match of the name
// in the question section of the request.
-func (rule *exactTTLRule) Rewrite(ctx context.Context, state request.Request) Result {
- if rule.From == state.Name() {
- return RewriteDone
- }
- return RewriteIgnored
+func (rule *exactTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ return rule.responseRule(rule.From == state.Name())
}
// Rewrite rewrites the current request when the name begins with the matching string.
-func (rule *prefixTTLRule) Rewrite(ctx context.Context, state request.Request) Result {
- if strings.HasPrefix(state.Name(), rule.Prefix) {
- return RewriteDone
- }
- return RewriteIgnored
+func (rule *prefixTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ return rule.responseRule(strings.HasPrefix(state.Name(), rule.Prefix))
}
// Rewrite rewrites the current request when the name ends with the matching string.
-func (rule *suffixTTLRule) Rewrite(ctx context.Context, state request.Request) Result {
- if strings.HasSuffix(state.Name(), rule.Suffix) {
- return RewriteDone
- }
- return RewriteIgnored
+func (rule *suffixTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ return rule.responseRule(strings.HasSuffix(state.Name(), rule.Suffix))
}
// Rewrite rewrites the current request based upon partial match of the
// name in the question section of the request.
-func (rule *substringTTLRule) Rewrite(ctx context.Context, state request.Request) Result {
- if strings.Contains(state.Name(), rule.Substring) {
- return RewriteDone
- }
- return RewriteIgnored
+func (rule *substringTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ return rule.responseRule(strings.Contains(state.Name(), rule.Substring))
}
// Rewrite rewrites the current request when the name in the question
// section of the request matches a regular expression.
-func (rule *regexTTLRule) Rewrite(ctx context.Context, state request.Request) Result {
- regexGroups := rule.Pattern.FindStringSubmatch(state.Name())
- if len(regexGroups) == 0 {
- return RewriteIgnored
- }
- return RewriteDone
+func (rule *regexTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
+ return rule.responseRule(len(rule.Pattern.FindStringSubmatch(state.Name())) != 0)
}
// newTTLRule creates a name matching rule based on exact, partial, or regex match
@@ -106,43 +116,23 @@ func newTTLRule(nextAction string, args ...string) (Rule, error) {
switch strings.ToLower(args[0]) {
case ExactMatch:
return &exactTTLRule{
- nextAction,
+ newTTLRuleBase(nextAction, ttl),
plugin.Name(args[1]).Normalize(),
- []ResponseRule{{
- Active: true,
- Type: "ttl",
- TTL: ttl,
- }},
}, nil
case PrefixMatch:
return &prefixTTLRule{
- nextAction,
+ newTTLRuleBase(nextAction, ttl),
plugin.Name(args[1]).Normalize(),
- []ResponseRule{{
- Active: true,
- Type: "ttl",
- TTL: ttl,
- }},
}, nil
case SuffixMatch:
return &suffixTTLRule{
- nextAction,
+ newTTLRuleBase(nextAction, ttl),
plugin.Name(args[1]).Normalize(),
- []ResponseRule{{
- Active: true,
- Type: "ttl",
- TTL: ttl,
- }},
}, nil
case SubstringMatch:
return &substringTTLRule{
- nextAction,
+ newTTLRuleBase(nextAction, ttl),
plugin.Name(args[1]).Normalize(),
- []ResponseRule{{
- Active: true,
- Type: "ttl",
- TTL: ttl,
- }},
}, nil
case RegexMatch:
regexPattern, err := regexp.Compile(args[1])
@@ -150,13 +140,8 @@ func newTTLRule(nextAction string, args ...string) (Rule, error) {
return nil, fmt.Errorf("invalid regex pattern in a ttl rule: %s", args[1])
}
return &regexTTLRule{
- nextAction,
+ newTTLRuleBase(nextAction, ttl),
regexPattern,
- []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")
@@ -166,48 +151,11 @@ func newTTLRule(nextAction string, args ...string) (Rule, error) {
return nil, fmt.Errorf("many few arguments for a ttl rule")
}
return &exactTTLRule{
- nextAction,
+ newTTLRuleBase(nextAction, ttl),
plugin.Name(args[0]).Normalize(),
- []ResponseRule{{
- Active: true,
- Type: "ttl",
- TTL: ttl,
- }},
}, nil
}
-// Mode returns the processing nextAction
-func (rule *exactTTLRule) Mode() string { return rule.NextAction }
-func (rule *prefixTTLRule) Mode() string { return rule.NextAction }
-func (rule *suffixTTLRule) Mode() string { return rule.NextAction }
-func (rule *substringTTLRule) Mode() string { return rule.NextAction }
-func (rule *regexTTLRule) Mode() string { return rule.NextAction }
-
-// GetResponseRules returns rules to rewrite the response with. Currently not implemented.
-func (rule *exactTTLRule) GetResponseRules() []ResponseRule {
- return rule.ResponseRules
-}
-
-// GetResponseRules returns rules to rewrite the response with. Currently not implemented.
-func (rule *prefixTTLRule) GetResponseRules() []ResponseRule {
- return rule.ResponseRules
-}
-
-// GetResponseRules returns rules to rewrite the response with. Currently not implemented.
-func (rule *suffixTTLRule) GetResponseRules() []ResponseRule {
- return rule.ResponseRules
-}
-
-// GetResponseRules returns rules to rewrite the response with. Currently not implemented.
-func (rule *substringTTLRule) GetResponseRules() []ResponseRule {
- return rule.ResponseRules
-}
-
-// 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.
func isValidTTL(v string) (uint32, bool) {
i, err := strconv.Atoi(v)
diff --git a/plugin/rewrite/ttl_test.go b/plugin/rewrite/ttl_test.go
index 359807299..9ac0807ac 100644
--- a/plugin/rewrite/ttl_test.go
+++ b/plugin/rewrite/ttl_test.go
@@ -121,9 +121,8 @@ func doTTLTests(rules []Rule, t *testing.T) {
m.Question[0].Qclass = dns.ClassINET
m.Answer = tc.answer
rw := Rewrite{
- Next: plugin.HandlerFunc(msgPrinter),
- Rules: rules,
- noRevert: false,
+ Next: plugin.HandlerFunc(msgPrinter),
+ Rules: rules,
}
rec := dnstest.NewRecorder(&test.ResponseWriter{})
rw.ServeDNS(ctx, rec, m)
diff --git a/plugin/rewrite/type.go b/plugin/rewrite/type.go
index 44f5047b5..63796e9dc 100644
--- a/plugin/rewrite/type.go
+++ b/plugin/rewrite/type.go
@@ -31,18 +31,15 @@ func newTypeRule(nextAction string, args ...string) (Rule, error) {
}
// Rewrite rewrites the current request.
-func (rule *typeRule) Rewrite(ctx context.Context, state request.Request) Result {
+func (rule *typeRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
if rule.fromType > 0 && rule.toType > 0 {
if state.QType() == rule.fromType {
state.Req.Question[0].Qtype = rule.toType
- return RewriteDone
+ return nil, RewriteDone
}
}
- return RewriteIgnored
+ return nil, RewriteIgnored
}
// Mode returns the processing mode.
func (rule *typeRule) Mode() string { return rule.nextAction }
-
-// GetResponseRules return rules to rewrite the response with. Currently not implemented.
-func (rule *typeRule) GetResponseRules() []ResponseRule { return []ResponseRule{} }