mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 16:24:19 -04:00
feature: plugin/rewrite: rewrite ANSWER SECTION (#1318)
Resolves: #1313
This commit is contained in:
committed by
John Belamaric
parent
cb3190bab1
commit
258c163bb0
@@ -41,3 +41,8 @@ func (rule *classRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
|||||||
func (rule *classRule) Mode() string {
|
func (rule *classRule) Mode() string {
|
||||||
return rule.NextAction
|
return rule.NextAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *classRule) GetResponseRule() ResponseRule {
|
||||||
|
return ResponseRule{}
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ func (rule *edns0NsidRule) Mode() string {
|
|||||||
return rule.mode
|
return rule.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *edns0NsidRule) GetResponseRule() ResponseRule {
|
||||||
|
return ResponseRule{}
|
||||||
|
}
|
||||||
|
|
||||||
// Rewrite will alter the request EDNS0 local options
|
// Rewrite will alter the request EDNS0 local options
|
||||||
func (rule *edns0LocalRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
func (rule *edns0LocalRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
||||||
result := RewriteIgnored
|
result := RewriteIgnored
|
||||||
@@ -115,6 +120,11 @@ func (rule *edns0LocalRule) Mode() string {
|
|||||||
return rule.mode
|
return rule.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *edns0LocalRule) GetResponseRule() 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 {
|
||||||
@@ -312,6 +322,11 @@ func (rule *edns0VariableRule) Mode() string {
|
|||||||
return rule.mode
|
return rule.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *edns0VariableRule) GetResponseRule() ResponseRule {
|
||||||
|
return ResponseRule{}
|
||||||
|
}
|
||||||
|
|
||||||
func isValidVariable(variable string) bool {
|
func isValidVariable(variable string) bool {
|
||||||
switch variable {
|
switch variable {
|
||||||
case
|
case
|
||||||
@@ -423,6 +438,11 @@ func (rule *edns0SubnetRule) Mode() string {
|
|||||||
return rule.mode
|
return rule.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *edns0SubnetRule) GetResponseRule() ResponseRule {
|
||||||
|
return ResponseRule{}
|
||||||
|
}
|
||||||
|
|
||||||
// These are all defined actions.
|
// These are all defined actions.
|
||||||
const (
|
const (
|
||||||
Replace = "replace"
|
Replace = "replace"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type regexNameRule struct {
|
|||||||
NextAction string
|
NextAction string
|
||||||
Pattern *regexp.Regexp
|
Pattern *regexp.Regexp
|
||||||
Replacement string
|
Replacement string
|
||||||
|
ResponseRule
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -113,9 +114,6 @@ func newNameRule(nextAction string, args ...string) (Rule, error) {
|
|||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return nil, fmt.Errorf("too few arguments for a name rule")
|
return nil, fmt.Errorf("too few arguments for a name rule")
|
||||||
}
|
}
|
||||||
if len(args) > 3 {
|
|
||||||
return nil, fmt.Errorf("exceeded the number of arguments for a name rule")
|
|
||||||
}
|
|
||||||
if len(args) == 3 {
|
if len(args) == 3 {
|
||||||
switch strings.ToLower(args[0]) {
|
switch strings.ToLower(args[0]) {
|
||||||
case ExactMatch:
|
case ExactMatch:
|
||||||
@@ -131,11 +129,45 @@ func newNameRule(nextAction string, args ...string) (Rule, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Invalid regex pattern in a name rule: %s", args[1])
|
return nil, fmt.Errorf("Invalid regex pattern in a name rule: %s", args[1])
|
||||||
}
|
}
|
||||||
return ®exNameRule{nextAction, regexPattern, plugin.Name(args[2]).Normalize()}, nil
|
return ®exNameRule{nextAction, regexPattern, plugin.Name(args[2]).Normalize(), ResponseRule{}}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("A name rule supports only exact, prefix, suffix, substring, and regex name matching")
|
return nil, fmt.Errorf("A name rule supports only exact, prefix, suffix, substring, and regex name matching")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(args) == 7 {
|
||||||
|
if strings.ToLower(args[0]) == RegexMatch {
|
||||||
|
if args[3] != "answer" {
|
||||||
|
return nil, fmt.Errorf("exceeded the number of arguments for a regex name rule")
|
||||||
|
}
|
||||||
|
switch strings.ToLower(args[4]) {
|
||||||
|
case "name":
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("exceeded the number of arguments for a regex name rule")
|
||||||
|
}
|
||||||
|
regexPattern, err := regexp.Compile(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid regex pattern in a name rule: %s", args)
|
||||||
|
}
|
||||||
|
responseRegexPattern, err := regexp.Compile(args[5])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid regex pattern in a name rule: %s", args)
|
||||||
|
}
|
||||||
|
return ®exNameRule{
|
||||||
|
nextAction,
|
||||||
|
regexPattern,
|
||||||
|
plugin.Name(args[2]).Normalize(),
|
||||||
|
ResponseRule{
|
||||||
|
Active: true,
|
||||||
|
Pattern: responseRegexPattern,
|
||||||
|
Replacement: plugin.Name(args[6]).Normalize(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("the rewrite of response is supported only for name regex rule")
|
||||||
|
}
|
||||||
|
if len(args) > 3 && len(args) != 7 {
|
||||||
|
return nil, fmt.Errorf("exceeded the number of arguments for a name rule")
|
||||||
|
}
|
||||||
return &nameRule{nextAction, plugin.Name(args[0]).Normalize(), plugin.Name(args[1]).Normalize()}, nil
|
return &nameRule{nextAction, plugin.Name(args[0]).Normalize(), plugin.Name(args[1]).Normalize()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,3 +191,28 @@ func (rule *substringNameRule) Mode() string {
|
|||||||
func (rule *regexNameRule) Mode() string {
|
func (rule *regexNameRule) Mode() string {
|
||||||
return rule.NextAction
|
return rule.NextAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *nameRule) GetResponseRule() ResponseRule {
|
||||||
|
return ResponseRule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *prefixNameRule) GetResponseRule() ResponseRule {
|
||||||
|
return ResponseRule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *suffixNameRule) GetResponseRule() ResponseRule {
|
||||||
|
return ResponseRule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *substringNameRule) GetResponseRule() ResponseRule {
|
||||||
|
return ResponseRule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with.
|
||||||
|
func (rule *regexNameRule) GetResponseRule() ResponseRule {
|
||||||
|
return rule.ResponseRule
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +1,61 @@
|
|||||||
package rewrite
|
package rewrite
|
||||||
|
|
||||||
import "github.com/miekg/dns"
|
import (
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseRule contains a rule to rewrite a response with.
|
||||||
|
type ResponseRule struct {
|
||||||
|
Active bool
|
||||||
|
Pattern *regexp.Regexp
|
||||||
|
Replacement string
|
||||||
|
}
|
||||||
|
|
||||||
// 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 miek.nl/HINFO/IN'
|
// dig will complain with ';; Question section mismatch: got miek.nl/HINFO/IN'
|
||||||
type ResponseReverter struct {
|
type ResponseReverter struct {
|
||||||
dns.ResponseWriter
|
dns.ResponseWriter
|
||||||
original dns.Question
|
originalQuestion dns.Question
|
||||||
|
ResponseRewrite bool
|
||||||
|
ResponseRules []ResponseRule
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) *ResponseReverter {
|
||||||
return &ResponseReverter{
|
return &ResponseReverter{
|
||||||
ResponseWriter: w,
|
ResponseWriter: w,
|
||||||
original: r.Question[0],
|
originalQuestion: r.Question[0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMsg records the status code and calls the
|
// WriteMsg records the status code and calls the
|
||||||
// underlying ResponseWriter's WriteMsg method.
|
// underlying ResponseWriter's WriteMsg method.
|
||||||
func (r *ResponseReverter) WriteMsg(res *dns.Msg) error {
|
func (r *ResponseReverter) WriteMsg(res *dns.Msg) error {
|
||||||
res.Question[0] = r.original
|
res.Question[0] = r.originalQuestion
|
||||||
|
if r.ResponseRewrite {
|
||||||
|
for _, rr := range res.Answer {
|
||||||
|
name := rr.(*dns.A).Hdr.Name
|
||||||
|
for _, rule := range r.ResponseRules {
|
||||||
|
regexGroups := rule.Pattern.FindStringSubmatch(name)
|
||||||
|
if len(regexGroups) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := rule.Replacement
|
||||||
|
for groupIndex, groupValue := range regexGroups {
|
||||||
|
groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
|
||||||
|
if strings.Contains(s, groupIndexStr) {
|
||||||
|
s = strings.Replace(s, groupIndexStr, groupValue, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name = s
|
||||||
|
}
|
||||||
|
rr.(*dns.A).Hdr.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
return r.ResponseWriter.WriteMsg(res)
|
return r.ResponseWriter.WriteMsg(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ func (rw Rewrite) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
|
|||||||
for _, rule := range rw.Rules {
|
for _, rule := range rw.Rules {
|
||||||
switch result := rule.Rewrite(w, r); result {
|
switch result := rule.Rewrite(w, r); result {
|
||||||
case RewriteDone:
|
case RewriteDone:
|
||||||
|
respRule := rule.GetResponseRule()
|
||||||
|
if respRule.Active == true {
|
||||||
|
wr.ResponseRewrite = true
|
||||||
|
wr.ResponseRules = append(wr.ResponseRules, respRule)
|
||||||
|
}
|
||||||
if rule.Mode() == Stop {
|
if rule.Mode() == Stop {
|
||||||
if rw.noRevert {
|
if rw.noRevert {
|
||||||
return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, w, r)
|
return plugin.NextOrFailure(rw.Name(), rw.Next, ctx, w, r)
|
||||||
@@ -70,8 +75,10 @@ func (rw Rewrite) Name() string { return "rewrite" }
|
|||||||
type Rule interface {
|
type Rule interface {
|
||||||
// Rewrite rewrites the current request.
|
// Rewrite rewrites the current request.
|
||||||
Rewrite(dns.ResponseWriter, *dns.Msg) Result
|
Rewrite(dns.ResponseWriter, *dns.Msg) Result
|
||||||
// Mode returns the processing mode stop or continue
|
// Mode returns the processing mode stop or continue.
|
||||||
Mode() string
|
Mode() string
|
||||||
|
// GetResponseRule returns the rule to rewrite response with, if any.
|
||||||
|
GetResponseRule() ResponseRule
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRule(args ...string) (Rule, error) {
|
func newRule(args ...string) (Rule, error) {
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ func TestNewRule(t *testing.T) {
|
|||||||
{[]string{"name", "substring", "a.com", "b.com"}, false, reflect.TypeOf(&substringNameRule{})},
|
{[]string{"name", "substring", "a.com", "b.com"}, false, reflect.TypeOf(&substringNameRule{})},
|
||||||
{[]string{"name", "regex", "([a])\\.com", "new-{1}.com"}, false, reflect.TypeOf(®exNameRule{})},
|
{[]string{"name", "regex", "([a])\\.com", "new-{1}.com"}, false, reflect.TypeOf(®exNameRule{})},
|
||||||
{[]string{"name", "regex", "([a]\\.com", "new-{1}.com"}, true, nil},
|
{[]string{"name", "regex", "([a]\\.com", "new-{1}.com"}, true, nil},
|
||||||
|
{[]string{"name", "regex", "(dns)\\.(core)\\.(rocks)", "{2}.{1}.{3}", "answer", "name", "(core)\\.(dns)\\.(rocks)", "{2}.{1}.{3}"}, false, reflect.TypeOf(®exNameRule{})},
|
||||||
|
{[]string{"name", "regex", "(adns)\\.(core)\\.(rocks)", "{2}.{1}.{3}", "answer", "name", "(core)\\.(adns)\\.(rocks)", "{2}.{1}.{3}", "too.long", "way.too.long"}, true, nil},
|
||||||
|
{[]string{"name", "regex", "(bdns)\\.(core)\\.(rocks)", "{2}.{1}.{3}", "NoAnswer", "name", "(core)\\.(bdns)\\.(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", "\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", "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},
|
||||||
@@ -152,6 +159,8 @@ func TestRewrite(t *testing.T) {
|
|||||||
rules := []Rule{}
|
rules := []Rule{}
|
||||||
r, _ := newNameRule("stop", "from.nl.", "to.nl.")
|
r, _ := newNameRule("stop", "from.nl.", "to.nl.")
|
||||||
rules = append(rules, r)
|
rules = append(rules, r)
|
||||||
|
r, _ = newNameRule("stop", "regex", "(core)\\.(dns)\\.(rocks)\\.(nl)", "{2}.{1}.{3}.{4}", "answer", "name", "(dns)\\.(core)\\.(rocks)\\.(nl)", "{2}.{1}.{3}.{4}")
|
||||||
|
rules = append(rules, r)
|
||||||
r, _ = newNameRule("stop", "exact", "from.exact.nl.", "to.nl.")
|
r, _ = newNameRule("stop", "exact", "from.exact.nl.", "to.nl.")
|
||||||
rules = append(rules, r)
|
rules = append(rules, r)
|
||||||
r, _ = newNameRule("stop", "prefix", "prefix", "to")
|
r, _ = newNameRule("stop", "prefix", "prefix", "to")
|
||||||
@@ -203,6 +212,7 @@ func TestRewrite(t *testing.T) {
|
|||||||
{"a.nl.", dns.TypeANY, dns.ClassCHAOS, "a.nl.", dns.TypeANY, dns.ClassINET},
|
{"a.nl.", dns.TypeANY, dns.ClassCHAOS, "a.nl.", dns.TypeANY, dns.ClassINET},
|
||||||
// class gets rewritten twice because of continue/stop logic: HS to CH, CH to IN
|
// class gets rewritten twice because of continue/stop logic: HS to CH, CH to IN
|
||||||
{"a.nl.", dns.TypeANY, 4, "a.nl.", dns.TypeANY, dns.ClassINET},
|
{"a.nl.", dns.TypeANY, 4, "a.nl.", dns.TypeANY, dns.ClassINET},
|
||||||
|
{"core.dns.rocks.nl.", dns.TypeA, dns.ClassINET, "dns.core.rocks.nl.", dns.TypeA, dns.ClassINET},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
@@ -224,6 +234,13 @@ func TestRewrite(t *testing.T) {
|
|||||||
if resp.Question[0].Qclass != tc.toC {
|
if resp.Question[0].Qclass != tc.toC {
|
||||||
t.Errorf("Test %d: Expected Class to be '%d' but was '%d'", i, tc.toC, resp.Question[0].Qclass)
|
t.Errorf("Test %d: Expected Class to be '%d' but was '%d'", i, tc.toC, resp.Question[0].Qclass)
|
||||||
}
|
}
|
||||||
|
if tc.fromT == dns.TypeA && tc.toT == dns.TypeA {
|
||||||
|
if len(resp.Answer) > 0 {
|
||||||
|
if resp.Answer[0].(*dns.A).Hdr.Name != tc.to {
|
||||||
|
t.Errorf("Test %d: Expected Answer Name to be %q but was %q", i, tc.to, resp.Answer[0].(*dns.A).Hdr.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package rewrite
|
|||||||
import (
|
import (
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,6 +31,12 @@ func rewriteParse(c *caddy.Controller) ([]Rule, error) {
|
|||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
|
if len(args) < 2 {
|
||||||
|
// Handles rules out of nested instructions, i.e. the ones enclosed in curly brackets
|
||||||
|
for c.NextBlock() {
|
||||||
|
args = append(args, c.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
rule, err := newRule(args...)
|
rule, err := newRule(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -42,3 +42,8 @@ func (rule *typeRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
|||||||
func (rule *typeRule) Mode() string {
|
func (rule *typeRule) Mode() string {
|
||||||
return rule.nextAction
|
return rule.nextAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetResponseRule return a rule to rewrite the response with. Currently not implemented.
|
||||||
|
func (rule *typeRule) GetResponseRule() ResponseRule {
|
||||||
|
return ResponseRule{}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user