mirror of
				https://github.com/coredns/coredns.git
				synced 2025-11-03 18:53:13 -05:00 
			
		
		
		
	Forward information that a upstream response is truncated when rewriting a CNAME. Otherwise, the cache plugin stores the truncated resonse, making it impossible to receive the full response as a client via TCP. Signed-off-by: Yannick Epstein <yannicke@spotify.com>
		
			
				
	
	
		
			156 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package rewrite
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"regexp"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/coredns/coredns/plugin/pkg/log"
 | 
						|
	"github.com/coredns/coredns/plugin/pkg/upstream"
 | 
						|
	"github.com/coredns/coredns/request"
 | 
						|
 | 
						|
	"github.com/miekg/dns"
 | 
						|
)
 | 
						|
 | 
						|
// UpstreamInt wraps the Upstream API for dependency injection during testing
 | 
						|
type UpstreamInt interface {
 | 
						|
	Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error)
 | 
						|
}
 | 
						|
 | 
						|
// cnameTargetRule is cname target rewrite rule.
 | 
						|
type cnameTargetRule struct {
 | 
						|
	rewriteType     string
 | 
						|
	paramFromTarget string
 | 
						|
	paramToTarget   string
 | 
						|
	nextAction      string
 | 
						|
	Upstream        UpstreamInt // Upstream for looking up external names during the resolution process.
 | 
						|
}
 | 
						|
 | 
						|
// cnameTargetRuleWithReqState is cname target rewrite rule state
 | 
						|
type cnameTargetRuleWithReqState struct {
 | 
						|
	rule  cnameTargetRule
 | 
						|
	state request.Request
 | 
						|
	ctx   context.Context
 | 
						|
}
 | 
						|
 | 
						|
func (r *cnameTargetRule) getFromAndToTarget(inputCName string) (from string, to string) {
 | 
						|
	switch r.rewriteType {
 | 
						|
	case ExactMatch:
 | 
						|
		return r.paramFromTarget, r.paramToTarget
 | 
						|
	case PrefixMatch:
 | 
						|
		if strings.HasPrefix(inputCName, r.paramFromTarget) {
 | 
						|
			return inputCName, r.paramToTarget + strings.TrimPrefix(inputCName, r.paramFromTarget)
 | 
						|
		}
 | 
						|
	case SuffixMatch:
 | 
						|
		if strings.HasSuffix(inputCName, r.paramFromTarget) {
 | 
						|
			return inputCName, strings.TrimSuffix(inputCName, r.paramFromTarget) + r.paramToTarget
 | 
						|
		}
 | 
						|
	case SubstringMatch:
 | 
						|
		if strings.Contains(inputCName, r.paramFromTarget) {
 | 
						|
			return inputCName, strings.ReplaceAll(inputCName, r.paramFromTarget, r.paramToTarget)
 | 
						|
		}
 | 
						|
	case RegexMatch:
 | 
						|
		pattern := regexp.MustCompile(r.paramFromTarget)
 | 
						|
		regexGroups := pattern.FindStringSubmatch(inputCName)
 | 
						|
		if len(regexGroups) == 0 {
 | 
						|
			return "", ""
 | 
						|
		}
 | 
						|
		substitution := r.paramToTarget
 | 
						|
		for groupIndex, groupValue := range regexGroups {
 | 
						|
			groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
 | 
						|
			substitution = strings.ReplaceAll(substitution, groupIndexStr, groupValue)
 | 
						|
		}
 | 
						|
		return inputCName, substitution
 | 
						|
	}
 | 
						|
	return "", ""
 | 
						|
}
 | 
						|
 | 
						|
func (r *cnameTargetRuleWithReqState) RewriteResponse(res *dns.Msg, rr dns.RR) {
 | 
						|
	// logic to rewrite the cname target of dns response
 | 
						|
	switch rr.Header().Rrtype {
 | 
						|
	case dns.TypeCNAME:
 | 
						|
		// rename the target of the cname response
 | 
						|
		if cname, ok := rr.(*dns.CNAME); ok {
 | 
						|
			fromTarget, toTarget := r.rule.getFromAndToTarget(cname.Target)
 | 
						|
			if cname.Target == fromTarget {
 | 
						|
				// create upstream request with the new target with the same qtype
 | 
						|
				r.state.Req.Question[0].Name = toTarget
 | 
						|
				upRes, err := r.rule.Upstream.Lookup(r.ctx, r.state, toTarget, r.state.Req.Question[0].Qtype)
 | 
						|
 | 
						|
				if err != nil {
 | 
						|
					log.Errorf("Error upstream request %v", err)
 | 
						|
				}
 | 
						|
 | 
						|
				var newAnswer []dns.RR
 | 
						|
				// iterate over first upstram response
 | 
						|
				// add the cname record to the new answer
 | 
						|
				for _, rr := range res.Answer {
 | 
						|
					if cname, ok := rr.(*dns.CNAME); ok {
 | 
						|
						// change the target name in the response
 | 
						|
						cname.Target = toTarget
 | 
						|
						newAnswer = append(newAnswer, rr)
 | 
						|
					}
 | 
						|
				}
 | 
						|
				// iterate over upstream response received
 | 
						|
				for _, rr := range upRes.Answer {
 | 
						|
					if rr.Header().Name == toTarget {
 | 
						|
						newAnswer = append(newAnswer, rr)
 | 
						|
					}
 | 
						|
				}
 | 
						|
				res.Answer = newAnswer
 | 
						|
				// if not propagated, the truncated response might get cached,
 | 
						|
				// and it will be impossible to resolve the full response
 | 
						|
				res.Truncated = upRes.Truncated
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func newCNAMERule(nextAction string, args ...string) (Rule, error) {
 | 
						|
	var rewriteType string
 | 
						|
	var paramFromTarget, paramToTarget string
 | 
						|
	if len(args) == 3 {
 | 
						|
		rewriteType = (strings.ToLower(args[0]))
 | 
						|
		switch rewriteType {
 | 
						|
		case ExactMatch:
 | 
						|
		case PrefixMatch:
 | 
						|
		case SuffixMatch:
 | 
						|
		case SubstringMatch:
 | 
						|
		case RegexMatch:
 | 
						|
		default:
 | 
						|
			return nil, fmt.Errorf("unknown cname rewrite type: %s", rewriteType)
 | 
						|
		}
 | 
						|
		paramFromTarget, paramToTarget = strings.ToLower(args[1]), strings.ToLower(args[2])
 | 
						|
	} else if len(args) == 2 {
 | 
						|
		rewriteType = ExactMatch
 | 
						|
		paramFromTarget, paramToTarget = strings.ToLower(args[0]), strings.ToLower(args[1])
 | 
						|
	} else {
 | 
						|
		return nil, fmt.Errorf("too few (%d) arguments for a cname rule", len(args))
 | 
						|
	}
 | 
						|
	rule := cnameTargetRule{
 | 
						|
		rewriteType:     rewriteType,
 | 
						|
		paramFromTarget: paramFromTarget,
 | 
						|
		paramToTarget:   paramToTarget,
 | 
						|
		nextAction:      nextAction,
 | 
						|
		Upstream:        upstream.New(),
 | 
						|
	}
 | 
						|
	return &rule, nil
 | 
						|
}
 | 
						|
 | 
						|
// Rewrite rewrites the current request.
 | 
						|
func (r *cnameTargetRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
 | 
						|
	if r != nil && len(r.rewriteType) > 0 && len(r.paramFromTarget) > 0 && len(r.paramToTarget) > 0 {
 | 
						|
		return ResponseRules{&cnameTargetRuleWithReqState{
 | 
						|
			rule:  *r,
 | 
						|
			state: state,
 | 
						|
			ctx:   ctx,
 | 
						|
		}}, RewriteDone
 | 
						|
	}
 | 
						|
	return nil, RewriteIgnored
 | 
						|
}
 | 
						|
 | 
						|
// Mode returns the processing mode.
 | 
						|
func (r *cnameTargetRule) Mode() string { return r.nextAction }
 |