mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 18:23:13 -04:00 
			
		
		
		
	* cname target rewrite part in answer sec tion Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * upstream request Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * fix looping issue Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * support exact, prefix, suffix, substring, and regex types for cname rewrite Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * support any qtype, corrected prefix, suffix, substring types behavior Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * unit tests added, mocked the upstream call Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * fix lint errors Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * add newline to fix test issue Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * add default rewrite type, add readme Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * readme grammar fix Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * reuse rewrite types Signed-off-by: amila <amila.15@cse.mrt.ac.lk> * comment fixed Signed-off-by: amila <amila.15@cse.mrt.ac.lk> --------- Signed-off-by: amila <amila.15@cse.mrt.ac.lk>
		
			
				
	
	
		
			450 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			450 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package rewrite
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/coredns/coredns/plugin"
 | |
| 	"github.com/coredns/coredns/request"
 | |
| 
 | |
| 	"github.com/miekg/dns"
 | |
| )
 | |
| 
 | |
| // stringRewriter rewrites a string
 | |
| type stringRewriter interface {
 | |
| 	rewriteString(src string) string
 | |
| }
 | |
| 
 | |
| // regexStringRewriter can be used to rewrite strings by regex pattern.
 | |
| // it contains all the information required to detect and execute a rewrite
 | |
| // on a string.
 | |
| type regexStringRewriter struct {
 | |
| 	pattern     *regexp.Regexp
 | |
| 	replacement string
 | |
| }
 | |
| 
 | |
| var _ stringRewriter = ®exStringRewriter{}
 | |
| 
 | |
| func newStringRewriter(pattern *regexp.Regexp, replacement string) stringRewriter {
 | |
| 	return ®exStringRewriter{pattern, replacement}
 | |
| }
 | |
| 
 | |
| func (r *regexStringRewriter) rewriteString(src string) string {
 | |
| 	regexGroups := r.pattern.FindStringSubmatch(src)
 | |
| 	if len(regexGroups) == 0 {
 | |
| 		return src
 | |
| 	}
 | |
| 	s := r.replacement
 | |
| 	for groupIndex, groupValue := range regexGroups {
 | |
| 		groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
 | |
| 		s = strings.Replace(s, groupIndexStr, groupValue, -1)
 | |
| 	}
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| // remapStringRewriter maps a dedicated string to another string
 | |
| // it also maps a the domain of a sub domain.
 | |
| type remapStringRewriter struct {
 | |
| 	orig        string
 | |
| 	replacement string
 | |
| }
 | |
| 
 | |
| var _ stringRewriter = &remapStringRewriter{}
 | |
| 
 | |
| func newRemapStringRewriter(orig, replacement string) stringRewriter {
 | |
| 	return &remapStringRewriter{orig, replacement}
 | |
| }
 | |
| 
 | |
| func (r *remapStringRewriter) rewriteString(src string) string {
 | |
| 	if src == r.orig {
 | |
| 		return r.replacement
 | |
| 	}
 | |
| 	if strings.HasSuffix(src, "."+r.orig) {
 | |
| 		return src[0:len(src)-len(r.orig)] + r.replacement
 | |
| 	}
 | |
| 	return src
 | |
| }
 | |
| 
 | |
| // suffixStringRewriter maps a dedicated suffix string to another string
 | |
| type suffixStringRewriter struct {
 | |
| 	suffix      string
 | |
| 	replacement string
 | |
| }
 | |
| 
 | |
| var _ stringRewriter = &suffixStringRewriter{}
 | |
| 
 | |
| func newSuffixStringRewriter(orig, replacement string) stringRewriter {
 | |
| 	return &suffixStringRewriter{orig, replacement}
 | |
| }
 | |
| 
 | |
| func (r *suffixStringRewriter) rewriteString(src string) string {
 | |
| 	if strings.HasSuffix(src, r.suffix) {
 | |
| 		return strings.TrimSuffix(src, r.suffix) + r.replacement
 | |
| 	}
 | |
| 	return src
 | |
| }
 | |
| 
 | |
| // nameRewriterResponseRule maps a record name according to a stringRewriter.
 | |
| type nameRewriterResponseRule struct {
 | |
| 	stringRewriter
 | |
| }
 | |
| 
 | |
| func (r *nameRewriterResponseRule) RewriteResponse(res *dns.Msg, 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(res *dns.Msg, rr dns.RR) {
 | |
| 	value := getRecordValueForRewrite(rr)
 | |
| 	if value != "" {
 | |
| 		new := r.rewriteString(value)
 | |
| 		if new != value {
 | |
| 			setRewrittenRecordValue(rr, new)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	// ExactMatch matches only on exact match of the name in the question section of a request
 | |
| 	ExactMatch = "exact"
 | |
| 	// PrefixMatch matches when the name begins with the matching string
 | |
| 	PrefixMatch = "prefix"
 | |
| 	// SuffixMatch matches when the name ends with the matching string
 | |
| 	SuffixMatch = "suffix"
 | |
| 	// SubstringMatch matches on partial match of the name in the question section of a request
 | |
| 	SubstringMatch = "substring"
 | |
| 	// RegexMatch matches when the name in the question section of a request matches a regular expression
 | |
| 	RegexMatch = "regex"
 | |
| 
 | |
| 	// AnswerMatch matches an answer rewrite
 | |
| 	AnswerMatch = "answer"
 | |
| 	// AutoMatch matches the auto name answer rewrite
 | |
| 	AutoMatch = "auto"
 | |
| 	// NameMatch matches the name answer rewrite
 | |
| 	NameMatch = "name"
 | |
| 	// ValueMatch matches the value answer rewrite
 | |
| 	ValueMatch = "value"
 | |
| )
 | |
| 
 | |
| 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.
 | |
| type exactNameRule struct {
 | |
| 	nameRuleBase
 | |
| 	from string
 | |
| }
 | |
| 
 | |
| func newExactNameRule(nextAction string, orig, replacement string, answers ResponseRules) Rule {
 | |
| 	return &exactNameRule{
 | |
| 		newNameRuleBase(nextAction, true, replacement, answers),
 | |
| 		orig,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (rule *exactNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
 | |
| 	if rule.from == state.Name() {
 | |
| 		state.Req.Question[0].Name = rule.replacement
 | |
| 		return rule.responseRuleFor(state)
 | |
| 	}
 | |
| 	return nil, RewriteIgnored
 | |
| }
 | |
| 
 | |
| // 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.
 | |
| type substringNameRule struct {
 | |
| 	nameRuleBase
 | |
| 	substring string
 | |
| }
 | |
| 
 | |
| func newSubstringNameRule(nextAction string, auto bool, substring, replacement string, answers ResponseRules) Rule {
 | |
| 	return &substringNameRule{
 | |
| 		newNameRuleBase(nextAction, auto, replacement, answers),
 | |
| 		substring,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (rule *substringNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
 | |
| 	if strings.Contains(state.Name(), rule.substring) {
 | |
| 		state.Req.Question[0].Name = strings.Replace(state.Name(), rule.substring, rule.replacement, -1)
 | |
| 		return rule.responseRuleFor(state)
 | |
| 	}
 | |
| 	return 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 {
 | |
| 		groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
 | |
| 		s = strings.Replace(s, groupIndexStr, groupValue, -1)
 | |
| 	}
 | |
| 	state.Req.Question[0].Name = s
 | |
| 	return rule.responseRuleFor(state)
 | |
| }
 | |
| 
 | |
| // newNameRule creates a name matching rule based on exact, partial, or regex match
 | |
| func newNameRule(nextAction string, args ...string) (Rule, error) {
 | |
| 	var matchType, rewriteQuestionFrom, rewriteQuestionTo string
 | |
| 	if len(args) < 2 {
 | |
| 		return nil, fmt.Errorf("too few arguments for a name rule")
 | |
| 	}
 | |
| 	if len(args) == 2 {
 | |
| 		matchType = ExactMatch
 | |
| 		rewriteQuestionFrom = plugin.Name(args[0]).Normalize()
 | |
| 		rewriteQuestionTo = plugin.Name(args[1]).Normalize()
 | |
| 	}
 | |
| 	if len(args) >= 3 {
 | |
| 		matchType = strings.ToLower(args[0])
 | |
| 		if matchType == RegexMatch {
 | |
| 			rewriteQuestionFrom = args[1]
 | |
| 			rewriteQuestionTo = args[2]
 | |
| 		} else {
 | |
| 			rewriteQuestionFrom = plugin.Name(args[1]).Normalize()
 | |
| 			rewriteQuestionTo = plugin.Name(args[2]).Normalize()
 | |
| 		}
 | |
| 	}
 | |
| 	if matchType == ExactMatch || matchType == SuffixMatch {
 | |
| 		if !hasClosingDot(rewriteQuestionFrom) {
 | |
| 			rewriteQuestionFrom = rewriteQuestionFrom + "."
 | |
| 		}
 | |
| 		if !hasClosingDot(rewriteQuestionTo) {
 | |
| 			rewriteQuestionTo = rewriteQuestionTo + "."
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var err error
 | |
| 	var answers ResponseRules
 | |
| 	auto := false
 | |
| 	if len(args) > 3 {
 | |
| 		auto, answers, err = parseAnswerRules(matchType, args[3:])
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	switch matchType {
 | |
| 	case ExactMatch:
 | |
| 		if _, err := isValidRegexPattern(rewriteQuestionTo, rewriteQuestionFrom); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return newExactNameRule(nextAction, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
 | |
| 	case PrefixMatch:
 | |
| 		return newPrefixNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
 | |
| 	case SuffixMatch:
 | |
| 		return newSuffixNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
 | |
| 	case SubstringMatch:
 | |
| 		return newSubstringNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
 | |
| 	case RegexMatch:
 | |
| 		rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		rewriteQuestionTo := plugin.Name(args[2]).Normalize()
 | |
| 		return newRegexNameRule(nextAction, auto, rewriteQuestionFromPattern, rewriteQuestionTo, answers), nil
 | |
| 	default:
 | |
| 		return nil, fmt.Errorf("name rule supports only exact, prefix, suffix, substring, and regex name matching, received: %s", matchType)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func parseAnswerRules(name string, args []string) (auto bool, rules ResponseRules, err error) {
 | |
| 	auto = false
 | |
| 	arg := 0
 | |
| 	nameRules := 0
 | |
| 	last := ""
 | |
| 	if len(args) < 2 {
 | |
| 		return false, nil, fmt.Errorf("invalid arguments for %s rule", name)
 | |
| 	}
 | |
| 	for arg < len(args) {
 | |
| 		if last == "" && args[arg] != AnswerMatch {
 | |
| 			if last == "" {
 | |
| 				return false, nil, fmt.Errorf("exceeded the number of arguments for a non-answer rule argument for %s rule", name)
 | |
| 			}
 | |
| 			return false, nil, fmt.Errorf("exceeded the number of arguments for %s answer rule for %s rule", last, name)
 | |
| 		}
 | |
| 		if args[arg] == AnswerMatch {
 | |
| 			arg++
 | |
| 		}
 | |
| 		if len(args)-arg == 0 {
 | |
| 			return false, nil, fmt.Errorf("type missing for answer rule for %s rule", name)
 | |
| 		}
 | |
| 		last = args[arg]
 | |
| 		arg++
 | |
| 		switch last {
 | |
| 		case AutoMatch:
 | |
| 			auto = true
 | |
| 			continue
 | |
| 		case NameMatch:
 | |
| 			if len(args)-arg < 2 {
 | |
| 				return false, nil, fmt.Errorf("%s answer rule for %s rule: 2 arguments required", last, name)
 | |
| 			}
 | |
| 			rewriteAnswerFrom := args[arg]
 | |
| 			rewriteAnswerTo := args[arg+1]
 | |
| 			rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo)
 | |
| 			rewriteAnswerTo = plugin.Name(rewriteAnswerTo).Normalize()
 | |
| 			if err != nil {
 | |
| 				return 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)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if auto && nameRules > 0 {
 | |
| 		return false, nil, fmt.Errorf("auto name answer rule cannot be combined with explicit name anwer rules")
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // hasClosingDot returns true if s has a closing dot at the end.
 | |
| func hasClosingDot(s string) bool {
 | |
| 	return strings.HasSuffix(s, ".")
 | |
| }
 | |
| 
 | |
| // getSubExprUsage returns the number of subexpressions used in s.
 | |
| func getSubExprUsage(s string) int {
 | |
| 	subExprUsage := 0
 | |
| 	for i := 0; i <= 100; i++ {
 | |
| 		if strings.Contains(s, "{"+strconv.Itoa(i)+"}") {
 | |
| 			subExprUsage++
 | |
| 		}
 | |
| 	}
 | |
| 	return subExprUsage
 | |
| }
 | |
| 
 | |
| // isValidRegexPattern returns a regular expression for pattern matching or errors, if any.
 | |
| func isValidRegexPattern(rewriteFrom, rewriteTo string) (*regexp.Regexp, error) {
 | |
| 	rewriteFromPattern, err := regexp.Compile(rewriteFrom)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("invalid regex matching pattern: %s", rewriteFrom)
 | |
| 	}
 | |
| 	if getSubExprUsage(rewriteTo) > rewriteFromPattern.NumSubexp() {
 | |
| 		return nil, fmt.Errorf("the rewrite regex pattern (%s) uses more subexpressions than its corresponding matching regex pattern (%s)", rewriteTo, rewriteFrom)
 | |
| 	}
 | |
| 	return rewriteFromPattern, nil
 | |
| }
 |