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 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.
|
* 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
|
package rewrite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nameRule struct {
|
type nameRule struct {
|
||||||
From, To string
|
NextAction string
|
||||||
|
From string
|
||||||
|
To string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNameRule(from, to string) (Rule, error) {
|
type prefixNameRule struct {
|
||||||
return &nameRule{plugin.Name(from).Normalize(), plugin.Name(to).Normalize()}, nil
|
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 {
|
func (rule *nameRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
||||||
if rule.From == r.Question[0].Name {
|
if rule.From == r.Question[0].Name {
|
||||||
r.Question[0].Name = rule.To
|
r.Question[0].Name = rule.To
|
||||||
@@ -23,7 +62,100 @@ func (rule *nameRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
|||||||
return RewriteIgnored
|
return RewriteIgnored
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode returns the processing mode
|
// Rewrite rewrites the current request when the name begins with the matching string
|
||||||
func (rule *nameRule) Mode() string {
|
func (rule *prefixNameRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result {
|
||||||
return Stop
|
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
|
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)
|
return nil, fmt.Errorf("%s rules must have exactly two arguments", ruleType)
|
||||||
}
|
}
|
||||||
switch ruleType {
|
switch ruleType {
|
||||||
case "name":
|
case "name":
|
||||||
return newNameRule(args[startArg], args[startArg+1])
|
return newNameRule(mode, args[startArg:]...)
|
||||||
case "class":
|
case "class":
|
||||||
return newClassRule(args[startArg], args[startArg+1])
|
return newClassRule(args[startArg], args[startArg+1])
|
||||||
case "type":
|
case "type":
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ func TestNewRule(t *testing.T) {
|
|||||||
{[]string{"name", "a.com"}, true, nil},
|
{[]string{"name", "a.com"}, true, nil},
|
||||||
{[]string{"name", "a.com", "b.com", "c.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", "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"}, true, nil},
|
||||||
{[]string{"type", "a"}, true, nil},
|
{[]string{"type", "a"}, true, nil},
|
||||||
{[]string{"type", "any", "a", "a"}, true, nil},
|
{[]string{"type", "any", "a", "a"}, true, nil},
|
||||||
@@ -143,7 +150,17 @@ func TestNewRule(t *testing.T) {
|
|||||||
|
|
||||||
func TestRewrite(t *testing.T) {
|
func TestRewrite(t *testing.T) {
|
||||||
rules := []Rule{}
|
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)
|
rules = append(rules, r)
|
||||||
r, _ = newClassRule("CH", "IN")
|
r, _ = newClassRule("CH", "IN")
|
||||||
rules = append(rules, r)
|
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},
|
{"a.nl.", dns.TypeANY, dns.ClassINET, "a.nl.", dns.TypeHINFO, dns.ClassINET},
|
||||||
// name is rewritten, type is not.
|
// name is rewritten, type is not.
|
||||||
{"from.nl.", dns.TypeANY, dns.ClassINET, "to.nl.", dns.TypeANY, dns.ClassINET},
|
{"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.
|
// 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},
|
{"a.nl.", dns.TypeANY, dns.ClassCHAOS, "a.nl.", dns.TypeANY, dns.ClassINET},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user