mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	plugin/rewrite: streamline the ResponseRule handling. (#4473)
* plugin/rewrite: streamline the ResponseRule handling. The functionality of a response rule is now completely encapsulated behind a `ResponseRule` interface. This significantly simplifies the complete processing flow, it enables more flexible response handling and it is possible to eliminate lots of state flags, ifs and switches. Based on the new flexibility the pull request also enables to support a response name rewrite for all name rewrite types. To be compatible, an explicit `answer auto` option is added to support a best effort response rewrite (name and value). Additionally now all name rewrite rules support additional name and value reponse rewrite options. Using this feature it is also possible now to rewrite a complete sub domain hierarchy to a single domain name combined with a correct rewrite (#2389). Signed-off-by: Uwe Krueger <uwe.krueger@sap.com> * revert policy Signed-off-by: Uwe Krueger <uwe.krueger@sap.com> Co-authored-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
		| @@ -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... | 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. | * **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.); |    * `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`. | 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` - 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. |      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. |    * `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. |    * `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. |    * `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 | * **FROM** is the name (exact, suffix, prefix, substring, or regex) or type to match | ||||||
| * **TO** is the destination name or type to rewrite to | * **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 | If you specify multiple rules and an incoming query matches multiple rules, the rewrite | ||||||
| will behave as follows: | will behave as follows: | ||||||
| @@ -49,7 +62,7 @@ client. | |||||||
| The syntax for name rewriting is as follows: | 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: | 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 | * **suffix**: when the name ends with the matching string | ||||||
| * **regex**: when the name in the question section of a request matches a regular expression | * **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 | The following instruction allows rewriting names in the query that | ||||||
| contain the substring `service.us-west-1.example.org`: | contain the substring `service.us-west-1.example.org`: | ||||||
| @@ -95,7 +109,8 @@ rewrite name suffix .schmoogle.com. .google.com. | |||||||
|  |  | ||||||
| ### Response Rewrites | ### 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 | 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` | requests, because some DNS resolvers treat mismatches between the `QUESTION SECTION` | ||||||
| and `ANSWER SECTION` as a man-in-the-middle attack (MITM). | 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. | 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 | The following configuration snippet allows for rewriting of the | ||||||
| `ANSWER SECTION`, provided that the `QUESTION SECTION` was rewritten: | `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 | 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 | 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 | (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 | regular expression and a rewrite name as parameters and works in the same way as the | ||||||
| `answer name` rule. | `answer name` rule. | ||||||
|  |  | ||||||
| @@ -173,20 +219,29 @@ rewrite [continue|stop] { | |||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Note that the above syntax is strict.  For response rewrites, only `name` | Note that the above syntax is strict.  For response rewrites, only `name` | ||||||
| rules are allowed to match the question section, and only by match type | rules are allowed to match the question section. The answer rewrite must be | ||||||
| `regex`. The answer rewrite must be after the name, as in the | after the name, as in the syntax example. | ||||||
| syntax example. |  | ||||||
|  |  | ||||||
| An alternate syntax for rewriting a DNS request and response is as | #### Multiple Response Rewrites | ||||||
| follows: |  | ||||||
|  |  | ||||||
|  | `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. | ||||||
|  |  | ||||||
|  | ```options | ||||||
|  | answer (auto | (name|value FROM TO)) { [answer] (auto | (name|value FROM TO)) } | ||||||
| ``` | ``` | ||||||
| rewrite [continue|stop] name regex STRING STRING answer name STRING STRING [answer value STRING STRING] |  | ||||||
|  | For example: | ||||||
|  | ``` | ||||||
|  | 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, | When using `exact` name rewrite rules, the answer gets rewritten automatically, | ||||||
| and there is no need to define `answer name`. The rule below | and there is no need to define `answer name auto`. But it is still possible to define | ||||||
| rewrites the name in a request from `RED` to `BLUE`, and subsequently | 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 | rewrites the name in a corresponding response from `BLUE` to `RED`. The | ||||||
| client in the request would see only `RED` and no `BLUE`. | 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 | 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. | `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 | 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 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. | * 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. |  | ||||||
|   | |||||||
| @@ -30,18 +30,15 @@ func newClassRule(nextAction string, args ...string) (Rule, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request. | // 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 rule.fromClass > 0 && rule.toClass > 0 { | ||||||
| 		if state.Req.Question[0].Qclass == rule.fromClass { | 		if state.Req.Question[0].Qclass == rule.fromClass { | ||||||
| 			state.Req.Question[0].Qclass = rule.toClass | 			state.Req.Question[0].Qclass = rule.toClass | ||||||
| 			return RewriteDone | 			return nil, RewriteDone | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return RewriteIgnored | 	return nil, RewriteIgnored | ||||||
| } | } | ||||||
|  |  | ||||||
| // Mode returns the processing mode. | // Mode returns the processing mode. | ||||||
| func (rule *classRule) Mode() string { return rule.NextAction } | func (rule *classRule) Mode() string { return rule.NextAction } | ||||||
|  |  | ||||||
| // GetResponseRules return rules to rewrite the response with. Currently not implemented. |  | ||||||
| func (rule *classRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } |  | ||||||
|   | |||||||
| @@ -49,14 +49,14 @@ func setupEdns0Opt(r *dns.Msg) *dns.OPT { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite will alter the request EDNS0 NSID option | // 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) | 	o := setupEdns0Opt(state.Req) | ||||||
|  |  | ||||||
| 	for _, s := range o.Option { | 	for _, s := range o.Option { | ||||||
| 		if e, ok := s.(*dns.EDNS0_NSID); ok { | 		if e, ok := s.(*dns.EDNS0_NSID); ok { | ||||||
| 			if rule.action == Replace || rule.action == Set { | 			if rule.action == Replace || rule.action == Set { | ||||||
| 				e.Nsid = "" // make sure it is empty for request | 				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 | 	// add option if not found | ||||||
| 	if rule.action == Append || rule.action == Set { | 	if rule.action == Append || rule.action == Set { | ||||||
| 		o.Option = append(o.Option, &dns.EDNS0_NSID{Code: dns.EDNS0NSID, Nsid: ""}) | 		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. | // Mode returns the processing mode. | ||||||
| func (rule *edns0NsidRule) Mode() string { return rule.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. | // 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) | 	o := setupEdns0Opt(state.Req) | ||||||
|  |  | ||||||
| 	for _, s := range o.Option { | 	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.code == e.Code { | ||||||
| 				if rule.action == Replace || rule.action == Set { | 				if rule.action == Replace || rule.action == Set { | ||||||
| 					e.Data = rule.data | 					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 | 	// add option if not found | ||||||
| 	if rule.action == Append || rule.action == Set { | 	if rule.action == Append || rule.action == Set { | ||||||
| 		o.Option = append(o.Option, &dns.EDNS0_LOCAL{Code: rule.code, Data: rule.data}) | 		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. | // Mode returns the processing mode. | ||||||
| func (rule *edns0LocalRule) Mode() string { return rule.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 | // newEdns0Rule creates an EDNS0 rule of the appropriate type based on the args | ||||||
| func newEdns0Rule(mode string, args ...string) (Rule, error) { | func newEdns0Rule(mode string, args ...string) (Rule, error) { | ||||||
| 	if len(args) < 2 { | 	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. | // 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) | 	data, err := rule.ruleData(ctx, state) | ||||||
| 	if err != nil || data == nil { | 	if err != nil || data == nil { | ||||||
| 		return RewriteIgnored | 		return nil, RewriteIgnored | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	o := setupEdns0Opt(state.Req) | 	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.code == e.Code { | ||||||
| 				if rule.action == Replace || rule.action == Set { | 				if rule.action == Replace || rule.action == Set { | ||||||
| 					e.Data = data | 					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 | 	// add option if not found | ||||||
| 	if rule.action == Append || rule.action == Set { | 	if rule.action == Append || rule.action == Set { | ||||||
| 		o.Option = append(o.Option, &dns.EDNS0_LOCAL{Code: rule.code, Data: data}) | 		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. | // Mode returns the processing mode. | ||||||
| func (rule *edns0VariableRule) Mode() string { return rule.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 { | func isValidVariable(variable string) bool { | ||||||
| 	switch variable { | 	switch variable { | ||||||
| 	case | 	case | ||||||
| @@ -333,17 +324,17 @@ func (rule *edns0SubnetRule) fillEcsData(state request.Request, ecs *dns.EDNS0_S | |||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite will alter the request EDNS0 subnet option. | // 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) | 	o := setupEdns0Opt(state.Req) | ||||||
|  |  | ||||||
| 	for _, s := range o.Option { | 	for _, s := range o.Option { | ||||||
| 		if e, ok := s.(*dns.EDNS0_SUBNET); ok { | 		if e, ok := s.(*dns.EDNS0_SUBNET); ok { | ||||||
| 			if rule.action == Replace || rule.action == Set { | 			if rule.action == Replace || rule.action == Set { | ||||||
| 				if rule.fillEcsData(state, e) == nil { | 				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} | 		opt := &dns.EDNS0_SUBNET{Code: dns.EDNS0SUBNET} | ||||||
| 		if rule.fillEcsData(state, opt) == nil { | 		if rule.fillEcsData(state, opt) == nil { | ||||||
| 			o.Option = append(o.Option, opt) | 			o.Option = append(o.Option, opt) | ||||||
| 			return RewriteDone | 			return nil, RewriteDone | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return RewriteIgnored | 	return nil, RewriteIgnored | ||||||
| } | } | ||||||
|  |  | ||||||
| // Mode returns the processing mode | // Mode returns the processing mode | ||||||
| func (rule *edns0SubnetRule) Mode() string { return rule.mode } | func (rule *edns0SubnetRule) Mode() string { return rule.mode } | ||||||
|  |  | ||||||
| // GetResponseRules return rules to rewrite the response with. Currently not implemented. |  | ||||||
| func (rule *edns0SubnetRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } |  | ||||||
|  |  | ||||||
| // These are all defined actions. | // These are all defined actions. | ||||||
| const ( | const ( | ||||||
| 	Replace = "replace" | 	Replace = "replace" | ||||||
|   | |||||||
| @@ -9,38 +9,106 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/coredns/coredns/plugin" | 	"github.com/coredns/coredns/plugin" | ||||||
| 	"github.com/coredns/coredns/request" | 	"github.com/coredns/coredns/request" | ||||||
|  |  | ||||||
|  | 	"github.com/miekg/dns" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type exactNameRule struct { | // stringRewriter rewrites a string | ||||||
| 	NextAction string | type stringRewriter interface { | ||||||
| 	From       string | 	rewriteString(src string) string | ||||||
| 	To         string |  | ||||||
| 	ResponseRule |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type prefixNameRule struct { | // regexStringRewriter can be used to rewrite strings by regex pattern. | ||||||
| 	NextAction  string | // it contains all the information required to detect and execute a rewrite | ||||||
| 	Prefix      string | // on a string. | ||||||
| 	Replacement string | type regexStringRewriter struct { | ||||||
|  | 	pattern     *regexp.Regexp | ||||||
|  | 	replacement string | ||||||
| } | } | ||||||
|  |  | ||||||
| type suffixNameRule struct { | var _ stringRewriter = ®exStringRewriter{} | ||||||
| 	NextAction  string |  | ||||||
| 	Suffix      string | func newStringRewriter(pattern *regexp.Regexp, replacement string) stringRewriter { | ||||||
| 	Replacement string | 	return ®exStringRewriter{pattern, replacement} | ||||||
| } | } | ||||||
|  |  | ||||||
| type substringNameRule struct { | func (r *regexStringRewriter) rewriteString(src string) string { | ||||||
| 	NextAction  string | 	regexGroups := r.pattern.FindStringSubmatch(src) | ||||||
| 	Substring   string | 	if len(regexGroups) == 0 { | ||||||
| 	Replacement string | 		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 { | // remapStringRewriter maps a dedicated string to another string | ||||||
| 	NextAction    string | // it also maps a the domain of a sub domain. | ||||||
| 	Pattern       *regexp.Regexp | type remapStringRewriter struct { | ||||||
| 	Replacement   string | 	orig        string | ||||||
| 	ResponseRules []ResponseRule | 	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 ( | const ( | ||||||
| @@ -54,60 +122,173 @@ const ( | |||||||
| 	SubstringMatch = "substring" | 	SubstringMatch = "substring" | ||||||
| 	// RegexMatch matches when the name in the question section of a request matches a regular expression | 	// RegexMatch matches when the name in the question section of a request matches a regular expression | ||||||
| 	RegexMatch = "regex" | 	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. | // in the question section of the request. | ||||||
| func (rule *exactNameRule) Rewrite(ctx context.Context, state request.Request) Result { | type exactNameRule struct { | ||||||
| 	if rule.From == state.Name() { | 	nameRuleBase | ||||||
| 		state.Req.Question[0].Name = rule.To | 	from string | ||||||
| 		return RewriteDone |  | ||||||
| 	} |  | ||||||
| 	return RewriteIgnored |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request when the name begins with the matching string. | func newExactNameRule(nextAction string, orig, replacement string, answers ResponseRules) Rule { | ||||||
| func (rule *prefixNameRule) Rewrite(ctx context.Context, state request.Request) Result { | 	return &exactNameRule{ | ||||||
| 	if strings.HasPrefix(state.Name(), rule.Prefix) { | 		newNameRuleBase(nextAction, true, replacement, answers), | ||||||
| 		state.Req.Question[0].Name = rule.Replacement + strings.TrimPrefix(state.Name(), rule.Prefix) | 		orig, | ||||||
| 		return RewriteDone |  | ||||||
| 	} | 	} | ||||||
| 	return RewriteIgnored |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request when the name ends with the matching string. | func (rule *exactNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) { | ||||||
| func (rule *suffixNameRule) Rewrite(ctx context.Context, state request.Request) Result { | 	if rule.from == state.Name() { | ||||||
| 	if strings.HasSuffix(state.Name(), rule.Suffix) { | 		state.Req.Question[0].Name = rule.replacement | ||||||
| 		state.Req.Question[0].Name = strings.TrimSuffix(state.Name(), rule.Suffix) + rule.Replacement | 		return rule.responseRuleFor(state) | ||||||
| 		return RewriteDone |  | ||||||
| 	} | 	} | ||||||
| 	return RewriteIgnored | 	return nil, RewriteIgnored | ||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request based upon partial match of the | // prefixNameRule rewrites the current request when the name begins with the matching string. | ||||||
|  | type prefixNameRule struct { | ||||||
|  | 	nameRuleBase | ||||||
|  | 	prefix string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newPrefixNameRule(nextAction string, auto bool, prefix, replacement string, answers ResponseRules) Rule { | ||||||
|  | 	return &prefixNameRule{ | ||||||
|  | 		newNameRuleBase(nextAction, auto, replacement, answers), | ||||||
|  | 		prefix, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 nil, RewriteIgnored | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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. | // name in the question section of the request. | ||||||
| func (rule *substringNameRule) Rewrite(ctx context.Context, state request.Request) Result { | type substringNameRule struct { | ||||||
| 	if strings.Contains(state.Name(), rule.Substring) { | 	nameRuleBase | ||||||
| 		state.Req.Question[0].Name = strings.Replace(state.Name(), rule.Substring, rule.Replacement, -1) | 	substring string | ||||||
| 		return RewriteDone |  | ||||||
| 	} |  | ||||||
| 	return RewriteIgnored |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request when the name in the question | func newSubstringNameRule(nextAction string, auto bool, substring, replacement string, answers ResponseRules) Rule { | ||||||
| // section of the request matches a regular expression. | 	return &substringNameRule{ | ||||||
| func (rule *regexNameRule) Rewrite(ctx context.Context, state request.Request) Result { | 		newNameRuleBase(nextAction, auto, replacement, answers), | ||||||
| 	regexGroups := rule.Pattern.FindStringSubmatch(state.Name()) | 		substring, | ||||||
| 	if len(regexGroups) == 0 { |  | ||||||
| 		return RewriteIgnored |  | ||||||
| 	} | 	} | ||||||
| 	s := rule.Replacement | } | ||||||
|  |  | ||||||
|  | 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 nil, RewriteIgnored | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // regexNameRule rewrites the current request when the name in the question | ||||||
|  | // section of the request matches a regular expression. | ||||||
|  | type regexNameRule struct { | ||||||
|  | 	nameRuleBase | ||||||
|  | 	pattern *regexp.Regexp | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newRegexNameRule(nextAction string, auto bool, pattern *regexp.Regexp, replacement string, answers ResponseRules) Rule { | ||||||
|  | 	return ®exNameRule{ | ||||||
|  | 		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 nil, RewriteIgnored | ||||||
|  | 	} | ||||||
|  | 	s := rule.replacement | ||||||
| 	for groupIndex, groupValue := range regexGroups { | 	for groupIndex, groupValue := range regexGroups { | ||||||
| 		groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}" | 		groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}" | ||||||
| 		s = strings.Replace(s, groupIndexStr, groupValue, -1) | 		s = strings.Replace(s, groupIndexStr, groupValue, -1) | ||||||
| 	} | 	} | ||||||
| 	state.Req.Question[0].Name = s | 	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 | // newNameRule creates a name matching rule based on exact, partial, or regex match | ||||||
| @@ -139,148 +320,105 @@ func newNameRule(nextAction string, args ...string) (Rule, error) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	//if len(args) > 3 && len(args) != 7 { | 	var err error | ||||||
| 	if len(args) > 3 && (len(args)-3)%4 != 0 { | 	var answers ResponseRules | ||||||
| 		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") | 	auto := false | ||||||
| 	} | 	if len(args) > 3 { | ||||||
|  | 		auto, answers, err = parseAnswerRules(matchType, args[3:]) | ||||||
| 	if len(args) < 7 { |  | ||||||
| 		switch matchType { |  | ||||||
| 		case ExactMatch: |  | ||||||
| 			rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteQuestionTo, rewriteQuestionFrom) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 			return &exactNameRule{ | 	} | ||||||
| 				nextAction, |  | ||||||
| 				rewriteQuestionFrom, | 	switch matchType { | ||||||
| 				rewriteQuestionTo, | 	case ExactMatch: | ||||||
| 				ResponseRule{ | 		if _, err := isValidRegexPattern(rewriteQuestionTo, rewriteQuestionFrom); err != nil { | ||||||
| 					Active:      true, | 			return nil, err | ||||||
| 					Type:        "name", | 		} | ||||||
| 					Pattern:     rewriteAnswerFromPattern, | 		return newExactNameRule(nextAction, rewriteQuestionFrom, rewriteQuestionTo, answers), nil | ||||||
| 					Replacement: rewriteQuestionFrom, |  | ||||||
| 				}, |  | ||||||
| 			}, nil |  | ||||||
| 	case PrefixMatch: | 	case PrefixMatch: | ||||||
| 			return &prefixNameRule{ | 		return newPrefixNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil | ||||||
| 				nextAction, |  | ||||||
| 				rewriteQuestionFrom, |  | ||||||
| 				rewriteQuestionTo, |  | ||||||
| 			}, nil |  | ||||||
| 	case SuffixMatch: | 	case SuffixMatch: | ||||||
| 			return &suffixNameRule{ | 		return newSuffixNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil | ||||||
| 				nextAction, |  | ||||||
| 				rewriteQuestionFrom, |  | ||||||
| 				rewriteQuestionTo, |  | ||||||
| 			}, nil |  | ||||||
| 	case SubstringMatch: | 	case SubstringMatch: | ||||||
| 			return &substringNameRule{ | 		return newSubstringNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil | ||||||
| 				nextAction, |  | ||||||
| 				rewriteQuestionFrom, |  | ||||||
| 				rewriteQuestionTo, |  | ||||||
| 			}, nil |  | ||||||
| 	case RegexMatch: | 	case RegexMatch: | ||||||
| 		rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo) | 		rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		rewriteQuestionTo := plugin.Name(args[2]).Normalize() | 		rewriteQuestionTo := plugin.Name(args[2]).Normalize() | ||||||
| 			return ®exNameRule{ | 		return newRegexNameRule(nextAction, auto, rewriteQuestionFromPattern, rewriteQuestionTo, answers), nil | ||||||
| 				nextAction, |  | ||||||
| 				rewriteQuestionFromPattern, |  | ||||||
| 				rewriteQuestionTo, |  | ||||||
| 				[]ResponseRule{{ |  | ||||||
| 					Type: "name", |  | ||||||
| 				}}, |  | ||||||
| 			}, nil |  | ||||||
| 	default: | 	default: | ||||||
| 		return nil, fmt.Errorf("name rule supports only exact, prefix, suffix, substring, and regex name matching, received: %s", matchType) | 		return nil, fmt.Errorf("name rule supports only exact, prefix, suffix, substring, and regex name matching, received: %s", matchType) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 	//if len(args) == 7 { |  | ||||||
| 	if (len(args)-3)%4 == 0 { |  | ||||||
| 		if matchType == RegexMatch { |  | ||||||
| 			rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 			rewriteQuestionTo = plugin.Name(args[2]).Normalize() |  | ||||||
|  |  | ||||||
| 			responseRuleCount := (len(args) - 3) / 4 | func parseAnswerRules(name string, args []string) (auto bool, rules ResponseRules, err error) { | ||||||
| 			responseRules := make([]ResponseRule, responseRuleCount) | 	auto = false | ||||||
| 			for i := 0; i < responseRuleCount; i++ { | 	arg := 0 | ||||||
| 				startIdx := 3 + (i * 4) | 	nameRules := 0 | ||||||
| 				responseRule, err := newResponseRule(args[startIdx : startIdx+4]) | 	last := "" | ||||||
| 				if err != nil { | 	if len(args) < 2 { | ||||||
| 					return nil, err | 		return false, nil, fmt.Errorf("invalid arguments for %s rule", name) | ||||||
| 				} |  | ||||||
| 				responseRules[i] = *responseRule |  | ||||||
| 	} | 	} | ||||||
|  | 	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 ®exNameRule{ |  | ||||||
| 				nextAction, |  | ||||||
| 				rewriteQuestionFromPattern, |  | ||||||
| 				rewriteQuestionTo, |  | ||||||
| 				responseRules, |  | ||||||
| 			}, nil |  | ||||||
| 			} | 			} | ||||||
| 		return nil, fmt.Errorf("the rewrite of response is supported only for name regex rule") | 			return false, nil, fmt.Errorf("exceeded the number of arguments for %s answer rule for %s rule", last, name) | ||||||
| 		} | 		} | ||||||
| 	return nil, fmt.Errorf("the rewrite rule is invalid: %s", args) | 		if args[arg] == AnswerMatch { | ||||||
|  | 			arg++ | ||||||
| 		} | 		} | ||||||
|  | 		if len(args)-arg == 0 { | ||||||
| // newResponseRule creates a new "answer name" or "answer value" response rule. | 			return false, nil, fmt.Errorf("type missing for answer rule for %s rule", name) | ||||||
| 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]) | 		last = args[arg] | ||||||
| 	switch rewriteAnswerField { | 		arg++ | ||||||
| 	case "name": | 		switch last { | ||||||
| 	case "value": | 		case AutoMatch: | ||||||
| 	default: | 			auto = true | ||||||
| 		return nil, fmt.Errorf("exceeded the number of arguments for a regex name rule") | 			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[2] | 			rewriteAnswerFrom := args[arg] | ||||||
| 	rewriteAnswerTo := args[3] | 			rewriteAnswerTo := args[arg+1] | ||||||
| 			rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo) | 			rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo) | ||||||
|  | 			rewriteAnswerTo = plugin.Name(rewriteAnswerTo).Normalize() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 		return nil, err | 				return false, nil, fmt.Errorf("%s answer rule for %s rule: %s", last, name, err) | ||||||
|  | 			} | ||||||
|  | 			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) | ||||||
|  | 			} | ||||||
|  | 			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) | ||||||
| 		} | 		} | ||||||
| 	rewriteAnswerTo = plugin.Name(args[3]).Normalize() |  | ||||||
|  |  | ||||||
| 	return &ResponseRule{ |  | ||||||
| 		Active:      true, |  | ||||||
| 		Type:        rewriteAnswerField, |  | ||||||
| 		Pattern:     rewriteAnswerFromPattern, |  | ||||||
| 		Replacement: rewriteAnswerTo, |  | ||||||
| 	}, nil |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| // Mode returns the processing nextAction | 	if auto && nameRules > 0 { | ||||||
| func (rule *exactNameRule) Mode() string     { return rule.NextAction } | 		return false, nil, fmt.Errorf("auto name answer rule cannot be combined with explicit name anwer rules") | ||||||
| func (rule *prefixNameRule) Mode() string    { return rule.NextAction } | 	} | ||||||
| func (rule *suffixNameRule) Mode() string    { return rule.NextAction } | 	return | ||||||
| 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} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // 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. | // hasClosingDot returns true if s has a closing dot at the end. | ||||||
| func hasClosingDot(s string) bool { | func hasClosingDot(s string) bool { | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ func TestRewriteIllegalName(t *testing.T) { | |||||||
| 	rw := Rewrite{ | 	rw := Rewrite{ | ||||||
| 		Next:         plugin.HandlerFunc(msgPrinter), | 		Next:         plugin.HandlerFunc(msgPrinter), | ||||||
| 		Rules:        []Rule{r}, | 		Rules:        []Rule{r}, | ||||||
| 		noRevert: true, | 		RevertPolicy: NoRevertPolicy(), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx := context.TODO() | 	ctx := context.TODO() | ||||||
| @@ -57,7 +57,7 @@ func TestRewriteNamePrefixSuffix(t *testing.T) { | |||||||
| 		rw := Rewrite{ | 		rw := Rewrite{ | ||||||
| 			Next:         plugin.HandlerFunc(msgPrinter), | 			Next:         plugin.HandlerFunc(msgPrinter), | ||||||
| 			Rules:        []Rule{r}, | 			Rules:        []Rule{r}, | ||||||
| 			noRevert: true, | 			RevertPolicy: NoRevertPolicy(), | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		m := new(dns.Msg) | 		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) { | func TestNewNameRule(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		next         string | 		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}.coredns.rocks"}, false}, | ||||||
| 		{"stop", []string{"regex", "(.*).coredns.rocks", "{1}.{2}.coredns.rocks"}, true}, | 		{"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{"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 { | 	for i, tc := range tests { | ||||||
| 		failed := false | 		failed := false | ||||||
|   | |||||||
| @@ -1,37 +1,69 @@ | |||||||
| package rewrite | package rewrite | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"regexp" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" | 	"github.com/miekg/dns" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ResponseRule contains a rule to rewrite a response with. | // RevertPolicy controls the overall reverting process | ||||||
| type ResponseRule struct { | type RevertPolicy interface { | ||||||
| 	Active      bool | 	DoRevert() bool | ||||||
| 	Type        string | 	DoQuestionRestore() bool | ||||||
| 	Pattern     *regexp.Regexp |  | ||||||
| 	Replacement string |  | ||||||
| 	TTL         uint32 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | 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 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. | // 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. | // 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' | // dig will complain with ';; Question section mismatch: got example.org/HINFO/IN' | ||||||
| type ResponseReverter struct { | type ResponseReverter struct { | ||||||
| 	dns.ResponseWriter | 	dns.ResponseWriter | ||||||
| 	originalQuestion dns.Question | 	originalQuestion dns.Question | ||||||
| 	ResponseRewrite  bool | 	ResponseRules    ResponseRules | ||||||
| 	ResponseRules    []ResponseRule | 	revertPolicy     RevertPolicy | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewResponseReverter returns a pointer to a new ResponseReverter. | // 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{ | 	return &ResponseReverter{ | ||||||
| 		ResponseWriter:   w, | 		ResponseWriter:   w, | ||||||
| 		originalQuestion: r.Question[0], | 		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. | 	// Deep copy 'res' as to not (e.g). rewrite a message that's also stored in the cache. | ||||||
| 	res := res1.Copy() | 	res := res1.Copy() | ||||||
|  |  | ||||||
|  | 	if r.revertPolicy.DoQuestionRestore() { | ||||||
| 		res.Question[0] = r.originalQuestion | 		res.Question[0] = r.originalQuestion | ||||||
| 	if r.ResponseRewrite { | 	} | ||||||
|  | 	if len(r.ResponseRules) > 0 { | ||||||
| 		for _, rr := range res.Ns { | 		for _, rr := range res.Ns { | ||||||
| 			rewriteResourceRecord(res, rr, r) | 			r.rewriteResourceRecord(res, rr) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		for _, rr := range res.Answer { | 		for _, rr := range res.Answer { | ||||||
| 			rewriteResourceRecord(res, rr, r) | 			r.rewriteResourceRecord(res, rr) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		for _, rr := range res.Extra { | 		for _, rr := range res.Extra { | ||||||
| 			rewriteResourceRecord(res, rr, r) | 			r.rewriteResourceRecord(res, rr) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| 	return r.ResponseWriter.WriteMsg(res) | 	return r.ResponseWriter.WriteMsg(res) | ||||||
| } | } | ||||||
|  |  | ||||||
| func rewriteResourceRecord(res *dns.Msg, rr dns.RR, r *ResponseReverter) { | func (r *ResponseReverter) rewriteResourceRecord(res *dns.Msg, rr dns.RR) { | ||||||
| 	var ( |  | ||||||
| 		isNameRewritten  bool |  | ||||||
| 		isTTLRewritten   bool |  | ||||||
| 		isValueRewritten bool |  | ||||||
| 		name             = rr.Header().Name |  | ||||||
| 		ttl              = rr.Header().Ttl |  | ||||||
| 		value            string |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	for _, rule := range r.ResponseRules { | 	for _, rule := range r.ResponseRules { | ||||||
| 		if rule.Type == "" { | 		rule.RewriteResponse(rr) | ||||||
| 			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 { | // Write is a wrapper that records the size of the message that gets written. | ||||||
| 		rr.Header().Name = name | func (r *ResponseReverter) Write(buf []byte) (int, error) { | ||||||
| 	} | 	n, err := r.ResponseWriter.Write(buf) | ||||||
| 	if isTTLRewritten { | 	return n, err | ||||||
| 		rr.Header().Ttl = ttl |  | ||||||
| 	} |  | ||||||
| 	if isValueRewritten { |  | ||||||
| 		setRewrittenRecordValue(rr, value) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func getRecordValueForRewrite(rr dns.RR) (name string) { | func getRecordValueForRewrite(rr dns.RR) (name string) { | ||||||
| @@ -136,24 +140,3 @@ func setRewrittenRecordValue(rr dns.RR, value string) { | |||||||
| 		rr.(*dns.SOA).Ns = value | 		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 |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ func doReverterTests(rules []Rule, t *testing.T) { | |||||||
| 		rw := Rewrite{ | 		rw := Rewrite{ | ||||||
| 			Next:         plugin.HandlerFunc(msgPrinter), | 			Next:         plugin.HandlerFunc(msgPrinter), | ||||||
| 			Rules:        rules, | 			Rules:        rules, | ||||||
| 			noRevert: tc.noRevert, | 			RevertPolicy: NewRevertPolicy(tc.noRevert, false), | ||||||
| 		} | 		} | ||||||
| 		rec := dnstest.NewRecorder(&test.ResponseWriter{}) | 		rec := dnstest.NewRecorder(&test.ResponseWriter{}) | ||||||
| 		rw.ServeDNS(ctx, rec, m) | 		rw.ServeDNS(ctx, rec, m) | ||||||
| @@ -81,38 +81,56 @@ var valueTests = []struct { | |||||||
| 	expectAnswerType uint16 | 	expectAnswerType uint16 | ||||||
| 	expectAddlName   string | 	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.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.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.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.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.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.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.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.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.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.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.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.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.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.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) { | func TestValueResponseReverter(t *testing.T) { | ||||||
|  |  | ||||||
| 	rules := []Rule{} | 	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) | 	rules = append(rules, r) | ||||||
|  |  | ||||||
| 	doValueReverterTests(rules, t) | 	doValueReverterTests("stop", rules, t) | ||||||
|  |  | ||||||
| 	rules = []Rule{} | 	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) | 	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() | 	ctx := context.TODO() | ||||||
| 	for i, tc := range valueTests { | 	for i, tc := range valueTests { | ||||||
| 		m := new(dns.Msg) | 		m := new(dns.Msg) | ||||||
| @@ -123,35 +141,39 @@ func doValueReverterTests(rules []Rule, t *testing.T) { | |||||||
| 		rw := Rewrite{ | 		rw := Rewrite{ | ||||||
| 			Next:         plugin.HandlerFunc(msgPrinter), | 			Next:         plugin.HandlerFunc(msgPrinter), | ||||||
| 			Rules:        rules, | 			Rules:        rules, | ||||||
| 			noRevert: tc.noRevert, | 			RevertPolicy: NewRevertPolicy(tc.noRevert, false), | ||||||
| 		} | 		} | ||||||
| 		rec := dnstest.NewRecorder(&test.ResponseWriter{}) | 		rec := dnstest.NewRecorder(&test.ResponseWriter{}) | ||||||
| 		rw.ServeDNS(ctx, rec, m) | 		rw.ServeDNS(ctx, rec, m) | ||||||
| 		resp := rec.Msg | 		resp := rec.Msg | ||||||
| 		if resp.Question[0].Name != tc.to { | 		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 { | 		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 { | 		if len(resp.Answer) <= 0 { | ||||||
| 			t.Error("Unexpected Answer Record Type / No Answers") | 			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 | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		value := getRecordValueForRewrite(resp.Answer[0]) | 		value := getRecordValueForRewrite(resp.Answer[0]) | ||||||
| 		if value != tc.expectValue { | 		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 { | 		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 | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if resp.Extra[0].Header().Name != tc.expectAddlName { | 		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) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -33,38 +33,32 @@ const ( | |||||||
| type Rewrite struct { | type Rewrite struct { | ||||||
| 	Next  plugin.Handler | 	Next  plugin.Handler | ||||||
| 	Rules []Rule | 	Rules []Rule | ||||||
| 	noRevert bool | 	RevertPolicy | ||||||
| } | } | ||||||
|  |  | ||||||
| // ServeDNS implements the plugin.Handler interface. | // ServeDNS implements the plugin.Handler interface. | ||||||
| func (rw Rewrite) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { | 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} | 	state := request.Request{W: w, Req: r} | ||||||
|  |  | ||||||
| 	for _, rule := range rw.Rules { | 	for _, rule := range rw.Rules { | ||||||
| 		switch result := rule.Rewrite(ctx, state); result { | 		respRules, result := rule.Rewrite(ctx, state) | ||||||
| 		case RewriteDone: | 		if result == RewriteDone { | ||||||
| 			if _, ok := dns.IsDomainName(state.Req.Question[0].Name); !ok { | 			if _, ok := dns.IsDomainName(state.Req.Question[0].Name); !ok { | ||||||
| 				err := fmt.Errorf("invalid name after rewrite: %s", state.Req.Question[0].Name) | 				err := fmt.Errorf("invalid name after rewrite: %s", state.Req.Question[0].Name) | ||||||
| 				state.Req.Question[0] = wr.originalQuestion | 				state.Req.Question[0] = wr.originalQuestion | ||||||
| 				return dns.RcodeServerFailure, err | 				return dns.RcodeServerFailure, err | ||||||
| 			} | 			} | ||||||
| 			for _, respRule := range rule.GetResponseRules() { | 			wr.ResponseRules = append(wr.ResponseRules, respRules...) | ||||||
| 				if respRule.Active { |  | ||||||
| 					wr.ResponseRewrite = true |  | ||||||
| 					wr.ResponseRules = append(wr.ResponseRules, respRule) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			if rule.Mode() == Stop { | 			if rule.Mode() == Stop { | ||||||
| 				if rw.noRevert { | 				break | ||||||
| 					return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, w, r) |  | ||||||
| 				} |  | ||||||
| 				return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, wr, r) |  | ||||||
| 			} |  | ||||||
| 		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, w, r) | ||||||
| 	} | 	} | ||||||
| 	return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, wr, 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. | // Rule describes a rewrite rule. | ||||||
| type Rule interface { | type Rule interface { | ||||||
| 	// Rewrite rewrites the current request. | 	// 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 returns the processing mode stop or continue. | ||||||
| 	Mode() string | 	Mode() string | ||||||
| 	// GetResponseRules returns rules to rewrite response with, if any. |  | ||||||
| 	GetResponseRules() []ResponseRule |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func newRule(args ...string) (Rule, error) { | func newRule(args ...string) (Rule, error) { | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package rewrite | |||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"fmt" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| @@ -16,6 +17,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func msgPrinter(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { | 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) | 	w.WriteMsg(r) | ||||||
| 	return 0, nil | 	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", "(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", "(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", "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{"name", "substring", "a.com", "b.com", "c.com"}, true, nil}, | ||||||
| 		{[]string{"type"}, true, nil}, | 		{[]string{"type"}, true, nil}, | ||||||
| 		{[]string{"type", "a"}, true, nil}, | 		{[]string{"type", "a"}, true, nil}, | ||||||
| @@ -187,7 +193,7 @@ func TestRewrite(t *testing.T) { | |||||||
| 	rw := Rewrite{ | 	rw := Rewrite{ | ||||||
| 		Next:         plugin.HandlerFunc(msgPrinter), | 		Next:         plugin.HandlerFunc(msgPrinter), | ||||||
| 		Rules:        rules, | 		Rules:        rules, | ||||||
| 		noRevert: true, | 		RevertPolicy: NoRevertPolicy(), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| @@ -249,7 +255,7 @@ func TestRewrite(t *testing.T) { | |||||||
| func TestRewriteEDNS0Local(t *testing.T) { | func TestRewriteEDNS0Local(t *testing.T) { | ||||||
| 	rw := Rewrite{ | 	rw := Rewrite{ | ||||||
| 		Next:         plugin.HandlerFunc(msgPrinter), | 		Next:         plugin.HandlerFunc(msgPrinter), | ||||||
| 		noRevert: true, | 		RevertPolicy: NoRevertPolicy(), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| @@ -338,7 +344,7 @@ func TestEdns0LocalMultiRule(t *testing.T) { | |||||||
| 	rw := Rewrite{ | 	rw := Rewrite{ | ||||||
| 		Next:         plugin.HandlerFunc(msgPrinter), | 		Next:         plugin.HandlerFunc(msgPrinter), | ||||||
| 		Rules:        rules, | 		Rules:        rules, | ||||||
| 		noRevert: true, | 		RevertPolicy: NoRevertPolicy(), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| @@ -448,7 +454,7 @@ func (tp testProvider) Metadata(ctx context.Context, state request.Request) cont | |||||||
| func TestRewriteEDNS0LocalVariable(t *testing.T) { | func TestRewriteEDNS0LocalVariable(t *testing.T) { | ||||||
| 	rw := Rewrite{ | 	rw := Rewrite{ | ||||||
| 		Next:         plugin.HandlerFunc(msgPrinter), | 		Next:         plugin.HandlerFunc(msgPrinter), | ||||||
| 		noRevert: true, | 		RevertPolicy: NoRevertPolicy(), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	expectedMetadata := []metadata.Provider{ | 	expectedMetadata := []metadata.Provider{ | ||||||
| @@ -570,7 +576,7 @@ func TestRewriteEDNS0LocalVariable(t *testing.T) { | |||||||
| func TestRewriteEDNS0Subnet(t *testing.T) { | func TestRewriteEDNS0Subnet(t *testing.T) { | ||||||
| 	rw := Rewrite{ | 	rw := Rewrite{ | ||||||
| 		Next:         plugin.HandlerFunc(msgPrinter), | 		Next:         plugin.HandlerFunc(msgPrinter), | ||||||
| 		noRevert: true, | 		RevertPolicy: NoRevertPolicy(), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
|   | |||||||
| @@ -44,12 +44,12 @@ func TestParse(t *testing.T) { | |||||||
| 		`rewrite stop { | 		`rewrite stop { | ||||||
|     name regex foo bar |     name regex foo bar | ||||||
|     answer name bar foo |     answer name bar foo | ||||||
|     name baz qux |     name baz | ||||||
| }`) | }`) | ||||||
| 	_, err = rewriteParse(c) | 	_, err = rewriteParse(c) | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| 		t.Errorf("Expected error but got success for invalid response rewrite") | 		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()) | 		t.Errorf("Got wrong error for invalid response rewrite: %v", err.Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,81 +9,91 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/coredns/coredns/plugin" | 	"github.com/coredns/coredns/plugin" | ||||||
| 	"github.com/coredns/coredns/request" | 	"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 { | type exactTTLRule struct { | ||||||
| 	NextAction    string | 	ttlRuleBase | ||||||
| 	From string | 	From string | ||||||
| 	ResponseRules []ResponseRule |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type prefixTTLRule struct { | type prefixTTLRule struct { | ||||||
| 	NextAction    string | 	ttlRuleBase | ||||||
| 	Prefix string | 	Prefix string | ||||||
| 	ResponseRules []ResponseRule |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type suffixTTLRule struct { | type suffixTTLRule struct { | ||||||
| 	NextAction    string | 	ttlRuleBase | ||||||
| 	Suffix string | 	Suffix string | ||||||
| 	ResponseRules []ResponseRule |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type substringTTLRule struct { | type substringTTLRule struct { | ||||||
| 	NextAction    string | 	ttlRuleBase | ||||||
| 	Substring string | 	Substring string | ||||||
| 	ResponseRules []ResponseRule |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type regexTTLRule struct { | type regexTTLRule struct { | ||||||
| 	NextAction    string | 	ttlRuleBase | ||||||
| 	Pattern *regexp.Regexp | 	Pattern *regexp.Regexp | ||||||
| 	ResponseRules []ResponseRule |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request based upon exact match of the name | // Rewrite rewrites the current request based upon exact match of the name | ||||||
| // in the question section of the request. | // in the question section of the request. | ||||||
| func (rule *exactTTLRule) Rewrite(ctx context.Context, state request.Request) Result { | func (rule *exactTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) { | ||||||
| 	if rule.From == state.Name() { | 	return rule.responseRule(rule.From == state.Name()) | ||||||
| 		return RewriteDone |  | ||||||
| 	} |  | ||||||
| 	return RewriteIgnored |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request when the name begins with the matching string. | // Rewrite rewrites the current request when the name begins with the matching string. | ||||||
| func (rule *prefixTTLRule) Rewrite(ctx context.Context, state request.Request) Result { | func (rule *prefixTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) { | ||||||
| 	if strings.HasPrefix(state.Name(), rule.Prefix) { | 	return rule.responseRule(strings.HasPrefix(state.Name(), rule.Prefix)) | ||||||
| 		return RewriteDone |  | ||||||
| 	} |  | ||||||
| 	return RewriteIgnored |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request when the name ends with the matching string. | // Rewrite rewrites the current request when the name ends with the matching string. | ||||||
| func (rule *suffixTTLRule) Rewrite(ctx context.Context, state request.Request) Result { | func (rule *suffixTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) { | ||||||
| 	if strings.HasSuffix(state.Name(), rule.Suffix) { | 	return rule.responseRule(strings.HasSuffix(state.Name(), rule.Suffix)) | ||||||
| 		return RewriteDone |  | ||||||
| 	} |  | ||||||
| 	return RewriteIgnored |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request based upon partial match of the | // Rewrite rewrites the current request based upon partial match of the | ||||||
| // name in the question section of the request. | // name in the question section of the request. | ||||||
| func (rule *substringTTLRule) Rewrite(ctx context.Context, state request.Request) Result { | func (rule *substringTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) { | ||||||
| 	if strings.Contains(state.Name(), rule.Substring) { | 	return rule.responseRule(strings.Contains(state.Name(), rule.Substring)) | ||||||
| 		return RewriteDone |  | ||||||
| 	} |  | ||||||
| 	return RewriteIgnored |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request when the name in the question | // Rewrite rewrites the current request when the name in the question | ||||||
| // section of the request matches a regular expression. | // section of the request matches a regular expression. | ||||||
| func (rule *regexTTLRule) Rewrite(ctx context.Context, state request.Request) Result { | func (rule *regexTTLRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) { | ||||||
| 	regexGroups := rule.Pattern.FindStringSubmatch(state.Name()) | 	return rule.responseRule(len(rule.Pattern.FindStringSubmatch(state.Name())) != 0) | ||||||
| 	if len(regexGroups) == 0 { |  | ||||||
| 		return RewriteIgnored |  | ||||||
| 	} |  | ||||||
| 	return RewriteDone |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // newTTLRule creates a name matching rule based on exact, partial, or regex match | // 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]) { | 		switch strings.ToLower(args[0]) { | ||||||
| 		case ExactMatch: | 		case ExactMatch: | ||||||
| 			return &exactTTLRule{ | 			return &exactTTLRule{ | ||||||
| 				nextAction, | 				newTTLRuleBase(nextAction, ttl), | ||||||
| 				plugin.Name(args[1]).Normalize(), | 				plugin.Name(args[1]).Normalize(), | ||||||
| 				[]ResponseRule{{ |  | ||||||
| 					Active: true, |  | ||||||
| 					Type:   "ttl", |  | ||||||
| 					TTL:    ttl, |  | ||||||
| 				}}, |  | ||||||
| 			}, nil | 			}, nil | ||||||
| 		case PrefixMatch: | 		case PrefixMatch: | ||||||
| 			return &prefixTTLRule{ | 			return &prefixTTLRule{ | ||||||
| 				nextAction, | 				newTTLRuleBase(nextAction, ttl), | ||||||
| 				plugin.Name(args[1]).Normalize(), | 				plugin.Name(args[1]).Normalize(), | ||||||
| 				[]ResponseRule{{ |  | ||||||
| 					Active: true, |  | ||||||
| 					Type:   "ttl", |  | ||||||
| 					TTL:    ttl, |  | ||||||
| 				}}, |  | ||||||
| 			}, nil | 			}, nil | ||||||
| 		case SuffixMatch: | 		case SuffixMatch: | ||||||
| 			return &suffixTTLRule{ | 			return &suffixTTLRule{ | ||||||
| 				nextAction, | 				newTTLRuleBase(nextAction, ttl), | ||||||
| 				plugin.Name(args[1]).Normalize(), | 				plugin.Name(args[1]).Normalize(), | ||||||
| 				[]ResponseRule{{ |  | ||||||
| 					Active: true, |  | ||||||
| 					Type:   "ttl", |  | ||||||
| 					TTL:    ttl, |  | ||||||
| 				}}, |  | ||||||
| 			}, nil | 			}, nil | ||||||
| 		case SubstringMatch: | 		case SubstringMatch: | ||||||
| 			return &substringTTLRule{ | 			return &substringTTLRule{ | ||||||
| 				nextAction, | 				newTTLRuleBase(nextAction, ttl), | ||||||
| 				plugin.Name(args[1]).Normalize(), | 				plugin.Name(args[1]).Normalize(), | ||||||
| 				[]ResponseRule{{ |  | ||||||
| 					Active: true, |  | ||||||
| 					Type:   "ttl", |  | ||||||
| 					TTL:    ttl, |  | ||||||
| 				}}, |  | ||||||
| 			}, nil | 			}, nil | ||||||
| 		case RegexMatch: | 		case RegexMatch: | ||||||
| 			regexPattern, err := regexp.Compile(args[1]) | 			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 nil, fmt.Errorf("invalid regex pattern in a ttl rule: %s", args[1]) | ||||||
| 			} | 			} | ||||||
| 			return ®exTTLRule{ | 			return ®exTTLRule{ | ||||||
| 				nextAction, | 				newTTLRuleBase(nextAction, ttl), | ||||||
| 				regexPattern, | 				regexPattern, | ||||||
| 				[]ResponseRule{{ |  | ||||||
| 					Active: true, |  | ||||||
| 					Type:   "ttl", |  | ||||||
| 					TTL:    ttl, |  | ||||||
| 				}}, |  | ||||||
| 			}, nil | 			}, nil | ||||||
| 		default: | 		default: | ||||||
| 			return nil, fmt.Errorf("ttl rule supports only exact, prefix, suffix, substring, and regex name matching") | 			return nil, fmt.Errorf("ttl rule supports only exact, prefix, suffix, substring, and regex name matching") | ||||||
| @@ -166,48 +151,11 @@ func newTTLRule(nextAction string, args ...string) (Rule, error) { | |||||||
| 		return nil, fmt.Errorf("many few arguments for a ttl rule") | 		return nil, fmt.Errorf("many few arguments for a ttl rule") | ||||||
| 	} | 	} | ||||||
| 	return &exactTTLRule{ | 	return &exactTTLRule{ | ||||||
| 		nextAction, | 		newTTLRuleBase(nextAction, ttl), | ||||||
| 		plugin.Name(args[0]).Normalize(), | 		plugin.Name(args[0]).Normalize(), | ||||||
| 		[]ResponseRule{{ |  | ||||||
| 			Active: true, |  | ||||||
| 			Type:   "ttl", |  | ||||||
| 			TTL:    ttl, |  | ||||||
| 		}}, |  | ||||||
| 	}, nil | 	}, 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. | // validTTL returns true if v is valid TTL value. | ||||||
| func isValidTTL(v string) (uint32, bool) { | func isValidTTL(v string) (uint32, bool) { | ||||||
| 	i, err := strconv.Atoi(v) | 	i, err := strconv.Atoi(v) | ||||||
|   | |||||||
| @@ -123,7 +123,6 @@ func doTTLTests(rules []Rule, t *testing.T) { | |||||||
| 		rw := Rewrite{ | 		rw := Rewrite{ | ||||||
| 			Next:  plugin.HandlerFunc(msgPrinter), | 			Next:  plugin.HandlerFunc(msgPrinter), | ||||||
| 			Rules: rules, | 			Rules: rules, | ||||||
| 			noRevert: false, |  | ||||||
| 		} | 		} | ||||||
| 		rec := dnstest.NewRecorder(&test.ResponseWriter{}) | 		rec := dnstest.NewRecorder(&test.ResponseWriter{}) | ||||||
| 		rw.ServeDNS(ctx, rec, m) | 		rw.ServeDNS(ctx, rec, m) | ||||||
|   | |||||||
| @@ -31,18 +31,15 @@ func newTypeRule(nextAction string, args ...string) (Rule, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Rewrite rewrites the current request. | // 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 rule.fromType > 0 && rule.toType > 0 { | ||||||
| 		if state.QType() == rule.fromType { | 		if state.QType() == rule.fromType { | ||||||
| 			state.Req.Question[0].Qtype = rule.toType | 			state.Req.Question[0].Qtype = rule.toType | ||||||
| 			return RewriteDone | 			return nil, RewriteDone | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return RewriteIgnored | 	return nil, RewriteIgnored | ||||||
| } | } | ||||||
|  |  | ||||||
| // Mode returns the processing mode. | // Mode returns the processing mode. | ||||||
| func (rule *typeRule) Mode() string { return rule.nextAction } | func (rule *typeRule) Mode() string { return rule.nextAction } | ||||||
|  |  | ||||||
| // GetResponseRules return rules to rewrite the response with. Currently not implemented. |  | ||||||
| func (rule *typeRule) GetResponseRules() []ResponseRule { return []ResponseRule{} } |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user