mirror of
				https://github.com/coredns/coredns.git
				synced 2025-11-02 10:13:14 -05:00 
			
		
		
		
	
		
			
	
	
		
			146 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			146 lines
		
	
	
		
			4.4 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
							 | 
						||
| 
								 | 
							
									state           request.Request
							 | 
						||
| 
								 | 
							
									ctx             context.Context
							 | 
						||
| 
								 | 
							
									Upstream        UpstreamInt // Upstream for looking up external names during the resolution process.
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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.Replace(inputCName, r.paramFromTarget, r.paramToTarget, -1)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									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.Replace(substitution, groupIndexStr, groupValue, -1)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return inputCName, substitution
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return "", ""
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (r *cnameTargetRule) 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.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.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 recieved
							 | 
						||
| 
								 | 
							
												for _, rr := range upRes.Answer {
							 | 
						||
| 
								 | 
							
													if rr.Header().Name == toTarget {
							 | 
						||
| 
								 | 
							
														newAnswer = append(newAnswer, rr)
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												res.Answer = newAnswer
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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 len(r.rewriteType) > 0 && len(r.paramFromTarget) > 0 && len(r.paramToTarget) > 0 {
							 | 
						||
| 
								 | 
							
										r.state = state
							 | 
						||
| 
								 | 
							
										r.ctx = ctx
							 | 
						||
| 
								 | 
							
										return ResponseRules{r}, RewriteDone
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return nil, RewriteIgnored
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Mode returns the processing mode.
							 | 
						||
| 
								 | 
							
								func (r *cnameTargetRule) Mode() string { return r.nextAction }
							 |