aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar John Belamaric <jbelamaric@infoblox.com> 2017-03-06 16:32:17 -0500
committerGravatar Miek Gieben <miek@miek.nl> 2017-03-06 21:32:17 +0000
commitef315ef3e23c69e853f3cf3b7357fc85ab653a21 (patch)
tree9c4cba1836713979a7e9a2aa6d3667a99f1876d3
parentd1bb4ea130dc8f5ef478f484e7576c311e5e8d39 (diff)
downloadcoredns-ef315ef3e23c69e853f3cf3b7357fc85ab653a21.tar.gz
coredns-ef315ef3e23c69e853f3cf3b7357fc85ab653a21.tar.zst
coredns-ef315ef3e23c69e853f3cf3b7357fc85ab653a21.zip
Rewrite edns0 (#561)
* Add edns0 code rewrite * check arg count * change `new`; set EDNS0 if request doesn't have it set * change set to replace_or_append * change to append_or_replace * return error in new * update documents * fixt UT * return error * go fmt * Rework for more general EDNS0 use Also changed how rules are created and validated. Implements EDNS0 NSID in addition to local. * go fmt * README updates, NSID tests and fixes * gofmt -s -w * Fix tests for rewrite syntax change * Add tests, fix error message * Review nits * Missed on nit * More tests, integration test, fix edns0 parse issue * Fix README, use RewriteIgnored * go fmt
-rw-r--r--middleware/rewrite/README.md76
-rw-r--r--middleware/rewrite/class.go22
-rw-r--r--middleware/rewrite/edns0.go141
-rw-r--r--middleware/rewrite/field.go11
-rw-r--r--middleware/rewrite/name.go15
-rw-r--r--middleware/rewrite/rewrite.go35
-rw-r--r--middleware/rewrite/rewrite_test.go280
-rw-r--r--middleware/rewrite/setup.go92
-rw-r--r--middleware/rewrite/setup_test.go25
-rw-r--r--middleware/rewrite/type.go21
-rw-r--r--test/middleware_dnssec_test.go2
-rw-r--r--test/middleware_test.go2
-rw-r--r--test/rewrite_test.go94
13 files changed, 559 insertions, 257 deletions
diff --git a/middleware/rewrite/README.md b/middleware/rewrite/README.md
index ed5aa58e1..3d0c6c321 100644
--- a/middleware/rewrite/README.md
+++ b/middleware/rewrite/README.md
@@ -23,79 +23,37 @@ rewritten; e.g., to rewrite CH queries to IN use `rewrite class CH IN`.
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 name miek.nl example.org`.
+When the FIELD is `edns0` an EDNS0 option can be appended to the request as described below.
+
If you specify multiple rules and an incoming query matches on multiple (simple) rules, only
the first rewrite is applied.
+## EDNS0 Options
-> Everything below this line has not been implemented, yet.
-
-~~~
-rewrite [basename] {
- regexp pattern
- ext extensions...
- if a cond b
- status code
- to destinations...
-}
-~~~
+Using FIELD edns0, you can set, append, or replace specific EDNS0 options on the request.
-* basepath is the base path to match before rewriting with a regular expression. Default is /.
-* regexp (shorthand: r) will match the path with the given regular expression pattern. Extremely high-load servers should avoid using regular expressions.
-* extensions... is a space-separated list of file extensions to include or ignore. Prefix an extension with ! to exclude an extension. The forward slash / symbol matches paths without file extensions.
-* if specifies a rewrite condition. Multiple ifs are AND-ed together. a and b are any string and may use request placeholders. cond is the condition, with possible values explained below.
-* status will respond with the given status code instead of performing a rewrite. In other words, use either "status" or "to" in your rule, but not both. The code must be a number in the format 2xx or 4xx.
-* destinations... is one or more space-separated paths to rewrite to, with support for request placeholders as well as numbered regular expression captures such as {1}, {2}, etc. Rewrite will check each destination in order and rewrite to the first destination that exists. Each one is checked as a file or, if it ends with /, as a directory. The last destination will act as the default if no other destination exists.
-"if" Conditions
+* `replace` will modify any matching (what that means may vary based on EDNS0 type) option with the specified option
+* `append` will add the option regardless of what options already exist
+* `set` will modify a matching option or add one if none is found
-The if keyword is a powerful way to describe your rule. It takes the format a cond b, where the values a and b are separated by cond, a condition. The condition can be any of these:
-
-~~~
-is = a equals b
-not = a does NOT equal b
-has = a has b as a substring (b is a substring of a)
-not_has = b is NOT a substring of a
-starts_with = b is a prefix of a
-ends_with = b is a suffix of a
-match = a matches b, where b is a regular expression
-not_match = a does NOT match b, where b is a regular expression
-~~~
+Currently supported are `EDNS0_LOCAL` and `EDNS0_NSID`.
-## Examples
+### `EDNS0_LOCAL`
-When requests come in for /mobile, actually serve /mobile/index.
-rewrite /mobile /mobile/index
-If the file is not favicon.ico and it is not a valid file or directory, serve the maintenance page if present, or finally, rewrite to index.php.
+This has two fields, code and data. A match is defined as having the same code. Data may be a string, or if
+it starts with `0x` it will be treated as hex. Example:
~~~
-rewrite {
- if {file} not favicon.ico
- to {path} {path}/ /maintenance.html /index.php
-}
+rewrite edns0 local set 0xffee 0x61626364
~~~
-If user agent includes "mobile" and path is not a valid file/directory, rewrite to the mobile index page.
+rewrites the first local option with code 0xffee, setting the data to "abcd". Equivalent:
~~~
-rewrite {
- if if {>User-agent} has mobile
- to {path} {path}/ /mobile/index.php
-}
+rewrite edns0 local set 0xffee abcd
~~~
-If the request path starts with /source, respond with HTTP 403 Forbidden.
+### `EDNS0_NSID`
-~~~
-rewrite {
- regexp ^/source
- status 403
-}
-~~~
-
-Rewrite /app to /index with a query string. {1} is the matched group (.*).
-
-~~~
-rewrite /app {
- r (.*)
- to /index?path={1}
-}
-~~~
+This has no fields; it will add an NSID option with an empty string for the NSID. If the option already exists
+and the action is `replace` or `set`, then the NSID in the option will be set to the empty string.
diff --git a/middleware/rewrite/class.go b/middleware/rewrite/class.go
index ba383df81..a2bd00d0f 100644
--- a/middleware/rewrite/class.go
+++ b/middleware/rewrite/class.go
@@ -1,26 +1,30 @@
-// Package rewrite is middleware for rewriting requests internally to something different.
package rewrite
import (
+ "fmt"
"strings"
"github.com/miekg/dns"
)
-// ClassRule is a class rewrite rule.
-type ClassRule struct {
+type classRule struct {
fromClass, toClass uint16
}
-// New initializes rule.
-func (rule ClassRule) New(args ...string) Rule {
- from, to := args[0], strings.Join(args[1:], " ")
- return &ClassRule{dns.StringToClass[from], dns.StringToClass[to]}
-
+func newClassRule(fromS, toS string) (Rule, error) {
+ var from, to uint16
+ var ok bool
+ if from, ok = dns.StringToClass[strings.ToUpper(fromS)]; !ok {
+ return nil, fmt.Errorf("invalid class %q", strings.ToUpper(fromS))
+ }
+ if to, ok = dns.StringToClass[strings.ToUpper(toS)]; !ok {
+ return nil, fmt.Errorf("invalid class %q", strings.ToUpper(toS))
+ }
+ return &classRule{fromClass: from, toClass: to}, nil
}
// Rewrite rewrites the the current request.
-func (rule ClassRule) Rewrite(r *dns.Msg) Result {
+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
diff --git a/middleware/rewrite/edns0.go b/middleware/rewrite/edns0.go
new file mode 100644
index 000000000..3ba214c6a
--- /dev/null
+++ b/middleware/rewrite/edns0.go
@@ -0,0 +1,141 @@
+// Package rewrite is middleware for rewriting requests internally to something different.
+package rewrite
+
+import (
+ "encoding/hex"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/miekg/dns"
+)
+
+// edns0LocalRule is a rewrite rule for EDNS0_LOCAL options
+type edns0LocalRule struct {
+ action string
+ code uint16
+ data []byte
+}
+
+// ends0NsidRule is a rewrite rule for EDNS0_NSID options
+type edns0NsidRule struct {
+ action string
+}
+
+// setupEdns0Opt will retrieve the EDNS0 OPT or create it if it does not exist
+func setupEdns0Opt(r *dns.Msg) *dns.OPT {
+ o := r.IsEdns0()
+ if o == nil {
+ r.SetEdns0(4096, true)
+ o = r.IsEdns0()
+ }
+ return o
+}
+
+// Rewrite will alter the request EDNS0 NSID option
+func (rule *edns0NsidRule) Rewrite(r *dns.Msg) Result {
+ result := RewriteIgnored
+ o := setupEdns0Opt(r)
+ found := false
+ for _, s := range o.Option {
+ switch e := s.(type) {
+ case *dns.EDNS0_NSID:
+ if rule.action == "replace" || rule.action == "set" {
+ e.Nsid = "" // make sure it is empty for request
+ result = RewriteDone
+ }
+ found = true
+ break
+ }
+ }
+
+ // add option if not found
+ if !found && (rule.action == "append" || rule.action == "set") {
+ o.SetDo(true)
+ o.Option = append(o.Option, &dns.EDNS0_NSID{Code: dns.EDNS0NSID, Nsid: ""})
+ result = RewriteDone
+ }
+
+ return result
+}
+
+// Rewrite will alter the request EDNS0 local options
+func (rule *edns0LocalRule) Rewrite(r *dns.Msg) Result {
+ result := RewriteIgnored
+ o := setupEdns0Opt(r)
+ found := false
+ for _, s := range o.Option {
+ switch e := s.(type) {
+ case *dns.EDNS0_LOCAL:
+ if rule.code == e.Code {
+ if rule.action == "replace" || rule.action == "set" {
+ e.Data = rule.data
+ result = RewriteDone
+ }
+ found = true
+ break
+ }
+ }
+ }
+
+ // add option if not found
+ if !found && (rule.action == "append" || rule.action == "set") {
+ o.SetDo(true)
+ var opt dns.EDNS0_LOCAL
+ opt.Code = rule.code
+ opt.Data = rule.data
+ o.Option = append(o.Option, &opt)
+ result = RewriteDone
+ }
+
+ return result
+}
+
+// newEdns0Rule creates an EDNS0 rule of the appropriate type based on the args
+func newEdns0Rule(args ...string) (Rule, error) {
+ if len(args) < 2 {
+ return nil, fmt.Errorf("Too few arguments for an EDNS0 rule")
+ }
+
+ ruleType := strings.ToLower(args[0])
+ action := strings.ToLower(args[1])
+ switch action {
+ case "append":
+ case "replace":
+ case "set":
+ default:
+ return nil, fmt.Errorf("invalid action: %q", action)
+ }
+
+ switch ruleType {
+ case "local":
+ if len(args) != 4 {
+ return nil, fmt.Errorf("EDNS0 local rules require exactly three args")
+ }
+ return newEdns0LocalRule(action, args[2], args[3])
+ case "nsid":
+ if len(args) != 2 {
+ return nil, fmt.Errorf("EDNS0 NSID rules do not accept args")
+ }
+ return &edns0NsidRule{action: action}, nil
+ default:
+ return nil, fmt.Errorf("invalid rule type %q", ruleType)
+ }
+}
+
+func newEdns0LocalRule(action, code, data string) (*edns0LocalRule, error) {
+ c, err := strconv.ParseUint(code, 0, 16)
+ if err != nil {
+ return nil, err
+ }
+
+ decoded := []byte(data)
+ if strings.HasPrefix(data, "0x") {
+ decoded, err = hex.DecodeString(data[2:])
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return &edns0LocalRule{action: action, code: uint16(c), data: decoded}, nil
+}
diff --git a/middleware/rewrite/field.go b/middleware/rewrite/field.go
deleted file mode 100644
index 2b5ae60be..000000000
--- a/middleware/rewrite/field.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// Package rewrite is middleware for rewriting requests internally to something different.
-package rewrite
-
-// Fields defines 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{},
-}
diff --git a/middleware/rewrite/name.go b/middleware/rewrite/name.go
index 6b885c5c5..69c387f10 100644
--- a/middleware/rewrite/name.go
+++ b/middleware/rewrite/name.go
@@ -1,26 +1,21 @@
-// Package rewrite is middleware for rewriting requests internally to something different.
package rewrite
import (
- "strings"
-
"github.com/coredns/coredns/middleware"
+
"github.com/miekg/dns"
)
-// NameRule is a name rewrite rule.
-type NameRule struct {
+type nameRule struct {
From, To string
}
-// New initializes a new rule.
-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()}
+func newNameRule(from, to string) (Rule, error) {
+ return &nameRule{middleware.Name(from).Normalize(), middleware.Name(to).Normalize()}, nil
}
// Rewrite rewrites the the current request.
-func (rule NameRule) Rewrite(r *dns.Msg) Result {
+func (rule *nameRule) Rewrite(r *dns.Msg) Result {
if rule.From == r.Question[0].Name {
r.Question[0].Name = rule.To
return RewriteDone
diff --git a/middleware/rewrite/rewrite.go b/middleware/rewrite/rewrite.go
index 24e57005e..2db97b2f1 100644
--- a/middleware/rewrite/rewrite.go
+++ b/middleware/rewrite/rewrite.go
@@ -1,9 +1,13 @@
-// Package rewrite is middleware for rewriting requests internally to something different.
package rewrite
import (
+ "fmt"
+ "strings"
+
"github.com/coredns/coredns/middleware"
+
"github.com/miekg/dns"
+
"golang.org/x/net/context"
)
@@ -52,10 +56,31 @@ func (rw Rewrite) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
// Name implements the Handler interface.
func (rw Rewrite) Name() string { return "rewrite" }
-// Rule describes an internal location rewrite rule.
+// Rule describes a rewrite rule.
type Rule interface {
- // Rewrite rewrites the internal location of the current request.
+ // Rewrite rewrites the current request.
Rewrite(*dns.Msg) Result
- // New returns a new rule.
- New(...string) Rule
+}
+
+func newRule(args ...string) (Rule, error) {
+ if len(args) == 0 {
+ return nil, fmt.Errorf("No rule type specified for rewrite")
+ }
+
+ ruleType := strings.ToLower(args[0])
+ if ruleType != "edns0" && len(args) != 3 {
+ return nil, fmt.Errorf("%s rules must have exactly two arguments", ruleType)
+ }
+ switch ruleType {
+ case "name":
+ return newNameRule(args[1], args[2])
+ case "class":
+ return newClassRule(args[1], args[2])
+ case "type":
+ return newTypeRule(args[1], args[2])
+ case "edns0":
+ return newEdns0Rule(args[1:]...)
+ default:
+ return nil, fmt.Errorf("invalid rule type %q", args[0])
+ }
}
diff --git a/middleware/rewrite/rewrite_test.go b/middleware/rewrite/rewrite_test.go
index 64d0d5eec..e4c0afc50 100644
--- a/middleware/rewrite/rewrite_test.go
+++ b/middleware/rewrite/rewrite_test.go
@@ -1,6 +1,8 @@
package rewrite
import (
+ "bytes"
+ "reflect"
"testing"
"github.com/coredns/coredns/middleware"
@@ -16,14 +18,73 @@ func msgPrinter(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, err
return 0, nil
}
+func TestNewRule(t *testing.T) {
+ tests := []struct {
+ args []string
+ shouldError bool
+ expType reflect.Type
+ }{
+ {[]string{}, true, nil},
+ {[]string{"foo"}, true, nil},
+ {[]string{"name"}, true, nil},
+ {[]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{"type"}, true, nil},
+ {[]string{"type", "a"}, true, nil},
+ {[]string{"type", "any", "a", "a"}, true, nil},
+ {[]string{"type", "any", "a"}, false, reflect.TypeOf(&typeRule{})},
+ {[]string{"type", "XY", "WV"}, true, nil},
+ {[]string{"type", "ANY", "WV"}, true, nil},
+ {[]string{"class"}, true, nil},
+ {[]string{"class", "IN"}, true, nil},
+ {[]string{"class", "ch", "in", "in"}, true, nil},
+ {[]string{"class", "ch", "in"}, false, reflect.TypeOf(&classRule{})},
+ {[]string{"class", "XY", "WV"}, true, nil},
+ {[]string{"class", "IN", "WV"}, true, nil},
+ {[]string{"edns0"}, true, nil},
+ {[]string{"edns0", "local"}, true, nil},
+ {[]string{"edns0", "local", "set"}, true, nil},
+ {[]string{"edns0", "local", "set", "0xffee"}, true, nil},
+ {[]string{"edns0", "local", "set", "65518", "abcdefg"}, false, reflect.TypeOf(&edns0LocalRule{})},
+ {[]string{"edns0", "local", "set", "0xffee", "abcdefg"}, false, reflect.TypeOf(&edns0LocalRule{})},
+ {[]string{"edns0", "local", "append", "0xffee", "abcdefg"}, false, reflect.TypeOf(&edns0LocalRule{})},
+ {[]string{"edns0", "local", "replace", "0xffee", "abcdefg"}, false, reflect.TypeOf(&edns0LocalRule{})},
+ {[]string{"edns0", "local", "foo", "0xffee", "abcdefg"}, true, nil},
+ {[]string{"edns0", "local", "set", "0xffee", "0xabcdefg"}, true, nil},
+ {[]string{"edns0", "nsid", "set", "junk"}, true, nil},
+ {[]string{"edns0", "nsid", "set"}, false, reflect.TypeOf(&edns0NsidRule{})},
+ {[]string{"edns0", "nsid", "append"}, false, reflect.TypeOf(&edns0NsidRule{})},
+ {[]string{"edns0", "nsid", "replace"}, false, reflect.TypeOf(&edns0NsidRule{})},
+ {[]string{"edns0", "nsid", "foo"}, true, nil},
+ }
+
+ for i, tc := range tests {
+ r, err := newRule(tc.args...)
+ if err == nil && tc.shouldError {
+ t.Errorf("Test %d: expected error but got success", i)
+ } else if err != nil && !tc.shouldError {
+ t.Errorf("Test %d: expected success but got error: %s", i, err)
+ }
+
+ if !tc.shouldError && reflect.TypeOf(r) != tc.expType {
+ t.Errorf("Test %d: expected %q but got %q", i, tc.expType, r)
+ }
+ }
+}
+
func TestRewrite(t *testing.T) {
+ rules := []Rule{}
+ r, _ := newNameRule("from.nl.", "to.nl.")
+ rules = append(rules, r)
+ r, _ = newClassRule("CH", "IN")
+ rules = append(rules, r)
+ r, _ = newTypeRule("ANY", "HINFO")
+ rules = append(rules, r)
+
rw := Rewrite{
- Next: middleware.HandlerFunc(msgPrinter),
- Rules: []Rule{
- Fields["name"].New("from.nl.", "to.nl."),
- Fields["class"].New("CH", "IN"),
- Fields["type"].New("ANY", "HINFO"),
- },
+ Next: middleware.HandlerFunc(msgPrinter),
+ Rules: rules,
noRevert: true,
}
@@ -56,7 +117,7 @@ func TestRewrite(t *testing.T) {
resp := rec.Msg
if resp.Question[0].Name != tc.to {
- t.Errorf("Test %d: Expected Name to be '%s' but was '%s'", i, tc.to, resp.Question[0].Name)
+ t.Errorf("Test %d: Expected Name to be %q but was %q", i, tc.to, resp.Question[0].Name)
}
if resp.Question[0].Qtype != tc.toT {
t.Errorf("Test %d: Expected Type to be '%d' but was '%d'", i, tc.toT, resp.Question[0].Qtype)
@@ -65,79 +126,162 @@ func TestRewrite(t *testing.T) {
t.Errorf("Test %d: Expected Class to be '%d' but was '%d'", i, tc.toC, resp.Question[0].Qclass)
}
}
+}
+
+func TestRewriteEDNS0Local(t *testing.T) {
+ rw := Rewrite{
+ Next: middleware.HandlerFunc(msgPrinter),
+ noRevert: true,
+ }
- /*
- regexps := [][]string{
- {"/reg/", ".*", "/to", ""},
- {"/r/", "[a-z]+", "/toaz", "!.html|"},
- {"/url/", "a([a-z0-9]*)s([A-Z]{2})", "/to/{path}", ""},
- {"/ab/", "ab", "/ab?{query}", ".txt|"},
- {"/ab/", "ab", "/ab?type=html&{query}", ".html|"},
- {"/abc/", "ab", "/abc/{file}", ".html|"},
- {"/abcd/", "ab", "/a/{dir}/{file}", ".html|"},
- {"/abcde/", "ab", "/a#{fragment}", ".html|"},
- {"/ab/", `.*\.jpg`, "/ajpg", ""},
- {"/reggrp", `/ad/([0-9]+)([a-z]*)`, "/a{1}/{2}", ""},
- {"/reg2grp", `(.*)`, "/{1}", ""},
- {"/reg3grp", `(.*)/(.*)/(.*)`, "/{1}{2}{3}", ""},
+ tests := []struct {
+ fromOpts []dns.EDNS0
+ args []string
+ toOpts []dns.EDNS0
+ }{
+ {
+ []dns.EDNS0{},
+ []string{"local", "set", "0xffee", "0xabcdef"},
+ []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte{0xab, 0xcd, 0xef}}},
+ },
+ {
+ []dns.EDNS0{},
+ []string{"local", "append", "0xffee", "abcdefghijklmnop"},
+ []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte("abcdefghijklmnop")}},
+ },
+ {
+ []dns.EDNS0{},
+ []string{"local", "replace", "0xffee", "abcdefghijklmnop"},
+ []dns.EDNS0{},
+ },
+ {
+ []dns.EDNS0{},
+ []string{"nsid", "set"},
+ []dns.EDNS0{&dns.EDNS0_NSID{Code: dns.EDNS0NSID, Nsid: ""}},
+ },
+ {
+ []dns.EDNS0{},
+ []string{"nsid", "append"},
+ []dns.EDNS0{&dns.EDNS0_NSID{Code: dns.EDNS0NSID, Nsid: ""}},
+ },
+ {
+ []dns.EDNS0{},
+ []string{"nsid", "replace"},
+ []dns.EDNS0{},
+ },
+ }
+
+ ctx := context.TODO()
+ for i, tc := range tests {
+ m := new(dns.Msg)
+ m.SetQuestion("example.com.", dns.TypeA)
+ m.Question[0].Qclass = dns.ClassINET
+
+ r, err := newEdns0Rule(tc.args...)
+ if err != nil {
+ t.Errorf("Error creating test rule: %s", err)
+ continue
}
+ rw.Rules = []Rule{r}
- for _, regexpRule := range regexps {
- var ext []string
- if s := strings.Split(regexpRule[3], "|"); len(s) > 1 {
- ext = s[:len(s)-1]
- }
- rule, err := NewComplexRule(regexpRule[0], regexpRule[1], regexpRule[2], 0, ext, nil)
- if err != nil {
- t.Fatal(err)
- }
- rw.Rules = append(rw.Rules, rule)
+ rec := dnsrecorder.New(&test.ResponseWriter{})
+ rw.ServeDNS(ctx, rec, m)
+
+ resp := rec.Msg
+ o := resp.IsEdns0()
+ if o == nil {
+ t.Errorf("Test %d: EDNS0 options not set", i)
+ continue
}
- */
- /*
- statusTests := []struct {
- status int
- base string
- to string
- regexp string
- statusExpected bool
- }{
- {400, "/status", "", "", true},
- {400, "/ignore", "", "", false},
- {400, "/", "", "^/ignore", false},
- {400, "/", "", "(.*)", true},
- {400, "/status", "", "", true},
+ if !optsEqual(o.Option, tc.toOpts) {
+ t.Errorf("Test %d: Expected %v but got %v", i, tc.toOpts, o)
}
+ }
+}
- for i, s := range statusTests {
- urlPath := fmt.Sprintf("/status%d", i)
- rule, err := NewComplexRule(s.base, s.regexp, s.to, s.status, nil, nil)
- if err != nil {
- t.Fatalf("Test %d: No error expected for rule but found %v", i, err)
- }
- rw.Rules = []Rule{rule}
- req, err := http.NewRequest("GET", urlPath, nil)
- if err != nil {
- t.Fatalf("Test %d: Could not create HTTP request: %v", i, err)
- }
+func TestEdns0LocalMultiRule(t *testing.T) {
+ rules := []Rule{}
+ r, _ := newEdns0Rule("local", "replace", "0xffee", "abcdef")
+ rules = append(rules, r)
+ r, _ = newEdns0Rule("local", "set", "0xffee", "fedcba")
+ rules = append(rules, r)
+
+ rw := Rewrite{
+ Next: middleware.HandlerFunc(msgPrinter),
+ Rules: rules,
+ noRevert: true,
+ }
+
+ tests := []struct {
+ fromOpts []dns.EDNS0
+ toOpts []dns.EDNS0
+ }{
+ {
+ nil,
+ []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte("fedcba")}},
+ },
+ {
+ []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte("foobar")}},
+ []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte("abcdef")}},
+ },
+ }
- rec := httptest.NewRecorder()
- code, err := rw.ServeHTTP(rec, req)
- if err != nil {
- t.Fatalf("Test %d: No error expected for handler but found %v", i, err)
+ ctx := context.TODO()
+ for i, tc := range tests {
+ m := new(dns.Msg)
+ m.SetQuestion("example.com.", dns.TypeA)
+ m.Question[0].Qclass = dns.ClassINET
+ if tc.fromOpts != nil {
+ o := m.IsEdns0()
+ if o == nil {
+ m.SetEdns0(4096, true)
+ o = m.IsEdns0()
}
- if s.statusExpected {
- if rec.Body.String() != "" {
- t.Errorf("Test %d: Expected empty body but found %s", i, rec.Body.String())
+ o.Option = append(o.Option, tc.fromOpts...)
+ }
+ rec := dnsrecorder.New(&test.ResponseWriter{})
+ rw.ServeDNS(ctx, rec, m)
+
+ resp := rec.Msg
+ o := resp.IsEdns0()
+ if o == nil {
+ t.Errorf("Test %d: EDNS0 options not set", i)
+ continue
+ }
+ if !optsEqual(o.Option, tc.toOpts) {
+ t.Errorf("Test %d: Expected %v but got %v", i, tc.toOpts, o)
+ }
+ }
+}
+
+func optsEqual(a, b []dns.EDNS0) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i := range a {
+ switch aa := a[i].(type) {
+ case *dns.EDNS0_LOCAL:
+ if bb, ok := b[i].(*dns.EDNS0_LOCAL); ok {
+ if aa.Code != bb.Code {
+ return false
}
- if code != s.status {
- t.Errorf("Test %d: Expected status code %d found %d", i, s.status, code)
+ if !bytes.Equal(aa.Data, bb.Data) {
+ return false
}
} else {
- if code != 0 {
- t.Errorf("Test %d: Expected no status code found %d", i, code)
+ return false
+ }
+ case *dns.EDNS0_NSID:
+ if bb, ok := b[i].(*dns.EDNS0_NSID); ok {
+ if aa.Nsid != bb.Nsid {
+ return false
}
+ } else {
+ return false
}
+ default:
+ return false
}
- */
+ }
+ return true
}
diff --git a/middleware/rewrite/setup.go b/middleware/rewrite/setup.go
index 9befa30a1..156129f70 100644
--- a/middleware/rewrite/setup.go
+++ b/middleware/rewrite/setup.go
@@ -1,8 +1,6 @@
package rewrite
import (
- "log"
-
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
@@ -30,93 +28,15 @@ func setup(c *caddy.Controller) error {
}
func rewriteParse(c *caddy.Controller) ([]Rule, error) {
- var simpleRules []Rule
- var regexpRules []Rule
+ var rules []Rule
for c.Next() {
- var rule Rule
- /*
- var base = "."
- var err error
- var pattern, to string
- var status int
- var ifs []If
- var ext []string
- */
-
args := c.RemainingArgs()
-
- switch len(args) {
- case 1:
- /*
- base = args[0]
- fallthrough
- */
- case 0:
- /*
- for c.NextBlock() {
- switch c.Val() {
- case "r", "regexp":
- if !c.NextArg() {
- return nil, c.ArgErr()
- }
- pattern = c.Val()
- case "to":
- args1 := c.RemainingArgs()
- if len(args1) == 0 {
- return nil, c.ArgErr()
- }
- to = strings.Join(args1, " ")
- case "ext": // TODO(miek): fix or remove
- args1 := c.RemainingArgs()
- if len(args1) == 0 {
- return nil, c.ArgErr()
- }
- ext = args1
- case "if":
- args1 := c.RemainingArgs()
- if len(args1) != 3 {
- return nil, c.ArgErr()
- }
- ifCond, err := NewIf(args1[0], args1[1], args1[2])
- if err != nil {
- return nil, err
- }
- ifs = append(ifs, ifCond)
- case "status": // TODO(miek): fix or remove
- if !c.NextArg() {
- return nil, c.ArgErr()
- }
- status, _ = strconv.Atoi(c.Val())
- if status < 200 || (status > 299 && status < 400) || status > 499 {
- return nil, c.Err("status must be 2xx or 4xx")
- }
- default:
- return nil, c.ArgErr()
- }
- }
- // ensure to or status is specified
- if to == "" && status == 0 {
- return nil, c.ArgErr()
- }
- // TODO(miek): complex rules
- if rule, err = NewComplexRule(base, pattern, to, status, ext, ifs); err != nil {
- return nil, err
- }
- regexpRules = append(regexpRules, rule)
- */
-
- // the only unhandled case is 2 and above
- default:
- if _, ok := Fields[args[0]]; ok {
- 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)
- }
+ rule, err := newRule(args...)
+ if err != nil {
+ return nil, err
}
+ rules = append(rules, rule)
}
-
- // put simple rules in front to avoid regexp computation for them
- return append(simpleRules, regexpRules...), nil
+ return rules, nil
}
diff --git a/middleware/rewrite/setup_test.go b/middleware/rewrite/setup_test.go
new file mode 100644
index 000000000..67ef88e18
--- /dev/null
+++ b/middleware/rewrite/setup_test.go
@@ -0,0 +1,25 @@
+package rewrite
+
+import (
+ "testing"
+
+ "github.com/mholt/caddy"
+)
+
+func TestParse(t *testing.T) {
+ c := caddy.NewTestController("dns", `rewrite`)
+ _, err := rewriteParse(c)
+ if err == nil {
+ t.Errorf("Expected error but found nil for `rewrite`")
+ }
+ c = caddy.NewTestController("dns", `rewrite name`)
+ _, err = rewriteParse(c)
+ if err == nil {
+ t.Errorf("Expected error but found nil for `rewrite name`")
+ }
+ c = caddy.NewTestController("dns", `rewrite name a.com b.com`)
+ _, err = rewriteParse(c)
+ if err != nil {
+ t.Errorf("Expected success but found %s for `rewrite name a.com b.com`", err)
+ }
+}
diff --git a/middleware/rewrite/type.go b/middleware/rewrite/type.go
index a04361c79..c2b7866d7 100644
--- a/middleware/rewrite/type.go
+++ b/middleware/rewrite/type.go
@@ -2,24 +2,31 @@
package rewrite
import (
+ "fmt"
"strings"
"github.com/miekg/dns"
)
-// TypeRule is a type rewrite rule.
-type TypeRule struct {
+// typeRule is a type rewrite rule.
+type typeRule struct {
fromType, toType uint16
}
-// New initializes a rule.
-func (rule TypeRule) New(args ...string) Rule {
- from, to := args[0], strings.Join(args[1:], " ")
- return &TypeRule{dns.StringToType[from], dns.StringToType[to]}
+func newTypeRule(fromS, toS string) (Rule, error) {
+ var from, to uint16
+ var ok bool
+ if from, ok = dns.StringToType[strings.ToUpper(fromS)]; !ok {
+ return nil, fmt.Errorf("invalid type %q", strings.ToUpper(fromS))
+ }
+ if to, ok = dns.StringToType[strings.ToUpper(toS)]; !ok {
+ return nil, fmt.Errorf("invalid type %q", strings.ToUpper(toS))
+ }
+ return &typeRule{fromType: from, toType: to}, nil
}
// Rewrite rewrites the the current request.
-func (rule TypeRule) Rewrite(r *dns.Msg) Result {
+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
diff --git a/test/middleware_dnssec_test.go b/test/middleware_dnssec_test.go
index 819ee66d7..34df9bb0f 100644
--- a/test/middleware_dnssec_test.go
+++ b/test/middleware_dnssec_test.go
@@ -23,7 +23,7 @@ func TestLookupBalanceRewriteCacheDnssec(t *testing.T) {
corefile := `example.org:0 {
file ` + name + `
- rewrite ANY HINFO
+ rewrite type ANY HINFO
dnssec {
key file ` + base + `
}
diff --git a/test/middleware_test.go b/test/middleware_test.go
index ef468cdf7..10bd3864c 100644
--- a/test/middleware_test.go
+++ b/test/middleware_test.go
@@ -20,7 +20,7 @@ func benchmarkLookupBalanceRewriteCache(b *testing.B) {
corefile := `example.org:0 {
file ` + name + `
- rewrite ANY HINFO
+ rewrite type ANY HINFO
loadbalance
}
`
diff --git a/test/rewrite_test.go b/test/rewrite_test.go
new file mode 100644
index 000000000..9aedd7417
--- /dev/null
+++ b/test/rewrite_test.go
@@ -0,0 +1,94 @@
+package test
+
+import (
+ "bytes"
+ "io/ioutil"
+ "log"
+ "testing"
+
+ "github.com/miekg/dns"
+)
+
+func TestRewrite(t *testing.T) {
+ t.Parallel()
+ corefile := `.:0 {
+ rewrite type MX a
+ rewrite edns0 local set 0xffee hello-world
+ erratic . {
+ drop 0
+ }
+}`
+
+ i, err := CoreDNSServer(corefile)
+ if err != nil {
+ t.Fatalf("Could not get CoreDNS serving instance: %s", err)
+ }
+
+ udp, _ := CoreDNSServerPorts(i, 0)
+ if udp == "" {
+ t.Fatalf("Could not get UDP listening port")
+ }
+ defer i.Stop()
+
+ log.SetOutput(ioutil.Discard)
+
+ testMX(t, udp)
+ testEdns0(t, udp)
+}
+
+func testMX(t *testing.T, server string) {
+ m := new(dns.Msg)
+ m.SetQuestion("example.com.", dns.TypeMX)
+
+ r, err := dns.Exchange(m, server)
+ if err != nil {
+ t.Fatalf("Expected to receive reply, but didn't: %s", err)
+ }
+
+ // expect answer section with A record in it
+ if len(r.Answer) == 0 {
+ t.Error("Expected to at least one RR in the answer section, got none")
+ }
+ if r.Answer[0].Header().Rrtype != dns.TypeA {
+ t.Errorf("Expected RR to A, got: %d", r.Answer[0].Header().Rrtype)
+ }
+ if r.Answer[0].(*dns.A).A.String() != "192.0.2.53" {
+ t.Errorf("Expected 192.0.2.53, got: %s", r.Answer[0].(*dns.A).A.String())
+ }
+}
+
+func testEdns0(t *testing.T, server string) {
+ m := new(dns.Msg)
+ m.SetQuestion("example.com.", dns.TypeA)
+
+ r, err := dns.Exchange(m, server)
+ if err != nil {
+ t.Fatalf("Expected to receive reply, but didn't: %s", err)
+ }
+
+ // expect answer section with A record in it
+ if len(r.Answer) == 0 {
+ t.Error("Expected to at least one RR in the answer section, got none")
+ }
+ if r.Answer[0].Header().Rrtype != dns.TypeA {
+ t.Errorf("Expected RR to A, got: %d", r.Answer[0].Header().Rrtype)
+ }
+ if r.Answer[0].(*dns.A).A.String() != "192.0.2.53" {
+ t.Errorf("Expected 192.0.2.53, got: %s", r.Answer[0].(*dns.A).A.String())
+ }
+ o := r.IsEdns0()
+ if o == nil || len(o.Option) == 0 {
+ t.Error("Expected EDNS0 options but got none")
+ } else {
+ if e, ok := o.Option[0].(*dns.EDNS0_LOCAL); ok {
+ if e.Code != 0xffee {
+ t.Errorf("Expected EDNS_LOCAL code 0xffee but got %x", e.Code)
+ }
+ if !bytes.Equal(e.Data, []byte("hello-world")) {
+ t.Errorf("Expected EDNS_LOCAL data 'hello-world' but got %q", e.Data)
+ }
+ } else {
+ t.Errorf("Expected EDNS0_LOCAL but got %v", o.Option[0])
+ }
+ }
+}