mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 16:24:19 -04:00
This commit is contained in:
committed by
John Belamaric
parent
556a289d9a
commit
d35f2c73ec
@@ -96,3 +96,48 @@ rewrite edns0 subnet set 24 56
|
||||
|
||||
* If the query has source IP as IPv4, the first 24 bits in the IP will be the network subnet.
|
||||
* If the query has source IP as IPv6, the first 56 bits in the IP will be the network subnet.
|
||||
|
||||
### Name Field Rewrites
|
||||
|
||||
The `rewrite` plugin offers the ability to match on the name in the question section of
|
||||
a DNS request. The match could be exact, substring, or based on a prefix, suffix, or regular
|
||||
expression.
|
||||
|
||||
The syntax for the name re-writing is as follows:
|
||||
|
||||
```
|
||||
rewrite [continue|stop] name [exact|prefix|suffix|substring|regex] STRING STRING
|
||||
```
|
||||
|
||||
The match type, i.e. `exact`, `substring`, etc., triggers re-write:
|
||||
|
||||
* **exact** (default): on exact match of the name in the question section of a request
|
||||
* **substring**: on a partial match of the name in the question section of a request
|
||||
* **prefix**: when the name begins with the matching string
|
||||
* **suffix**: when the name ends with the matching string
|
||||
* **regex**: when the name in the question section of a request matches a regular expression
|
||||
|
||||
If the match type is omitted, the `exact` match type is being assumed.
|
||||
|
||||
The following instruction allows re-writing the name in the query that
|
||||
contains `service.us-west-1.example.org` substring.
|
||||
|
||||
```
|
||||
rewrite name substring service.us-west-1.example.org service.us-west-1.consul
|
||||
```
|
||||
|
||||
Thus:
|
||||
* Incoming Request Name: `ftp.service.us-west-1.example.org`
|
||||
* Re-written Request Name: `ftp.service.us-west-1.consul`
|
||||
|
||||
The following instruction uses regular expressions. The name in a request
|
||||
matching `(.*)-(us-west-1)\.example\.org` regular expression is being replaces with
|
||||
`{1}.service.{2}.consul`, where `{1}` and `{2}` are regular expression match groups.
|
||||
|
||||
```
|
||||
rewrite name regex (.*)-(us-west-1)\.example\.org {1}.service.{2}.consul
|
||||
```
|
||||
|
||||
Thus:
|
||||
* Incoming Request Name: `ftp-us-west-1.example.org`
|
||||
* Re-written Request Name: `ftp.service.us-west-1.consul`
|
||||
|
||||
@@ -1,20 +1,59 @@
|
||||
package rewrite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type nameRule struct {
|
||||
From, To string
|
||||
NextAction string
|
||||
From string
|
||||
To string
|
||||
}
|
||||
|
||||
func newNameRule(from, to string) (Rule, error) {
|
||||
return &nameRule{plugin.Name(from).Normalize(), plugin.Name(to).Normalize()}, nil
|
||||
type prefixNameRule struct {
|
||||
NextAction string
|
||||
Prefix string
|
||||
Replacement string
|
||||
}
|
||||
|
||||
// Rewrite rewrites the the current request.
|
||||
type suffixNameRule struct {
|
||||
NextAction string
|
||||
Suffix string
|
||||
Replacement string
|
||||
}
|
||||
|
||||
type substringNameRule struct {
|
||||
NextAction string
|
||||
Substring string
|
||||
Replacement string
|
||||
}
|
||||
|
||||
type regexNameRule struct {
|
||||
NextAction string
|
||||
Pattern *regexp.Regexp
|
||||
Replacement string
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// Rewrite rewrites the current request based upon exact match of the name
|
||||
// in the question section of the request
|
||||
func (rule *nameRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
||||
if rule.From == r.Question[0].Name {
|
||||
r.Question[0].Name = rule.To
|
||||
@@ -23,7 +62,100 @@ func (rule *nameRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
||||
return RewriteIgnored
|
||||
}
|
||||
|
||||
// Mode returns the processing mode
|
||||
func (rule *nameRule) Mode() string {
|
||||
return Stop
|
||||
// Rewrite rewrites the current request when the name begins with the matching string
|
||||
func (rule *prefixNameRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
||||
if strings.HasPrefix(r.Question[0].Name, rule.Prefix) {
|
||||
r.Question[0].Name = rule.Replacement + strings.TrimLeft(r.Question[0].Name, rule.Prefix)
|
||||
return RewriteDone
|
||||
}
|
||||
return RewriteIgnored
|
||||
}
|
||||
|
||||
// Rewrite rewrites the current request when the name ends with the matching string
|
||||
func (rule *suffixNameRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
||||
if strings.HasSuffix(r.Question[0].Name, rule.Suffix) {
|
||||
r.Question[0].Name = strings.TrimRight(r.Question[0].Name, rule.Suffix) + rule.Replacement
|
||||
return RewriteDone
|
||||
}
|
||||
return RewriteIgnored
|
||||
}
|
||||
|
||||
// Rewrite rewrites the current request based upon partial match of the
|
||||
// name in the question section of the request
|
||||
func (rule *substringNameRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
||||
if strings.Contains(r.Question[0].Name, rule.Substring) {
|
||||
r.Question[0].Name = strings.Replace(r.Question[0].Name, rule.Substring, rule.Replacement, -1)
|
||||
return RewriteDone
|
||||
}
|
||||
return RewriteIgnored
|
||||
}
|
||||
|
||||
// Rewrite rewrites the current request when the name in the question
|
||||
// section of the request matches a regular expression
|
||||
func (rule *regexNameRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
||||
regexGroups := rule.Pattern.FindStringSubmatch(r.Question[0].Name)
|
||||
if len(regexGroups) == 0 {
|
||||
return RewriteIgnored
|
||||
}
|
||||
s := rule.Replacement
|
||||
for groupIndex, groupValue := range regexGroups {
|
||||
groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
|
||||
if strings.Contains(s, groupIndexStr) {
|
||||
s = strings.Replace(s, groupIndexStr, groupValue, -1)
|
||||
}
|
||||
}
|
||||
r.Question[0].Name = s
|
||||
return RewriteDone
|
||||
}
|
||||
|
||||
// newNameRule creates a name matching rule based on exact, partial, or regex match
|
||||
func newNameRule(nextAction string, args ...string) (Rule, error) {
|
||||
if len(args) < 2 {
|
||||
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 {
|
||||
switch strings.ToLower(args[0]) {
|
||||
case ExactMatch:
|
||||
return &nameRule{nextAction, plugin.Name(args[1]).Normalize(), plugin.Name(args[2]).Normalize()}, nil
|
||||
case PrefixMatch:
|
||||
return &prefixNameRule{nextAction, plugin.Name(args[1]).Normalize(), plugin.Name(args[2]).Normalize()}, nil
|
||||
case SuffixMatch:
|
||||
return &suffixNameRule{nextAction, plugin.Name(args[1]).Normalize(), plugin.Name(args[2]).Normalize()}, nil
|
||||
case SubstringMatch:
|
||||
return &substringNameRule{nextAction, plugin.Name(args[1]).Normalize(), plugin.Name(args[2]).Normalize()}, nil
|
||||
case RegexMatch:
|
||||
regexPattern, err := regexp.Compile(args[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid regex pattern in a name rule: %s", args[1])
|
||||
}
|
||||
return ®exNameRule{nextAction, regexPattern, plugin.Name(args[2]).Normalize()}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("A name rule supports only exact, prefix, suffix, substring, and regex name matching")
|
||||
}
|
||||
}
|
||||
return &nameRule{nextAction, plugin.Name(args[0]).Normalize(), plugin.Name(args[1]).Normalize()}, nil
|
||||
}
|
||||
|
||||
// Mode returns the processing nextAction
|
||||
func (rule *nameRule) Mode() string {
|
||||
return rule.NextAction
|
||||
}
|
||||
|
||||
func (rule *prefixNameRule) Mode() string {
|
||||
return rule.NextAction
|
||||
}
|
||||
|
||||
func (rule *suffixNameRule) Mode() string {
|
||||
return rule.NextAction
|
||||
}
|
||||
|
||||
func (rule *substringNameRule) Mode() string {
|
||||
return rule.NextAction
|
||||
}
|
||||
|
||||
func (rule *regexNameRule) Mode() string {
|
||||
return rule.NextAction
|
||||
}
|
||||
|
||||
@@ -100,12 +100,12 @@ func newRule(args ...string) (Rule, error) {
|
||||
startArg = 1
|
||||
}
|
||||
|
||||
if ruleType != "edns0" && expectNumArgs != 3 {
|
||||
if ruleType != "edns0" && ruleType != "name" && expectNumArgs != 3 {
|
||||
return nil, fmt.Errorf("%s rules must have exactly two arguments", ruleType)
|
||||
}
|
||||
switch ruleType {
|
||||
case "name":
|
||||
return newNameRule(args[startArg], args[startArg+1])
|
||||
return newNameRule(mode, args[startArg:]...)
|
||||
case "class":
|
||||
return newClassRule(args[startArg], args[startArg+1])
|
||||
case "type":
|
||||
|
||||
@@ -30,6 +30,13 @@ func TestNewRule(t *testing.T) {
|
||||
{[]string{"name", "a.com"}, true, nil},
|
||||
{[]string{"name", "a.com", "b.com", "c.com"}, true, nil},
|
||||
{[]string{"name", "a.com", "b.com"}, false, reflect.TypeOf(&nameRule{})},
|
||||
{[]string{"name", "exact", "a.com", "b.com"}, false, reflect.TypeOf(&nameRule{})},
|
||||
{[]string{"name", "prefix", "a.com", "b.com"}, false, reflect.TypeOf(&prefixNameRule{})},
|
||||
{[]string{"name", "suffix", "a.com", "b.com"}, false, reflect.TypeOf(&suffixNameRule{})},
|
||||
{[]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"}, true, nil},
|
||||
{[]string{"name", "substring", "a.com", "b.com", "c.com"}, true, nil},
|
||||
{[]string{"type"}, true, nil},
|
||||
{[]string{"type", "a"}, true, nil},
|
||||
{[]string{"type", "any", "a", "a"}, true, nil},
|
||||
@@ -143,7 +150,17 @@ func TestNewRule(t *testing.T) {
|
||||
|
||||
func TestRewrite(t *testing.T) {
|
||||
rules := []Rule{}
|
||||
r, _ := newNameRule("from.nl.", "to.nl.")
|
||||
r, _ := newNameRule("stop", "from.nl.", "to.nl.")
|
||||
rules = append(rules, r)
|
||||
r, _ = newNameRule("stop", "exact", "from.exact.nl.", "to.nl.")
|
||||
rules = append(rules, r)
|
||||
r, _ = newNameRule("stop", "prefix", "prefix", "to")
|
||||
rules = append(rules, r)
|
||||
r, _ = newNameRule("stop", "suffix", ".suffix.", ".nl.")
|
||||
rules = append(rules, r)
|
||||
r, _ = newNameRule("stop", "substring", "from.substring", "to")
|
||||
rules = append(rules, r)
|
||||
r, _ = newNameRule("stop", "regex", "(f.*m)\\.regex\\.(nl)", "to.{2}")
|
||||
rules = append(rules, r)
|
||||
r, _ = newClassRule("CH", "IN")
|
||||
rules = append(rules, r)
|
||||
@@ -170,6 +187,11 @@ func TestRewrite(t *testing.T) {
|
||||
{"a.nl.", dns.TypeANY, dns.ClassINET, "a.nl.", dns.TypeHINFO, dns.ClassINET},
|
||||
// name is rewritten, type is not.
|
||||
{"from.nl.", dns.TypeANY, dns.ClassINET, "to.nl.", dns.TypeANY, dns.ClassINET},
|
||||
{"from.exact.nl.", dns.TypeA, dns.ClassINET, "to.nl.", dns.TypeA, dns.ClassINET},
|
||||
{"prefix.nl.", dns.TypeA, dns.ClassINET, "to.nl.", dns.TypeA, dns.ClassINET},
|
||||
{"to.suffix.", dns.TypeA, dns.ClassINET, "to.nl.", dns.TypeA, dns.ClassINET},
|
||||
{"from.substring.nl.", dns.TypeA, dns.ClassINET, "to.nl.", dns.TypeA, dns.ClassINET},
|
||||
{"from.regex.nl.", dns.TypeA, dns.ClassINET, "to.nl.", dns.TypeA, dns.ClassINET},
|
||||
// name is not, type is, but class is, because class is the 2nd rule.
|
||||
{"a.nl.", dns.TypeANY, dns.ClassCHAOS, "a.nl.", dns.TypeANY, dns.ClassINET},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user