Add field keywords to rewrite middleware (#497)

* Require Field for rewrite rules

* review feedback changes

* fix ut

* fix typo, add warning message
This commit is contained in:
Bob Wasniak 2017-02-07 16:53:16 -05:00 committed by Miek Gieben
parent b8e75509cc
commit fa1c90a479
8 changed files with 124 additions and 76 deletions

View file

@ -7,24 +7,26 @@ accommodate most dynamic back-end applications.
## Syntax ## Syntax
~~~ ~~~
rewrite FROM TO rewrite FIELD FROM TO
~~~ ~~~
* **FIELD** is (`type`, `class`, `name`, ...)
* **FROM** is the exact name of type to match * **FROM** is the exact name of type to match
* **TO** is the destination name or type to rewrite to * **TO** is the destination name or type to rewrite to
If from *and* to look like a DNS type (`A`, `MX`, etc.), the type of the message will be rewriten; When the FIELD is `type` and FROM is (`A`, `MX`, etc.), the type of the message will be rewritten;
e.g., to rewrite ANY queries to HINFO, use `rewrite ANY HINFO`. e.g., to rewrite ANY queries to HINFO, use `rewrite type ANY HINFO`.
If from *and* to look like a DNS class (`IN`, `CH`, or `HS`) the class of the message will be When the FIELD is `class` and FROM is (`IN`, `CH`, or `HS`) the class of the message will be
rewritten; e.g., to rewrite CH queries to IN use `rewrite CH IN`. rewritten; e.g., to rewrite CH queries to IN use `rewrite class CH IN`.
If it does not look like a type a name is assumed and the qname in the message is rewritten; this When the FIELD is `name` the query name in the message is rewritten; this
needs to be a full match of the name, e.g., `rewrite miek.nl example.org`. needs to be a full match of the name, e.g., `rewrite name miek.nl example.org`.
If you specify multiple rules and an incoming query matches on multiple (simple) rules, only If you specify multiple rules and an incoming query matches on multiple (simple) rules, only
the first rewrite is applied. the first rewrite is applied.
> Everything below this line has not been implemented, yet. > Everything below this line has not been implemented, yet.
~~~ ~~~

View file

@ -0,0 +1,31 @@
// Package rewrite is middleware for rewriting requests internally to something different.
package rewrite
import (
"strings"
"github.com/miekg/dns"
)
// ClassRule is a class rewrite rule.
type ClassRule struct {
fromClass, toClass uint16
}
// Initializer
func (rule ClassRule) New(args ...string) Rule {
from, to := args[0], strings.Join(args[1:], " ")
return &ClassRule{dns.StringToClass[from], dns.StringToClass[to]}
}
// Rewrite rewrites the the current request.
func (rule ClassRule) Rewrite(r *dns.Msg) Result {
if rule.fromClass > 0 && rule.toClass > 0 {
if r.Question[0].Qclass == rule.fromClass {
r.Question[0].Qclass = rule.toClass
return RewriteDone
}
}
return RewriteIgnored
}

View file

@ -0,0 +1,13 @@
// Package rewrite is middleware for rewriting requests internally to something different.
package rewrite
/*
Additional FIELD keywords may be implemented to support more rewrite use-cases.
New Rule types must be added to the Fields map.
The type must implement `New` and `Rewrite` functions.
*/
var Fields = map[string]Rule{
"name": NameRule{},
"type": TypeRule{},
"class": ClassRule{},
}

View file

@ -0,0 +1,29 @@
// Package rewrite is middleware for rewriting requests internally to something different.
package rewrite
import (
"strings"
"github.com/miekg/coredns/middleware"
"github.com/miekg/dns"
)
// NameRule is a name rewrite rule.
type NameRule struct {
From, To string
}
// Initializer
func (rule NameRule) New(args ...string) Rule {
from, to := args[0], strings.Join(args[1:], " ")
return &NameRule{middleware.Name(from).Normalize(), middleware.Name(to).Normalize()}
}
// Rewrite rewrites the the current request.
func (rule NameRule) Rewrite(r *dns.Msg) Result {
if rule.From == r.Question[0].Name {
r.Question[0].Name = rule.To
return RewriteDone
}
return RewriteIgnored
}

View file

@ -2,10 +2,7 @@
package rewrite package rewrite
import ( import (
"strings"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -59,64 +56,6 @@ func (rw Rewrite) Name() string { return "rewrite" }
type Rule interface { type Rule interface {
// Rewrite rewrites the internal location of the current request. // Rewrite rewrites the internal location of the current request.
Rewrite(*dns.Msg) Result Rewrite(*dns.Msg) Result
} // New returns a new rule.
New(...string) Rule
// SimpleRule is a simple rewrite rule. If the From and To look like a type
// the type of the request is rewritten, otherwise the name is.
// Note: TSIG signed requests will be invalid.
type SimpleRule struct {
From, To string
fromType, toType uint16
fromClass, toClass uint16
}
// NewSimpleRule creates a new Simple Rule
func NewSimpleRule(from, to string) SimpleRule {
tpf := dns.StringToType[from]
tpt := dns.StringToType[to]
// ANY is both a type and class, ANY class rewritting is way more less frequent
// so we default to ANY as a type.
clf := dns.StringToClass[from]
clt := dns.StringToClass[to]
if from == "ANY" {
clf = 0
clt = 0
}
// It's only a type/class if uppercase is used.
if from != strings.ToUpper(from) {
tpf = 0
clf = 0
from = middleware.Name(from).Normalize()
}
if to != strings.ToUpper(to) {
tpt = 0
clt = 0
to = middleware.Name(to).Normalize()
}
return SimpleRule{From: from, To: to, fromType: tpf, toType: tpt, fromClass: clf, toClass: clt}
}
// Rewrite rewrites the the current request.
func (s SimpleRule) Rewrite(r *dns.Msg) Result {
if s.fromType > 0 && s.toType > 0 {
if r.Question[0].Qtype == s.fromType {
r.Question[0].Qtype = s.toType
return RewriteDone
}
}
if s.fromClass > 0 && s.toClass > 0 {
if r.Question[0].Qclass == s.fromClass {
r.Question[0].Qclass = s.toClass
return RewriteDone
}
}
if s.From == r.Question[0].Name {
r.Question[0].Name = s.To
return RewriteDone
}
return RewriteIgnored
} }

View file

@ -20,9 +20,9 @@ func TestRewrite(t *testing.T) {
rw := Rewrite{ rw := Rewrite{
Next: middleware.HandlerFunc(msgPrinter), Next: middleware.HandlerFunc(msgPrinter),
Rules: []Rule{ Rules: []Rule{
NewSimpleRule("from.nl.", "to.nl."), Fields["name"].New("from.nl.", "to.nl."),
NewSimpleRule("CH", "IN"), Fields["class"].New("CH", "IN"),
NewSimpleRule("ANY", "HINFO"), Fields["type"].New("ANY", "HINFO"),
}, },
noRevert: true, noRevert: true,
} }

View file

@ -1,7 +1,7 @@
package rewrite package rewrite
import ( import (
"strings" "log"
"github.com/miekg/coredns/core/dnsserver" "github.com/miekg/coredns/core/dnsserver"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
@ -108,8 +108,12 @@ func rewriteParse(c *caddy.Controller) ([]Rule, error) {
// the only unhandled case is 2 and above // the only unhandled case is 2 and above
default: default:
rule = NewSimpleRule(args[0], strings.Join(args[1:], " ")) if _, ok := Fields[args[0]]; ok {
simpleRules = append(simpleRules, rule) rule = Fields[args[0]].New(args[1:]...)
simpleRules = append(simpleRules, rule)
} else {
log.Printf("[WARN] %s is not a valid field, ignore %s", args[0], args)
}
} }
} }

View file

@ -0,0 +1,30 @@
// Package rewrite is middleware for rewriting requests internally to something different.
package rewrite
import (
"strings"
"github.com/miekg/dns"
)
// TypeRule is a type rewrite rule.
type TypeRule struct {
fromType, toType uint16
}
// Initializer
func (rule TypeRule) New(args ...string) Rule {
from, to := args[0], strings.Join(args[1:], " ")
return &TypeRule{dns.StringToType[from], dns.StringToType[to]}
}
// Rewrite rewrites the the current request.
func (rule TypeRule) Rewrite(r *dns.Msg) Result {
if rule.fromType > 0 && rule.toType > 0 {
if r.Question[0].Qtype == rule.fromType {
r.Question[0].Qtype = rule.toType
return RewriteDone
}
}
return RewriteIgnored
}