* Fix typos and add todo to fix spelling of dnsutil.MaximumDefaulTTL Signed-off-by: Zhizhen He <hezhizhen.yi@gmail.com>
145 lines
4.4 KiB
Go
145 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 received
|
|
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 }
|