diff options
Diffstat (limited to 'middleware')
308 files changed, 0 insertions, 29222 deletions
diff --git a/middleware/auto/README.md b/middleware/auto/README.md deleted file mode 100644 index b69c1c291..000000000 --- a/middleware/auto/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# auto - -*auto* enables serving zone data from an RFC 1035-style master file which is automatically picked -up from disk. - -The *auto* middleware is used for an "old-style" DNS server. It serves from a preloaded file that exists -on disk. If the zone file contains signatures (i.e. is signed, i.e. DNSSEC) correct DNSSEC answers -are returned. Only NSEC is supported! If you use this setup *you* are responsible for resigning the -zonefile. New zones or changed zone are automatically picked up from disk. - -## Syntax - -~~~ -auto [ZONES...] { - directory DIR [REGEXP ORIGIN_TEMPLATE [TIMEOUT]] - no_reload - upstream ADDRESS... -} -~~~ - -**ZONES** zones it should be authoritative for. If empty, the zones from the configuration block -are used. - -* `directory` loads zones from the speficied **DIR**. If a file name matches **REGEXP** it will be - used to extract the origin. **ORIGIN_TEMPLATE** will be used as a template for the origin. Strings - like `{<number>}` are replaced with the respective matches in the file name, i.e. `{1}` is the - first match, `{2}` is the second, etc.. The default is: `db\.(.*) {1}` e.g. from a file with the - name `db.example.com`, the extracted origin will be `example.com`. **TIMEOUT** specifies how often - CoreDNS should scan the directory, the default is every 60 seconds. This value is in seconds. - The minimum value is 1 second. -* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the - file. This option disables that behavior. -* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs) - pointing to external names. **ADDRESS** can be an IP address, and IP:port or a string pointing to - a file that is structured as /etc/resolv.conf. - -All directives from the *file* middleware are supported. Note that *auto* will load all zones found, -even though the directive might only receive queries for a specific zone. I.e: - -~~~ -auto example.org { - directory /etc/coredns/zones -} -~~~ -Will happily pick up a zone for `example.COM`, except it will never be queried, because the *auto* -directive only is authoritative for `example.ORG`. - -## Examples - -Load `org` domains from `/etc/coredns/zones/org` and allow transfers to the internet, but send -notifies to 10.240.1.1 - -~~~ -auto org { - directory /etc/coredns/zones/org - transfer to * - transfer to 10.240.1.1 -} -~~~ - -Load `org` domains from `/etc/coredns/zones/org` and looks for file names as `www.db.example.org`, -where `example.org` is the origin. Scan every 45 seconds. - -~~~ -auto org { - directory /etc/coredns/zones/org www\.db\.(.*) {1} 45 -} -~~~ diff --git a/middleware/auto/auto.go b/middleware/auto/auto.go deleted file mode 100644 index 31fe53e23..000000000 --- a/middleware/auto/auto.go +++ /dev/null @@ -1,96 +0,0 @@ -// Package auto implements an on-the-fly loading file backend. -package auto - -import ( - "regexp" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/file" - "github.com/coredns/coredns/middleware/metrics" - "github.com/coredns/coredns/middleware/proxy" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -type ( - // Auto holds the zones and the loader configuration for automatically loading zones. - Auto struct { - Next middleware.Handler - *Zones - - metrics *metrics.Metrics - loader - } - - loader struct { - directory string - template string - re *regexp.Regexp - - // In the future this should be something like ZoneMeta that contains all this stuff. - transferTo []string - noReload bool - proxy proxy.Proxy // Proxy for looking up names during the resolution process - - duration time.Duration - } -) - -// ServeDNS implements the middleware.Handle interface. -func (a Auto) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - qname := state.Name() - - // TODO(miek): match the qname better in the map - - // Precheck with the origins, i.e. are we allowed to looks here. - zone := middleware.Zones(a.Zones.Origins()).Matches(qname) - if zone == "" { - return middleware.NextOrFailure(a.Name(), a.Next, ctx, w, r) - } - - // Now the real zone. - zone = middleware.Zones(a.Zones.Names()).Matches(qname) - - a.Zones.RLock() - z, ok := a.Zones.Z[zone] - a.Zones.RUnlock() - - if !ok || z == nil { - return dns.RcodeServerFailure, nil - } - - if state.QType() == dns.TypeAXFR || state.QType() == dns.TypeIXFR { - xfr := file.Xfr{Zone: z} - return xfr.ServeDNS(ctx, w, r) - } - - answer, ns, extra, result := z.Lookup(state, qname) - - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - m.Answer, m.Ns, m.Extra = answer, ns, extra - - switch result { - case file.Success: - case file.NoData: - case file.NameError: - m.Rcode = dns.RcodeNameError - case file.Delegation: - m.Authoritative = false - case file.ServerFailure: - return dns.RcodeServerFailure, nil - } - - state.SizeAndDo(m) - m, _ = state.Scrub(m) - w.WriteMsg(m) - return dns.RcodeSuccess, nil -} - -// Name implements the Handler interface. -func (a Auto) Name() string { return "auto" } diff --git a/middleware/auto/regexp.go b/middleware/auto/regexp.go deleted file mode 100644 index fa424ec7e..000000000 --- a/middleware/auto/regexp.go +++ /dev/null @@ -1,20 +0,0 @@ -package auto - -// rewriteToExpand rewrites our template string to one that we can give to regexp.ExpandString. This basically -// involves prefixing any '{' with a '$'. -func rewriteToExpand(s string) string { - // Pretty dumb at the moment, every { will get a $ prefixed. - // Also wasteful as we build the string with +=. This is OKish - // as we do this during config parsing. - - copy := "" - - for _, c := range s { - if c == '{' { - copy += "$" - } - copy += string(c) - } - - return copy -} diff --git a/middleware/auto/regexp_test.go b/middleware/auto/regexp_test.go deleted file mode 100644 index 17c35eb90..000000000 --- a/middleware/auto/regexp_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package auto - -import "testing" - -func TestRewriteToExpand(t *testing.T) { - tests := []struct { - in string - expected string - }{ - {in: "", expected: ""}, - {in: "{1}", expected: "${1}"}, - {in: "{1", expected: "${1"}, - } - for i, tc := range tests { - got := rewriteToExpand(tc.in) - if got != tc.expected { - t.Errorf("Test %d: Expected error %v, but got %v", i, tc.expected, got) - } - } -} diff --git a/middleware/auto/setup.go b/middleware/auto/setup.go deleted file mode 100644 index 426fb93b2..000000000 --- a/middleware/auto/setup.go +++ /dev/null @@ -1,172 +0,0 @@ -package auto - -import ( - "log" - "os" - "path" - "regexp" - "strconv" - "time" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/file" - "github.com/coredns/coredns/middleware/metrics" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/proxy" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("auto", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - a, err := autoParse(c) - if err != nil { - return middleware.Error("auto", err) - } - - c.OnStartup(func() error { - m := dnsserver.GetConfig(c).Handler("prometheus") - if m == nil { - return nil - } - (&a).metrics = m.(*metrics.Metrics) - return nil - }) - - walkChan := make(chan bool) - - c.OnStartup(func() error { - err := a.Walk() - if err != nil { - return err - } - - go func() { - ticker := time.NewTicker(a.loader.duration) - for { - select { - case <-walkChan: - return - case <-ticker.C: - a.Walk() - } - } - }() - return nil - }) - - c.OnShutdown(func() error { - close(walkChan) - return nil - }) - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - a.Next = next - return a - }) - - return nil -} - -func autoParse(c *caddy.Controller) (Auto, error) { - var a = Auto{ - loader: loader{template: "${1}", re: regexp.MustCompile(`db\.(.*)`), duration: 60 * time.Second}, - Zones: &Zones{}, - } - - config := dnsserver.GetConfig(c) - - for c.Next() { - // auto [ZONES...] - a.Zones.origins = make([]string, len(c.ServerBlockKeys)) - copy(a.Zones.origins, c.ServerBlockKeys) - - args := c.RemainingArgs() - if len(args) > 0 { - a.Zones.origins = args - } - for i := range a.Zones.origins { - a.Zones.origins[i] = middleware.Host(a.Zones.origins[i]).Normalize() - } - - for c.NextBlock() { - switch c.Val() { - case "directory": // directory DIR [REGEXP [TEMPLATE] [DURATION]] - if !c.NextArg() { - return a, c.ArgErr() - } - a.loader.directory = c.Val() - if !path.IsAbs(a.loader.directory) && config.Root != "" { - a.loader.directory = path.Join(config.Root, a.loader.directory) - } - _, err := os.Stat(a.loader.directory) - if err != nil { - if os.IsNotExist(err) { - log.Printf("[WARNING] Directory does not exist: %s", a.loader.directory) - } else { - return a, c.Errf("Unable to access root path '%s': %v", a.loader.directory, err) - } - } - - // regexp - if c.NextArg() { - a.loader.re, err = regexp.Compile(c.Val()) - if err != nil { - return a, err - } - if a.loader.re.NumSubexp() == 0 { - return a, c.Errf("Need at least one sub expression") - } - } - - // template - if c.NextArg() { - a.loader.template = rewriteToExpand(c.Val()) - } - - // duration - if c.NextArg() { - i, err := strconv.Atoi(c.Val()) - if err != nil { - return a, err - } - if i < 1 { - i = 1 - } - a.loader.duration = time.Duration(i) * time.Second - } - - case "no_reload": - a.loader.noReload = true - - case "upstream": - args := c.RemainingArgs() - if len(args) == 0 { - return a, c.ArgErr() - } - ups, err := dnsutil.ParseHostPortOrFile(args...) - if err != nil { - return a, err - } - a.loader.proxy = proxy.NewLookup(ups) - - default: - t, _, e := file.TransferParse(c, false) - if e != nil { - return a, e - } - if t != nil { - a.loader.transferTo = append(a.loader.transferTo, t...) - } - } - } - } - return a, nil -} diff --git a/middleware/auto/setup_test.go b/middleware/auto/setup_test.go deleted file mode 100644 index 9754551d2..000000000 --- a/middleware/auto/setup_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package auto - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestAutoParse(t *testing.T) { - tests := []struct { - inputFileRules string - shouldErr bool - expectedDirectory string - expectedTempl string - expectedRe string - expectedTo []string - }{ - { - `auto example.org { - directory /tmp - transfer to 127.0.0.1 - }`, - false, "/tmp", "${1}", `db\.(.*)`, []string{"127.0.0.1:53"}, - }, - { - `auto 10.0.0.0/24 { - directory /tmp - }`, - false, "/tmp", "${1}", `db\.(.*)`, nil, - }, - { - `auto { - directory /tmp - no_reload - }`, - false, "/tmp", "${1}", `db\.(.*)`, nil, - }, - { - `auto { - directory /tmp (.*) bliep - }`, - false, "/tmp", "bliep", `(.*)`, nil, - }, - { - `auto { - directory /tmp (.*) bliep 10 - }`, - false, "/tmp", "bliep", `(.*)`, nil, - }, - { - `auto { - directory /tmp (.*) bliep - transfer to 127.0.0.1 - transfer to 127.0.0.2 - upstream 8.8.8.8 - }`, - false, "/tmp", "bliep", `(.*)`, []string{"127.0.0.1:53", "127.0.0.2:53"}, - }, - // errors - { - `auto example.org { - directory - }`, - true, "", "${1}", `db\.(.*)`, nil, - }, - { - `auto example.org { - directory /tmp * {1} - }`, - true, "", "${1}", ``, nil, - }, - { - `auto example.org { - directory /tmp * {1} aa - }`, - true, "", "${1}", ``, nil, - }, - { - `auto example.org { - directory /tmp .* {1} - }`, - true, "", "${1}", ``, nil, - }, - { - `auto example.org { - directory /tmp .* {1} - }`, - true, "", "${1}", ``, nil, - }, - { - `auto example.org { - directory /tmp .* {1} - }`, - true, "", "${1}", ``, nil, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.inputFileRules) - a, err := autoParse(c) - - if err == nil && test.shouldErr { - t.Fatalf("Test %d expected errors, but got no error", i) - } else if err != nil && !test.shouldErr { - t.Fatalf("Test %d expected no errors, but got '%v'", i, err) - } else if !test.shouldErr { - if a.loader.directory != test.expectedDirectory { - t.Fatalf("Test %d expected %v, got %v", i, test.expectedDirectory, a.loader.directory) - } - if a.loader.template != test.expectedTempl { - t.Fatalf("Test %d expected %v, got %v", i, test.expectedTempl, a.loader.template) - } - if a.loader.re.String() != test.expectedRe { - t.Fatalf("Test %d expected %v, got %v", i, test.expectedRe, a.loader.re) - } - if test.expectedTo != nil { - for j, got := range a.loader.transferTo { - if got != test.expectedTo[j] { - t.Fatalf("Test %d expected %v, got %v", i, test.expectedTo[j], got) - } - } - } - } - } -} diff --git a/middleware/auto/walk.go b/middleware/auto/walk.go deleted file mode 100644 index 1bb351691..000000000 --- a/middleware/auto/walk.go +++ /dev/null @@ -1,109 +0,0 @@ -package auto - -import ( - "log" - "os" - "path" - "path/filepath" - "regexp" - - "github.com/coredns/coredns/middleware/file" - - "github.com/miekg/dns" -) - -// Walk will recursively walk of the file under l.directory and adds the one that match l.re. -func (a Auto) Walk() error { - - // TODO(miek): should add something so that we don't stomp on each other. - - toDelete := make(map[string]bool) - for _, n := range a.Zones.Names() { - toDelete[n] = true - } - - filepath.Walk(a.loader.directory, func(path string, info os.FileInfo, err error) error { - if info == nil || info.IsDir() { - return nil - } - - match, origin := matches(a.loader.re, info.Name(), a.loader.template) - if !match { - return nil - } - - if _, ok := a.Zones.Z[origin]; ok { - // we already have this zone - toDelete[origin] = false - return nil - } - - reader, err := os.Open(path) - if err != nil { - log.Printf("[WARNING] Opening %s failed: %s", path, err) - return nil - } - defer reader.Close() - - // Serial for loading a zone is 0, because it is a new zone. - zo, err := file.Parse(reader, origin, path, 0) - if err != nil { - log.Printf("[WARNING] Parse zone `%s': %v", origin, err) - return nil - } - - zo.NoReload = a.loader.noReload - zo.Proxy = a.loader.proxy - zo.TransferTo = a.loader.transferTo - - a.Zones.Add(zo, origin) - - if a.metrics != nil { - a.metrics.AddZone(origin) - } - - zo.Notify() - - log.Printf("[INFO] Inserting zone `%s' from: %s", origin, path) - - toDelete[origin] = false - - return nil - }) - - for origin, ok := range toDelete { - if !ok { - continue - } - - if a.metrics != nil { - a.metrics.RemoveZone(origin) - } - - a.Zones.Remove(origin) - - log.Printf("[INFO] Deleting zone `%s'", origin) - } - - return nil -} - -// matches matches re to filename, if is is a match, the subexpression will be used to expand -// template to an origin. When match is true that origin is returned. Origin is fully qualified. -func matches(re *regexp.Regexp, filename, template string) (match bool, origin string) { - base := path.Base(filename) - - matches := re.FindStringSubmatchIndex(base) - if matches == nil { - return false, "" - } - - by := re.ExpandString(nil, template, base, matches) - if by == nil { - return false, "" - } - - origin = dns.Fqdn(string(by)) - - return true, origin -} diff --git a/middleware/auto/walk_test.go b/middleware/auto/walk_test.go deleted file mode 100644 index 29b9dbb55..000000000 --- a/middleware/auto/walk_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package auto - -import ( - "io/ioutil" - "log" - "os" - "path" - "regexp" - "testing" -) - -var dbFiles = []string{"db.example.org", "aa.example.org"} - -const zoneContent = `; testzone -@ IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082534 7200 3600 1209600 3600 - NS a.iana-servers.net. - NS b.iana-servers.net. - -www IN A 127.0.0.1 -` - -func TestWalk(t *testing.T) { - log.SetOutput(ioutil.Discard) - - tempdir, err := createFiles() - if err != nil { - if tempdir != "" { - os.RemoveAll(tempdir) - } - t.Fatal(err) - } - defer os.RemoveAll(tempdir) - - ldr := loader{ - directory: tempdir, - re: regexp.MustCompile(`db\.(.*)`), - template: `${1}`, - } - - a := Auto{ - loader: ldr, - Zones: &Zones{}, - } - - a.Walk() - - // db.example.org and db.example.com should be here (created in createFiles) - for _, name := range []string{"example.com.", "example.org."} { - if _, ok := a.Zones.Z[name]; !ok { - t.Errorf("%s should have been added", name) - } - } -} - -func TestWalkNonExistent(t *testing.T) { - log.SetOutput(ioutil.Discard) - - nonExistingDir := "highly_unlikely_to_exist_dir" - - ldr := loader{ - directory: nonExistingDir, - re: regexp.MustCompile(`db\.(.*)`), - template: `${1}`, - } - - a := Auto{ - loader: ldr, - Zones: &Zones{}, - } - - a.Walk() -} - -func createFiles() (string, error) { - dir, err := ioutil.TempDir(os.TempDir(), "coredns") - if err != nil { - return dir, err - } - - for _, name := range dbFiles { - if err := ioutil.WriteFile(path.Join(dir, name), []byte(zoneContent), 0644); err != nil { - return dir, err - } - } - // symlinks - if err = os.Symlink(path.Join(dir, "db.example.org"), path.Join(dir, "db.example.com")); err != nil { - return dir, err - } - if err = os.Symlink(path.Join(dir, "db.example.org"), path.Join(dir, "aa.example.com")); err != nil { - return dir, err - } - - return dir, nil -} diff --git a/middleware/auto/watcher_test.go b/middleware/auto/watcher_test.go deleted file mode 100644 index 329d8dc85..000000000 --- a/middleware/auto/watcher_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package auto - -import ( - "io/ioutil" - "log" - "os" - "path" - "regexp" - "testing" -) - -func TestWatcher(t *testing.T) { - log.SetOutput(ioutil.Discard) - - tempdir, err := createFiles() - if err != nil { - if tempdir != "" { - os.RemoveAll(tempdir) - } - t.Fatal(err) - } - defer os.RemoveAll(tempdir) - - ldr := loader{ - directory: tempdir, - re: regexp.MustCompile(`db\.(.*)`), - template: `${1}`, - } - - a := Auto{ - loader: ldr, - Zones: &Zones{}, - } - - a.Walk() - - // example.org and example.com should exist - if x := len(a.Zones.Z["example.org."].All()); x != 4 { - t.Fatalf("Expected 4 RRs, got %d", x) - } - if x := len(a.Zones.Z["example.com."].All()); x != 4 { - t.Fatalf("Expected 4 RRs, got %d", x) - } - - // Now remove one file, rescan and see if it's gone. - if err := os.Remove(path.Join(tempdir, "db.example.com")); err != nil { - t.Fatal(err) - } - - a.Walk() - - if _, ok := a.Zones.Z["example.com."]; ok { - t.Errorf("Expected %q to be gone.", "example.com.") - } - if _, ok := a.Zones.Z["example.org."]; !ok { - t.Errorf("Expected %q to still be there.", "example.org.") - } -} diff --git a/middleware/auto/zone.go b/middleware/auto/zone.go deleted file mode 100644 index ead26541e..000000000 --- a/middleware/auto/zone.go +++ /dev/null @@ -1,76 +0,0 @@ -// Package auto implements a on-the-fly loading file backend. -package auto - -import ( - "sync" - - "github.com/coredns/coredns/middleware/file" -) - -// Zones maps zone names to a *Zone. This keep track of what we zones we have loaded at -// any one time. -type Zones struct { - Z map[string]*file.Zone // A map mapping zone (origin) to the Zone's data. - names []string // All the keys from the map Z as a string slice. - - origins []string // Any origins from the server block. - - sync.RWMutex -} - -// Names returns the names from z. -func (z *Zones) Names() []string { - z.RLock() - n := z.names - z.RUnlock() - return n -} - -// Origins returns the origins from z. -func (z *Zones) Origins() []string { - // doesn't need locking, because there aren't multiple Go routines accessing it. - return z.origins -} - -// Zones returns a zone with origin name from z, nil when not found. -func (z *Zones) Zones(name string) *file.Zone { - z.RLock() - zo := z.Z[name] - z.RUnlock() - return zo -} - -// Add adds a new zone into z. If zo.NoReload is false, the -// reload goroutine is started. -func (z *Zones) Add(zo *file.Zone, name string) { - z.Lock() - - if z.Z == nil { - z.Z = make(map[string]*file.Zone) - } - - z.Z[name] = zo - z.names = append(z.names, name) - zo.Reload() - - z.Unlock() -} - -// Remove removes the zone named name from z. It also stop the the zone's reload goroutine. -func (z *Zones) Remove(name string) { - z.Lock() - - if zo, ok := z.Z[name]; ok && !zo.NoReload { - zo.ReloadShutdown <- true - } - - delete(z.Z, name) - - // TODO(miek): just regenerate Names (might be bad if you have a lot of zones...) - z.names = []string{} - for n := range z.Z { - z.names = append(z.names, n) - } - - z.Unlock() -} diff --git a/middleware/autopath/README.md b/middleware/autopath/README.md deleted file mode 100644 index 582b11f4e..000000000 --- a/middleware/autopath/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# autopath - -The *autopath* middleware allows CoreDNS to perform server side search path completion. -If it sees a query that matches the first element of the configured search path, *autopath* will -follow the chain of search path elements and returns the first reply that is not NXDOMAIN. -On any failures the original reply is returned. - -Because *autopath* returns a reply for a name that wasn't the original question it will add a CNAME -that points from the original name (with the search path element in it) to the name of this answer. - -## Syntax - -~~~ -autopath [ZONE..] RESOLV-CONF -~~~ - -* **ZONES** zones *autopath* should be authoritative for. -* **RESOLV-CONF** points to a `resolv.conf` like file or uses a special syntax to point to another - middleware. For instance `@kubernetes`, will call out to the kubernetes middleware (for each - query) to retrieve the search list it should use. - -Currently the following set of middleware has implemented *autopath*: - -* *kubernetes* -* *erratic* - -## Examples - -~~~ -autopath my-resolv.conf -~~~ - -Use `my-resolv.conf` as the file to get the search path from. This file only needs so have one line: -`search domain1 domain2 ...` - -~~~ -autopath @kubernetes -~~~ - -Use the search path dynamically retrieved from the kubernetes middleware. - -## Bugs - -When the *cache* middleware is enabled it is possible for pods in different namespaces to get the -same answer. diff --git a/middleware/autopath/autopath.go b/middleware/autopath/autopath.go deleted file mode 100644 index 94db8bc56..000000000 --- a/middleware/autopath/autopath.go +++ /dev/null @@ -1,152 +0,0 @@ -/* -Package autopath implements autopathing. This is a hack; it shortcuts the -client's search path resolution by performing these lookups on the server... - -The server has a copy (via AutoPathFunc) of the client's search path and on -receiving a query it first establish if the suffix matches the FIRST configured -element. If no match can be found the query will be forwarded up the middleware -chain without interference (iff 'fallthrough' has been set). - -If the query is deemed to fall in the search path the server will perform the -queries with each element of the search path appended in sequence until a -non-NXDOMAIN answer has been found. That reply will then be returned to the -client - with some CNAME hackery to let the client accept the reply. - -If all queries return NXDOMAIN we return the original as-is and let the client -continue searching. The client will go to the next element in the search path, -but we won’t do any more autopathing. It means that in the failure case, you do -more work, since the server looks it up, then the client still needs to go -through the search path. - -It is assume the search path ordering is identical between server and client. - -Midldeware implementing autopath, must have a function called `AutoPath` of type -autopath.Func. Note the searchpath must be ending with the empty string. - -I.e: - -func (m Middleware ) AutoPath(state request.Request) []string { - return []string{"first", "second", "last", ""} -} -*/ -package autopath - -import ( - "log" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/pkg/nonwriter" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Func defines the function middleware should implement to return a search -// path to the autopath middleware. The last element of the slice must be the empty string. -// If Func returns a nil slice, no autopathing will be done. -type Func func(request.Request) []string - -// AutoPath perform autopath: service side search path completion. -type AutoPath struct { - Next middleware.Handler - Zones []string - - // Search always includes "" as the last element, so we try the base query with out any search paths added as well. - search []string - searchFunc Func -} - -// ServeDNS implements the middleware.Handle interface. -func (a *AutoPath) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - - zone := middleware.Zones(a.Zones).Matches(state.Name()) - if zone == "" { - return middleware.NextOrFailure(a.Name(), a.Next, ctx, w, r) - } - - // Check if autopath should be done, searchFunc takes precedence over the local configured search path. - var err error - searchpath := a.search - - if a.searchFunc != nil { - searchpath = a.searchFunc(state) - } - - if len(searchpath) == 0 { - log.Printf("[WARNING] No search path available for autopath") - return middleware.NextOrFailure(a.Name(), a.Next, ctx, w, r) - } - - if !firstInSearchPath(state.Name(), searchpath) { - return middleware.NextOrFailure(a.Name(), a.Next, ctx, w, r) - } - - origQName := state.QName() - - // Establish base name of the query. I.e what was originally asked. - base, err := dnsutil.TrimZone(state.QName(), searchpath[0]) // TODO(miek): we loose the original case of the query here. - if err != nil { - return dns.RcodeServerFailure, err - } - - firstReply := new(dns.Msg) - firstRcode := 0 - var firstErr error - - ar := r.Copy() - // Walk the search path and see if we can get a non-nxdomain - if they all fail we return the first - // query we've done and return that as-is. This means the client will do the search path walk again... - for i, s := range searchpath { - newQName := base + "." + s - ar.Question[0].Name = newQName - nw := nonwriter.New(w) - - rcode, err := middleware.NextOrFailure(a.Name(), a.Next, ctx, nw, ar) - if err != nil { - // Return now - not sure if this is the best. We should also check if the write has happened. - return rcode, err - } - if i == 0 { - firstReply = nw.Msg - firstRcode = rcode - firstErr = err - } - - if !middleware.ClientWrite(rcode) { - continue - } - - if nw.Msg.Rcode == dns.RcodeNameError { - continue - } - - msg := nw.Msg - cnamer(msg, origQName) - - // Write whatever non-nxdomain answer we've found. - w.WriteMsg(msg) - return rcode, err - - } - if middleware.ClientWrite(firstRcode) { - w.WriteMsg(firstReply) - } - return firstRcode, firstErr -} - -// Name implements the Handler interface. -func (a *AutoPath) Name() string { return "autopath" } - -// firstInSearchPath checks if name is equal to are a sibling of the first element in the search path. -func firstInSearchPath(name string, searchpath []string) bool { - if name == searchpath[0] { - return true - } - if dns.IsSubDomain(searchpath[0], name) { - return true - } - return false -} diff --git a/middleware/autopath/autopath_test.go b/middleware/autopath/autopath_test.go deleted file mode 100644 index b99744ab8..000000000 --- a/middleware/autopath/autopath_test.go +++ /dev/null @@ -1,166 +0,0 @@ -package autopath - -import ( - "testing" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var autopathTestCases = []test.Case{ - { - // search path expansion. - Qname: "b.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.CNAME("b.example.org. 3600 IN CNAME b.com."), - test.A("b.com." + defaultA), - }, - }, - { - // No search path expansion - Qname: "a.example.com.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("a.example.com." + defaultA), - }, - }, -} - -func newTestAutoPath() *AutoPath { - ap := new(AutoPath) - ap.Zones = []string{"."} - ap.Next = nextHandler(map[string]int{ - "b.example.org.": dns.RcodeNameError, - "b.com.": dns.RcodeSuccess, - "a.example.com.": dns.RcodeSuccess, - }) - - ap.search = []string{"example.org.", "example.com.", "com.", ""} - return ap -} - -func TestAutoPath(t *testing.T) { - ap := newTestAutoPath() - ctx := context.TODO() - - for _, tc := range autopathTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := ap.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - continue - } - - // No sorting here as we want to check if the CNAME sits *before* the - // test of the answer. - resp := rec.Msg - - if !test.Header(t, tc, resp) { - t.Logf("%v\n", resp) - continue - } - if !test.Section(t, tc, test.Answer, resp.Answer) { - t.Logf("%v\n", resp) - } - if !test.Section(t, tc, test.Ns, resp.Ns) { - t.Logf("%v\n", resp) - } - if !test.Section(t, tc, test.Extra, resp.Extra) { - t.Logf("%v\n", resp) - } - } -} - -var autopathNoAnswerTestCases = []test.Case{ - { - // search path expansion, no answer - Qname: "c.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.CNAME("b.example.org. 3600 IN CNAME b.com."), - test.A("b.com." + defaultA), - }, - }, -} - -func TestAutoPathNoAnswer(t *testing.T) { - ap := newTestAutoPath() - ctx := context.TODO() - - for _, tc := range autopathNoAnswerTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - rcode, err := ap.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - continue - } - if middleware.ClientWrite(rcode) { - t.Fatalf("expected no client write, got one for rcode %d", rcode) - } - } -} - -// nextHandler returns a Handler that returns an answer for the question in the -// request per the domain->answer map. On success an RR will be returned: "qname 3600 IN A 127.0.0.53" -func nextHandler(mm map[string]int) test.Handler { - return test.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - rcode, ok := mm[r.Question[0].Name] - if !ok { - return dns.RcodeServerFailure, nil - } - - m := new(dns.Msg) - m.SetReply(r) - - switch rcode { - case dns.RcodeNameError: - m.Rcode = rcode - m.Ns = []dns.RR{soa} - w.WriteMsg(m) - return m.Rcode, nil - - case dns.RcodeSuccess: - m.Rcode = rcode - a, _ := dns.NewRR(r.Question[0].Name + defaultA) - m.Answer = []dns.RR{a} - - w.WriteMsg(m) - return m.Rcode, nil - default: - panic("nextHandler: unhandled rcode") - } - }) -} - -const defaultA = " 3600 IN A 127.0.0.53" - -var soa = func() dns.RR { - s, _ := dns.NewRR("example.org. 1800 IN SOA example.org. example.org. 1502165581 14400 3600 604800 14400") - return s -}() - -func TestInSearchPath(t *testing.T) { - a := AutoPath{search: []string{"default.svc.cluster.local.", "svc.cluster.local.", "cluster.local."}} - - tests := []struct { - qname string - b bool - }{ - {"google.com", false}, - {"default.svc.cluster.local.", true}, - {"a.default.svc.cluster.local.", true}, - {"a.b.svc.cluster.local.", false}, - } - for i, tc := range tests { - got := firstInSearchPath(tc.qname, a.search) - if got != tc.b { - t.Errorf("Test %d, got %v, expected %v", i, got, tc.b) - } - } -} diff --git a/middleware/autopath/cname.go b/middleware/autopath/cname.go deleted file mode 100644 index 3b2c60f4e..000000000 --- a/middleware/autopath/cname.go +++ /dev/null @@ -1,25 +0,0 @@ -package autopath - -import ( - "strings" - - "github.com/miekg/dns" -) - -// cnamer will prefix the answer section with a cname that points from original qname to the -// name of the first RR. It will also update the question section and put original in there. -func cnamer(m *dns.Msg, original string) { - for _, a := range m.Answer { - if strings.EqualFold(original, a.Header().Name) { - continue - } - m.Answer = append(m.Answer, nil) - copy(m.Answer[1:], m.Answer) - m.Answer[0] = &dns.CNAME{ - Hdr: dns.RR_Header{Name: original, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: a.Header().Ttl}, - Target: a.Header().Name, - } - break - } - m.Question[0].Name = original -} diff --git a/middleware/autopath/setup.go b/middleware/autopath/setup.go deleted file mode 100644 index 368d92cf4..000000000 --- a/middleware/autopath/setup.go +++ /dev/null @@ -1,93 +0,0 @@ -package autopath - -import ( - "fmt" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/erratic" - "github.com/coredns/coredns/middleware/kubernetes" - - "github.com/mholt/caddy" - "github.com/miekg/dns" -) - -func init() { - caddy.RegisterPlugin("autopath", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) - -} - -func setup(c *caddy.Controller) error { - ap, mw, err := autoPathParse(c) - if err != nil { - return middleware.Error("autopath", err) - } - - // Do this in OnStartup, so all middleware has been initialized. - c.OnStartup(func() error { - m := dnsserver.GetConfig(c).Handler(mw) - if m == nil { - return nil - } - if x, ok := m.(*kubernetes.Kubernetes); ok { - ap.searchFunc = x.AutoPath - } - if x, ok := m.(*erratic.Erratic); ok { - ap.searchFunc = x.AutoPath - } - return nil - }) - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - ap.Next = next - return ap - }) - - return nil -} - -// allowedMiddleware has a list of middleware that can be used by autopath. -var allowedMiddleware = map[string]bool{ - "@kubernetes": true, - "@erratic": true, -} - -func autoPathParse(c *caddy.Controller) (*AutoPath, string, error) { - ap := &AutoPath{} - mw := "" - - for c.Next() { - zoneAndresolv := c.RemainingArgs() - if len(zoneAndresolv) < 1 { - return ap, "", fmt.Errorf("no resolv-conf specified") - } - resolv := zoneAndresolv[len(zoneAndresolv)-1] - if resolv[0] == '@' { - _, ok := allowedMiddleware[resolv] - if ok { - mw = resolv[1:] - } - } else { - // assume file on disk - rc, err := dns.ClientConfigFromFile(resolv) - if err != nil { - return ap, "", fmt.Errorf("failed to parse %q: %v", resolv, err) - } - ap.search = rc.Search - middleware.Zones(ap.search).Normalize() - ap.search = append(ap.search, "") // sentinal value as demanded. - } - ap.Zones = zoneAndresolv[:len(zoneAndresolv)-1] - if len(ap.Zones) == 0 { - ap.Zones = make([]string, len(c.ServerBlockKeys)) - copy(ap.Zones, c.ServerBlockKeys) - } - for i, str := range ap.Zones { - ap.Zones[i] = middleware.Host(str).Normalize() - } - } - return ap, mw, nil -} diff --git a/middleware/autopath/setup_test.go b/middleware/autopath/setup_test.go deleted file mode 100644 index 0f086c5bb..000000000 --- a/middleware/autopath/setup_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package autopath - -import ( - "os" - "reflect" - "strings" - "testing" - - "github.com/coredns/coredns/middleware/test" - - "github.com/mholt/caddy" -) - -func TestSetupAutoPath(t *testing.T) { - resolv, rm, err := test.TempFile(os.TempDir(), resolvConf) - if err != nil { - t.Fatalf("Could not create resolv.conf test file %s: %s", resolvConf, err) - } - defer rm() - - tests := []struct { - input string - shouldErr bool - expectedZone string - expectedMw string // expected middleware. - expectedSearch []string // expected search path - expectedErrContent string // substring from the expected error. Empty for positive cases. - }{ - // positive - {`autopath @kubernetes`, false, "", "kubernetes", nil, ""}, - {`autopath example.org @kubernetes`, false, "example.org.", "kubernetes", nil, ""}, - {`autopath 10.0.0.0/8 @kubernetes`, false, "10.in-addr.arpa.", "kubernetes", nil, ""}, - {`autopath ` + resolv, false, "", "", []string{"bar.com.", "baz.com.", ""}, ""}, - // negative - {`autopath kubernetes`, true, "", "", nil, "open kubernetes: no such file or directory"}, - {`autopath`, true, "", "", nil, "no resolv-conf"}, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - ap, mw, err := autoPathParse(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - } - - if !strings.Contains(err.Error(), test.expectedErrContent) { - t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) - } - } - - if !test.shouldErr && mw != test.expectedMw { - t.Errorf("Test %d, Middleware not correctly set for input %s. Expected: %s, actual: %s", i, test.input, test.expectedMw, mw) - } - if !test.shouldErr && ap.search != nil { - if !reflect.DeepEqual(test.expectedSearch, ap.search) { - t.Errorf("Test %d, wrong searchpath for input %s. Expected: '%v', actual: '%v'", i, test.input, test.expectedSearch, ap.search) - } - } - if !test.shouldErr && test.expectedZone != "" { - if test.expectedZone != ap.Zones[0] { - t.Errorf("Test %d, expected zone %q for input %s, got: %q", i, test.expectedZone, test.input, ap.Zones[0]) - } - } - } -} - -const resolvConf = `nameserver 1.2.3.4 -domain foo.com -search bar.com baz.com -options ndots:5 -` diff --git a/middleware/backend.go b/middleware/backend.go deleted file mode 100644 index bf4bf8967..000000000 --- a/middleware/backend.go +++ /dev/null @@ -1,32 +0,0 @@ -package middleware - -import ( - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// ServiceBackend defines a (dynamic) backend that returns a slice of service definitions. -type ServiceBackend interface { - // Services communicates with the backend to retrieve the service definition. Exact indicates - // on exact much are that we are allowed to recurs. - Services(state request.Request, exact bool, opt Options) ([]msg.Service, error) - - // Reverse communicates with the backend to retrieve service definition based on a IP address - // instead of a name. I.e. a reverse DNS lookup. - Reverse(state request.Request, exact bool, opt Options) ([]msg.Service, error) - - // Lookup is used to find records else where. - Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) - - // Returns _all_ services that matches a certain name. - // Note: it does not implement a specific service. - Records(state request.Request, exact bool) ([]msg.Service, error) - - // IsNameError return true if err indicated a record not found condition - IsNameError(err error) bool -} - -// Options are extra options that can be specified for a lookup. -type Options struct{} diff --git a/middleware/backend_lookup.go b/middleware/backend_lookup.go deleted file mode 100644 index 9e451feed..000000000 --- a/middleware/backend_lookup.go +++ /dev/null @@ -1,410 +0,0 @@ -package middleware - -import ( - "fmt" - "math" - "net" - "time" - - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// A returns A records from Backend or an error. -func A(b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, err error) { - services, err := b.Services(state, false, opt) - if err != nil { - return nil, err - } - - for _, serv := range services { - - what, ip := serv.HostType() - - switch what { - case dns.TypeCNAME: - if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { - // x CNAME x is a direct loop, don't add those - continue - } - - newRecord := serv.NewCNAME(state.QName(), serv.Host) - if len(previousRecords) > 7 { - // don't add it, and just continue - continue - } - if dnsutil.DuplicateCNAME(newRecord, previousRecords) { - continue - } - - state1 := state.NewWithQuestion(serv.Host, state.QType()) - nextRecords, err := A(b, zone, state1, append(previousRecords, newRecord), opt) - - if err == nil { - // Not only have we found something we should add the CNAME and the IP addresses. - if len(nextRecords) > 0 { - records = append(records, newRecord) - records = append(records, nextRecords...) - } - continue - } - // This means we can not complete the CNAME, try to look else where. - target := newRecord.Target - if dns.IsSubDomain(zone, target) { - // We should already have found it - continue - } - // Lookup - m1, e1 := b.Lookup(state, target, state.QType()) - if e1 != nil { - continue - } - // Len(m1.Answer) > 0 here is well? - records = append(records, newRecord) - records = append(records, m1.Answer...) - continue - - case dns.TypeA: - records = append(records, serv.NewA(state.QName(), ip)) - - case dns.TypeAAAA: - // nodata? - } - } - return records, nil -} - -// AAAA returns AAAA records from Backend or an error. -func AAAA(b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, err error) { - services, err := b.Services(state, false, opt) - if err != nil { - return nil, err - } - - for _, serv := range services { - - what, ip := serv.HostType() - - switch what { - case dns.TypeCNAME: - // Try to resolve as CNAME if it's not an IP, but only if we don't create loops. - if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { - // x CNAME x is a direct loop, don't add those - continue - } - - newRecord := serv.NewCNAME(state.QName(), serv.Host) - if len(previousRecords) > 7 { - // don't add it, and just continue - continue - } - if dnsutil.DuplicateCNAME(newRecord, previousRecords) { - continue - } - - state1 := state.NewWithQuestion(serv.Host, state.QType()) - nextRecords, err := AAAA(b, zone, state1, append(previousRecords, newRecord), opt) - - if err == nil { - // Not only have we found something we should add the CNAME and the IP addresses. - if len(nextRecords) > 0 { - records = append(records, newRecord) - records = append(records, nextRecords...) - } - continue - } - // This means we can not complete the CNAME, try to look else where. - target := newRecord.Target - if dns.IsSubDomain(zone, target) { - // We should already have found it - continue - } - m1, e1 := b.Lookup(state, target, state.QType()) - if e1 != nil { - continue - } - // Len(m1.Answer) > 0 here is well? - records = append(records, newRecord) - records = append(records, m1.Answer...) - continue - // both here again - - case dns.TypeA: - // nada? - - case dns.TypeAAAA: - records = append(records, serv.NewAAAA(state.QName(), ip)) - } - } - return records, nil -} - -// SRV returns SRV records from the Backend. -// If the Target is not a name but an IP address, a name is created on the fly. -func SRV(b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, err error) { - services, err := b.Services(state, false, opt) - if err != nil { - return nil, nil, err - } - - // Looping twice to get the right weight vs priority - w := make(map[int]int) - for _, serv := range services { - weight := 100 - if serv.Weight != 0 { - weight = serv.Weight - } - if _, ok := w[serv.Priority]; !ok { - w[serv.Priority] = weight - continue - } - w[serv.Priority] += weight - } - lookup := make(map[string]bool) - for _, serv := range services { - w1 := 100.0 / float64(w[serv.Priority]) - if serv.Weight == 0 { - w1 *= 100 - } else { - w1 *= float64(serv.Weight) - } - weight := uint16(math.Floor(w1)) - - what, ip := serv.HostType() - - switch what { - case dns.TypeCNAME: - srv := serv.NewSRV(state.QName(), weight) - records = append(records, srv) - - if _, ok := lookup[srv.Target]; ok { - break - } - - lookup[srv.Target] = true - - if !dns.IsSubDomain(zone, srv.Target) { - m1, e1 := b.Lookup(state, srv.Target, dns.TypeA) - if e1 == nil { - extra = append(extra, m1.Answer...) - } - - m1, e1 = b.Lookup(state, srv.Target, dns.TypeAAAA) - if e1 == nil { - // If we have seen CNAME's we *assume* that they are already added. - for _, a := range m1.Answer { - if _, ok := a.(*dns.CNAME); !ok { - extra = append(extra, a) - } - } - } - break - } - // Internal name, we should have some info on them, either v4 or v6 - // Clients expect a complete answer, because we are a recursor in their view. - state1 := state.NewWithQuestion(srv.Target, dns.TypeA) - addr, e1 := A(b, zone, state1, nil, opt) - if e1 == nil { - extra = append(extra, addr...) - } - // IPv6 lookups here as well? AAAA(zone, state1, nil). - - case dns.TypeA, dns.TypeAAAA: - serv.Host = msg.Domain(serv.Key) - srv := serv.NewSRV(state.QName(), weight) - - records = append(records, srv) - extra = append(extra, newAddress(serv, srv.Target, ip, what)) - } - } - return records, extra, nil -} - -// MX returns MX records from the Backend. If the Target is not a name but an IP address, a name is created on the fly. -func MX(b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, err error) { - services, err := b.Services(state, false, opt) - if err != nil { - return nil, nil, err - } - - lookup := make(map[string]bool) - for _, serv := range services { - if !serv.Mail { - continue - } - what, ip := serv.HostType() - switch what { - case dns.TypeCNAME: - mx := serv.NewMX(state.QName()) - records = append(records, mx) - if _, ok := lookup[mx.Mx]; ok { - break - } - - lookup[mx.Mx] = true - - if !dns.IsSubDomain(zone, mx.Mx) { - m1, e1 := b.Lookup(state, mx.Mx, dns.TypeA) - if e1 == nil { - extra = append(extra, m1.Answer...) - } - - m1, e1 = b.Lookup(state, mx.Mx, dns.TypeAAAA) - if e1 == nil { - // If we have seen CNAME's we *assume* that they are already added. - for _, a := range m1.Answer { - if _, ok := a.(*dns.CNAME); !ok { - extra = append(extra, a) - } - } - } - break - } - // Internal name - state1 := state.NewWithQuestion(mx.Mx, dns.TypeA) - addr, e1 := A(b, zone, state1, nil, opt) - if e1 == nil { - extra = append(extra, addr...) - } - // e.AAAA as well - - case dns.TypeA, dns.TypeAAAA: - serv.Host = msg.Domain(serv.Key) - records = append(records, serv.NewMX(state.QName())) - extra = append(extra, newAddress(serv, serv.Host, ip, what)) - } - } - return records, extra, nil -} - -// CNAME returns CNAME records from the backend or an error. -func CNAME(b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, err error) { - services, err := b.Services(state, true, opt) - if err != nil { - return nil, err - } - - if len(services) > 0 { - serv := services[0] - if ip := net.ParseIP(serv.Host); ip == nil { - records = append(records, serv.NewCNAME(state.QName(), serv.Host)) - } - } - return records, nil -} - -// TXT returns TXT records from Backend or an error. -func TXT(b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, err error) { - services, err := b.Services(state, false, opt) - if err != nil { - return nil, err - } - - for _, serv := range services { - if serv.Text == "" { - continue - } - records = append(records, serv.NewTXT(state.QName())) - } - return records, nil -} - -// PTR returns the PTR records from the backend, only services that have a domain name as host are included. -func PTR(b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, err error) { - services, err := b.Reverse(state, true, opt) - if err != nil { - return nil, err - } - - for _, serv := range services { - if ip := net.ParseIP(serv.Host); ip == nil { - records = append(records, serv.NewPTR(state.QName(), serv.Host)) - } - } - return records, nil -} - -// NS returns NS records from the backend -func NS(b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, err error) { - // NS record for this zone live in a special place, ns.dns.<zone>. Fake our lookup. - // only a tad bit fishy... - old := state.QName() - - state.Clear() - state.Req.Question[0].Name = "ns.dns." + zone - services, err := b.Services(state, false, opt) - if err != nil { - return nil, nil, err - } - // ... and reset - state.Req.Question[0].Name = old - - for _, serv := range services { - what, ip := serv.HostType() - switch what { - case dns.TypeCNAME: - return nil, nil, fmt.Errorf("NS record must be an IP address: %s", serv.Host) - - case dns.TypeA, dns.TypeAAAA: - serv.Host = msg.Domain(serv.Key) - records = append(records, serv.NewNS(state.QName())) - extra = append(extra, newAddress(serv, serv.Host, ip, what)) - } - } - return records, extra, nil -} - -// SOA returns a SOA record from the backend. -func SOA(b ServiceBackend, zone string, state request.Request, opt Options) ([]dns.RR, error) { - header := dns.RR_Header{Name: zone, Rrtype: dns.TypeSOA, Ttl: 300, Class: dns.ClassINET} - - Mbox := hostmaster + "." - Ns := "ns.dns." - if zone[0] != '.' { - Mbox += zone - Ns += zone - } - - soa := &dns.SOA{Hdr: header, - Mbox: Mbox, - Ns: Ns, - Serial: uint32(time.Now().Unix()), - Refresh: 7200, - Retry: 1800, - Expire: 86400, - Minttl: minTTL, - } - return []dns.RR{soa}, nil -} - -// BackendError writes an error response to the client. -func BackendError(b ServiceBackend, zone string, rcode int, state request.Request, err error, opt Options) (int, error) { - m := new(dns.Msg) - m.SetRcode(state.Req, rcode) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - m.Ns, _ = SOA(b, zone, state, opt) - - state.SizeAndDo(m) - state.W.WriteMsg(m) - // Return success as the rcode to signal we have written to the client. - return dns.RcodeSuccess, err -} - -func newAddress(s msg.Service, name string, ip net.IP, what uint16) dns.RR { - - hdr := dns.RR_Header{Name: name, Rrtype: what, Class: dns.ClassINET, Ttl: s.TTL} - - if what == dns.TypeA { - return &dns.A{Hdr: hdr, A: ip} - } - // Should always be dns.TypeAAAA - return &dns.AAAA{Hdr: hdr, AAAA: ip} -} - -const ( - minTTL = 60 - hostmaster = "hostmaster" -) diff --git a/middleware/bind/README.md b/middleware/bind/README.md deleted file mode 100644 index 57b3c1e18..000000000 --- a/middleware/bind/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# bind - -*bind* overrides the host to which the server should bind. - -Normally, the listener binds to the wildcard host. However, you may force the listener to bind to -another IP instead. This directive accepts only an address, not a port. - -## Syntax - -~~~ txt -bind ADDRESS -~~~ - -**ADDRESS** is the IP address to bind to. - -## Examples - -To make your socket accessible only to that machine, bind to IP 127.0.0.1 (localhost): - -~~~ txt -bind 127.0.0.1 -~~~ diff --git a/middleware/bind/bind.go b/middleware/bind/bind.go deleted file mode 100644 index bd3c32b51..000000000 --- a/middleware/bind/bind.go +++ /dev/null @@ -1,11 +0,0 @@ -// Package bind allows binding to a specific interface instead of bind to all of them. -package bind - -import "github.com/mholt/caddy" - -func init() { - caddy.RegisterPlugin("bind", caddy.Plugin{ - ServerType: "dns", - Action: setupBind, - }) -} diff --git a/middleware/bind/bind_test.go b/middleware/bind/bind_test.go deleted file mode 100644 index 11556f0bd..000000000 --- a/middleware/bind/bind_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package bind - -import ( - "testing" - - "github.com/coredns/coredns/core/dnsserver" - - "github.com/mholt/caddy" -) - -func TestSetupBind(t *testing.T) { - c := caddy.NewTestController("dns", `bind 1.2.3.4`) - err := setupBind(c) - if err != nil { - t.Fatalf("Expected no errors, but got: %v", err) - } - - cfg := dnsserver.GetConfig(c) - if got, want := cfg.ListenHost, "1.2.3.4"; got != want { - t.Errorf("Expected the config's ListenHost to be %s, was %s", want, got) - } -} - -func TestBindAddress(t *testing.T) { - c := caddy.NewTestController("dns", `bind 1.2.3.bla`) - err := setupBind(c) - if err == nil { - t.Fatalf("Expected errors, but got none") - } -} diff --git a/middleware/bind/setup.go b/middleware/bind/setup.go deleted file mode 100644 index 1e7178cc3..000000000 --- a/middleware/bind/setup.go +++ /dev/null @@ -1,24 +0,0 @@ -package bind - -import ( - "fmt" - "net" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func setupBind(c *caddy.Controller) error { - config := dnsserver.GetConfig(c) - for c.Next() { - if !c.Args(&config.ListenHost) { - return middleware.Error("bind", c.ArgErr()) - } - } - if net.ParseIP(config.ListenHost) == nil { - return middleware.Error("bind", fmt.Errorf("not a valid IP address: %s", config.ListenHost)) - } - return nil -} diff --git a/middleware/cache/README.md b/middleware/cache/README.md deleted file mode 100644 index 6477fe891..000000000 --- a/middleware/cache/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# cache - -*cache* enables a frontend cache. It will cache all records except zone transfers and metadata records. - -## Syntax - -~~~ txt -cache [TTL] [ZONES...] -~~~ - -* **TTL** max TTL in seconds. If not specified, the maximum TTL will be used which is 3600 for - noerror responses and 1800 for denial of existence ones. - Setting a TTL of 300 *cache 300* would cache the record up to 300 seconds. -* **ZONES** zones it should cache for. If empty, the zones from the configuration block are used. - -Each element in the cache is cached according to its TTL (with **TTL** as the max). -For the negative cache, the SOA's MinTTL value is used. A cache can contain up to 10,000 items by -default. A TTL of zero is not allowed. - -If you want more control: - -~~~ txt -cache [TTL] [ZONES...] { - success CAPACITY [TTL] - denial CAPACITY [TTL] - prefetch AMOUNT [[DURATION] [PERCENTAGE%]] -} -~~~ - -* **TTL** and **ZONES** as above. -* `success`, override the settings for caching successful responses, **CAPACITY** indicates the maximum - number of packets we cache before we start evicting (*randomly*). **TTL** overrides the cache maximum TTL. -* `denial`, override the settings for caching denial of existence responses, **CAPACITY** indicates the maximum - number of packets we cache before we start evicting (LRU). **TTL** overrides the cache maximum TTL. - There is a third category (`error`) but those responses are never cached. -* `prefetch`, will prefetch popular items when they are about to be expunged from the cache. - Popular means **AMOUNT** queries have been seen no gaps of **DURATION** or more between them. - **DURATION** defaults to 1m. Prefetching will happen when the TTL drops below **PERCENTAGE**, - which defaults to `10%`. Values should be in the range `[10%, 90%]`. Note the percent sign is - mandatory. **PERCENTAGE** is treated as an `int`. - -The minimum TTL allowed on resource records is 5 seconds. - -## Metrics - -If monitoring is enabled (via the *prometheus* directive) then the following metrics are exported: - -* coredns_cache_size{type} - Total elements in the cache by cache type. -* coredns_cache_capacity{type} - Total capacity of the cache by cache type. -* coredns_cache_hits_total{type} - Counter of cache hits by cache type. -* coredns_cache_misses_total - Counter of cache misses. - -Cache types are either "denial" or "success". - -## Examples - -Enable caching for all zones, but cap everything to a TTL of 10 seconds: - -~~~ -cache 10 -~~~ - -Proxy to Google Public DNS and only cache responses for example.org (or below). - -~~~ -proxy . 8.8.8.8:53 -cache example.org -~~~ diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go deleted file mode 100644 index 434efa296..000000000 --- a/middleware/cache/cache.go +++ /dev/null @@ -1,167 +0,0 @@ -// Package cache implements a cache. -package cache - -import ( - "encoding/binary" - "hash/fnv" - "log" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/cache" - "github.com/coredns/coredns/middleware/pkg/response" - - "github.com/miekg/dns" -) - -// Cache is middleware that looks up responses in a cache and caches replies. -// It has a success and a denial of existence cache. -type Cache struct { - Next middleware.Handler - Zones []string - - ncache *cache.Cache - ncap int - nttl time.Duration - - pcache *cache.Cache - pcap int - pttl time.Duration - - // Prefetch. - prefetch int - duration time.Duration - percentage int -} - -// Return key under which we store the item, -1 will be returned if we don't store the -// message. -// Currently we do not cache Truncated, errors zone transfers or dynamic update messages. -func key(m *dns.Msg, t response.Type, do bool) int { - // We don't store truncated responses. - if m.Truncated { - return -1 - } - // Nor errors or Meta or Update - if t == response.OtherError || t == response.Meta || t == response.Update { - return -1 - } - - return int(hash(m.Question[0].Name, m.Question[0].Qtype, do)) -} - -var one = []byte("1") -var zero = []byte("0") - -func hash(qname string, qtype uint16, do bool) uint32 { - h := fnv.New32() - - if do { - h.Write(one) - } else { - h.Write(zero) - } - - b := make([]byte, 2) - binary.BigEndian.PutUint16(b, qtype) - h.Write(b) - - for i := range qname { - c := qname[i] - if c >= 'A' && c <= 'Z' { - c += 'a' - 'A' - } - h.Write([]byte{c}) - } - - return h.Sum32() -} - -// ResponseWriter is a response writer that caches the reply message. -type ResponseWriter struct { - dns.ResponseWriter - *Cache - - prefetch bool // When true write nothing back to the client. -} - -// WriteMsg implements the dns.ResponseWriter interface. -func (w *ResponseWriter) WriteMsg(res *dns.Msg) error { - do := false - mt, opt := response.Typify(res, time.Now().UTC()) - if opt != nil { - do = opt.Do() - } - - // key returns empty string for anything we don't want to cache. - key := key(res, mt, do) - - duration := w.pttl - if mt == response.NameError || mt == response.NoData { - duration = w.nttl - } - - msgTTL := minMsgTTL(res, mt) - if msgTTL < duration { - duration = msgTTL - } - - if key != -1 { - w.set(res, key, mt, duration) - - cacheSize.WithLabelValues(Success).Set(float64(w.pcache.Len())) - cacheSize.WithLabelValues(Denial).Set(float64(w.ncache.Len())) - } - - if w.prefetch { - return nil - } - - return w.ResponseWriter.WriteMsg(res) -} - -func (w *ResponseWriter) set(m *dns.Msg, key int, mt response.Type, duration time.Duration) { - if key == -1 { - log.Printf("[ERROR] Caching called with empty cache key") - return - } - - switch mt { - case response.NoError, response.Delegation: - i := newItem(m, duration) - w.pcache.Add(uint32(key), i) - - case response.NameError, response.NoData: - i := newItem(m, duration) - w.ncache.Add(uint32(key), i) - - case response.OtherError: - // don't cache these - default: - log.Printf("[WARNING] Caching called with unknown classification: %d", mt) - } -} - -// Write implements the dns.ResponseWriter interface. -func (w *ResponseWriter) Write(buf []byte) (int, error) { - log.Printf("[WARNING] Caching called with Write: not caching reply") - if w.prefetch { - return 0, nil - } - n, err := w.ResponseWriter.Write(buf) - return n, err -} - -const ( - maxTTL = 1 * time.Hour - maxNTTL = 30 * time.Minute - - minTTL = 5 // seconds - - defaultCap = 10000 // default capacity of the cache. - - // Success is the class for caching positive caching. - Success = "success" - // Denial is the class defined for negative caching. - Denial = "denial" -) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go deleted file mode 100644 index f364e69f1..000000000 --- a/middleware/cache/cache_test.go +++ /dev/null @@ -1,251 +0,0 @@ -package cache - -import ( - "io/ioutil" - "log" - "testing" - "time" - - "golang.org/x/net/context" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/cache" - "github.com/coredns/coredns/middleware/pkg/response" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" -) - -type cacheTestCase struct { - test.Case - in test.Case - AuthenticatedData bool - Authoritative bool - RecursionAvailable bool - Truncated bool - shouldCache bool -} - -var cacheTestCases = []cacheTestCase{ - { - RecursionAvailable: true, AuthenticatedData: true, Authoritative: true, - Case: test.Case{ - Qname: "miek.nl.", Qtype: dns.TypeMX, - Answer: []dns.RR{ - test.MX("miek.nl. 3600 IN MX 1 aspmx.l.google.com."), - test.MX("miek.nl. 3600 IN MX 10 aspmx2.googlemail.com."), - }, - }, - in: test.Case{ - Qname: "miek.nl.", Qtype: dns.TypeMX, - Answer: []dns.RR{ - test.MX("miek.nl. 3601 IN MX 1 aspmx.l.google.com."), - test.MX("miek.nl. 3601 IN MX 10 aspmx2.googlemail.com."), - }, - }, - shouldCache: true, - }, - { - RecursionAvailable: true, AuthenticatedData: true, Authoritative: true, - Case: test.Case{ - Qname: "mIEK.nL.", Qtype: dns.TypeMX, - Answer: []dns.RR{ - test.MX("mIEK.nL. 3600 IN MX 1 aspmx.l.google.com."), - test.MX("mIEK.nL. 3600 IN MX 10 aspmx2.googlemail.com."), - }, - }, - in: test.Case{ - Qname: "mIEK.nL.", Qtype: dns.TypeMX, - Answer: []dns.RR{ - test.MX("mIEK.nL. 3601 IN MX 1 aspmx.l.google.com."), - test.MX("mIEK.nL. 3601 IN MX 10 aspmx2.googlemail.com."), - }, - }, - shouldCache: true, - }, - { - Truncated: true, - Case: test.Case{ - Qname: "miek.nl.", Qtype: dns.TypeMX, - Answer: []dns.RR{test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com.")}, - }, - in: test.Case{}, - shouldCache: false, - }, - { - RecursionAvailable: true, Authoritative: true, - Case: test.Case{ - Rcode: dns.RcodeNameError, - Qname: "example.org.", Qtype: dns.TypeA, - Ns: []dns.RR{ - test.SOA("example.org. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082540 7200 3600 1209600 3600"), - }, - }, - in: test.Case{ - Rcode: dns.RcodeNameError, - Qname: "example.org.", Qtype: dns.TypeA, - Ns: []dns.RR{ - test.SOA("example.org. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082540 7200 3600 1209600 3600"), - }, - }, - shouldCache: true, - }, - { - RecursionAvailable: true, Authoritative: true, - Case: test.Case{ - Qname: "miek.nl.", Qtype: dns.TypeMX, - Do: true, - Answer: []dns.RR{ - test.MX("miek.nl. 3600 IN MX 1 aspmx.l.google.com."), - test.MX("miek.nl. 3600 IN MX 10 aspmx2.googlemail.com."), - test.RRSIG("miek.nl. 3600 IN RRSIG MX 8 2 1800 20160521031301 20160421031301 12051 miek.nl. lAaEzB5teQLLKyDenatmyhca7blLRg9DoGNrhe3NReBZN5C5/pMQk8Jc u25hv2fW23/SLm5IC2zaDpp2Fzgm6Jf7e90/yLcwQPuE7JjS55WMF+HE LEh7Z6AEb+Iq4BWmNhUz6gPxD4d9eRMs7EAzk13o1NYi5/JhfL6IlaYy qkc="), - }, - }, - in: test.Case{ - Qname: "miek.nl.", Qtype: dns.TypeMX, - Do: true, - Answer: []dns.RR{ - test.MX("miek.nl. 3600 IN MX 1 aspmx.l.google.com."), - test.MX("miek.nl. 3600 IN MX 10 aspmx2.googlemail.com."), - test.RRSIG("miek.nl. 1800 IN RRSIG MX 8 2 1800 20160521031301 20160421031301 12051 miek.nl. lAaEzB5teQLLKyDenatmyhca7blLRg9DoGNrhe3NReBZN5C5/pMQk8Jc u25hv2fW23/SLm5IC2zaDpp2Fzgm6Jf7e90/yLcwQPuE7JjS55WMF+HE LEh7Z6AEb+Iq4BWmNhUz6gPxD4d9eRMs7EAzk13o1NYi5/JhfL6IlaYy qkc="), - }, - }, - shouldCache: false, - }, - { - RecursionAvailable: true, Authoritative: true, - Case: test.Case{ - Qname: "example.org.", Qtype: dns.TypeMX, - Do: true, - Answer: []dns.RR{ - test.MX("example.org. 3600 IN MX 1 aspmx.l.google.com."), - test.MX("example.org. 3600 IN MX 10 aspmx2.googlemail.com."), - test.RRSIG("example.org. 3600 IN RRSIG MX 8 2 1800 20170521031301 20170421031301 12051 miek.nl. lAaEzB5teQLLKyDenatmyhca7blLRg9DoGNrhe3NReBZN5C5/pMQk8Jc u25hv2fW23/SLm5IC2zaDpp2Fzgm6Jf7e90/yLcwQPuE7JjS55WMF+HE LEh7Z6AEb+Iq4BWmNhUz6gPxD4d9eRMs7EAzk13o1NYi5/JhfL6IlaYy qkc="), - }, - }, - in: test.Case{ - Qname: "example.org.", Qtype: dns.TypeMX, - Do: true, - Answer: []dns.RR{ - test.MX("example.org. 3600 IN MX 1 aspmx.l.google.com."), - test.MX("example.org. 3600 IN MX 10 aspmx2.googlemail.com."), - test.RRSIG("example.org. 1800 IN RRSIG MX 8 2 1800 20170521031301 20170421031301 12051 miek.nl. lAaEzB5teQLLKyDenatmyhca7blLRg9DoGNrhe3NReBZN5C5/pMQk8Jc u25hv2fW23/SLm5IC2zaDpp2Fzgm6Jf7e90/yLcwQPuE7JjS55WMF+HE LEh7Z6AEb+Iq4BWmNhUz6gPxD4d9eRMs7EAzk13o1NYi5/JhfL6IlaYy qkc="), - }, - }, - shouldCache: true, - }, -} - -func cacheMsg(m *dns.Msg, tc cacheTestCase) *dns.Msg { - m.RecursionAvailable = tc.RecursionAvailable - m.AuthenticatedData = tc.AuthenticatedData - m.Authoritative = tc.Authoritative - m.Rcode = tc.Rcode - m.Truncated = tc.Truncated - m.Answer = tc.in.Answer - m.Ns = tc.in.Ns - // m.Extra = tc.in.Extra don't copy Extra, because we don't care and fake EDNS0 DO with tc.Do. - return m -} - -func newTestCache(ttl time.Duration) (*Cache, *ResponseWriter) { - c := &Cache{Zones: []string{"."}, pcap: defaultCap, ncap: defaultCap, pttl: ttl, nttl: ttl} - c.pcache = cache.New(c.pcap) - c.ncache = cache.New(c.ncap) - - crr := &ResponseWriter{ResponseWriter: nil, Cache: c} - return c, crr -} - -func TestCache(t *testing.T) { - now, _ := time.Parse(time.UnixDate, "Fri Apr 21 10:51:21 BST 2017") - utc := now.UTC() - - c, crr := newTestCache(maxTTL) - - log.SetOutput(ioutil.Discard) - - for _, tc := range cacheTestCases { - m := tc.in.Msg() - m = cacheMsg(m, tc) - do := tc.in.Do - - mt, _ := response.Typify(m, utc) - k := key(m, mt, do) - - crr.set(m, k, mt, c.pttl) - - name := middleware.Name(m.Question[0].Name).Normalize() - qtype := m.Question[0].Qtype - - i, _ := c.get(time.Now().UTC(), name, qtype, do) - ok := i != nil - - if ok != tc.shouldCache { - t.Errorf("cached message that should not have been cached: %s", name) - continue - } - - if ok { - resp := i.toMsg(m) - - if !test.Header(t, tc.Case, resp) { - t.Logf("%v\n", resp) - continue - } - - if !test.Section(t, tc.Case, test.Answer, resp.Answer) { - t.Logf("%v\n", resp) - } - if !test.Section(t, tc.Case, test.Ns, resp.Ns) { - t.Logf("%v\n", resp) - - } - if !test.Section(t, tc.Case, test.Extra, resp.Extra) { - t.Logf("%v\n", resp) - } - } - } -} - -func BenchmarkCacheResponse(b *testing.B) { - c := &Cache{Zones: []string{"."}, pcap: defaultCap, ncap: defaultCap, pttl: maxTTL, nttl: maxTTL} - c.pcache = cache.New(c.pcap) - c.ncache = cache.New(c.ncap) - c.prefetch = 1 - c.duration = 1 * time.Second - c.Next = BackendHandler() - - ctx := context.TODO() - - reqs := make([]*dns.Msg, 5) - for i, q := range []string{"example1", "example2", "a", "b", "ddd"} { - reqs[i] = new(dns.Msg) - reqs[i].SetQuestion(q+".example.org.", dns.TypeA) - } - - b.RunParallel(func(pb *testing.PB) { - i := 0 - for pb.Next() { - req := reqs[i] - c.ServeDNS(ctx, &test.ResponseWriter{}, req) - i++ - i = i % 5 - } - }) -} - -func BackendHandler() middleware.Handler { - return middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - m := new(dns.Msg) - m.SetReply(r) - m.Response = true - m.RecursionAvailable = true - - owner := m.Question[0].Name - m.Answer = []dns.RR{test.A(owner + " 303 IN A 127.0.0.53")} - - w.WriteMsg(m) - return dns.RcodeSuccess, nil - }) -} diff --git a/middleware/cache/freq/freq.go b/middleware/cache/freq/freq.go deleted file mode 100644 index f545f222e..000000000 --- a/middleware/cache/freq/freq.go +++ /dev/null @@ -1,55 +0,0 @@ -// Package freq keeps track of last X seen events. The events themselves are not stored -// here. So the Freq type should be added next to the thing it is tracking. -package freq - -import ( - "sync" - "time" -) - -// Freq tracks the frequencies of things. -type Freq struct { - // Last time we saw a query for this element. - last time.Time - // Number of this in the last time slice. - hits int - - sync.RWMutex -} - -// New returns a new initialized Freq. -func New(t time.Time) *Freq { - return &Freq{last: t, hits: 0} -} - -// Update updates the number of hits. Last time seen will be set to now. -// If the last time we've seen this entity is within now - d, we increment hits, otherwise -// we reset hits to 1. It returns the number of hits. -func (f *Freq) Update(d time.Duration, now time.Time) int { - earliest := now.Add(-1 * d) - f.Lock() - defer f.Unlock() - if f.last.Before(earliest) { - f.last = now - f.hits = 1 - return f.hits - } - f.last = now - f.hits++ - return f.hits -} - -// Hits returns the number of hits that we have seen, according to the updates we have done to f. -func (f *Freq) Hits() int { - f.RLock() - defer f.RUnlock() - return f.hits -} - -// Reset resets f to time t and hits to hits. -func (f *Freq) Reset(t time.Time, hits int) { - f.Lock() - defer f.Unlock() - f.last = t - f.hits = hits -} diff --git a/middleware/cache/freq/freq_test.go b/middleware/cache/freq/freq_test.go deleted file mode 100644 index 740194c86..000000000 --- a/middleware/cache/freq/freq_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package freq - -import ( - "testing" - "time" -) - -func TestFreqUpdate(t *testing.T) { - now := time.Now().UTC() - f := New(now) - window := 1 * time.Minute - - f.Update(window, time.Now().UTC()) - f.Update(window, time.Now().UTC()) - f.Update(window, time.Now().UTC()) - hitsCheck(t, f, 3) - - f.Reset(now, 0) - history := time.Now().UTC().Add(-3 * time.Minute) - f.Update(window, history) - hitsCheck(t, f, 1) -} - -func TestReset(t *testing.T) { - f := New(time.Now().UTC()) - f.Update(1*time.Minute, time.Now().UTC()) - hitsCheck(t, f, 1) - f.Reset(time.Now().UTC(), 0) - hitsCheck(t, f, 0) -} - -func hitsCheck(t *testing.T, f *Freq, expected int) { - if x := f.Hits(); x != expected { - t.Fatalf("Expected hits to be %d, got %d", expected, x) - } -} diff --git a/middleware/cache/handler.go b/middleware/cache/handler.go deleted file mode 100644 index 90c63d93a..000000000 --- a/middleware/cache/handler.go +++ /dev/null @@ -1,119 +0,0 @@ -package cache - -import ( - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "github.com/prometheus/client_golang/prometheus" - "golang.org/x/net/context" -) - -// ServeDNS implements the middleware.Handler interface. -func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - - qname := state.Name() - qtype := state.QType() - zone := middleware.Zones(c.Zones).Matches(qname) - if zone == "" { - return c.Next.ServeDNS(ctx, w, r) - } - - do := state.Do() // TODO(): might need more from OPT record? Like the actual bufsize? - - now := time.Now().UTC() - - i, ttl := c.get(now, qname, qtype, do) - if i != nil && ttl > 0 { - resp := i.toMsg(r) - - state.SizeAndDo(resp) - resp, _ = state.Scrub(resp) - w.WriteMsg(resp) - - i.Freq.Update(c.duration, now) - - pct := 100 - if i.origTTL != 0 { // you'll never know - pct = int(float64(ttl) / float64(i.origTTL) * 100) - } - - if c.prefetch > 0 && i.Freq.Hits() > c.prefetch && pct < c.percentage { - // When prefetching we loose the item i, and with it the frequency - // that we've gathered sofar. See we copy the frequencies info back - // into the new item that was stored in the cache. - prr := &ResponseWriter{ResponseWriter: w, Cache: c, prefetch: true} - middleware.NextOrFailure(c.Name(), c.Next, ctx, prr, r) - - if i1, _ := c.get(now, qname, qtype, do); i1 != nil { - i1.Freq.Reset(now, i.Freq.Hits()) - } - } - - return dns.RcodeSuccess, nil - } - - crr := &ResponseWriter{ResponseWriter: w, Cache: c} - return middleware.NextOrFailure(c.Name(), c.Next, ctx, crr, r) -} - -// Name implements the Handler interface. -func (c *Cache) Name() string { return "cache" } - -func (c *Cache) get(now time.Time, qname string, qtype uint16, do bool) (*item, int) { - k := hash(qname, qtype, do) - - if i, ok := c.ncache.Get(k); ok { - cacheHits.WithLabelValues(Denial).Inc() - return i.(*item), i.(*item).ttl(now) - } - - if i, ok := c.pcache.Get(k); ok { - cacheHits.WithLabelValues(Success).Inc() - return i.(*item), i.(*item).ttl(now) - } - cacheMisses.Inc() - return nil, 0 -} - -var ( - cacheSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "size", - Help: "The number of elements in the cache.", - }, []string{"type"}) - - cacheCapacity = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "capacity", - Help: "The cache's capacity.", - }, []string{"type"}) - - cacheHits = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "hits_total", - Help: "The count of cache hits.", - }, []string{"type"}) - - cacheMisses = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "misses_total", - Help: "The count of cache misses.", - }) -) - -const subsystem = "cache" - -func init() { - prometheus.MustRegister(cacheSize) - prometheus.MustRegister(cacheCapacity) - prometheus.MustRegister(cacheHits) - prometheus.MustRegister(cacheMisses) -} diff --git a/middleware/cache/item.go b/middleware/cache/item.go deleted file mode 100644 index 02571ac5c..000000000 --- a/middleware/cache/item.go +++ /dev/null @@ -1,116 +0,0 @@ -package cache - -import ( - "time" - - "github.com/coredns/coredns/middleware/cache/freq" - "github.com/coredns/coredns/middleware/pkg/response" - "github.com/miekg/dns" -) - -type item struct { - Rcode int - Authoritative bool - AuthenticatedData bool - RecursionAvailable bool - Answer []dns.RR - Ns []dns.RR - Extra []dns.RR - - origTTL uint32 - stored time.Time - - *freq.Freq -} - -func newItem(m *dns.Msg, d time.Duration) *item { - i := new(item) - i.Rcode = m.Rcode - i.Authoritative = m.Authoritative - i.AuthenticatedData = m.AuthenticatedData - i.RecursionAvailable = m.RecursionAvailable - i.Answer = m.Answer - i.Ns = m.Ns - i.Extra = make([]dns.RR, len(m.Extra)) - // Don't copy OPT record as these are hop-by-hop. - j := 0 - for _, e := range m.Extra { - if e.Header().Rrtype == dns.TypeOPT { - continue - } - i.Extra[j] = e - j++ - } - i.Extra = i.Extra[:j] - - i.origTTL = uint32(d.Seconds()) - i.stored = time.Now().UTC() - - i.Freq = new(freq.Freq) - - return i -} - -// toMsg turns i into a message, it tailors the reply to m. -// The Authoritative bit is always set to 0, because the answer is from the cache. -func (i *item) toMsg(m *dns.Msg) *dns.Msg { - m1 := new(dns.Msg) - m1.SetReply(m) - - m1.Authoritative = false - m1.AuthenticatedData = i.AuthenticatedData - m1.RecursionAvailable = i.RecursionAvailable - m1.Rcode = i.Rcode - m1.Compress = true - - m1.Answer = make([]dns.RR, len(i.Answer)) - m1.Ns = make([]dns.RR, len(i.Ns)) - m1.Extra = make([]dns.RR, len(i.Extra)) - - ttl := uint32(i.ttl(time.Now())) - if ttl < minTTL { - ttl = minTTL - } - - for j, r := range i.Answer { - m1.Answer[j] = dns.Copy(r) - m1.Answer[j].Header().Ttl = ttl - } - for j, r := range i.Ns { - m1.Ns[j] = dns.Copy(r) - m1.Ns[j].Header().Ttl = ttl - } - for j, r := range i.Extra { - m1.Extra[j] = dns.Copy(r) - if m1.Extra[j].Header().Rrtype != dns.TypeOPT { - m1.Extra[j].Header().Ttl = ttl - } - } - return m1 -} - -func (i *item) ttl(now time.Time) int { - ttl := int(i.origTTL) - int(now.UTC().Sub(i.stored).Seconds()) - return ttl -} - -func minMsgTTL(m *dns.Msg, mt response.Type) time.Duration { - if mt != response.NoError && mt != response.NameError && mt != response.NoData { - return 0 - } - - minTTL := maxTTL - for _, r := range append(m.Answer, m.Ns...) { - switch mt { - case response.NameError, response.NoData: - if r.Header().Rrtype == dns.TypeSOA { - return time.Duration(r.(*dns.SOA).Minttl) * time.Second - } - case response.NoError, response.Delegation: - if r.Header().Ttl < uint32(minTTL.Seconds()) { - minTTL = time.Duration(r.Header().Ttl) * time.Second - } - } - } - return minTTL -} diff --git a/middleware/cache/prefech_test.go b/middleware/cache/prefech_test.go deleted file mode 100644 index 69ad5f92a..000000000 --- a/middleware/cache/prefech_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package cache - -import ( - "fmt" - "testing" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/cache" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - - "github.com/coredns/coredns/middleware/test" - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var p = false - -func TestPrefetch(t *testing.T) { - c := &Cache{Zones: []string{"."}, pcap: defaultCap, ncap: defaultCap, pttl: maxTTL, nttl: maxTTL} - c.pcache = cache.New(c.pcap) - c.ncache = cache.New(c.ncap) - c.prefetch = 1 - c.duration = 1 * time.Second - c.Next = PrefetchHandler(t, dns.RcodeSuccess, nil) - - ctx := context.TODO() - - req := new(dns.Msg) - req.SetQuestion("lowttl.example.org.", dns.TypeA) - - rec := dnsrecorder.New(&test.ResponseWriter{}) - - c.ServeDNS(ctx, rec, req) - p = true // prefetch should be true for the 2nd fetch - c.ServeDNS(ctx, rec, req) -} - -func PrefetchHandler(t *testing.T, rcode int, err error) middleware.Handler { - return middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - m := new(dns.Msg) - m.SetQuestion("lowttl.example.org.", dns.TypeA) - m.Response = true - m.RecursionAvailable = true - m.Answer = append(m.Answer, test.A("lowttl.example.org. 80 IN A 127.0.0.53")) - if p != w.(*ResponseWriter).prefetch { - err = fmt.Errorf("cache prefetch not equal to p: got %t, want %t", p, w.(*ResponseWriter).prefetch) - t.Fatal(err) - } - - w.WriteMsg(m) - return rcode, err - }) -} diff --git a/middleware/cache/setup.go b/middleware/cache/setup.go deleted file mode 100644 index 65cfb70d1..000000000 --- a/middleware/cache/setup.go +++ /dev/null @@ -1,170 +0,0 @@ -package cache - -import ( - "fmt" - "strconv" - "time" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/cache" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("cache", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - ca, err := cacheParse(c) - if err != nil { - return middleware.Error("cache", err) - } - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - ca.Next = next - return ca - }) - - // Export the capacity for the metrics. This only happens once, because this is a re-load change only. - cacheCapacity.WithLabelValues(Success).Set(float64(ca.pcap)) - cacheCapacity.WithLabelValues(Denial).Set(float64(ca.ncap)) - - return nil -} - -func cacheParse(c *caddy.Controller) (*Cache, error) { - - ca := &Cache{pcap: defaultCap, ncap: defaultCap, pttl: maxTTL, nttl: maxNTTL, prefetch: 0, duration: 1 * time.Minute} - - for c.Next() { - // cache [ttl] [zones..] - origins := make([]string, len(c.ServerBlockKeys)) - copy(origins, c.ServerBlockKeys) - args := c.RemainingArgs() - - if len(args) > 0 { - // first args may be just a number, then it is the ttl, if not it is a zone - ttl, err := strconv.Atoi(args[0]) - if err == nil { - // Reserve 0 (and smaller for future things) - if ttl <= 0 { - return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", ttl) - } - ca.pttl = time.Duration(ttl) * time.Second - ca.nttl = time.Duration(ttl) * time.Second - args = args[1:] - } - if len(args) > 0 { - copy(origins, args) - } - } - - // Refinements? In an extra block. - for c.NextBlock() { - switch c.Val() { - // first number is cap, second is an new ttl - case Success: - args := c.RemainingArgs() - if len(args) == 0 { - return nil, c.ArgErr() - } - pcap, err := strconv.Atoi(args[0]) - if err != nil { - return nil, err - } - ca.pcap = pcap - if len(args) > 1 { - pttl, err := strconv.Atoi(args[1]) - if err != nil { - return nil, err - } - // Reserve 0 (and smaller for future things) - if pttl <= 0 { - return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", pttl) - } - ca.pttl = time.Duration(pttl) * time.Second - } - case Denial: - args := c.RemainingArgs() - if len(args) == 0 { - return nil, c.ArgErr() - } - ncap, err := strconv.Atoi(args[0]) - if err != nil { - return nil, err - } - ca.ncap = ncap - if len(args) > 1 { - nttl, err := strconv.Atoi(args[1]) - if err != nil { - return nil, err - } - // Reserve 0 (and smaller for future things) - if nttl <= 0 { - return nil, fmt.Errorf("cache TTL can not be zero or negative: %d", nttl) - } - ca.nttl = time.Duration(nttl) * time.Second - } - case "prefetch": - args := c.RemainingArgs() - if len(args) == 0 || len(args) > 3 { - return nil, c.ArgErr() - } - amount, err := strconv.Atoi(args[0]) - if err != nil { - return nil, err - } - if amount < 0 { - return nil, fmt.Errorf("prefetch amount should be positive: %d", amount) - } - ca.prefetch = amount - - ca.duration = 1 * time.Minute - ca.percentage = 10 - if len(args) > 1 { - dur, err := time.ParseDuration(args[1]) - if err != nil { - return nil, err - } - ca.duration = dur - } - if len(args) > 2 { - pct := args[2] - if x := pct[len(pct)-1]; x != '%' { - return nil, fmt.Errorf("last character of percentage should be `%%`, but is: %q", x) - } - pct = pct[:len(pct)-1] - - num, err := strconv.Atoi(pct) - if err != nil { - return nil, err - } - if num < 10 || num > 90 { - return nil, fmt.Errorf("percentage should fall in range [10, 90]: %d", num) - } - ca.percentage = num - } - - default: - return nil, c.ArgErr() - } - } - - for i := range origins { - origins[i] = middleware.Host(origins[i]).Normalize() - } - - ca.Zones = origins - - ca.pcache = cache.New(ca.pcap) - ca.ncache = cache.New(ca.ncap) - - return ca, nil - } - - return nil, nil -} diff --git a/middleware/cache/setup_test.go b/middleware/cache/setup_test.go deleted file mode 100644 index afc2ecc13..000000000 --- a/middleware/cache/setup_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package cache - -import ( - "testing" - "time" - - "github.com/mholt/caddy" -) - -func TestSetup(t *testing.T) { - tests := []struct { - input string - shouldErr bool - expectedNcap int - expectedPcap int - expectedNttl time.Duration - expectedPttl time.Duration - expectedPrefetch int - }{ - {`cache`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0}, - {`cache {}`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0}, - {`cache example.nl { - success 10 - }`, false, defaultCap, 10, maxNTTL, maxTTL, 0}, - {`cache example.nl { - success 10 - denial 10 15 - }`, false, 10, 10, 15 * time.Second, maxTTL, 0}, - {`cache 25 example.nl { - success 10 - denial 10 15 - }`, false, 10, 10, 15 * time.Second, 25 * time.Second, 0}, - {`cache aaa example.nl`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 0}, - {`cache { - prefetch 10 - }`, false, defaultCap, defaultCap, maxNTTL, maxTTL, 10}, - - // fails - {`cache example.nl { - success - denial 10 15 - }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, - {`cache example.nl { - success 15 - denial aaa - }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, - {`cache example.nl { - positive 15 - negative aaa - }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, - {`cache 0 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, - {`cache -1 example.nl`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, - {`cache 1 example.nl { - positive 0 - }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, - {`cache 1 example.nl { - positive 0 - prefetch -1 - }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, - {`cache 1 example.nl { - prefetch 0 blurp - }`, true, defaultCap, defaultCap, maxTTL, maxTTL, 0}, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - ca, err := cacheParse(c) - if test.shouldErr && err == nil { - t.Errorf("Test %v: Expected error but found nil", i) - continue - } else if !test.shouldErr && err != nil { - t.Errorf("Test %v: Expected no error but found error: %v", i, err) - continue - } - if test.shouldErr && err != nil { - continue - } - - if ca.ncap != test.expectedNcap { - t.Errorf("Test %v: Expected ncap %v but found: %v", i, test.expectedNcap, ca.ncap) - } - if ca.pcap != test.expectedPcap { - t.Errorf("Test %v: Expected pcap %v but found: %v", i, test.expectedPcap, ca.pcap) - } - if ca.nttl != test.expectedNttl { - t.Errorf("Test %v: Expected nttl %v but found: %v", i, test.expectedNttl, ca.nttl) - } - if ca.pttl != test.expectedPttl { - t.Errorf("Test %v: Expected pttl %v but found: %v", i, test.expectedPttl, ca.pttl) - } - if ca.prefetch != test.expectedPrefetch { - t.Errorf("Test %v: Expected prefetch %v but found: %v", i, test.expectedPrefetch, ca.prefetch) - } - } -} diff --git a/middleware/chaos/README.md b/middleware/chaos/README.md deleted file mode 100644 index 264ab0601..000000000 --- a/middleware/chaos/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# chaos - -The *chaos* middleware allows CoreDNS to respond to TXT queries in the CH class. - -This is useful for retrieving version or author information from the server. - -## Syntax - -~~~ -chaos [VERSION] [AUTHORS...] -~~~ - -* **VERSION** is the version to return. Defaults to `CoreDNS-<version>`, if not set. -* **AUTHORS** is what authors to return. No default. - -Note that you have to make sure that this middleware will get actual queries for the -following zones: `version.bind`, `version.server`, `authors.bind`, `hostname.bind` and -`id.server`. - -## Examples - -Specify all the zones in full. - -~~~ corefile -version.bind version.server authors.bind hostname.bind id.server { - chaos CoreDNS-001 info@coredns.io -} -~~~ - -Or just default to `.`: - -~~~ corefile -. { - chaos CoreDNS-001 info@coredns.io -} -~~~ - -And test with `dig`: - -~~~ txt -% dig @localhost CH TXT version.bind -... -;; ANSWER SECTION: -version.bind. 0 CH TXT "CoreDNS-001" -... -~~~ diff --git a/middleware/chaos/chaos.go b/middleware/chaos/chaos.go deleted file mode 100644 index e320a1b7a..000000000 --- a/middleware/chaos/chaos.go +++ /dev/null @@ -1,62 +0,0 @@ -// Package chaos implements a middleware that answer to 'CH version.bind TXT' type queries. -package chaos - -import ( - "os" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Chaos allows CoreDNS to reply to CH TXT queries and return author or -// version information. -type Chaos struct { - Next middleware.Handler - Version string - Authors map[string]bool -} - -// ServeDNS implements the middleware.Handler interface. -func (c Chaos) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - if state.QClass() != dns.ClassCHAOS || state.QType() != dns.TypeTXT { - return middleware.NextOrFailure(c.Name(), c.Next, ctx, w, r) - } - - m := new(dns.Msg) - m.SetReply(r) - - hdr := dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} - switch state.Name() { - default: - return c.Next.ServeDNS(ctx, w, r) - case "authors.bind.": - for a := range c.Authors { - m.Answer = append(m.Answer, &dns.TXT{Hdr: hdr, Txt: []string{trim(a)}}) - } - case "version.bind.", "version.server.": - m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(c.Version)}}} - case "hostname.bind.", "id.server.": - hostname, err := os.Hostname() - if err != nil { - hostname = "localhost" - } - m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(hostname)}}} - } - state.SizeAndDo(m) - w.WriteMsg(m) - return 0, nil -} - -// Name implements the Handler interface. -func (c Chaos) Name() string { return "chaos" } - -func trim(s string) string { - if len(s) < 256 { - return s - } - return s[:255] -} diff --git a/middleware/chaos/chaos_test.go b/middleware/chaos/chaos_test.go deleted file mode 100644 index dd383370b..000000000 --- a/middleware/chaos/chaos_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package chaos - -import ( - "testing" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestChaos(t *testing.T) { - em := Chaos{ - Version: version, - Authors: map[string]bool{"Miek Gieben": true}, - } - - tests := []struct { - next middleware.Handler - qname string - qtype uint16 - expectedCode int - expectedReply string - expectedErr error - }{ - { - next: test.NextHandler(dns.RcodeSuccess, nil), - qname: "version.bind", - expectedCode: dns.RcodeSuccess, - expectedReply: version, - expectedErr: nil, - }, - { - next: test.NextHandler(dns.RcodeSuccess, nil), - qname: "authors.bind", - expectedCode: dns.RcodeSuccess, - expectedReply: "Miek Gieben", - expectedErr: nil, - }, - { - next: test.NextHandler(dns.RcodeSuccess, nil), - qname: "authors.bind", - qtype: dns.TypeSRV, - expectedCode: dns.RcodeSuccess, - expectedErr: nil, - }, - } - - ctx := context.TODO() - - for i, tc := range tests { - req := new(dns.Msg) - if tc.qtype == 0 { - tc.qtype = dns.TypeTXT - } - req.SetQuestion(dns.Fqdn(tc.qname), tc.qtype) - req.Question[0].Qclass = dns.ClassCHAOS - em.Next = tc.next - - rec := dnsrecorder.New(&test.ResponseWriter{}) - code, err := em.ServeDNS(ctx, rec, req) - - if err != tc.expectedErr { - t.Errorf("Test %d: Expected error %v, but got %v", i, tc.expectedErr, err) - } - if code != int(tc.expectedCode) { - t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code) - } - if tc.expectedReply != "" { - answer := rec.Msg.Answer[0].(*dns.TXT).Txt[0] - if answer != tc.expectedReply { - t.Errorf("Test %d: Expected answer %s, but got %s", i, tc.expectedReply, answer) - } - } - } -} - -const version = "CoreDNS-001" diff --git a/middleware/chaos/setup.go b/middleware/chaos/setup.go deleted file mode 100644 index 17b4f90cc..000000000 --- a/middleware/chaos/setup.go +++ /dev/null @@ -1,55 +0,0 @@ -package chaos - -import ( - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("chaos", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) - -} - -func setup(c *caddy.Controller) error { - version, authors, err := chaosParse(c) - if err != nil { - return middleware.Error("chaos", err) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return Chaos{Next: next, Version: version, Authors: authors} - }) - - return nil -} - -func chaosParse(c *caddy.Controller) (string, map[string]bool, error) { - // Set here so we pick up AppName and AppVersion that get set in coremain's init(). - chaosVersion = caddy.AppName + "-" + caddy.AppVersion - - version := "" - authors := make(map[string]bool) - - for c.Next() { - args := c.RemainingArgs() - if len(args) == 0 { - return chaosVersion, nil, nil - } - if len(args) == 1 { - return args[0], nil, nil - } - version = args[0] - for _, a := range args[1:] { - authors[a] = true - } - return version, authors, nil - } - return version, authors, nil -} - -var chaosVersion string diff --git a/middleware/chaos/setup_test.go b/middleware/chaos/setup_test.go deleted file mode 100644 index 6f3c13fb3..000000000 --- a/middleware/chaos/setup_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package chaos - -import ( - "strings" - "testing" - - "github.com/mholt/caddy" -) - -func TestSetupChaos(t *testing.T) { - tests := []struct { - input string - shouldErr bool - expectedVersion string // expected version. - expectedAuthor string // expected author (string, although we get a map). - expectedErrContent string // substring from the expected error. Empty for positive cases. - }{ - // positive - { - `chaos v2`, false, "v2", "", "", - }, - { - `chaos v3 "Miek Gieben"`, false, "v3", "Miek Gieben", "", - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - version, authors, err := chaosParse(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - } - - if !strings.Contains(err.Error(), test.expectedErrContent) { - t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) - } - } - - if !test.shouldErr && version != test.expectedVersion { - t.Errorf("Chaos not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedVersion, version) - } - if !test.shouldErr && authors != nil { - if _, ok := authors[test.expectedAuthor]; !ok { - t.Errorf("Chaos not correctly set for input %s. Expected: '%s', actual: '%s'", test.input, test.expectedAuthor, "Miek Gieben") - } - } - } -} diff --git a/middleware/debug/README.md b/middleware/debug/README.md deleted file mode 100644 index cae6d0ff8..000000000 --- a/middleware/debug/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# debug - -*debug* disables the automatic recovery upon a CoreDNS crash so that you'll get a nice stack trace. - -Note that the *errors* middleware (if loaded) will also set a `recover` negating this setting. -The main use of *debug* is to help testing. - -## Syntax - -~~~ txt -debug -~~~ - -## Examples - -Disable CoreDNS' ability to recover from crashes: - -~~~ txt -debug -~~~ diff --git a/middleware/debug/debug.go b/middleware/debug/debug.go deleted file mode 100644 index f92a27554..000000000 --- a/middleware/debug/debug.go +++ /dev/null @@ -1,28 +0,0 @@ -package debug - -import ( - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("debug", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - config := dnsserver.GetConfig(c) - - for c.Next() { - if c.NextArg() { - return middleware.Error("debug", c.ArgErr()) - } - config.Debug = true - } - - return nil -} diff --git a/middleware/debug/debug_test.go b/middleware/debug/debug_test.go deleted file mode 100644 index a4802fee5..000000000 --- a/middleware/debug/debug_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package debug - -import ( - "io/ioutil" - "log" - "testing" - - "github.com/coredns/coredns/core/dnsserver" - - "github.com/mholt/caddy" -) - -func TestDebug(t *testing.T) { - log.SetOutput(ioutil.Discard) - - tests := []struct { - input string - shouldErr bool - expectedDebug bool - }{ - // positive - { - `debug`, false, true, - }, - // negative - { - `debug off`, true, false, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - err := setup(c) - cfg := dnsserver.GetConfig(c) - - if test.shouldErr && err == nil { - t.Fatalf("Test %d: Expected error but found %s for input %s", i, err, test.input) - } - - if err != nil { - if !test.shouldErr { - t.Fatalf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - } - } - if cfg.Debug != test.expectedDebug { - t.Fatalf("Test %d: Expected debug to be: %t, but got: %t, input: %s", i, test.expectedDebug, cfg.Debug, test.input) - } - } -} diff --git a/middleware/dnssec/README.md b/middleware/dnssec/README.md deleted file mode 100644 index 2422ea640..000000000 --- a/middleware/dnssec/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# dnssec - -*dnssec* enables on-the-fly DNSSEC signing of served data. - -## Syntax - -~~~ -dnssec [ZONES... ] { - key file KEY... - cache_capacity CAPACITY -} -~~~ - -The specified key is used for all signing operations. The DNSSEC signing will treat this key a -CSK (common signing key), forgoing the ZSK/KSK split. All signing operations are done online. -Authenticated denial of existence is implemented with NSEC black lies. Using ECDSA as an algorithm -is preferred as this leads to smaller signatures (compared to RSA). NSEC3 is *not* supported. - -If multiple *dnssec* middlewares are specified in the same zone, the last one specified will be -used ( see [bugs](#bugs) ). - -* `ZONES` zones that should be signed. If empty, the zones from the configuration block - are used. - -* `key file` indicates that key file(s) should be read from disk. When multiple keys are specified, RRsets - will be signed with all keys. Generating a key can be done with `dnssec-keygen`: `dnssec-keygen -a - ECDSAP256SHA256 <zonename>`. A key created for zone *A* can be safely used for zone *B*. The name of the - key file can be specified as one of the following formats - - * basename of the generated key `Kexample.org+013+45330` - - * generated public key `Kexample.org+013+45330.key` - - * generated private key `Kexample.org+013+45330.private` - -* `cache_capacity` indicates the capacity of the cache. The dnssec middleware uses a cache to store - RRSIGs. The default capacity is 10000. - -## Metrics - -If monitoring is enabled (via the *prometheus* directive) then the following metrics are exported: - -* coredns_dnssec_cache_size{type} - total elements in the cache, type is "signature". -* coredns_dnssec_cache_capacity{type} - total capacity of the cache, type is "signature". -* coredns_dnssec_cache_hits_total - Counter of cache hits. -* coredns_dnssec_cache_misses_total - Counter of cache misses. - -## Examples - -Sign responses for `example.org` with the key "Kexample.org.+013+45330.key". - -~~~ -example.org:53 { - dnssec { - key file /etc/coredns/Kexample.org.+013+45330 - } - whoami -} -~~~ - -Sign responses for a kubernetes zone with the key "Kcluster.local+013+45129.key". - -~~~ -cluster.local:53 { - kubernetes cluster.local - dnssec cluster.local { - key file /etc/coredns/Kcluster.local+013+45129 - } -} -~~~ - -## Bugs - -Multiple *dnssec* middlewares inside one server stanza will silently overwrite earlier ones, here -`example.local` will overwrite the one for `cluster.local`. - -~~~ -.:53 { - kubernetes cluster.local - dnssec cluster.local { - key file /etc/coredns/cluster.local - } - dnssec example.local { - key file /etc/coredns/example.local - } - whoami -} -~~~ diff --git a/middleware/dnssec/black_lies.go b/middleware/dnssec/black_lies.go deleted file mode 100644 index 527b2fc3e..000000000 --- a/middleware/dnssec/black_lies.go +++ /dev/null @@ -1,24 +0,0 @@ -package dnssec - -import "github.com/miekg/dns" - -// nsec returns an NSEC useful for NXDOMAIN respsones. -// See https://tools.ietf.org/html/draft-valsorda-dnsop-black-lies-00 -// For example, a request for the non-existing name a.example.com would -// cause the following NSEC record to be generated: -// a.example.com. 3600 IN NSEC \000.a.example.com. ( RRSIG NSEC ) -// This inturn makes every NXDOMAIN answer a NODATA one, don't forget to flip -// the header rcode to NOERROR. -func (d Dnssec) nsec(name, zone string, ttl, incep, expir uint32) ([]dns.RR, error) { - nsec := &dns.NSEC{} - nsec.Hdr = dns.RR_Header{Name: name, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeNSEC} - nsec.NextDomain = "\\000." + name - nsec.TypeBitMap = []uint16{dns.TypeRRSIG, dns.TypeNSEC} - - sigs, err := d.sign([]dns.RR{nsec}, zone, ttl, incep, expir) - if err != nil { - return nil, err - } - - return append(sigs, nsec), nil -} diff --git a/middleware/dnssec/black_lies_test.go b/middleware/dnssec/black_lies_test.go deleted file mode 100644 index 342e7bb3a..000000000 --- a/middleware/dnssec/black_lies_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package dnssec - -import ( - "testing" - "time" - - "github.com/coredns/coredns/middleware/test" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -func TestZoneSigningBlackLies(t *testing.T) { - d, rm1, rm2 := newDnssec(t, []string{"miek.nl."}) - defer rm1() - defer rm2() - - m := testNxdomainMsg() - state := request.Request{Req: m} - m = d.Sign(state, "miek.nl.", time.Now().UTC()) - if !section(m.Ns, 2) { - t.Errorf("authority section should have 2 sig") - } - var nsec *dns.NSEC - for _, r := range m.Ns { - if r.Header().Rrtype == dns.TypeNSEC { - nsec = r.(*dns.NSEC) - } - } - if m.Rcode != dns.RcodeSuccess { - t.Errorf("expected rcode %d, got %d", dns.RcodeSuccess, m.Rcode) - } - if nsec == nil { - t.Fatalf("expected NSEC, got none") - } - if nsec.Hdr.Name != "ww.miek.nl." { - t.Errorf("expected %s, got %s", "ww.miek.nl.", nsec.Hdr.Name) - } - if nsec.NextDomain != "\\000.ww.miek.nl." { - t.Errorf("expected %s, got %s", "\\000.ww.miek.nl.", nsec.NextDomain) - } -} - -func testNxdomainMsg() *dns.Msg { - return &dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeNameError}, - Question: []dns.Question{{Name: "ww.miek.nl.", Qclass: dns.ClassINET, Qtype: dns.TypeTXT}}, - Ns: []dns.RR{test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1461471181 14400 3600 604800 14400")}, - } -} diff --git a/middleware/dnssec/cache.go b/middleware/dnssec/cache.go deleted file mode 100644 index ea95b73b4..000000000 --- a/middleware/dnssec/cache.go +++ /dev/null @@ -1,22 +0,0 @@ -package dnssec - -import ( - "hash/fnv" - - "github.com/miekg/dns" -) - -// hash serializes the RRset and return a signature cache key. -func hash(rrs []dns.RR) uint32 { - h := fnv.New32() - buf := make([]byte, 256) - for _, r := range rrs { - off, err := dns.PackRR(r, buf, 0, nil, false) - if err == nil { - h.Write(buf[:off]) - } - } - - i := h.Sum32() - return i -} diff --git a/middleware/dnssec/cache_test.go b/middleware/dnssec/cache_test.go deleted file mode 100644 index 7d42a90df..000000000 --- a/middleware/dnssec/cache_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package dnssec - -import ( - "testing" - "time" - - "github.com/coredns/coredns/middleware/pkg/cache" - "github.com/coredns/coredns/middleware/test" - "github.com/coredns/coredns/request" -) - -func TestCacheSet(t *testing.T) { - fPriv, rmPriv, _ := test.TempFile(".", privKey) - fPub, rmPub, _ := test.TempFile(".", pubKey) - defer rmPriv() - defer rmPub() - - dnskey, err := ParseKeyFile(fPub, fPriv) - if err != nil { - t.Fatalf("failed to parse key: %v\n", err) - } - - c := cache.New(defaultCap) - m := testMsg() - state := request.Request{Req: m} - k := hash(m.Answer) // calculate *before* we add the sig - d := New([]string{"miek.nl."}, []*DNSKEY{dnskey}, nil, c) - d.Sign(state, "miek.nl.", time.Now().UTC()) - - _, ok := d.get(k) - if !ok { - t.Errorf("signature was not added to the cache") - } -} diff --git a/middleware/dnssec/dnskey.go b/middleware/dnssec/dnskey.go deleted file mode 100644 index ce787ab54..000000000 --- a/middleware/dnssec/dnskey.go +++ /dev/null @@ -1,72 +0,0 @@ -package dnssec - -import ( - "crypto" - "crypto/ecdsa" - "crypto/rsa" - "errors" - "os" - "time" - - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// DNSKEY holds a DNSSEC public and private key used for on-the-fly signing. -type DNSKEY struct { - K *dns.DNSKEY - s crypto.Signer - keytag uint16 -} - -// ParseKeyFile read a DNSSEC keyfile as generated by dnssec-keygen or other -// utilities. It adds ".key" for the public key and ".private" for the private key. -func ParseKeyFile(pubFile, privFile string) (*DNSKEY, error) { - f, e := os.Open(pubFile) - if e != nil { - return nil, e - } - k, e := dns.ReadRR(f, pubFile) - if e != nil { - return nil, e - } - - f, e = os.Open(privFile) - if e != nil { - return nil, e - } - p, e := k.(*dns.DNSKEY).ReadPrivateKey(f, privFile) - if e != nil { - return nil, e - } - - if v, ok := p.(*rsa.PrivateKey); ok { - return &DNSKEY{k.(*dns.DNSKEY), v, k.(*dns.DNSKEY).KeyTag()}, nil - } - if v, ok := p.(*ecdsa.PrivateKey); ok { - return &DNSKEY{k.(*dns.DNSKEY), v, k.(*dns.DNSKEY).KeyTag()}, nil - } - return &DNSKEY{k.(*dns.DNSKEY), nil, 0}, errors.New("no known? private key found") -} - -// getDNSKEY returns the correct DNSKEY to the client. Signatures are added when do is true. -func (d Dnssec) getDNSKEY(state request.Request, zone string, do bool) *dns.Msg { - keys := make([]dns.RR, len(d.keys)) - for i, k := range d.keys { - keys[i] = dns.Copy(k.K) - keys[i].Header().Name = zone - } - m := new(dns.Msg) - m.SetReply(state.Req) - m.Answer = keys - if !do { - return m - } - - incep, expir := incepExpir(time.Now().UTC()) - if sigs, err := d.sign(keys, zone, 3600, incep, expir); err == nil { - m.Answer = append(m.Answer, sigs...) - } - return m -} diff --git a/middleware/dnssec/dnssec.go b/middleware/dnssec/dnssec.go deleted file mode 100644 index 4e1e70217..000000000 --- a/middleware/dnssec/dnssec.go +++ /dev/null @@ -1,135 +0,0 @@ -// Package dnssec implements a middleware that signs responses on-the-fly using -// NSEC black lies. -package dnssec - -import ( - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/cache" - "github.com/coredns/coredns/middleware/pkg/response" - "github.com/coredns/coredns/middleware/pkg/singleflight" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// Dnssec signs the reply on-the-fly. -type Dnssec struct { - Next middleware.Handler - - zones []string - keys []*DNSKEY - inflight *singleflight.Group - cache *cache.Cache -} - -// New returns a new Dnssec. -func New(zones []string, keys []*DNSKEY, next middleware.Handler, c *cache.Cache) Dnssec { - return Dnssec{Next: next, - zones: zones, - keys: keys, - cache: c, - inflight: new(singleflight.Group), - } -} - -// Sign signs the message in state. it takes care of negative or nodata responses. It -// uses NSEC black lies for authenticated denial of existence. Signatures -// creates will be cached for a short while. By default we sign for 8 days, -// starting 3 hours ago. -func (d Dnssec) Sign(state request.Request, zone string, now time.Time) *dns.Msg { - req := state.Req - - mt, _ := response.Typify(req, time.Now().UTC()) // TODO(miek): need opt record here? - if mt == response.Delegation { - // TODO(miek): uh, signing DS record?!?! - return req - } - - incep, expir := incepExpir(now) - - if mt == response.NameError { - if req.Ns[0].Header().Rrtype != dns.TypeSOA || len(req.Ns) > 1 { - return req - } - - ttl := req.Ns[0].Header().Ttl - - if sigs, err := d.sign(req.Ns, zone, ttl, incep, expir); err == nil { - req.Ns = append(req.Ns, sigs...) - } - if sigs, err := d.nsec(state.Name(), zone, ttl, incep, expir); err == nil { - req.Ns = append(req.Ns, sigs...) - } - if len(req.Ns) > 1 { // actually added nsec and sigs, reset the rcode - req.Rcode = dns.RcodeSuccess - } - return req - } - - for _, r := range rrSets(req.Answer) { - ttl := r[0].Header().Ttl - if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil { - req.Answer = append(req.Answer, sigs...) - } - } - for _, r := range rrSets(req.Ns) { - ttl := r[0].Header().Ttl - if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil { - req.Ns = append(req.Ns, sigs...) - } - } - for _, r := range rrSets(req.Extra) { - ttl := r[0].Header().Ttl - if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil { - req.Extra = append(sigs, req.Extra...) // prepend to leave OPT alone - } - } - return req -} - -func (d Dnssec) sign(rrs []dns.RR, signerName string, ttl, incep, expir uint32) ([]dns.RR, error) { - k := hash(rrs) - sgs, ok := d.get(k) - if ok { - return sgs, nil - } - - sigs, err := d.inflight.Do(k, func() (interface{}, error) { - sigs := make([]dns.RR, len(d.keys)) - var e error - for i, k := range d.keys { - sig := k.newRRSIG(signerName, ttl, incep, expir) - e = sig.Sign(k.s, rrs) - sigs[i] = sig - } - d.set(k, sigs) - return sigs, e - }) - return sigs.([]dns.RR), err -} - -func (d Dnssec) set(key uint32, sigs []dns.RR) { - d.cache.Add(key, sigs) -} - -func (d Dnssec) get(key uint32) ([]dns.RR, bool) { - if s, ok := d.cache.Get(key); ok { - cacheHits.Inc() - return s.([]dns.RR), true - } - cacheMisses.Inc() - return nil, false -} - -func incepExpir(now time.Time) (uint32, uint32) { - incep := uint32(now.Add(-3 * time.Hour).Unix()) // -(2+1) hours, be sure to catch daylight saving time and such - expir := uint32(now.Add(eightDays).Unix()) // sign for 8 days - return incep, expir -} - -const ( - eightDays = 8 * 24 * time.Hour - defaultCap = 10000 // default capacity of the cache. -) diff --git a/middleware/dnssec/dnssec_test.go b/middleware/dnssec/dnssec_test.go deleted file mode 100644 index 3549a7c8f..000000000 --- a/middleware/dnssec/dnssec_test.go +++ /dev/null @@ -1,219 +0,0 @@ -package dnssec - -import ( - "testing" - "time" - - "github.com/coredns/coredns/middleware/pkg/cache" - "github.com/coredns/coredns/middleware/test" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -func TestZoneSigning(t *testing.T) { - d, rm1, rm2 := newDnssec(t, []string{"miek.nl."}) - defer rm1() - defer rm2() - - m := testMsg() - state := request.Request{Req: m} - - m = d.Sign(state, "miek.nl.", time.Now().UTC()) - if !section(m.Answer, 1) { - t.Errorf("answer section should have 1 sig") - } - if !section(m.Ns, 1) { - t.Errorf("authority section should have 1 sig") - } -} - -func TestZoneSigningDouble(t *testing.T) { - d, rm1, rm2 := newDnssec(t, []string{"miek.nl."}) - defer rm1() - defer rm2() - - fPriv1, rmPriv1, _ := test.TempFile(".", privKey1) - fPub1, rmPub1, _ := test.TempFile(".", pubKey1) - defer rmPriv1() - defer rmPub1() - - key1, err := ParseKeyFile(fPub1, fPriv1) - if err != nil { - t.Fatalf("failed to parse key: %v\n", err) - } - d.keys = append(d.keys, key1) - - m := testMsg() - state := request.Request{Req: m} - m = d.Sign(state, "miek.nl.", time.Now().UTC()) - if !section(m.Answer, 2) { - t.Errorf("answer section should have 1 sig") - } - if !section(m.Ns, 2) { - t.Errorf("authority section should have 1 sig") - } -} - -// TestSigningDifferentZone tests if a key for miek.nl and be used for example.org. -func TestSigningDifferentZone(t *testing.T) { - fPriv, rmPriv, _ := test.TempFile(".", privKey) - fPub, rmPub, _ := test.TempFile(".", pubKey) - defer rmPriv() - defer rmPub() - - key, err := ParseKeyFile(fPub, fPriv) - if err != nil { - t.Fatalf("failed to parse key: %v\n", err) - } - - m := testMsgEx() - state := request.Request{Req: m} - c := cache.New(defaultCap) - d := New([]string{"example.org."}, []*DNSKEY{key}, nil, c) - m = d.Sign(state, "example.org.", time.Now().UTC()) - if !section(m.Answer, 1) { - t.Errorf("answer section should have 1 sig") - t.Logf("%+v\n", m) - } - if !section(m.Ns, 1) { - t.Errorf("authority section should have 1 sig") - t.Logf("%+v\n", m) - } -} - -func TestSigningCname(t *testing.T) { - d, rm1, rm2 := newDnssec(t, []string{"miek.nl."}) - defer rm1() - defer rm2() - - m := testMsgCname() - state := request.Request{Req: m} - m = d.Sign(state, "miek.nl.", time.Now().UTC()) - if !section(m.Answer, 1) { - t.Errorf("answer section should have 1 sig") - } -} - -func TestZoneSigningDelegation(t *testing.T) { - d, rm1, rm2 := newDnssec(t, []string{"miek.nl."}) - defer rm1() - defer rm2() - - m := testDelegationMsg() - state := request.Request{Req: m} - m = d.Sign(state, "miek.nl.", time.Now().UTC()) - if !section(m.Ns, 0) { - t.Errorf("authority section should have 0 sig") - t.Logf("%v\n", m) - } - if !section(m.Extra, 0) { - t.Errorf("answer section should have 0 sig") - t.Logf("%v\n", m) - } -} - -func TestSigningDname(t *testing.T) { - d, rm1, rm2 := newDnssec(t, []string{"miek.nl."}) - defer rm1() - defer rm2() - - m := testMsgDname() - state := request.Request{Req: m} - // We sign *everything* we see, also the synthesized CNAME. - m = d.Sign(state, "miek.nl.", time.Now().UTC()) - if !section(m.Answer, 3) { - t.Errorf("answer section should have 3 sig") - } -} - -func section(rss []dns.RR, nrSigs int) bool { - i := 0 - for _, r := range rss { - if r.Header().Rrtype == dns.TypeRRSIG { - i++ - } - } - return nrSigs == i -} - -func testMsg() *dns.Msg { - // don't care about the message header - return &dns.Msg{ - Answer: []dns.RR{test.MX("miek.nl. 1703 IN MX 1 aspmx.l.google.com.")}, - Ns: []dns.RR{test.NS("miek.nl. 1703 IN NS omval.tednet.nl.")}, - } -} -func testMsgEx() *dns.Msg { - return &dns.Msg{ - Answer: []dns.RR{test.MX("example.org. 1703 IN MX 1 aspmx.l.google.com.")}, - Ns: []dns.RR{test.NS("example.org. 1703 IN NS omval.tednet.nl.")}, - } -} - -func testMsgCname() *dns.Msg { - return &dns.Msg{ - Answer: []dns.RR{test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl.")}, - } -} - -func testDelegationMsg() *dns.Msg { - return &dns.Msg{ - Ns: []dns.RR{ - test.NS("miek.nl. 3600 IN NS linode.atoom.net."), - test.NS("miek.nl. 3600 IN NS ns-ext.nlnetlabs.nl."), - test.NS("miek.nl. 3600 IN NS omval.tednet.nl."), - }, - Extra: []dns.RR{ - test.A("omval.tednet.nl. 3600 IN A 185.49.141.42"), - test.AAAA("omval.tednet.nl. 3600 IN AAAA 2a04:b900:0:100::42"), - }, - } -} - -func testMsgDname() *dns.Msg { - return &dns.Msg{ - Answer: []dns.RR{ - test.CNAME("a.dname.miek.nl. 1800 IN CNAME a.test.miek.nl."), - test.A("a.test.miek.nl. 1800 IN A 139.162.196.78"), - test.DNAME("dname.miek.nl. 1800 IN DNAME test.miek.nl."), - }, - } -} - -func newDnssec(t *testing.T, zones []string) (Dnssec, func(), func()) { - k, rm1, rm2 := newKey(t) - c := cache.New(defaultCap) - d := New(zones, []*DNSKEY{k}, nil, c) - return d, rm1, rm2 -} - -func newKey(t *testing.T) (*DNSKEY, func(), func()) { - fPriv, rmPriv, _ := test.TempFile(".", privKey) - fPub, rmPub, _ := test.TempFile(".", pubKey) - - key, err := ParseKeyFile(fPub, fPriv) - if err != nil { - t.Fatalf("failed to parse key: %v\n", err) - } - return key, rmPriv, rmPub -} - -const ( - pubKey = `miek.nl. IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4BXPP3gwhetiOUMnGA+x09nqzgF5IY OyjWB7N3rXqQbnOSILhH1hnuyh7mmA==` - privKey = `Private-key-format: v1.3 -Algorithm: 13 (ECDSAP256SHA256) -PrivateKey: /4BZk8AFvyW5hL3cOLSVxIp1RTqHSAEloWUxj86p3gs= -Created: 20160423195532 -Publish: 20160423195532 -Activate: 20160423195532 -` - pubKey1 = `example.org. IN DNSKEY 257 3 13 tVRWNSGpHZbCi7Pr7OmbADVUO3MxJ0Lb8Lk3o/HBHqCxf5K/J50lFqRa 98lkdAIiFOVRy8LyMvjwmxZKwB5MNw==` - privKey1 = `Private-key-format: v1.3 -Algorithm: 13 (ECDSAP256SHA256) -PrivateKey: i8j4OfDGT8CQt24SDwLz2hg9yx4qKOEOh1LvbAuSp1c= -Created: 20160423211746 -Publish: 20160423211746 -Activate: 20160423211746 -` -) diff --git a/middleware/dnssec/handler.go b/middleware/dnssec/handler.go deleted file mode 100644 index dab02e469..000000000 --- a/middleware/dnssec/handler.go +++ /dev/null @@ -1,82 +0,0 @@ -package dnssec - -import ( - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "github.com/prometheus/client_golang/prometheus" - "golang.org/x/net/context" -) - -// ServeDNS implements the middleware.Handler interface. -func (d Dnssec) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - - do := state.Do() - qname := state.Name() - qtype := state.QType() - zone := middleware.Zones(d.zones).Matches(qname) - if zone == "" { - return middleware.NextOrFailure(d.Name(), d.Next, ctx, w, r) - } - - // Intercept queries for DNSKEY, but only if one of the zones matches the qname, otherwise we let - // the query through. - if qtype == dns.TypeDNSKEY { - for _, z := range d.zones { - if qname == z { - resp := d.getDNSKEY(state, z, do) - resp.Authoritative = true - state.SizeAndDo(resp) - w.WriteMsg(resp) - return dns.RcodeSuccess, nil - } - } - } - - drr := &ResponseWriter{w, d} - return middleware.NextOrFailure(d.Name(), d.Next, ctx, drr, r) -} - -var ( - cacheSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "cache_size", - Help: "The number of elements in the dnssec cache.", - }, []string{"type"}) - - cacheCapacity = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "cache_capacity", - Help: "The dnssec cache's capacity.", - }, []string{"type"}) - - cacheHits = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "cache_hits_total", - Help: "The count of cache hits.", - }) - - cacheMisses = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "cache_misses_total", - Help: "The count of cache misses.", - }) -) - -// Name implements the Handler interface. -func (d Dnssec) Name() string { return "dnssec" } - -const subsystem = "dnssec" - -func init() { - prometheus.MustRegister(cacheSize) - prometheus.MustRegister(cacheCapacity) - prometheus.MustRegister(cacheHits) - prometheus.MustRegister(cacheMisses) -} diff --git a/middleware/dnssec/handler_test.go b/middleware/dnssec/handler_test.go deleted file mode 100644 index 19d0ef01f..000000000 --- a/middleware/dnssec/handler_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package dnssec - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/file" - "github.com/coredns/coredns/middleware/pkg/cache" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var dnssecTestCases = []test.Case{ - { - Qname: "miek.nl.", Qtype: dns.TypeDNSKEY, - Answer: []dns.RR{ - test.DNSKEY("miek.nl. 3600 IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4"), - }, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeDNSKEY, Do: true, - Answer: []dns.RR{ - test.DNSKEY("miek.nl. 3600 IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4"), - test.RRSIG("miek.nl. 3600 IN RRSIG DNSKEY 13 2 3600 20160503150844 20160425120844 18512 miek.nl. Iw/kNOyM"), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, -} - -var dnsTestCases = []test.Case{ - { - Qname: "miek.nl.", Qtype: dns.TypeDNSKEY, - Answer: []dns.RR{ - test.DNSKEY("miek.nl. 3600 IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4"), - }, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeMX, - Answer: []dns.RR{ - test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."), - }, - Ns: []dns.RR{ - test.NS("miek.nl. 1800 IN NS linode.atoom.net."), - }, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeMX, Do: true, - Answer: []dns.RR{ - test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."), - test.RRSIG("miek.nl. 1800 IN RRSIG MX 13 2 3600 20160503192428 20160425162428 18512 miek.nl. 4nxuGKitXjPVA9zP1JIUvA09"), - }, - Ns: []dns.RR{ - test.NS("miek.nl. 1800 IN NS linode.atoom.net."), - test.RRSIG("miek.nl. 1800 IN RRSIG NS 13 2 3600 20161217114912 20161209084912 18512 miek.nl. ad9gA8VWgF1H8ze9/0Rk2Q=="), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "www.miek.nl.", Qtype: dns.TypeAAAA, Do: true, - Answer: []dns.RR{ - test.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - test.RRSIG("a.miek.nl. 1800 IN RRSIG AAAA 13 3 3600 20160503193047 20160425163047 18512 miek.nl. UAyMG+gcnoXW3"), - test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."), - test.RRSIG("www.miek.nl. 1800 IN RRSIG CNAME 13 3 3600 20160503193047 20160425163047 18512 miek.nl. E3qGZn"), - }, - Ns: []dns.RR{ - test.NS("miek.nl. 1800 IN NS linode.atoom.net."), - test.RRSIG("miek.nl. 1800 IN RRSIG NS 13 2 3600 20161217114912 20161209084912 18512 miek.nl. ad9gA8VWgF1H8ze9/0Rk2Q=="), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "www.example.org.", Qtype: dns.TypeAAAA, Do: true, - Rcode: dns.RcodeServerFailure, - // Extra: []dns.RR{test.OPT(4096, true)}, // test.ErrorHandler is a simple handler that does not do EDNS. - }, -} - -func TestLookupZone(t *testing.T) { - zone, err := file.Parse(strings.NewReader(dbMiekNL), "miek.nl.", "stdin", 0) - if err != nil { - return - } - fm := file.File{Next: test.ErrorHandler(), Zones: file.Zones{Z: map[string]*file.Zone{"miek.nl.": zone}, Names: []string{"miek.nl."}}} - dnskey, rm1, rm2 := newKey(t) - defer rm1() - defer rm2() - c := cache.New(defaultCap) - dh := New([]string{"miek.nl."}, []*DNSKEY{dnskey}, fm, c) - ctx := context.TODO() - - for _, tc := range dnsTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := dh.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -func TestLookupDNSKEY(t *testing.T) { - dnskey, rm1, rm2 := newKey(t) - defer rm1() - defer rm2() - c := cache.New(defaultCap) - dh := New([]string{"miek.nl."}, []*DNSKEY{dnskey}, test.ErrorHandler(), c) - ctx := context.TODO() - - for _, tc := range dnssecTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := dh.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - return - } - - resp := rec.Msg - if !resp.Authoritative { - t.Errorf("Authoritative Answer should be true, got false") - } - - test.SortAndCheck(t, resp, tc) - } -} - -const dbMiekNL = ` -$TTL 30M -$ORIGIN miek.nl. -@ IN SOA linode.atoom.net. miek.miek.nl. ( - 1282630057 ; Serial - 4H ; Refresh - 1H ; Retry - 7D ; Expire - 4H ) ; Negative Cache TTL - IN NS linode.atoom.net. - - IN MX 1 aspmx.l.google.com. - - IN A 139.162.196.78 - IN AAAA 2a01:7e00::f03c:91ff:fef1:6735 - -a IN A 139.162.196.78 - IN AAAA 2a01:7e00::f03c:91ff:fef1:6735 -www IN CNAME a` diff --git a/middleware/dnssec/responsewriter.go b/middleware/dnssec/responsewriter.go deleted file mode 100644 index d3c34d114..000000000 --- a/middleware/dnssec/responsewriter.go +++ /dev/null @@ -1,49 +0,0 @@ -package dnssec - -import ( - "log" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// ResponseWriter sign the response on the fly. -type ResponseWriter struct { - dns.ResponseWriter - d Dnssec -} - -// WriteMsg implements the dns.ResponseWriter interface. -func (d *ResponseWriter) WriteMsg(res *dns.Msg) error { - // By definition we should sign anything that comes back, we should still figure out for - // which zone it should be. - state := request.Request{W: d.ResponseWriter, Req: res} - - qname := state.Name() - zone := middleware.Zones(d.d.zones).Matches(qname) - if zone == "" { - return d.ResponseWriter.WriteMsg(res) - } - - if state.Do() { - res = d.d.Sign(state, zone, time.Now().UTC()) - - cacheSize.WithLabelValues("signature").Set(float64(d.d.cache.Len())) - } - state.SizeAndDo(res) - - return d.ResponseWriter.WriteMsg(res) -} - -// Write implements the dns.ResponseWriter interface. -func (d *ResponseWriter) Write(buf []byte) (int, error) { - log.Printf("[WARNING] Dnssec called with Write: not signing reply") - n, err := d.ResponseWriter.Write(buf) - return n, err -} - -// Hijack implements the dns.ResponseWriter interface. -func (d *ResponseWriter) Hijack() { d.ResponseWriter.Hijack() } diff --git a/middleware/dnssec/rrsig.go b/middleware/dnssec/rrsig.go deleted file mode 100644 index c68413622..000000000 --- a/middleware/dnssec/rrsig.go +++ /dev/null @@ -1,53 +0,0 @@ -package dnssec - -import "github.com/miekg/dns" - -// newRRSIG return a new RRSIG, with all fields filled out, except the signed data. -func (k *DNSKEY) newRRSIG(signerName string, ttl, incep, expir uint32) *dns.RRSIG { - sig := new(dns.RRSIG) - - sig.Hdr.Rrtype = dns.TypeRRSIG - sig.Algorithm = k.K.Algorithm - sig.KeyTag = k.keytag - sig.SignerName = signerName - sig.Hdr.Ttl = ttl - sig.OrigTtl = origTTL - - sig.Inception = incep - sig.Expiration = expir - - return sig -} - -type rrset struct { - qname string - qtype uint16 -} - -// rrSets returns rrs as a map of RRsets. It skips RRSIG and OPT records as those don't need to be signed. -func rrSets(rrs []dns.RR) map[rrset][]dns.RR { - m := make(map[rrset][]dns.RR) - - for _, r := range rrs { - if r.Header().Rrtype == dns.TypeRRSIG || r.Header().Rrtype == dns.TypeOPT { - continue - } - - if s, ok := m[rrset{r.Header().Name, r.Header().Rrtype}]; ok { - s = append(s, r) - m[rrset{r.Header().Name, r.Header().Rrtype}] = s - continue - } - - s := make([]dns.RR, 1, 3) - s[0] = r - m[rrset{r.Header().Name, r.Header().Rrtype}] = s - } - - if len(m) > 0 { - return m - } - return nil -} - -const origTTL = 3600 diff --git a/middleware/dnssec/setup.go b/middleware/dnssec/setup.go deleted file mode 100644 index 90425b711..000000000 --- a/middleware/dnssec/setup.go +++ /dev/null @@ -1,128 +0,0 @@ -package dnssec - -import ( - "fmt" - "strconv" - "strings" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/cache" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("dnssec", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - zones, keys, capacity, err := dnssecParse(c) - if err != nil { - return middleware.Error("dnssec", err) - } - - ca := cache.New(capacity) - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return New(zones, keys, next, ca) - }) - - // Export the capacity for the metrics. This only happens once, because this is a re-load change only. - cacheCapacity.WithLabelValues("signature").Set(float64(capacity)) - - return nil -} - -func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) { - zones := []string{} - - keys := []*DNSKEY{} - - capacity := defaultCap - for c.Next() { - // dnssec [zones...] - zones = make([]string, len(c.ServerBlockKeys)) - copy(zones, c.ServerBlockKeys) - args := c.RemainingArgs() - if len(args) > 0 { - zones = args - } - - for c.NextBlock() { - switch c.Val() { - case "key": - k, e := keyParse(c) - if e != nil { - return nil, nil, 0, e - } - keys = append(keys, k...) - case "cache_capacity": - if !c.NextArg() { - return nil, nil, 0, c.ArgErr() - } - value := c.Val() - cacheCap, err := strconv.Atoi(value) - if err != nil { - return nil, nil, 0, err - } - capacity = cacheCap - } - - } - } - for i := range zones { - zones[i] = middleware.Host(zones[i]).Normalize() - } - - // Check if each keys owner name can actually sign the zones we want them to sign - for _, k := range keys { - kname := middleware.Name(k.K.Header().Name) - ok := false - for i := range zones { - if kname.Matches(zones[i]) { - ok = true - break - } - } - if !ok { - return zones, keys, capacity, fmt.Errorf("key %s (keyid: %d) can not sign any of the zones", string(kname), k.keytag) - } - } - - return zones, keys, capacity, nil -} - -func keyParse(c *caddy.Controller) ([]*DNSKEY, error) { - keys := []*DNSKEY{} - - if !c.NextArg() { - return nil, c.ArgErr() - } - value := c.Val() - if value == "file" { - ks := c.RemainingArgs() - if len(ks) == 0 { - return nil, c.ArgErr() - } - - for _, k := range ks { - base := k - // Kmiek.nl.+013+26205.key, handle .private or without extension: Kmiek.nl.+013+26205 - if strings.HasSuffix(k, ".key") { - base = k[:len(k)-4] - } - if strings.HasSuffix(k, ".private") { - base = k[:len(k)-8] - } - k, err := ParseKeyFile(base+".key", base+".private") - if err != nil { - return nil, err - } - keys = append(keys, k) - } - } - return keys, nil -} diff --git a/middleware/dnssec/setup_test.go b/middleware/dnssec/setup_test.go deleted file mode 100644 index 99a71279d..000000000 --- a/middleware/dnssec/setup_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package dnssec - -import ( - "io/ioutil" - "os" - "strings" - "testing" - - "github.com/mholt/caddy" -) - -func TestSetupDnssec(t *testing.T) { - if err := ioutil.WriteFile("Kcluster.local.key", []byte(keypub), 0644); err != nil { - t.Fatalf("Failed to write pub key file: %s", err) - } - defer func() { os.Remove("Kcluster.local.key") }() - if err := ioutil.WriteFile("Kcluster.local.private", []byte(keypriv), 0644); err != nil { - t.Fatalf("Failed to write private key file: %s", err) - } - defer func() { os.Remove("Kcluster.local.private") }() - - tests := []struct { - input string - shouldErr bool - expectedZones []string - expectedKeys []string - expectedCapacity int - expectedErrContent string - }{ - {`dnssec`, false, nil, nil, defaultCap, ""}, - {`dnssec example.org`, false, []string{"example.org."}, nil, defaultCap, ""}, - {`dnssec 10.0.0.0/8`, false, []string{"10.in-addr.arpa."}, nil, defaultCap, ""}, - { - `dnssec example.org { - cache_capacity 100 - }`, false, []string{"example.org."}, nil, 100, "", - }, - { - `dnssec cluster.local { - key file Kcluster.local - }`, false, []string{"cluster.local."}, nil, defaultCap, "", - }, - { - `dnssec example.org cluster.local { - key file Kcluster.local - }`, false, []string{"example.org.", "cluster.local."}, nil, defaultCap, "", - }, - // fails - { - `dnssec example.org { - key file Kcluster.local - }`, true, []string{"example.org."}, nil, defaultCap, "can not sign any", - }, - { - `dnssec example.org { - key - }`, true, []string{"example.org."}, nil, defaultCap, "argument count", - }, - { - `dnssec example.org { - key file - }`, true, []string{"example.org."}, nil, defaultCap, "argument count", - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - zones, keys, capacity, err := dnssecParse(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - } - - if !strings.Contains(err.Error(), test.expectedErrContent) { - t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) - } - } - if !test.shouldErr { - for i, z := range test.expectedZones { - if zones[i] != z { - t.Errorf("Dnssec not correctly set for input %s. Expected: %s, actual: %s", test.input, z, zones[i]) - } - } - for i, k := range test.expectedKeys { - if k != keys[i].K.Header().Name { - t.Errorf("Dnssec not correctly set for input %s. Expected: '%s', actual: '%s'", test.input, k, keys[i].K.Header().Name) - } - } - if capacity != test.expectedCapacity { - t.Errorf("Dnssec not correctly set capacity for input '%s' Expected: '%d', actual: '%d'", test.input, capacity, test.expectedCapacity) - } - } - } -} - -const keypub = `; This is a zone-signing key, keyid 45330, for cluster.local. -; Created: 20170901060531 (Fri Sep 1 08:05:31 2017) -; Publish: 20170901060531 (Fri Sep 1 08:05:31 2017) -; Activate: 20170901060531 (Fri Sep 1 08:05:31 2017) -cluster.local. IN DNSKEY 256 3 5 AwEAAcFpDv+Cb23kFJowu+VU++b2N1uEHi6Ll9H0BzLasFOdJjEEclCO q/KlD4682vOMXxJNN8ZwOyiCa7Y0TEYqSwWvhHyn3bHCwuy4I6fss4Wd 7Y9dU+6QTgJ8LimGG40Iizjc9zqoU8Q+q81vIukpYWOHioHoY7hsWBvS RSlzDJk3` - -const keypriv = `Private-key-format: v1.3 -Algorithm: 5 (RSASHA1) -Modulus: wWkO/4JvbeQUmjC75VT75vY3W4QeLouX0fQHMtqwU50mMQRyUI6r8qUPjrza84xfEk03xnA7KIJrtjRMRipLBa+EfKfdscLC7Lgjp+yzhZ3tj11T7pBOAnwuKYYbjQiLONz3OqhTxD6rzW8i6SlhY4eKgehjuGxYG9JFKXMMmTc= -PublicExponent: AQAB -PrivateExponent: K5XyZFBPrjMVFX5gCZlyPyVDamNGrfSVXSIiMSqpS96BSdCXtmHAjCj4bZFPwkzi6+vs4tJN8p4ZifEVM0a6qwPZyENBrc2qbsweOXE6l8BaPVWFX30xvVRzGXuNtXxlBXE17zoHty5r5mRyRou1bc2HUS5otdkEjE30RiocQVk= -Prime1: 7RRFUxaZkVNVH1DaT/SV5Sb8kABB389qLwU++argeDCVf+Wm9BBlTrsz2U6bKlfpaUmYZKtCCd+CVxqzMyuu0w== -Prime2: 0NiY3d7Fa08IGY9L4TaFc02A721YcDNBBf95BP31qGvwnYsLFM/1xZwaEsIjohg8g+m/GpyIlvNMbK6pywIVjQ== -Exponent1: XjXO8pype9mMmvwrNNix9DTQ6nxfsQugW30PMHGZ78kGr6NX++bEC0xS50jYWjRDGcbYGzD+9iNujSScD3qNZw== -Exponent2: wkoOhLIfhUIj7etikyUup2Ld5WAbW15DSrotstg0NrgcQ+Q7reP96BXeJ79WeREFE09cyvv/EjdLzPv81/CbbQ== -Coefficient: ah4LL0KLTO8kSKHK+X9Ud8grYi94QSNdbX11ge/eFcS/41QhDuZRTAFv4y0+IG+VWd+XzojLsQs+jzLe5GzINg== -Created: 20170901060531 -Publish: 20170901060531 -Activate: 20170901060531 -` diff --git a/middleware/dnstap/README.md b/middleware/dnstap/README.md deleted file mode 100644 index d91c9422c..000000000 --- a/middleware/dnstap/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# dnstap - -*dnstap* enables logging to dnstap, a flexible, structured binary log format for DNS software: http://dnstap.info. - -There is a buffer, expect at least 13 requests before the server sends its dnstap messages to the socket. - -## Syntax - -~~~ txt -dnstap SOCKET [full] -~~~ - -* **SOCKET** is the socket path supplied to the dnstap command line tool. -* `full` to include the wire-format DNS message. - -## Examples - -Log information about client requests and responses to */tmp/dnstap.sock*. - -~~~ txt -dnstap /tmp/dnstap.sock -~~~ - -Log information including the wire-format DNS message about client requests and responses to */tmp/dnstap.sock*. - -~~~ txt -dnstap unix:///tmp/dnstap.sock full -~~~ - -Log to a remote endpoint. - -~~~ txt -dnstap tcp://127.0.0.1:6000 full -~~~ - -## Dnstap command line tool - -~~~ sh -go get github.com/dnstap/golang-dnstap -cd $GOPATH/src/github.com/dnstap/golang-dnstap/dnstap -go build -./dnstap -~~~ - -The following command listens on the given socket and decodes messages to stdout. - -~~~ sh -dnstap -u /tmp/dnstap.sock -~~~ - -The following command listens on the given socket and saves message payloads to a binary dnstap-format log file. - -~~~ sh -dnstap -u /tmp/dnstap.sock -w /tmp/test.dnstap -~~~ - -Listen for dnstap messages on port 6000. - -~~~ sh -dnstap -l 127.0.0.1:6000 -~~~ diff --git a/middleware/dnstap/handler.go b/middleware/dnstap/handler.go deleted file mode 100644 index eb2924be5..000000000 --- a/middleware/dnstap/handler.go +++ /dev/null @@ -1,79 +0,0 @@ -package dnstap - -import ( - "fmt" - "io" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/dnstap/msg" - "github.com/coredns/coredns/middleware/dnstap/taprw" - - tap "github.com/dnstap/golang-dnstap" - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Dnstap is the dnstap handler. -type Dnstap struct { - Next middleware.Handler - Out io.Writer - Pack bool -} - -type ( - // Tapper is implemented by the Context passed by the dnstap handler. - Tapper interface { - TapMessage(*tap.Message) error - TapBuilder() msg.Builder - } - tapContext struct { - context.Context - Dnstap - } -) - -// TapperFromContext will return a Tapper if the dnstap middleware is enabled. -func TapperFromContext(ctx context.Context) (t Tapper) { - t, _ = ctx.(Tapper) - return -} - -func tapMessageTo(w io.Writer, m *tap.Message) error { - frame, err := msg.Marshal(m) - if err != nil { - return fmt.Errorf("marshal: %s", err) - } - _, err = w.Write(frame) - return err -} - -// TapMessage implements Tapper. -func (h Dnstap) TapMessage(m *tap.Message) error { - return tapMessageTo(h.Out, m) -} - -// TapBuilder implements Tapper. -func (h Dnstap) TapBuilder() msg.Builder { - return msg.Builder{Full: h.Pack} -} - -// ServeDNS logs the client query and response to dnstap and passes the dnstap Context. -func (h Dnstap) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - rw := &taprw.ResponseWriter{ResponseWriter: w, Tapper: &h, Query: r} - rw.QueryEpoch() - - code, err := middleware.NextOrFailure(h.Name(), h.Next, tapContext{ctx, h}, rw, r) - if err != nil { - // ignore dnstap errors - return code, err - } - - if err := rw.DnstapError(); err != nil { - return code, middleware.Error("dnstap", err) - } - - return code, nil -} - -// Name returns dnstap. -func (h Dnstap) Name() string { return "dnstap" } diff --git a/middleware/dnstap/handler_test.go b/middleware/dnstap/handler_test.go deleted file mode 100644 index dfdde582d..000000000 --- a/middleware/dnstap/handler_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package dnstap - -import ( - "errors" - "fmt" - "testing" - - "github.com/coredns/coredns/middleware/dnstap/test" - mwtest "github.com/coredns/coredns/middleware/test" - - tap "github.com/dnstap/golang-dnstap" - "github.com/golang/protobuf/proto" - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func testCase(t *testing.T, tapq, tapr *tap.Message, q, r *dns.Msg) { - w := writer{} - w.queue = append(w.queue, tapq, tapr) - h := Dnstap{ - Next: mwtest.HandlerFunc(func(_ context.Context, - w dns.ResponseWriter, _ *dns.Msg) (int, error) { - - return 0, w.WriteMsg(r) - }), - Out: &w, - Pack: false, - } - _, err := h.ServeDNS(context.TODO(), &mwtest.ResponseWriter{}, q) - if err != nil { - t.Fatal(err) - } -} - -type writer struct { - queue []*tap.Message -} - -func (w *writer) Write(b []byte) (int, error) { - e := tap.Dnstap{} - if err := proto.Unmarshal(b, &e); err != nil { - return 0, err - } - if len(w.queue) == 0 { - return 0, errors.New("message not expected") - } - if !test.MsgEqual(w.queue[0], e.Message) { - return 0, fmt.Errorf("want: %v, have: %v", w.queue[0], e.Message) - } - w.queue = w.queue[1:] - return len(b), nil -} - -func TestDnstap(t *testing.T) { - q := mwtest.Case{Qname: "example.org", Qtype: dns.TypeA}.Msg() - r := mwtest.Case{ - Qname: "example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - mwtest.A("example.org. 3600 IN A 10.0.0.1"), - }, - }.Msg() - tapq := test.TestingData().ToClientQuery() - tapr := test.TestingData().ToClientResponse() - testCase(t, tapq, tapr, q, r) -} diff --git a/middleware/dnstap/msg/msg.go b/middleware/dnstap/msg/msg.go deleted file mode 100644 index 0e2cd5d40..000000000 --- a/middleware/dnstap/msg/msg.go +++ /dev/null @@ -1,168 +0,0 @@ -// Package msg helps to build a dnstap Message. -package msg - -import ( - "errors" - "net" - "strconv" - "time" - - tap "github.com/dnstap/golang-dnstap" - "github.com/miekg/dns" -) - -// Builder helps to build Data by being aware of the dnstap middleware configuration. -type Builder struct { - Full bool - Data -} - -// AddrMsg parses the info of net.Addr and dns.Msg. -func (b *Builder) AddrMsg(a net.Addr, m *dns.Msg) (err error) { - err = b.RemoteAddr(a) - if err != nil { - return - } - return b.Msg(m) -} - -// Msg parses the info of dns.Msg. -func (b *Builder) Msg(m *dns.Msg) (err error) { - if b.Full { - err = b.Pack(m) - } - return -} - -// Data helps to build a dnstap Message. -// It can be transformed into the actual Message using this package. -type Data struct { - Packed []byte - SocketProto tap.SocketProtocol - SocketFam tap.SocketFamily - Address []byte - Port uint32 - TimeSec uint64 -} - -// HostPort decodes into Data any string returned by dnsutil.ParseHostPortOrFile. -func (d *Data) HostPort(addr string) error { - ip, port, err := net.SplitHostPort(addr) - if err != nil { - return err - } - p, err := strconv.ParseUint(port, 10, 32) - if err != nil { - return err - } - d.Port = uint32(p) - - if ip := net.ParseIP(ip); ip != nil { - d.Address = []byte(ip) - if ip := ip.To4(); ip != nil { - d.SocketFam = tap.SocketFamily_INET - } else { - d.SocketFam = tap.SocketFamily_INET6 - } - return nil - } - return errors.New("not an ip address") -} - -// RemoteAddr parses the information about the remote address into Data. -func (d *Data) RemoteAddr(remote net.Addr) error { - switch addr := remote.(type) { - case *net.TCPAddr: - d.Address = addr.IP - d.Port = uint32(addr.Port) - d.SocketProto = tap.SocketProtocol_TCP - case *net.UDPAddr: - d.Address = addr.IP - d.Port = uint32(addr.Port) - d.SocketProto = tap.SocketProtocol_UDP - default: - return errors.New("unknown remote address type") - } - - if a := net.IP(d.Address); a.To4() != nil { - d.SocketFam = tap.SocketFamily_INET - } else { - d.SocketFam = tap.SocketFamily_INET6 - } - - return nil -} - -// Pack encodes the DNS message into Data. -func (d *Data) Pack(m *dns.Msg) error { - packed, err := m.Pack() - if err != nil { - return err - } - d.Packed = packed - return nil -} - -// Epoch returns the epoch time in seconds. -func Epoch() uint64 { - return uint64(time.Now().Unix()) -} - -// Epoch sets the dnstap message epoch. -func (d *Data) Epoch() { - d.TimeSec = Epoch() -} - -// ToClientResponse transforms Data into a client response message. -func (d *Data) ToClientResponse() *tap.Message { - t := tap.Message_CLIENT_RESPONSE - return &tap.Message{ - Type: &t, - SocketFamily: &d.SocketFam, - SocketProtocol: &d.SocketProto, - ResponseTimeSec: &d.TimeSec, - ResponseMessage: d.Packed, - QueryAddress: d.Address, - QueryPort: &d.Port, - } -} - -// ToClientQuery transforms Data into a client query message. -func (d *Data) ToClientQuery() *tap.Message { - t := tap.Message_CLIENT_QUERY - return &tap.Message{ - Type: &t, - SocketFamily: &d.SocketFam, - SocketProtocol: &d.SocketProto, - QueryTimeSec: &d.TimeSec, - QueryMessage: d.Packed, - QueryAddress: d.Address, - QueryPort: &d.Port, - } -} - -// ToOutsideQuery transforms the data into a forwarder or resolver query message. -func (d *Data) ToOutsideQuery(t tap.Message_Type) *tap.Message { - return &tap.Message{ - Type: &t, - SocketFamily: &d.SocketFam, - SocketProtocol: &d.SocketProto, - QueryTimeSec: &d.TimeSec, - QueryMessage: d.Packed, - ResponseAddress: d.Address, - ResponsePort: &d.Port, - } -} - -// ToOutsideResponse transforms the data into a forwarder or resolver response message. -func (d *Data) ToOutsideResponse(t tap.Message_Type) *tap.Message { - return &tap.Message{ - Type: &t, - SocketFamily: &d.SocketFam, - SocketProtocol: &d.SocketProto, - ResponseTimeSec: &d.TimeSec, - ResponseMessage: d.Packed, - ResponseAddress: d.Address, - ResponsePort: &d.Port, - } -} diff --git a/middleware/dnstap/msg/msg_test.go b/middleware/dnstap/msg/msg_test.go deleted file mode 100644 index 2f80a90cd..000000000 --- a/middleware/dnstap/msg/msg_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package msg - -import ( - "net" - "reflect" - "testing" - - "github.com/coredns/coredns/middleware/test" - "github.com/coredns/coredns/request" - - tap "github.com/dnstap/golang-dnstap" - "github.com/miekg/dns" -) - -func testRequest(t *testing.T, expected Data, r request.Request) { - d := Data{} - if err := d.RemoteAddr(r.W.RemoteAddr()); err != nil { - t.Fail() - return - } - if d.SocketProto != expected.SocketProto || - d.SocketFam != expected.SocketFam || - !reflect.DeepEqual(d.Address, expected.Address) || - d.Port != expected.Port { - t.Fatalf("expected: %v, have: %v", expected, d) - return - } -} -func TestRequest(t *testing.T) { - testRequest(t, Data{ - SocketProto: tap.SocketProtocol_UDP, - SocketFam: tap.SocketFamily_INET, - Address: net.ParseIP("10.240.0.1"), - Port: 40212, - }, testingRequest()) -} -func testingRequest() request.Request { - m := new(dns.Msg) - m.SetQuestion("example.com.", dns.TypeA) - m.SetEdns0(4097, true) - return request.Request{W: &test.ResponseWriter{}, Req: m} -} diff --git a/middleware/dnstap/msg/wrapper.go b/middleware/dnstap/msg/wrapper.go deleted file mode 100644 index a74c604d8..000000000 --- a/middleware/dnstap/msg/wrapper.go +++ /dev/null @@ -1,26 +0,0 @@ -package msg - -import ( - "fmt" - - lib "github.com/dnstap/golang-dnstap" - "github.com/golang/protobuf/proto" -) - -func wrap(m *lib.Message) *lib.Dnstap { - t := lib.Dnstap_MESSAGE - return &lib.Dnstap{ - Type: &t, - Message: m, - } -} - -// Marshal encodes the message to a binary dnstap payload. -func Marshal(m *lib.Message) (data []byte, err error) { - data, err = proto.Marshal(wrap(m)) - if err != nil { - err = fmt.Errorf("proto: %s", err) - return - } - return -} diff --git a/middleware/dnstap/out/socket.go b/middleware/dnstap/out/socket.go deleted file mode 100644 index 520dcf1d8..000000000 --- a/middleware/dnstap/out/socket.go +++ /dev/null @@ -1,86 +0,0 @@ -package out - -import ( - "fmt" - "net" - - fs "github.com/farsightsec/golang-framestream" -) - -// Socket is a Frame Streams encoder over a UNIX socket. -type Socket struct { - path string - enc *fs.Encoder - conn net.Conn - err error -} - -func openSocket(s *Socket) error { - conn, err := net.Dial("unix", s.path) - if err != nil { - return err - } - s.conn = conn - - enc, err := fs.NewEncoder(conn, &fs.EncoderOptions{ - ContentType: []byte("protobuf:dnstap.Dnstap"), - Bidirectional: true, - }) - if err != nil { - return err - } - s.enc = enc - - s.err = nil - return nil -} - -// NewSocket will always return a new Socket. -// err if nothing is listening to it, it will attempt to reconnect on the next Write. -func NewSocket(path string) (s *Socket, err error) { - s = &Socket{path: path} - if err = openSocket(s); err != nil { - err = fmt.Errorf("open socket: %s", err) - s.err = err - return - } - return -} - -// Write a single Frame Streams frame. -func (s *Socket) Write(frame []byte) (int, error) { - if s.err != nil { - // is the dnstap tool listening? - if err := openSocket(s); err != nil { - return 0, fmt.Errorf("open socket: %s", err) - } - } - n, err := s.enc.Write(frame) - if err != nil { - // the dnstap command line tool is down - s.conn.Close() - s.err = err - return 0, err - } - return n, nil - -} - -// Close the socket and flush the remaining frames. -func (s *Socket) Close() error { - if s.err != nil { - // nothing to close - return nil - } - - defer s.conn.Close() - - if err := s.enc.Flush(); err != nil { - return fmt.Errorf("flush: %s", err) - } - if err := s.enc.Close(); err != nil { - return err - } - - return nil -} diff --git a/middleware/dnstap/out/socket_test.go b/middleware/dnstap/out/socket_test.go deleted file mode 100644 index 050a38d36..000000000 --- a/middleware/dnstap/out/socket_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package out - -import ( - "net" - "testing" - - fs "github.com/farsightsec/golang-framestream" -) - -func acceptOne(t *testing.T, l net.Listener) { - server, err := l.Accept() - if err != nil { - t.Fatalf("server accept: %s", err) - return - } - - dec, err := fs.NewDecoder(server, &fs.DecoderOptions{ - ContentType: []byte("protobuf:dnstap.Dnstap"), - Bidirectional: true, - }) - if err != nil { - t.Fatalf("server decoder: %s", err) - return - } - - if _, err := dec.Decode(); err != nil { - t.Errorf("server decode: %s", err) - } - - if err := server.Close(); err != nil { - t.Error(err) - } -} -func sendOne(socket *Socket) error { - if _, err := socket.Write([]byte("frame")); err != nil { - return err - } - if err := socket.enc.Flush(); err != nil { - // Would happen during Write in real life. - socket.conn.Close() - socket.err = err - return err - } - return nil -} -func TestSocket(t *testing.T) { - socket, err := NewSocket("dnstap.sock") - if err == nil { - t.Fatal("new socket: not listening but no error") - return - } - - if err := sendOne(socket); err == nil { - t.Fatal("not listening but no error") - return - } - - l, err := net.Listen("unix", "dnstap.sock") - if err != nil { - t.Fatal(err) - return - } - - wait := make(chan bool) - go func() { - acceptOne(t, l) - wait <- true - }() - - if err := sendOne(socket); err != nil { - t.Fatalf("send one: %s", err) - return - } - - <-wait - if err := sendOne(socket); err == nil { - panic("must fail") - } - - go func() { - acceptOne(t, l) - wait <- true - }() - - if err := sendOne(socket); err != nil { - t.Fatalf("send one: %s", err) - return - } - - <-wait - if err := l.Close(); err != nil { - t.Error(err) - } -} diff --git a/middleware/dnstap/out/tcp.go b/middleware/dnstap/out/tcp.go deleted file mode 100644 index 8d2c25270..000000000 --- a/middleware/dnstap/out/tcp.go +++ /dev/null @@ -1,59 +0,0 @@ -package out - -import ( - "net" - "time" - - fs "github.com/farsightsec/golang-framestream" -) - -// TCP is a Frame Streams encoder over TCP. -type TCP struct { - address string - frames [][]byte -} - -// NewTCP returns a TCP writer. -func NewTCP(address string) *TCP { - s := &TCP{address: address} - s.frames = make([][]byte, 0, 13) // 13 messages buffer - return s -} - -// Write a single Frame Streams frame. -func (s *TCP) Write(frame []byte) (n int, err error) { - s.frames = append(s.frames, frame) - if len(s.frames) == cap(s.frames) { - return len(frame), s.Flush() - } - return len(frame), nil -} - -// Flush the remaining frames. -func (s *TCP) Flush() error { - defer func() { - s.frames = s.frames[0:] - }() - c, err := net.DialTimeout("tcp", s.address, time.Second) - if err != nil { - return err - } - enc, err := fs.NewEncoder(c, &fs.EncoderOptions{ - ContentType: []byte("protobuf:dnstap.Dnstap"), - Bidirectional: true, - }) - if err != nil { - return err - } - for _, frame := range s.frames { - if _, err = enc.Write(frame); err != nil { - return err - } - } - return enc.Flush() -} - -// Close is an alias to Flush to satisfy io.WriteCloser similarly to type Socket. -func (s *TCP) Close() error { - return s.Flush() -} diff --git a/middleware/dnstap/out/tcp_test.go b/middleware/dnstap/out/tcp_test.go deleted file mode 100644 index 113603cd4..000000000 --- a/middleware/dnstap/out/tcp_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package out - -import ( - "net" - "testing" -) - -func sendOneTCP(tcp *TCP) error { - if _, err := tcp.Write([]byte("frame")); err != nil { - return err - } - if err := tcp.Flush(); err != nil { - return err - } - return nil -} -func TestTCP(t *testing.T) { - tcp := NewTCP("localhost:14000") - - if err := sendOneTCP(tcp); err == nil { - t.Fatal("Not listening but no error.") - return - } - - l, err := net.Listen("tcp", "localhost:14000") - if err != nil { - t.Fatal(err) - return - } - - wait := make(chan bool) - go func() { - acceptOne(t, l) - wait <- true - }() - - if err := sendOneTCP(tcp); err != nil { - t.Fatalf("send one: %s", err) - return - } - - <-wait - - // TODO: When the server isn't responding according to the framestream protocol - // the thread is blocked. - /* - if err := sendOneTCP(tcp); err == nil { - panic("must fail") - } - */ - - go func() { - acceptOne(t, l) - wait <- true - }() - - if err := sendOneTCP(tcp); err != nil { - t.Fatalf("send one: %s", err) - return - } - - <-wait - if err := l.Close(); err != nil { - t.Error(err) - } -} diff --git a/middleware/dnstap/setup.go b/middleware/dnstap/setup.go deleted file mode 100644 index 63a3eb099..000000000 --- a/middleware/dnstap/setup.go +++ /dev/null @@ -1,98 +0,0 @@ -package dnstap - -import ( - "fmt" - "io" - "log" - "strings" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/dnstap/out" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyfile" -) - -func init() { - caddy.RegisterPlugin("dnstap", caddy.Plugin{ - ServerType: "dns", - Action: wrapSetup, - }) -} - -func wrapSetup(c *caddy.Controller) error { - if err := setup(c); err != nil { - return middleware.Error("dnstap", err) - } - return nil -} - -type config struct { - target string - socket bool - full bool -} - -func parseConfig(d *caddyfile.Dispenser) (c config, err error) { - d.Next() // directive name - - if !d.Args(&c.target) { - return c, d.ArgErr() - } - - if strings.HasPrefix(c.target, "tcp://") { - // remote IP endpoint - servers, err := dnsutil.ParseHostPortOrFile(c.target[6:]) - if err != nil { - return c, d.ArgErr() - } - c.target = servers[0] - } else { - // default to UNIX socket - if strings.HasPrefix(c.target, "unix://") { - c.target = c.target[7:] - } - c.socket = true - } - - c.full = d.NextArg() && d.Val() == "full" - - return -} - -func setup(c *caddy.Controller) error { - conf, err := parseConfig(&c.Dispenser) - if err != nil { - return err - } - - dnstap := Dnstap{Pack: conf.full} - - var o io.WriteCloser - if conf.socket { - o, err = out.NewSocket(conf.target) - if err != nil { - log.Printf("[WARN] Can't connect to %s at the moment: %s", conf.target, err) - } - } else { - o = out.NewTCP(conf.target) - } - dnstap.Out = o - - c.OnShutdown(func() error { - if err := o.Close(); err != nil { - return fmt.Errorf("output: %s", err) - } - return nil - }) - - dnsserver.GetConfig(c).AddMiddleware( - func(next middleware.Handler) middleware.Handler { - dnstap.Next = next - return dnstap - }) - - return nil -} diff --git a/middleware/dnstap/setup_test.go b/middleware/dnstap/setup_test.go deleted file mode 100644 index fc1dc98e0..000000000 --- a/middleware/dnstap/setup_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package dnstap - -import ( - "github.com/mholt/caddy" - "testing" -) - -func TestConfig(t *testing.T) { - tests := []struct { - file string - path string - full bool - socket bool - fail bool - }{ - {"dnstap dnstap.sock full", "dnstap.sock", true, true, false}, - {"dnstap unix://dnstap.sock", "dnstap.sock", false, true, false}, - {"dnstap tcp://127.0.0.1:6000", "127.0.0.1:6000", false, false, false}, - {"dnstap", "fail", false, true, true}, - } - for _, c := range tests { - cad := caddy.NewTestController("dns", c.file) - conf, err := parseConfig(&cad.Dispenser) - if c.fail { - if err == nil { - t.Errorf("%s: %s", c.file, err) - } - } else if err != nil || conf.target != c.path || - conf.full != c.full || conf.socket != c.socket { - - t.Errorf("expected: %+v\nhave: %+v\nerror: %s\n", c, conf, err) - } - } -} diff --git a/middleware/dnstap/taprw/writer.go b/middleware/dnstap/taprw/writer.go deleted file mode 100644 index 99572afd9..000000000 --- a/middleware/dnstap/taprw/writer.go +++ /dev/null @@ -1,73 +0,0 @@ -// Package taprw takes a query and intercepts the response. -// It will log both after the response is written. -package taprw - -import ( - "fmt" - - "github.com/coredns/coredns/middleware/dnstap/msg" - - tap "github.com/dnstap/golang-dnstap" - "github.com/miekg/dns" -) - -// Tapper is what ResponseWriter needs to log to dnstap. -type Tapper interface { - TapMessage(m *tap.Message) error - TapBuilder() msg.Builder -} - -// ResponseWriter captures the client response and logs the query to dnstap. -// Single request use. -type ResponseWriter struct { - queryEpoch uint64 - Query *dns.Msg - dns.ResponseWriter - Tapper - err error -} - -// DnstapError check if a dnstap error occurred during Write and returns it. -func (w ResponseWriter) DnstapError() error { - return w.err -} - -// QueryEpoch sets the query epoch as reported by dnstap. -func (w *ResponseWriter) QueryEpoch() { - w.queryEpoch = msg.Epoch() -} - -// WriteMsg writes back the response to the client and THEN works on logging the request -// and response to dnstap. -// Dnstap errors are to be checked by DnstapError. -func (w *ResponseWriter) WriteMsg(resp *dns.Msg) (writeErr error) { - writeErr = w.ResponseWriter.WriteMsg(resp) - writeEpoch := msg.Epoch() - - b := w.TapBuilder() - b.TimeSec = w.queryEpoch - if err := func() (err error) { - err = b.AddrMsg(w.ResponseWriter.RemoteAddr(), w.Query) - if err != nil { - return - } - return w.TapMessage(b.ToClientQuery()) - }(); err != nil { - w.err = fmt.Errorf("client query: %s", err) - // don't forget to call DnstapError later - } - - if writeErr == nil { - if err := func() (err error) { - b.TimeSec = writeEpoch - if err = b.Msg(resp); err != nil { - return - } - return w.TapMessage(b.ToClientResponse()) - }(); err != nil { - w.err = fmt.Errorf("client response: %s", err) - } - } - - return -} diff --git a/middleware/dnstap/taprw/writer_test.go b/middleware/dnstap/taprw/writer_test.go deleted file mode 100644 index 426f1f580..000000000 --- a/middleware/dnstap/taprw/writer_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package taprw - -import ( - "errors" - "testing" - - "github.com/coredns/coredns/middleware/dnstap/msg" - "github.com/coredns/coredns/middleware/dnstap/test" - mwtest "github.com/coredns/coredns/middleware/test" - - tap "github.com/dnstap/golang-dnstap" - "github.com/miekg/dns" -) - -type TapFailer struct { -} - -func (TapFailer) TapMessage(*tap.Message) error { - return errors.New("failed") -} -func (TapFailer) TapBuilder() msg.Builder { - return msg.Builder{Full: true} -} - -func TestDnstapError(t *testing.T) { - rw := ResponseWriter{ - Query: new(dns.Msg), - ResponseWriter: &mwtest.ResponseWriter{}, - Tapper: TapFailer{}, - } - if err := rw.WriteMsg(new(dns.Msg)); err != nil { - t.Errorf("dnstap error during Write: %s", err) - } - if rw.DnstapError() == nil { - t.Fatal("no dnstap error") - } -} - -func testingMsg() (m *dns.Msg) { - m = new(dns.Msg) - m.SetQuestion("example.com.", dns.TypeA) - m.SetEdns0(4097, true) - return -} - -func TestClientQueryResponse(t *testing.T) { - trapper := test.TrapTapper{Full: true} - m := testingMsg() - rw := ResponseWriter{ - Query: m, - Tapper: &trapper, - ResponseWriter: &mwtest.ResponseWriter{}, - } - d := test.TestingData() - - // will the wire-format msg be reported? - bin, err := m.Pack() - if err != nil { - t.Fatal(err) - return - } - d.Packed = bin - - if err := rw.WriteMsg(m); err != nil { - t.Fatal(err) - return - } - if l := len(trapper.Trap); l != 2 { - t.Fatalf("%d msg trapped", l) - return - } - want := d.ToClientQuery() - have := trapper.Trap[0] - if !test.MsgEqual(want, have) { - t.Fatalf("query: want: %v\nhave: %v", want, have) - } - want = d.ToClientResponse() - have = trapper.Trap[1] - if !test.MsgEqual(want, have) { - t.Fatalf("response: want: %v\nhave: %v", want, have) - } -} diff --git a/middleware/dnstap/test/helpers.go b/middleware/dnstap/test/helpers.go deleted file mode 100644 index 46ba327ab..000000000 --- a/middleware/dnstap/test/helpers.go +++ /dev/null @@ -1,80 +0,0 @@ -package test - -import ( - "net" - "reflect" - - "github.com/coredns/coredns/middleware/dnstap/msg" - - tap "github.com/dnstap/golang-dnstap" - "golang.org/x/net/context" -) - -// Context is a message trap. -type Context struct { - context.Context - TrapTapper -} - -// TestingData returns the Data matching coredns/test.ResponseWriter. -func TestingData() (d *msg.Data) { - d = &msg.Data{ - SocketFam: tap.SocketFamily_INET, - SocketProto: tap.SocketProtocol_UDP, - Address: net.ParseIP("10.240.0.1"), - Port: 40212, - } - return -} - -type comp struct { - Type *tap.Message_Type - SF *tap.SocketFamily - SP *tap.SocketProtocol - QA []byte - RA []byte - QP *uint32 - RP *uint32 - QTSec bool - RTSec bool - RM []byte - QM []byte -} - -func toComp(m *tap.Message) comp { - return comp{ - Type: m.Type, - SF: m.SocketFamily, - SP: m.SocketProtocol, - QA: m.QueryAddress, - RA: m.ResponseAddress, - QP: m.QueryPort, - RP: m.ResponsePort, - QTSec: m.QueryTimeSec != nil, - RTSec: m.ResponseTimeSec != nil, - RM: m.ResponseMessage, - QM: m.QueryMessage, - } -} - -// MsgEqual compares two dnstap messages ignoring timestamps. -func MsgEqual(a, b *tap.Message) bool { - return reflect.DeepEqual(toComp(a), toComp(b)) -} - -// TrapTapper traps messages. -type TrapTapper struct { - Trap []*tap.Message - Full bool -} - -// TapMessage adds the message to the trap. -func (t *TrapTapper) TapMessage(m *tap.Message) error { - t.Trap = append(t.Trap, m) - return nil -} - -// TapBuilder returns a test msg.Builder. -func (t *TrapTapper) TapBuilder() msg.Builder { - return msg.Builder{Full: t.Full} -} diff --git a/middleware/erratic/README.md b/middleware/erratic/README.md deleted file mode 100644 index a84146fbb..000000000 --- a/middleware/erratic/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# erratic - -*erratic* is a middleware useful for testing client behavior. It returns a static response to all -queries, but the responses can be delayed, dropped or truncated. - -The *erratic* middleware will respond to every A or AAAA query. For any other type it will return -a SERVFAIL response. The reply for A will return 192.0.2.53 (see RFC 5737), for AAAA it returns -2001:DB8::53 (see RFC 3849). - -*erratic* can also be used in conjunction with the *autopath* middleware. This is mostly to aid in - testing. - -## Syntax - -~~~ txt -erratic { - drop [AMOUNT] - truncate [AMOUNT] - delay [AMOUNT [DURATION]] -} -~~~ - -* `drop`: drop 1 per **AMOUNT** of queries, the default is 2. -* `truncate`: truncate 1 per **AMOUNT** of queries, the default is 2. -* `delay`: delay 1 per **AMOUNT** of queries for **DURATION**, the default for **AMOUNT** is 2 and - the default for **DURATION** is 100ms. - -## Examples - -~~~ txt -.:53 { - erratic { - drop 3 - } -} -~~~ - -Or even shorter if the defaults suits you. Note this only drops queries, it does not delay them. - -~~~ txt -. { - erratic -} -~~~ - -Delay 1 in 3 queries for 50ms - -~~~ txt -. { - erratic { - delay 3 50ms - } -} -~~~ - -Delay 1 in 3 and truncate 1 in 5. - -~~~ txt -. { - erratic { - delay 3 5ms - truncate 5 - } -} -~~~ - -Drop every second query. - -~~~ txt -. { - erratic { - drop 2 - truncate 2 - } -} -~~~ diff --git a/middleware/erratic/autopath.go b/middleware/erratic/autopath.go deleted file mode 100644 index 79b0ec847..000000000 --- a/middleware/erratic/autopath.go +++ /dev/null @@ -1,8 +0,0 @@ -package erratic - -import "github.com/coredns/coredns/request" - -// AutoPath implements the AutoPathFunc call from the autopath middleware. -func (e *Erratic) AutoPath(state request.Request) []string { - return []string{"a.example.org.", "b.example.org.", ""} -} diff --git a/middleware/erratic/erratic.go b/middleware/erratic/erratic.go deleted file mode 100644 index b05e45f03..000000000 --- a/middleware/erratic/erratic.go +++ /dev/null @@ -1,95 +0,0 @@ -// Package erratic implements a middleware that returns erratic answers (delayed, dropped). -package erratic - -import ( - "sync/atomic" - "time" - - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Erratic is a middleware that returns erratic repsonses to each client. -type Erratic struct { - drop uint64 - - delay uint64 - duration time.Duration - - truncate uint64 - - q uint64 // counter of queries -} - -// ServeDNS implements the middleware.Handler interface. -func (e *Erratic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - drop := false - delay := false - trunc := false - - queryNr := atomic.LoadUint64(&e.q) - atomic.AddUint64(&e.q, 1) - - if e.drop > 0 && queryNr%e.drop == 0 { - drop = true - } - if e.delay > 0 && queryNr%e.delay == 0 { - delay = true - } - if e.truncate > 0 && queryNr&e.truncate == 0 { - trunc = true - } - - m := new(dns.Msg) - m.SetReply(r) - m.Compress = true - m.Authoritative = true - if trunc { - m.Truncated = true - } - - // small dance to copy rrA or rrAAAA into a non-pointer var that allows us to overwrite the ownername - // in a non-racy way. - switch state.QType() { - case dns.TypeA: - rr := *(rrA.(*dns.A)) - rr.Header().Name = state.QName() - m.Answer = append(m.Answer, &rr) - case dns.TypeAAAA: - rr := *(rrAAAA.(*dns.AAAA)) - rr.Header().Name = state.QName() - m.Answer = append(m.Answer, &rr) - default: - if !drop { - if delay { - time.Sleep(e.duration) - } - // coredns will return error. - return dns.RcodeServerFailure, nil - } - } - - if drop { - return 0, nil - } - - if delay { - time.Sleep(e.duration) - } - - state.SizeAndDo(m) - w.WriteMsg(m) - - return 0, nil -} - -// Name implements the Handler interface. -func (e *Erratic) Name() string { return "erratic" } - -var ( - rrA, _ = dns.NewRR(". IN 0 A 192.0.2.53") - rrAAAA, _ = dns.NewRR(". IN 0 AAAA 2001:DB8::53") -) diff --git a/middleware/erratic/erratic_test.go b/middleware/erratic/erratic_test.go deleted file mode 100644 index 4b54e0c12..000000000 --- a/middleware/erratic/erratic_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package erratic - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestErraticDrop(t *testing.T) { - e := &Erratic{drop: 2} // 50% drops - - tests := []struct { - expectedCode int - expectedErr error - drop bool - }{ - {expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: true}, - {expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: false}, - } - - ctx := context.TODO() - - for i, tc := range tests { - req := new(dns.Msg) - req.SetQuestion("example.org.", dns.TypeA) - - rec := dnsrecorder.New(&test.ResponseWriter{}) - code, err := e.ServeDNS(ctx, rec, req) - - if err != tc.expectedErr { - t.Errorf("Test %d: Expected error %q, but got %q", i, tc.expectedErr, err) - } - if code != int(tc.expectedCode) { - t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code) - } - - if tc.drop && rec.Msg != nil { - t.Errorf("Test %d: Expected dropped message, but got %q", i, rec.Msg.Question[0].Name) - } - } -} - -func TestErraticTruncate(t *testing.T) { - e := &Erratic{truncate: 2} // 50% drops - - tests := []struct { - expectedCode int - expectedErr error - truncate bool - }{ - {expectedCode: dns.RcodeSuccess, expectedErr: nil, truncate: true}, - {expectedCode: dns.RcodeSuccess, expectedErr: nil, truncate: false}, - } - - ctx := context.TODO() - - for i, tc := range tests { - req := new(dns.Msg) - req.SetQuestion("example.org.", dns.TypeA) - - rec := dnsrecorder.New(&test.ResponseWriter{}) - code, err := e.ServeDNS(ctx, rec, req) - - if err != tc.expectedErr { - t.Errorf("Test %d: Expected error %q, but got %q", i, tc.expectedErr, err) - } - if code != int(tc.expectedCode) { - t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code) - } - - if tc.truncate && !rec.Msg.Truncated { - t.Errorf("Test %d: Expected truncated message, but got %q", i, rec.Msg.Question[0].Name) - } - } -} diff --git a/middleware/erratic/setup.go b/middleware/erratic/setup.go deleted file mode 100644 index 98db02247..000000000 --- a/middleware/erratic/setup.go +++ /dev/null @@ -1,117 +0,0 @@ -package erratic - -import ( - "fmt" - "strconv" - "time" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("erratic", caddy.Plugin{ - ServerType: "dns", - Action: setupErratic, - }) -} - -func setupErratic(c *caddy.Controller) error { - e, err := parseErratic(c) - if err != nil { - return middleware.Error("erratic", err) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return e - }) - - return nil -} - -func parseErratic(c *caddy.Controller) (*Erratic, error) { - e := &Erratic{drop: 2} - drop := false // true if we've seen the drop keyword - - for c.Next() { // 'erratic' - for c.NextBlock() { - switch c.Val() { - case "drop": - args := c.RemainingArgs() - if len(args) > 1 { - return nil, c.ArgErr() - } - - if len(args) == 0 { - continue - } - - amount, err := strconv.ParseInt(args[0], 10, 32) - if err != nil { - return nil, err - } - if amount < 0 { - return nil, fmt.Errorf("illegal amount value given %q", args[0]) - } - e.drop = uint64(amount) - drop = true - case "delay": - args := c.RemainingArgs() - if len(args) > 2 { - return nil, c.ArgErr() - } - - // Defaults. - e.delay = 2 - e.duration = 100 * time.Millisecond - if len(args) == 0 { - continue - } - - amount, err := strconv.ParseInt(args[0], 10, 32) - if err != nil { - return nil, err - } - if amount < 0 { - return nil, fmt.Errorf("illegal amount value given %q", args[0]) - } - e.delay = uint64(amount) - - if len(args) > 1 { - duration, err := time.ParseDuration(args[1]) - if err != nil { - return nil, err - } - e.duration = duration - } - case "truncate": - args := c.RemainingArgs() - if len(args) > 1 { - return nil, c.ArgErr() - } - - if len(args) == 0 { - continue - } - - amount, err := strconv.ParseInt(args[0], 10, 32) - if err != nil { - return nil, err - } - if amount < 0 { - return nil, fmt.Errorf("illegal amount value given %q", args[0]) - } - e.truncate = uint64(amount) - default: - return nil, c.Errf("unknown property '%s'", c.Val()) - } - } - } - if (e.delay > 0 || e.truncate > 0) && !drop { // delay is set, but we've haven't seen a drop keyword, remove default drop stuff - e.drop = 0 - } - - return e, nil -} diff --git a/middleware/erratic/setup_test.go b/middleware/erratic/setup_test.go deleted file mode 100644 index 759845f7a..000000000 --- a/middleware/erratic/setup_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package erratic - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestSetupErratic(t *testing.T) { - c := caddy.NewTestController("dns", `erratic { - drop - }`) - if err := setupErratic(c); err != nil { - t.Fatalf("Test 1, expected no errors, but got: %q", err) - } - - c = caddy.NewTestController("dns", `erratic`) - if err := setupErratic(c); err != nil { - t.Fatalf("Test 2, expected no errors, but got: %q", err) - } - - c = caddy.NewTestController("dns", `erratic { - drop -1 - }`) - if err := setupErratic(c); err == nil { - t.Fatalf("Test 4, expected errors, but got: %q", err) - } -} - -func TestParseErratic(t *testing.T) { - tests := []struct { - input string - shouldErr bool - drop uint64 - delay uint64 - truncate uint64 - }{ - // oks - {`erratic`, false, 2, 0, 0}, - {`erratic { - drop 2 - delay 3 1ms - - }`, false, 2, 3, 0}, - {`erratic { - truncate 2 - delay 3 1ms - - }`, false, 0, 3, 2}, - {`erraric { - drop 3 - delay - }`, false, 3, 2, 0}, - // fails - {`erratic { - drop -1 - }`, true, 0, 0, 0}, - {`erratic { - delay -1 - }`, true, 0, 0, 0}, - {`erratic { - delay 1 2 4 - }`, true, 0, 0, 0}, - {`erratic { - delay 15.a - }`, true, 0, 0, 0}, - {`erraric { - drop 3 - delay 3 bla - }`, true, 0, 0, 0}, - {`erraric { - truncate 15.a - }`, true, 0, 0, 0}, - {`erraric { - something-else - }`, true, 0, 0, 0}, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - e, err := parseErratic(c) - if test.shouldErr && err == nil { - t.Errorf("Test %v: Expected error but found nil", i) - continue - } else if !test.shouldErr && err != nil { - t.Errorf("Test %v: Expected no error but found error: %v", i, err) - continue - } - - if test.shouldErr { - continue - } - - if test.delay != e.delay { - t.Errorf("Test %v: Expected delay %d but found: %d", i, test.delay, e.delay) - } - if test.drop != e.drop { - t.Errorf("Test %v: Expected drop %d but found: %d", i, test.drop, e.drop) - } - if test.truncate != e.truncate { - t.Errorf("Test %v: Expected truncate %d but found: %d", i, test.truncate, e.truncate) - } - } -} diff --git a/middleware/errors/README.md b/middleware/errors/README.md deleted file mode 100644 index 21b8f4848..000000000 --- a/middleware/errors/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# errors - -*errors* enables error logging. - -Any errors encountered during the query processing will be printed to standard output. - -## Syntax - -~~~ -errors -~~~ - -## Examples - -Use the *whoami* to respond to queries and Log errors to standard output. - -~~~ corefile -. { - whoami - errors -} -~~~ diff --git a/middleware/errors/errors.go b/middleware/errors/errors.go deleted file mode 100644 index 9b0bfa946..000000000 --- a/middleware/errors/errors.go +++ /dev/null @@ -1,79 +0,0 @@ -// Package errors implements an HTTP error handling middleware. -package errors - -import ( - "fmt" - "log" - "runtime" - "strings" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// errorHandler handles DNS errors (and errors from other middleware). -type errorHandler struct { - Next middleware.Handler - LogFile string - Log *log.Logger -} - -// ServeDNS implements the middleware.Handler interface. -func (h errorHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - defer h.recovery(ctx, w, r) - - rcode, err := middleware.NextOrFailure(h.Name(), h.Next, ctx, w, r) - - if err != nil { - state := request.Request{W: w, Req: r} - errMsg := fmt.Sprintf("%s [ERROR %d %s %s] %v", time.Now().Format(timeFormat), rcode, state.Name(), state.Type(), err) - - h.Log.Println(errMsg) - } - - return rcode, err -} - -func (h errorHandler) Name() string { return "errors" } - -func (h errorHandler) recovery(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) { - rec := recover() - if rec == nil { - return - } - - // Obtain source of panic - // From: https://gist.github.com/swdunlop/9629168 - var name, file string // function name, file name - var line int - var pc [16]uintptr - n := runtime.Callers(3, pc[:]) - for _, pc := range pc[:n] { - fn := runtime.FuncForPC(pc) - if fn == nil { - continue - } - file, line = fn.FileLine(pc) - name = fn.Name() - if !strings.HasPrefix(name, "runtime.") { - break - } - } - - // Trim file path - delim := "/coredns/" - pkgPathPos := strings.Index(file, delim) - if pkgPathPos > -1 && len(file) > pkgPathPos+len(delim) { - file = file[pkgPathPos+len(delim):] - } - - panicMsg := fmt.Sprintf("%s [PANIC %s %s] %s:%d - %v", time.Now().Format(timeFormat), r.Question[0].Name, dns.Type(r.Question[0].Qtype), file, line, rec) - // Currently we don't use the function name, since file:line is more conventional - h.Log.Printf(panicMsg) -} - -const timeFormat = "02/Jan/2006:15:04:05 -0700" diff --git a/middleware/errors/errors_test.go b/middleware/errors/errors_test.go deleted file mode 100644 index 5e565964c..000000000 --- a/middleware/errors/errors_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package errors - -import ( - "bytes" - "errors" - "fmt" - "log" - "strings" - "testing" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestErrors(t *testing.T) { - buf := bytes.Buffer{} - em := errorHandler{Log: log.New(&buf, "", 0)} - - testErr := errors.New("test error") - tests := []struct { - next middleware.Handler - expectedCode int - expectedLog string - expectedErr error - }{ - { - next: genErrorHandler(dns.RcodeSuccess, nil), - expectedCode: dns.RcodeSuccess, - expectedLog: "", - expectedErr: nil, - }, - { - next: genErrorHandler(dns.RcodeNotAuth, testErr), - expectedCode: dns.RcodeNotAuth, - expectedLog: fmt.Sprintf("[ERROR %d %s] %v\n", dns.RcodeNotAuth, "example.org. A", testErr), - expectedErr: testErr, - }, - } - - ctx := context.TODO() - req := new(dns.Msg) - req.SetQuestion("example.org.", dns.TypeA) - - for i, tc := range tests { - em.Next = tc.next - buf.Reset() - rec := dnsrecorder.New(&test.ResponseWriter{}) - code, err := em.ServeDNS(ctx, rec, req) - - if err != tc.expectedErr { - t.Errorf("Test %d: Expected error %v, but got %v", - i, tc.expectedErr, err) - } - if code != tc.expectedCode { - t.Errorf("Test %d: Expected status code %d, but got %d", - i, tc.expectedCode, code) - } - if log := buf.String(); !strings.Contains(log, tc.expectedLog) { - t.Errorf("Test %d: Expected log %q, but got %q", - i, tc.expectedLog, log) - } - } -} - -func genErrorHandler(rcode int, err error) middleware.Handler { - return middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - return rcode, err - }) -} diff --git a/middleware/errors/setup.go b/middleware/errors/setup.go deleted file mode 100644 index af9a7bbff..000000000 --- a/middleware/errors/setup.go +++ /dev/null @@ -1,55 +0,0 @@ -package errors - -import ( - "fmt" - "log" - "os" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("errors", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - handler, err := errorsParse(c) - if err != nil { - return middleware.Error("errors", err) - } - - handler.Log = log.New(os.Stdout, "", 0) - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - handler.Next = next - return handler - }) - - return nil -} - -func errorsParse(c *caddy.Controller) (errorHandler, error) { - handler := errorHandler{} - - for c.Next() { - args := c.RemainingArgs() - switch len(args) { - case 0: - handler.LogFile = "stdout" - case 1: - if args[0] != "stdout" { - return handler, fmt.Errorf("invalid log file: %s", args[0]) - } - handler.LogFile = args[0] - default: - return handler, c.ArgErr() - } - } - return handler, nil -} diff --git a/middleware/errors/setup_test.go b/middleware/errors/setup_test.go deleted file mode 100644 index bae85da32..000000000 --- a/middleware/errors/setup_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package errors - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestErrorsParse(t *testing.T) { - tests := []struct { - inputErrorsRules string - shouldErr bool - expectedErrorHandler errorHandler - }{ - {`errors`, false, errorHandler{ - LogFile: "stdout", - }}, - {`errors stdout`, false, errorHandler{ - LogFile: "stdout", - }}, - {`errors errors.txt`, true, errorHandler{ - LogFile: "", - }}, - {`errors visible`, true, errorHandler{ - LogFile: "", - }}, - {`errors { log visible }`, true, errorHandler{ - LogFile: "stdout", - }}, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.inputErrorsRules) - actualErrorsRule, err := errorsParse(c) - - if err == nil && test.shouldErr { - t.Errorf("Test %d didn't error, but it should have", i) - } else if err != nil && !test.shouldErr { - t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) - } - if actualErrorsRule.LogFile != test.expectedErrorHandler.LogFile { - t.Errorf("Test %d expected LogFile to be %s, but got %s", - i, test.expectedErrorHandler.LogFile, actualErrorsRule.LogFile) - } - } -} diff --git a/middleware/etcd/README.md b/middleware/etcd/README.md deleted file mode 100644 index f7991eb7c..000000000 --- a/middleware/etcd/README.md +++ /dev/null @@ -1,109 +0,0 @@ -# etcd - -*etcd* enables reading zone data from an etcd instance. The data in etcd has to be encoded as -a [message](https://github.com/skynetservices/skydns/blob/2fcff74cdc9f9a7dd64189a447ef27ac354b725f/msg/service.go#L26) -like [SkyDNS](https://github.com/skynetservices/skydns). It should also work just like SkyDNS. - -The etcd middleware makes extensive use of the proxy middleware to forward and query other servers -in the network. - -## Syntax - -~~~ -etcd [ZONES...] -~~~ - -* **ZONES** zones etcd should be authoritative for. - -The path will default to `/skydns` the local etcd proxy (http://localhost:2379). -If no zones are specified the block's zone will be used as the zone. - -If you want to `round robin` A and AAAA responses look at the `loadbalance` middleware. - -~~~ -etcd [ZONES...] { - stubzones - fallthrough - path PATH - endpoint ENDPOINT... - upstream ADDRESS... - tls CERT KEY CACERT -} -~~~ - -* `stubzones` enables the stub zones feature. The stubzone is *only* done in the etcd tree located - under the *first* zone specified. -* `fallthrough` If zone matches but no record can be generated, pass request to the next middleware. -* **PATH** the path inside etcd. Defaults to "/skydns". -* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397". -* `upstream` upstream resolvers to be used resolve external names found in etcd (think CNAMEs) - pointing to external names. If you want CoreDNS to act as a proxy for clients, you'll need to add - the proxy middleware. **ADDRESS** can be an IP address, and IP:port or a string pointing to a file - that is structured as /etc/resolv.conf. -* `tls` followed by: - * no arguments, if the server certificate is signed by a system-installed CA and no client cert is needed - * a single argument that is the CA PEM file, if the server cert is not signed by a system CA and no client cert is needed - * two arguments - path to cert PEM file, the path to private key PEM file - if the server certificate is signed by a system-installed CA and a client certificate is needed - * three arguments - path to cert PEM file, path to client private key PEM file, path to CA PEM file - if the server certificate is not signed by a system-installed CA and client certificate is needed - -## Examples - -This is the default SkyDNS setup, with everying specified in full: - -~~~ -.:53 { - etcd skydns.local { - stubzones - path /skydns - endpoint http://localhost:2379 - upstream 8.8.8.8:53 8.8.4.4:53 - } - prometheus - cache 160 skydns.local - loadbalance - proxy . 8.8.8.8:53 8.8.4.4:53 -} -~~~ - -Or a setup where we use `/etc/resolv.conf` as the basis for the proxy and the upstream -when resolving external pointing CNAMEs. - -~~~ -.:53 { - etcd skydns.local { - path /skydns - upstream /etc/resolv.conf - } - cache 160 skydns.local - proxy . /etc/resolv.conf -} -~~~ - - -### Reverse zones - -Reverse zones are supported. You need to make CoreDNS aware of the fact that you are also -authoritative for the reverse. For instance if you want to add the reverse for 10.0.0.0/24, you'll -need to add the zone `0.0.10.in-addr.arpa` to the list of zones. (The fun starts with IPv6 reverse zones -in the ip6.arpa domain.) Showing a snippet of a Corefile: - -~~~ - etcd skydns.local 0.0.10.in-addr.arpa { - stubzones - ... -~~~ - -Next you'll need to populate the zone with reverse records, here we add a reverse for -10.0.0.127 pointing to reverse.skydns.local. - -~~~ -% curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/arpa/in-addr/10/0/0/127 \ - -d value='{"host":"reverse.skydns.local."}' -~~~ - -Querying with dig: - -~~~ -% dig @localhost -x 10.0.0.127 +short -reverse.atoom.net. -~~~ diff --git a/middleware/etcd/cname_test.go b/middleware/etcd/cname_test.go deleted file mode 100644 index 4c39491fd..000000000 --- a/middleware/etcd/cname_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// +build etcd - -package etcd - -// etcd needs to be running on http://localhost:2379 - -import ( - "testing" - - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" -) - -// Check the ordering of returned cname. -func TestCnameLookup(t *testing.T) { - etc := newEtcdMiddleware() - - for _, serv := range servicesCname { - set(t, etc, serv.Key, 0, serv) - defer delete(t, etc, serv.Key) - } - for _, tc := range dnsTestCasesCname { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := etc.ServeDNS(ctxt, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - return - } - - resp := rec.Msg - if !test.Header(t, tc, resp) { - t.Logf("%v\n", resp) - continue - } - if !test.Section(t, tc, test.Answer, resp.Answer) { - t.Logf("%v\n", resp) - } - if !test.Section(t, tc, test.Ns, resp.Ns) { - t.Logf("%v\n", resp) - } - if !test.Section(t, tc, test.Extra, resp.Extra) { - t.Logf("%v\n", resp) - } - } -} - -var servicesCname = []*msg.Service{ - {Host: "cname1.region2.skydns.test", Key: "a.server1.dev.region1.skydns.test."}, - {Host: "cname2.region2.skydns.test", Key: "cname1.region2.skydns.test."}, - {Host: "cname3.region2.skydns.test", Key: "cname2.region2.skydns.test."}, - {Host: "cname4.region2.skydns.test", Key: "cname3.region2.skydns.test."}, - {Host: "cname5.region2.skydns.test", Key: "cname4.region2.skydns.test."}, - {Host: "cname6.region2.skydns.test", Key: "cname5.region2.skydns.test."}, - {Host: "endpoint.region2.skydns.test", Key: "cname6.region2.skydns.test."}, - {Host: "10.240.0.1", Key: "endpoint.region2.skydns.test."}, -} - -var dnsTestCasesCname = []test.Case{ - { - Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{ - test.SRV("a.server1.dev.region1.skydns.test. 300 IN SRV 10 100 0 cname1.region2.skydns.test."), - }, - Extra: []dns.RR{ - test.CNAME("cname1.region2.skydns.test. 300 IN CNAME cname2.region2.skydns.test."), - test.CNAME("cname2.region2.skydns.test. 300 IN CNAME cname3.region2.skydns.test."), - test.CNAME("cname3.region2.skydns.test. 300 IN CNAME cname4.region2.skydns.test."), - test.CNAME("cname4.region2.skydns.test. 300 IN CNAME cname5.region2.skydns.test."), - test.CNAME("cname5.region2.skydns.test. 300 IN CNAME cname6.region2.skydns.test."), - test.CNAME("cname6.region2.skydns.test. 300 IN CNAME endpoint.region2.skydns.test."), - test.A("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"), - }, - }, -} diff --git a/middleware/etcd/etcd.go b/middleware/etcd/etcd.go deleted file mode 100644 index 5ef6e0a47..000000000 --- a/middleware/etcd/etcd.go +++ /dev/null @@ -1,188 +0,0 @@ -// Package etcd provides the etcd backend middleware. -package etcd - -import ( - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/cache" - "github.com/coredns/coredns/middleware/pkg/singleflight" - "github.com/coredns/coredns/middleware/proxy" - "github.com/coredns/coredns/request" - - etcdc "github.com/coreos/etcd/client" - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Etcd is a middleware talks to an etcd cluster. -type Etcd struct { - Next middleware.Handler - Fallthrough bool - Zones []string - PathPrefix string - Proxy proxy.Proxy // Proxy for looking up names during the resolution process - Client etcdc.KeysAPI - Ctx context.Context - Inflight *singleflight.Group - Stubmap *map[string]proxy.Proxy // list of proxies for stub resolving. - - endpoints []string // Stored here as well, to aid in testing. -} - -// Services implements the ServiceBackend interface. -func (e *Etcd) Services(state request.Request, exact bool, opt middleware.Options) (services []msg.Service, err error) { - services, err = e.Records(state, exact) - if err != nil { - return - } - - services = msg.Group(services) - return -} - -// Reverse implements the ServiceBackend interface. -func (e *Etcd) Reverse(state request.Request, exact bool, opt middleware.Options) (services []msg.Service, err error) { - return e.Services(state, exact, opt) -} - -// Lookup implements the ServiceBackend interface. -func (e *Etcd) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) { - return e.Proxy.Lookup(state, name, typ) -} - -// IsNameError implements the ServiceBackend interface. -func (e *Etcd) IsNameError(err error) bool { - if ee, ok := err.(etcdc.Error); ok && ee.Code == etcdc.ErrorCodeKeyNotFound { - return true - } - return false -} - -// Records looks up records in etcd. If exact is true, it will lookup just this -// name. This is used when find matches when completing SRV lookups for instance. -func (e *Etcd) Records(state request.Request, exact bool) ([]msg.Service, error) { - name := state.Name() - - path, star := msg.PathWithWildcard(name, e.PathPrefix) - r, err := e.get(path, true) - if err != nil { - return nil, err - } - segments := strings.Split(msg.Path(name, e.PathPrefix), "/") - switch { - case exact && r.Node.Dir: - return nil, nil - case r.Node.Dir: - return e.loopNodes(r.Node.Nodes, segments, star, nil) - default: - return e.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil) - } -} - -// get is a wrapper for client.Get that uses SingleInflight to suppress multiple outstanding queries. -func (e *Etcd) get(path string, recursive bool) (*etcdc.Response, error) { - - hash := cache.Hash([]byte(path)) - - resp, err := e.Inflight.Do(hash, func() (interface{}, error) { - ctx, cancel := context.WithTimeout(e.Ctx, etcdTimeout) - defer cancel() - r, e := e.Client.Get(ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive}) - if e != nil { - return nil, e - } - return r, e - }) - if err != nil { - return nil, err - } - return resp.(*etcdc.Response), err -} - -// skydns/local/skydns/east/staging/web -// skydns/local/skydns/west/production/web -// -// skydns/local/skydns/*/*/web -// skydns/local/skydns/*/web - -// loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname -// will be match against any wildcards when star is true. -func (e *Etcd) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[msg.Service]bool) (sx []msg.Service, err error) { - if bx == nil { - bx = make(map[msg.Service]bool) - } -Nodes: - for _, n := range ns { - if n.Dir { - nodes, err := e.loopNodes(n.Nodes, nameParts, star, bx) - if err != nil { - return nil, err - } - sx = append(sx, nodes...) - continue - } - if star { - keyParts := strings.Split(n.Key, "/") - for i, n := range nameParts { - if i > len(keyParts)-1 { - // name is longer than key - continue Nodes - } - if n == "*" || n == "any" { - continue - } - if keyParts[i] != n { - continue Nodes - } - } - } - serv := new(msg.Service) - if err := json.Unmarshal([]byte(n.Value), serv); err != nil { - return nil, fmt.Errorf("%s: %s", n.Key, err.Error()) - } - b := msg.Service{Host: serv.Host, Port: serv.Port, Priority: serv.Priority, Weight: serv.Weight, Text: serv.Text, Key: n.Key} - if _, ok := bx[b]; ok { - continue - } - bx[b] = true - - serv.Key = n.Key - serv.TTL = e.TTL(n, serv) - if serv.Priority == 0 { - serv.Priority = priority - } - sx = append(sx, *serv) - } - return sx, nil -} - -// TTL returns the smaller of the etcd TTL and the service's -// TTL. If neither of these are set (have a zero value), a default is used. -func (e *Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 { - etcdTTL := uint32(node.TTL) - - if etcdTTL == 0 && serv.TTL == 0 { - return ttl - } - if etcdTTL == 0 { - return serv.TTL - } - if serv.TTL == 0 { - return etcdTTL - } - if etcdTTL < serv.TTL { - return etcdTTL - } - return serv.TTL -} - -const ( - priority = 10 // default priority when nothing is set - ttl = 300 // default ttl when nothing is set - etcdTimeout = 5 * time.Second -) diff --git a/middleware/etcd/group_test.go b/middleware/etcd/group_test.go deleted file mode 100644 index 43ce4754a..000000000 --- a/middleware/etcd/group_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// +build etcd - -package etcd - -import ( - "testing" - - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" -) - -func TestGroupLookup(t *testing.T) { - etc := newEtcdMiddleware() - - for _, serv := range servicesGroup { - set(t, etc, serv.Key, 0, serv) - defer delete(t, etc, serv.Key) - } - for _, tc := range dnsTestCasesGroup { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := etc.ServeDNS(ctxt, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - continue - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -// Note the key is encoded as DNS name, while in "reality" it is a etcd path. -var servicesGroup = []*msg.Service{ - {Host: "127.0.0.1", Key: "a.dom.skydns.test.", Group: "g1"}, - {Host: "127.0.0.2", Key: "b.sub.dom.skydns.test.", Group: "g1"}, - - {Host: "127.0.0.1", Key: "a.dom2.skydns.test.", Group: "g1"}, - {Host: "127.0.0.2", Key: "b.sub.dom2.skydns.test.", Group: ""}, - - {Host: "127.0.0.1", Key: "a.dom1.skydns.test.", Group: "g1"}, - {Host: "127.0.0.2", Key: "b.sub.dom1.skydns.test.", Group: "g2"}, -} - -var dnsTestCasesGroup = []test.Case{ - // Groups - { - // hits the group 'g1' and only includes those records - Qname: "dom.skydns.test.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("dom.skydns.test. 300 IN A 127.0.0.1"), - test.A("dom.skydns.test. 300 IN A 127.0.0.2"), - }, - }, - { - // One has group, the other has not... Include the non-group always. - Qname: "dom2.skydns.test.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("dom2.skydns.test. 300 IN A 127.0.0.1"), - test.A("dom2.skydns.test. 300 IN A 127.0.0.2"), - }, - }, - { - // The groups differ. - Qname: "dom1.skydns.test.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("dom1.skydns.test. 300 IN A 127.0.0.1"), - }, - }, -} diff --git a/middleware/etcd/handler.go b/middleware/etcd/handler.go deleted file mode 100644 index 7857b1955..000000000 --- a/middleware/etcd/handler.go +++ /dev/null @@ -1,97 +0,0 @@ -package etcd - -import ( - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// ServeDNS implements the middleware.Handler interface. -func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - opt := middleware.Options{} - state := request.Request{W: w, Req: r} - - name := state.Name() - - // We need to check stubzones first, because we may get a request for a zone we - // are not auth. for *but* do have a stubzone forward for. If we do the stubzone - // handler will handle the request. - if e.Stubmap != nil && len(*e.Stubmap) > 0 { - for zone := range *e.Stubmap { - if middleware.Name(zone).Matches(name) { - stub := Stub{Etcd: e, Zone: zone} - return stub.ServeDNS(ctx, w, r) - } - } - } - - zone := middleware.Zones(e.Zones).Matches(state.Name()) - if zone == "" { - return middleware.NextOrFailure(e.Name(), e.Next, ctx, w, r) - } - - var ( - records, extra []dns.RR - err error - ) - switch state.Type() { - case "A": - records, err = middleware.A(e, zone, state, nil, opt) - case "AAAA": - records, err = middleware.AAAA(e, zone, state, nil, opt) - case "TXT": - records, err = middleware.TXT(e, zone, state, opt) - case "CNAME": - records, err = middleware.CNAME(e, zone, state, opt) - case "PTR": - records, err = middleware.PTR(e, zone, state, opt) - case "MX": - records, extra, err = middleware.MX(e, zone, state, opt) - case "SRV": - records, extra, err = middleware.SRV(e, zone, state, opt) - case "SOA": - records, err = middleware.SOA(e, zone, state, opt) - case "NS": - if state.Name() == zone { - records, extra, err = middleware.NS(e, zone, state, opt) - break - } - fallthrough - default: - // Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN - _, err = middleware.A(e, zone, state, nil, opt) - } - - if e.IsNameError(err) { - if e.Fallthrough { - return middleware.NextOrFailure(e.Name(), e.Next, ctx, w, r) - } - // Make err nil when returning here, so we don't log spam for NXDOMAIN. - return middleware.BackendError(e, zone, dns.RcodeNameError, state, nil /* err */, opt) - } - if err != nil { - return middleware.BackendError(e, zone, dns.RcodeServerFailure, state, err, opt) - } - - if len(records) == 0 { - return middleware.BackendError(e, zone, dns.RcodeSuccess, state, err, opt) - } - - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - m.Answer = append(m.Answer, records...) - m.Extra = append(m.Extra, extra...) - - m = dnsutil.Dedup(m) - state.SizeAndDo(m) - m, _ = state.Scrub(m) - w.WriteMsg(m) - return dns.RcodeSuccess, nil -} - -// Name implements the Handler interface. -func (e *Etcd) Name() string { return "etcd" } diff --git a/middleware/etcd/lookup_test.go b/middleware/etcd/lookup_test.go deleted file mode 100644 index 010bd66df..000000000 --- a/middleware/etcd/lookup_test.go +++ /dev/null @@ -1,273 +0,0 @@ -// +build etcd - -package etcd - -import ( - "context" - "encoding/json" - "testing" - "time" - - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/pkg/singleflight" - "github.com/coredns/coredns/middleware/pkg/tls" - "github.com/coredns/coredns/middleware/proxy" - "github.com/coredns/coredns/middleware/test" - - etcdc "github.com/coreos/etcd/client" - "github.com/miekg/dns" -) - -func init() { - ctxt = context.TODO() -} - -// Note the key is encoded as DNS name, while in "reality" it is a etcd path. -var services = []*msg.Service{ - {Host: "dev.server1", Port: 8080, Key: "a.server1.dev.region1.skydns.test."}, - {Host: "10.0.0.1", Port: 8080, Key: "a.server1.prod.region1.skydns.test."}, - {Host: "10.0.0.2", Port: 8080, Key: "b.server1.prod.region1.skydns.test."}, - {Host: "::1", Port: 8080, Key: "b.server6.prod.region1.skydns.test."}, - // Unresolvable internal name. - {Host: "unresolvable.skydns.test", Key: "cname.prod.region1.skydns.test."}, - // Priority. - {Host: "priority.server1", Priority: 333, Port: 8080, Key: "priority.skydns.test."}, - // Subdomain. - {Host: "sub.server1", Port: 0, Key: "a.sub.region1.skydns.test."}, - {Host: "sub.server2", Port: 80, Key: "b.sub.region1.skydns.test."}, - {Host: "10.0.0.1", Port: 8080, Key: "c.sub.region1.skydns.test."}, - // Cname loop. - {Host: "a.cname.skydns.test", Key: "b.cname.skydns.test."}, - {Host: "b.cname.skydns.test", Key: "a.cname.skydns.test."}, - // Nameservers. - {Host: "10.0.0.2", Key: "a.ns.dns.skydns.test."}, - {Host: "10.0.0.3", Key: "b.ns.dns.skydns.test."}, - // Reverse. - {Host: "reverse.example.com", Key: "1.0.0.10.in-addr.arpa."}, // 10.0.0.1 -} - -var dnsTestCases = []test.Case{ - // SRV Test - { - Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{test.SRV("a.server1.dev.region1.skydns.test. 300 SRV 10 100 8080 dev.server1.")}, - }, - // SRV Test (case test) - { - Qname: "a.SERVer1.dEv.region1.skydns.tEst.", Qtype: dns.TypeSRV, - Answer: []dns.RR{test.SRV("a.SERVer1.dEv.region1.skydns.tEst. 300 SRV 10 100 8080 dev.server1.")}, - }, - // NXDOMAIN Test - { - Qname: "doesnotexist.skydns.test.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), - }, - }, - // A Test - { - Qname: "a.server1.prod.region1.skydns.test.", Qtype: dns.TypeA, - Answer: []dns.RR{test.A("a.server1.prod.region1.skydns.test. 300 A 10.0.0.1")}, - }, - // SRV Test where target is IP address - { - Qname: "a.server1.prod.region1.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{test.SRV("a.server1.prod.region1.skydns.test. 300 SRV 10 100 8080 a.server1.prod.region1.skydns.test.")}, - Extra: []dns.RR{test.A("a.server1.prod.region1.skydns.test. 300 A 10.0.0.1")}, - }, - // AAAA Test - { - Qname: "b.server6.prod.region1.skydns.test.", Qtype: dns.TypeAAAA, - Answer: []dns.RR{test.AAAA("b.server6.prod.region1.skydns.test. 300 AAAA ::1")}, - }, - // Multiple A Record Test - { - Qname: "server1.prod.region1.skydns.test.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("server1.prod.region1.skydns.test. 300 A 10.0.0.1"), - test.A("server1.prod.region1.skydns.test. 300 A 10.0.0.2"), - }, - }, - // Priority Test - { - Qname: "priority.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{test.SRV("priority.skydns.test. 300 SRV 333 100 8080 priority.server1.")}, - }, - // Subdomain Test - { - Qname: "sub.region1.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{ - test.SRV("sub.region1.skydns.test. 300 IN SRV 10 33 0 sub.server1."), - test.SRV("sub.region1.skydns.test. 300 IN SRV 10 33 80 sub.server2."), - test.SRV("sub.region1.skydns.test. 300 IN SRV 10 33 8080 c.sub.region1.skydns.test."), - }, - Extra: []dns.RR{test.A("c.sub.region1.skydns.test. 300 IN A 10.0.0.1")}, - }, - // CNAME (unresolvable internal name) - { - Qname: "cname.prod.region1.skydns.test.", Qtype: dns.TypeA, - Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, - }, - // Wildcard Test - { - Qname: "*.region1.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{ - test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 0 sub.server1."), - test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 0 unresolvable.skydns.test."), - test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 80 sub.server2."), - test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 a.server1.prod.region1.skydns.test."), - test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 b.server1.prod.region1.skydns.test."), - test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 b.server6.prod.region1.skydns.test."), - test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 c.sub.region1.skydns.test."), - test.SRV("*.region1.skydns.test. 300 IN SRV 10 12 8080 dev.server1."), - }, - Extra: []dns.RR{ - test.A("a.server1.prod.region1.skydns.test. 300 IN A 10.0.0.1"), - test.A("b.server1.prod.region1.skydns.test. 300 IN A 10.0.0.2"), - test.AAAA("b.server6.prod.region1.skydns.test. 300 IN AAAA ::1"), - test.A("c.sub.region1.skydns.test. 300 IN A 10.0.0.1"), - }, - }, - // Wildcard Test - { - Qname: "prod.*.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{ - - test.SRV("prod.*.skydns.test. 300 IN SRV 10 25 0 unresolvable.skydns.test."), - test.SRV("prod.*.skydns.test. 300 IN SRV 10 25 8080 a.server1.prod.region1.skydns.test."), - test.SRV("prod.*.skydns.test. 300 IN SRV 10 25 8080 b.server1.prod.region1.skydns.test."), - test.SRV("prod.*.skydns.test. 300 IN SRV 10 25 8080 b.server6.prod.region1.skydns.test."), - }, - Extra: []dns.RR{ - test.A("a.server1.prod.region1.skydns.test. 300 IN A 10.0.0.1"), - test.A("b.server1.prod.region1.skydns.test. 300 IN A 10.0.0.2"), - test.AAAA("b.server6.prod.region1.skydns.test. 300 IN AAAA ::1"), - }, - }, - // Wildcard Test - { - Qname: "prod.any.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{ - test.SRV("prod.any.skydns.test. 300 IN SRV 10 25 0 unresolvable.skydns.test."), - test.SRV("prod.any.skydns.test. 300 IN SRV 10 25 8080 a.server1.prod.region1.skydns.test."), - test.SRV("prod.any.skydns.test. 300 IN SRV 10 25 8080 b.server1.prod.region1.skydns.test."), - test.SRV("prod.any.skydns.test. 300 IN SRV 10 25 8080 b.server6.prod.region1.skydns.test."), - }, - Extra: []dns.RR{ - test.A("a.server1.prod.region1.skydns.test. 300 IN A 10.0.0.1"), - test.A("b.server1.prod.region1.skydns.test. 300 IN A 10.0.0.2"), - test.AAAA("b.server6.prod.region1.skydns.test. 300 IN AAAA ::1"), - }, - }, - // CNAME loop detection - { - Qname: "a.cname.skydns.test.", Qtype: dns.TypeA, - Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407441600 28800 7200 604800 60")}, - }, - // NODATA Test - { - Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeTXT, - Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, - }, - // NODATA Test - { - Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeHINFO, - Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, - }, - // NXDOMAIN Test - { - Qname: "a.server1.nonexistent.region1.skydns.test.", Qtype: dns.TypeHINFO, Rcode: dns.RcodeNameError, - Ns: []dns.RR{test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, - }, - { - Qname: "skydns.test.", Qtype: dns.TypeSOA, - Answer: []dns.RR{test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1460498836 14400 3600 604800 60")}, - }, - // NS Record Test - { - Qname: "skydns.test.", Qtype: dns.TypeNS, - Answer: []dns.RR{ - test.NS("skydns.test. 300 NS a.ns.dns.skydns.test."), - test.NS("skydns.test. 300 NS b.ns.dns.skydns.test."), - }, - Extra: []dns.RR{ - test.A("a.ns.dns.skydns.test. 300 A 10.0.0.2"), - test.A("b.ns.dns.skydns.test. 300 A 10.0.0.3"), - }, - }, - // NS Record Test - { - Qname: "a.skydns.test.", Qtype: dns.TypeNS, Rcode: dns.RcodeNameError, - Ns: []dns.RR{test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1460498836 14400 3600 604800 60")}, - }, - // A Record For NS Record Test - { - Qname: "ns.dns.skydns.test.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("ns.dns.skydns.test. 300 A 10.0.0.2"), - test.A("ns.dns.skydns.test. 300 A 10.0.0.3"), - }, - }, - { - Qname: "skydns_extra.test.", Qtype: dns.TypeSOA, - Answer: []dns.RR{test.SOA("skydns_extra.test. 300 IN SOA ns.dns.skydns_extra.test. hostmaster.skydns_extra.test. 1460498836 14400 3600 604800 60")}, - }, - // Reverse lookup - { - Qname: "1.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR, - Answer: []dns.RR{test.PTR("1.0.0.10.in-addr.arpa. 300 PTR reverse.example.com.")}, - }, -} - -func newEtcdMiddleware() *Etcd { - ctxt = context.TODO() - - endpoints := []string{"http://localhost:2379"} - tlsc, _ := tls.NewTLSConfigFromArgs() - client, _ := newEtcdClient(endpoints, tlsc) - - return &Etcd{ - Proxy: proxy.NewLookup([]string{"8.8.8.8:53"}), - PathPrefix: "skydns", - Ctx: context.Background(), - Inflight: &singleflight.Group{}, - Zones: []string{"skydns.test.", "skydns_extra.test.", "in-addr.arpa."}, - Client: client, - } -} - -func set(t *testing.T, e *Etcd, k string, ttl time.Duration, m *msg.Service) { - b, err := json.Marshal(m) - if err != nil { - t.Fatal(err) - } - path, _ := msg.PathWithWildcard(k, e.PathPrefix) - e.Client.Set(ctxt, path, string(b), &etcdc.SetOptions{TTL: ttl}) -} - -func delete(t *testing.T, e *Etcd, k string) { - path, _ := msg.PathWithWildcard(k, e.PathPrefix) - e.Client.Delete(ctxt, path, &etcdc.DeleteOptions{Recursive: false}) -} - -func TestLookup(t *testing.T) { - etc := newEtcdMiddleware() - for _, serv := range services { - set(t, etc, serv.Key, 0, serv) - defer delete(t, etc, serv.Key) - } - - for _, tc := range dnsTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - etc.ServeDNS(ctxt, rec, m) - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -var ctxt context.Context diff --git a/middleware/etcd/msg/path.go b/middleware/etcd/msg/path.go deleted file mode 100644 index 2184b9fcd..000000000 --- a/middleware/etcd/msg/path.go +++ /dev/null @@ -1,48 +0,0 @@ -package msg - -import ( - "path" - "strings" - - "github.com/coredns/coredns/middleware/pkg/dnsutil" - - "github.com/miekg/dns" -) - -// Path converts a domainname to an etcd path. If s looks like service.staging.skydns.local., -// the resulting key will be /skydns/local/skydns/staging/service . -func Path(s, prefix string) string { - l := dns.SplitDomainName(s) - for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { - l[i], l[j] = l[j], l[i] - } - return path.Join(append([]string{"/" + prefix + "/"}, l...)...) -} - -// Domain is the opposite of Path. -func Domain(s string) string { - l := strings.Split(s, "/") - // start with 1, to strip /skydns - for i, j := 1, len(l)-1; i < j; i, j = i+1, j-1 { - l[i], l[j] = l[j], l[i] - } - return dnsutil.Join(l[1 : len(l)-1]) -} - -// PathWithWildcard ascts as Path, but if a name contains wildcards (* or any), the name will be -// chopped of before the (first) wildcard, and we do a highler evel search and -// later find the matching names. So service.*.skydns.local, will look for all -// services under skydns.local and will later check for names that match -// service.*.skydns.local. If a wildcard is found the returned bool is true. -func PathWithWildcard(s, prefix string) (string, bool) { - l := dns.SplitDomainName(s) - for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { - l[i], l[j] = l[j], l[i] - } - for i, k := range l { - if k == "*" || k == "any" { - return path.Join(append([]string{"/" + prefix + "/"}, l[:i]...)...), true - } - } - return path.Join(append([]string{"/" + prefix + "/"}, l...)...), false -} diff --git a/middleware/etcd/msg/path_test.go b/middleware/etcd/msg/path_test.go deleted file mode 100644 index a9ec59713..000000000 --- a/middleware/etcd/msg/path_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package msg - -import "testing" - -func TestPath(t *testing.T) { - for _, path := range []string{"mydns", "skydns"} { - result := Path("service.staging.skydns.local.", path) - if result != "/"+path+"/local/skydns/staging/service" { - t.Errorf("Failure to get domain's path with prefix: %s", result) - } - } -} diff --git a/middleware/etcd/msg/service.go b/middleware/etcd/msg/service.go deleted file mode 100644 index 9250cb634..000000000 --- a/middleware/etcd/msg/service.go +++ /dev/null @@ -1,203 +0,0 @@ -// Package msg defines the Service structure which is used for service discovery. -package msg - -import ( - "fmt" - "net" - "strings" - - "github.com/miekg/dns" -) - -// Service defines a discoverable service in etcd. It is the rdata from a SRV -// record, but with a twist. Host (Target in SRV) must be a domain name, but -// if it looks like an IP address (4/6), we will treat it like an IP address. -type Service struct { - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - Priority int `json:"priority,omitempty"` - Weight int `json:"weight,omitempty"` - Text string `json:"text,omitempty"` - Mail bool `json:"mail,omitempty"` // Be an MX record. Priority becomes Preference. - TTL uint32 `json:"ttl,omitempty"` - - // When a SRV record with a "Host: IP-address" is added, we synthesize - // a srv.Target domain name. Normally we convert the full Key where - // the record lives to a DNS name and use this as the srv.Target. When - // TargetStrip > 0 we strip the left most TargetStrip labels from the - // DNS name. - TargetStrip int `json:"targetstrip,omitempty"` - - // Group is used to group (or *not* to group) different services - // together. Services with an identical Group are returned in the same - // answer. - Group string `json:"group,omitempty"` - - // Etcd key where we found this service and ignored from json un-/marshalling - Key string `json:"-"` -} - -// RR returns an RR representation of s. It is in a condensed form to minimize space -// when this is returned in a DNS message. -// The RR will look like: -// 1.rails.production.east.skydns.local. 300 CH TXT "service1.example.com:8080(10,0,,false)[0,]" -// etcd Key Ttl Host:Port < see below > -// between parens: (Priority, Weight, Text (only first 200 bytes!), Mail) -// between blockquotes: [TargetStrip,Group] -// If the record is synthesised by CoreDNS (i.e. no lookup in etcd happened): -// -// TODO(miek): what to put here? -// -func (s *Service) RR() *dns.TXT { - l := len(s.Text) - if l > 200 { - l = 200 - } - t := new(dns.TXT) - t.Hdr.Class = dns.ClassCHAOS - t.Hdr.Ttl = s.TTL - t.Hdr.Rrtype = dns.TypeTXT - t.Hdr.Name = Domain(s.Key) - - t.Txt = make([]string, 1) - t.Txt[0] = fmt.Sprintf("%s:%d(%d,%d,%s,%t)[%d,%s]", - s.Host, s.Port, - s.Priority, s.Weight, s.Text[:l], s.Mail, - s.TargetStrip, s.Group) - return t -} - -// NewSRV returns a new SRV record based on the Service. -func (s *Service) NewSRV(name string, weight uint16) *dns.SRV { - host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip) - - return &dns.SRV{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: s.TTL}, - Priority: uint16(s.Priority), Weight: weight, Port: uint16(s.Port), Target: dns.Fqdn(host)} -} - -// NewMX returns a new MX record based on the Service. -func (s *Service) NewMX(name string) *dns.MX { - host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip) - - return &dns.MX{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: s.TTL}, - Preference: uint16(s.Priority), Mx: host} -} - -// NewA returns a new A record based on the Service. -func (s *Service) NewA(name string, ip net.IP) *dns.A { - return &dns.A{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.TTL}, A: ip} -} - -// NewAAAA returns a new AAAA record based on the Service. -func (s *Service) NewAAAA(name string, ip net.IP) *dns.AAAA { - return &dns.AAAA{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: s.TTL}, AAAA: ip} -} - -// NewCNAME returns a new CNAME record based on the Service. -func (s *Service) NewCNAME(name string, target string) *dns.CNAME { - return &dns.CNAME{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: s.TTL}, Target: dns.Fqdn(target)} -} - -// NewTXT returns a new TXT record based on the Service. -func (s *Service) NewTXT(name string) *dns.TXT { - return &dns.TXT{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: s.TTL}, Txt: split255(s.Text)} -} - -// NewPTR returns a new PTR record based on the Service. -func (s *Service) NewPTR(name string, target string) *dns.PTR { - return &dns.PTR{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: s.TTL}, Ptr: dns.Fqdn(target)} -} - -// NewNS returns a new NS record based on the Service. -func (s *Service) NewNS(name string) *dns.NS { - host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip) - return &dns.NS{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: s.TTL}, Ns: host} -} - -// Group checks the services in sx, it looks for a Group attribute on the shortest -// keys. If there are multiple shortest keys *and* the group attribute disagrees (and -// is not empty), we don't consider it a group. -// If a group is found, only services with *that* group (or no group) will be returned. -func Group(sx []Service) []Service { - if len(sx) == 0 { - return sx - } - - // Shortest key with group attribute sets the group for this set. - group := sx[0].Group - slashes := strings.Count(sx[0].Key, "/") - length := make([]int, len(sx)) - for i, s := range sx { - x := strings.Count(s.Key, "/") - length[i] = x - if x < slashes { - if s.Group == "" { - break - } - slashes = x - group = s.Group - } - } - - if group == "" { - return sx - } - - ret := []Service{} // with slice-tricks in sx we can prolly save this allocation (TODO) - - for i, s := range sx { - if s.Group == "" { - ret = append(ret, s) - continue - } - - // Disagreement on the same level - if length[i] == slashes && s.Group != group { - return sx - } - - if s.Group == group { - ret = append(ret, s) - } - } - return ret -} - -// Split255 splits a string into 255 byte chunks. -func split255(s string) []string { - if len(s) < 255 { - return []string{s} - } - sx := []string{} - p, i := 0, 255 - for { - if i <= len(s) { - sx = append(sx, s[p:i]) - } else { - sx = append(sx, s[p:]) - break - - } - p, i = p+255, i+255 - } - - return sx -} - -// targetStrip strips "targetstrip" labels from the left side of the fully qualified name. -func targetStrip(name string, targetStrip int) string { - if targetStrip == 0 { - return name - } - - offset, end := 0, false - for i := 0; i < targetStrip; i++ { - offset, end = dns.NextLabel(name, offset) - } - if end { - // We overshot the name, use the orignal one. - offset = 0 - } - name = name[offset:] - return name -} diff --git a/middleware/etcd/msg/service_test.go b/middleware/etcd/msg/service_test.go deleted file mode 100644 index 0c19ba95b..000000000 --- a/middleware/etcd/msg/service_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package msg - -import "testing" - -func TestSplit255(t *testing.T) { - xs := split255("abc") - if len(xs) != 1 && xs[0] != "abc" { - t.Errorf("Failure to split abc") - } - s := "" - for i := 0; i < 255; i++ { - s += "a" - } - xs = split255(s) - if len(xs) != 1 && xs[0] != s { - t.Errorf("failure to split 255 char long string") - } - s += "b" - xs = split255(s) - if len(xs) != 2 || xs[1] != "b" { - t.Errorf("failure to split 256 char long string: %d", len(xs)) - } - for i := 0; i < 255; i++ { - s += "a" - } - xs = split255(s) - if len(xs) != 3 || xs[2] != "a" { - t.Errorf("failure to split 510 char long string: %d", len(xs)) - } -} - -func TestGroup(t *testing.T) { - // Key are in the wrong order, but for this test it does not matter. - sx := Group( - []Service{ - {Host: "127.0.0.1", Group: "g1", Key: "b/sub/dom1/skydns/test"}, - {Host: "127.0.0.2", Group: "g2", Key: "a/dom1/skydns/test"}, - }, - ) - // Expecting to return the shortest key with a Group attribute. - if len(sx) != 1 { - t.Fatalf("failure to group zeroth set: %v", sx) - } - if sx[0].Key != "a/dom1/skydns/test" { - t.Fatalf("failure to group zeroth set: %v, wrong Key", sx) - } - - // Groups disagree, so we will not do anything. - sx = Group( - []Service{ - {Host: "server1", Group: "g1", Key: "region1/skydns/test"}, - {Host: "server2", Group: "g2", Key: "region1/skydns/test"}, - }, - ) - if len(sx) != 2 { - t.Fatalf("failure to group first set: %v", sx) - } - - // Group is g1, include only the top-level one. - sx = Group( - []Service{ - {Host: "server1", Group: "g1", Key: "a/dom/region1/skydns/test"}, - {Host: "server2", Group: "g2", Key: "a/subdom/dom/region1/skydns/test"}, - }, - ) - if len(sx) != 1 { - t.Fatalf("failure to group second set: %v", sx) - } - - // Groupless services must be included. - sx = Group( - []Service{ - {Host: "server1", Group: "g1", Key: "a/dom/region1/skydns/test"}, - {Host: "server2", Group: "g2", Key: "a/subdom/dom/region1/skydns/test"}, - {Host: "server2", Group: "", Key: "b/subdom/dom/region1/skydns/test"}, - }, - ) - if len(sx) != 2 { - t.Fatalf("failure to group third set: %v", sx) - } - - // Empty group on the highest level: include that one also. - sx = Group( - []Service{ - {Host: "server1", Group: "g1", Key: "a/dom/region1/skydns/test"}, - {Host: "server1", Group: "", Key: "b/dom/region1/skydns/test"}, - {Host: "server2", Group: "g2", Key: "a/subdom/dom/region1/skydns/test"}, - }, - ) - if len(sx) != 2 { - t.Fatalf("failure to group fourth set: %v", sx) - } - - // Empty group on the highest level: include that one also, and the rest. - sx = Group( - []Service{ - {Host: "server1", Group: "g5", Key: "a/dom/region1/skydns/test"}, - {Host: "server1", Group: "", Key: "b/dom/region1/skydns/test"}, - {Host: "server2", Group: "g5", Key: "a/subdom/dom/region1/skydns/test"}, - }, - ) - if len(sx) != 3 { - t.Fatalf("failure to group fith set: %v", sx) - } - - // One group. - sx = Group( - []Service{ - {Host: "server1", Group: "g6", Key: "a/dom/region1/skydns/test"}, - }, - ) - if len(sx) != 1 { - t.Fatalf("failure to group sixth set: %v", sx) - } - - // No group, once service - sx = Group( - []Service{ - {Host: "server1", Key: "a/dom/region1/skydns/test"}, - }, - ) - if len(sx) != 1 { - t.Fatalf("failure to group seventh set: %v", sx) - } -} diff --git a/middleware/etcd/msg/type.go b/middleware/etcd/msg/type.go deleted file mode 100644 index 7f3bfdbb9..000000000 --- a/middleware/etcd/msg/type.go +++ /dev/null @@ -1,33 +0,0 @@ -package msg - -import ( - "net" - - "github.com/miekg/dns" -) - -// HostType returns the DNS type of what is encoded in the Service Host field. We're reusing -// dns.TypeXXX to not reinvent a new set of identifiers. -// -// dns.TypeA: the service's Host field contains an A record. -// dns.TypeAAAA: the service's Host field contains an AAAA record. -// dns.TypeCNAME: the service's Host field contains a name. -// -// Note that a service can double/triple as a TXT record or MX record. -func (s *Service) HostType() (what uint16, normalized net.IP) { - - ip := net.ParseIP(s.Host) - - switch { - case ip == nil: - return dns.TypeCNAME, nil - - case ip.To4() != nil: - return dns.TypeA, ip.To4() - - case ip.To4() == nil: - return dns.TypeAAAA, ip.To16() - } - // This should never be reached. - return dns.TypeNone, nil -} diff --git a/middleware/etcd/msg/type_test.go b/middleware/etcd/msg/type_test.go deleted file mode 100644 index bad1eead0..000000000 --- a/middleware/etcd/msg/type_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package msg - -import ( - "testing" - - "github.com/miekg/dns" -) - -func TestType(t *testing.T) { - tests := []struct { - serv Service - expectedType uint16 - }{ - {Service{Host: "example.org"}, dns.TypeCNAME}, - {Service{Host: "127.0.0.1"}, dns.TypeA}, - {Service{Host: "2000::3"}, dns.TypeAAAA}, - {Service{Host: "2000..3"}, dns.TypeCNAME}, - {Service{Host: "127.0.0.257"}, dns.TypeCNAME}, - {Service{Host: "127.0.0.252", Mail: true}, dns.TypeA}, - {Service{Host: "127.0.0.252", Mail: true, Text: "a"}, dns.TypeA}, - {Service{Host: "127.0.0.254", Mail: false, Text: "a"}, dns.TypeA}, - } - - for i, tc := range tests { - what, _ := tc.serv.HostType() - if what != tc.expectedType { - t.Errorf("Test %d: Expected what %v, but got %v", i, tc.expectedType, what) - } - } - -} diff --git a/middleware/etcd/multi_test.go b/middleware/etcd/multi_test.go deleted file mode 100644 index 301b2c49e..000000000 --- a/middleware/etcd/multi_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// +build etcd - -package etcd - -import ( - "testing" - - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" -) - -func TestMultiLookup(t *testing.T) { - etc := newEtcdMiddleware() - etc.Zones = []string{"skydns.test.", "miek.nl."} - etc.Fallthrough = true - etc.Next = test.ErrorHandler() - - for _, serv := range servicesMulti { - set(t, etc, serv.Key, 0, serv) - defer delete(t, etc, serv.Key) - } - for _, tc := range dnsTestCasesMulti { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := etc.ServeDNS(ctxt, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -// Note the key is encoded as DNS name, while in "reality" it is a etcd path. -var servicesMulti = []*msg.Service{ - {Host: "dev.server1", Port: 8080, Key: "a.server1.dev.region1.skydns.test."}, - {Host: "dev.server1", Port: 8080, Key: "a.server1.dev.region1.miek.nl."}, - {Host: "dev.server1", Port: 8080, Key: "a.server1.dev.region1.example.org."}, -} - -var dnsTestCasesMulti = []test.Case{ - { - Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{test.SRV("a.server1.dev.region1.skydns.test. 300 SRV 10 100 8080 dev.server1.")}, - }, - { - Qname: "a.server1.dev.region1.miek.nl.", Qtype: dns.TypeSRV, - Answer: []dns.RR{test.SRV("a.server1.dev.region1.miek.nl. 300 SRV 10 100 8080 dev.server1.")}, - }, - { - Qname: "a.server1.dev.region1.example.org.", Qtype: dns.TypeSRV, Rcode: dns.RcodeServerFailure, - }, -} diff --git a/middleware/etcd/other_test.go b/middleware/etcd/other_test.go deleted file mode 100644 index b3849b9f7..000000000 --- a/middleware/etcd/other_test.go +++ /dev/null @@ -1,150 +0,0 @@ -// +build etcd - -// tests mx and txt records - -package etcd - -import ( - "fmt" - "strings" - "testing" - - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" -) - -func TestOtherLookup(t *testing.T) { - etc := newEtcdMiddleware() - - for _, serv := range servicesOther { - set(t, etc, serv.Key, 0, serv) - defer delete(t, etc, serv.Key) - } - for _, tc := range dnsTestCasesOther { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := etc.ServeDNS(ctxt, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - continue - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -// Note the key is encoded as DNS name, while in "reality" it is a etcd path. -var servicesOther = []*msg.Service{ - {Host: "dev.server1", Port: 8080, Key: "a.server1.dev.region1.skydns.test."}, - - // mx - {Host: "mx.skydns.test", Priority: 50, Mail: true, Key: "a.mail.skydns.test."}, - {Host: "mx.miek.nl", Priority: 50, Mail: true, Key: "b.mail.skydns.test."}, - {Host: "a.ipaddr.skydns.test", Priority: 30, Mail: true, Key: "a.mx.skydns.test."}, - - {Host: "a.ipaddr.skydns.test", Mail: true, Key: "a.mx2.skydns.test."}, - {Host: "b.ipaddr.skydns.test", Mail: true, Key: "b.mx2.skydns.test."}, - - {Host: "a.ipaddr.skydns.test", Priority: 20, Mail: true, Key: "a.mx3.skydns.test."}, - {Host: "a.ipaddr.skydns.test", Priority: 30, Mail: true, Key: "b.mx3.skydns.test."}, - - {Host: "172.16.1.1", Key: "a.ipaddr.skydns.test."}, - {Host: "172.16.1.2", Key: "b.ipaddr.skydns.test."}, - - // txt - {Text: "abc", Key: "a1.txt.skydns.test."}, - {Text: "abc abc", Key: "a2.txt.skydns.test."}, - // txt sizes - {Text: strings.Repeat("0", 400), Key: "large400.skydns.test."}, - {Text: strings.Repeat("0", 600), Key: "large600.skydns.test."}, - {Text: strings.Repeat("0", 2000), Key: "large2000.skydns.test."}, - - // duplicate ip address - {Host: "10.11.11.10", Key: "http.multiport.http.skydns.test.", Port: 80}, - {Host: "10.11.11.10", Key: "https.multiport.http.skydns.test.", Port: 443}, -} - -var dnsTestCasesOther = []test.Case{ - // MX Tests - { - // NODATA as this is not an Mail: true record. - Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeMX, - Ns: []dns.RR{ - test.SOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), - }, - }, - { - Qname: "a.mail.skydns.test.", Qtype: dns.TypeMX, - Answer: []dns.RR{test.MX("a.mail.skydns.test. 300 IN MX 50 mx.skydns.test.")}, - Extra: []dns.RR{ - test.A("a.ipaddr.skydns.test. 300 IN A 172.16.1.1"), - test.CNAME("mx.skydns.test. 300 IN CNAME a.ipaddr.skydns.test."), - }, - }, - { - Qname: "mx2.skydns.test.", Qtype: dns.TypeMX, - Answer: []dns.RR{ - test.MX("mx2.skydns.test. 300 IN MX 10 a.ipaddr.skydns.test."), - test.MX("mx2.skydns.test. 300 IN MX 10 b.ipaddr.skydns.test."), - }, - Extra: []dns.RR{ - test.A("a.ipaddr.skydns.test. 300 A 172.16.1.1"), - test.A("b.ipaddr.skydns.test. 300 A 172.16.1.2"), - }, - }, - // different priority, same host - { - Qname: "mx3.skydns.test.", Qtype: dns.TypeMX, - Answer: []dns.RR{ - test.MX("mx3.skydns.test. 300 IN MX 20 a.ipaddr.skydns.test."), - test.MX("mx3.skydns.test. 300 IN MX 30 a.ipaddr.skydns.test."), - }, - Extra: []dns.RR{ - test.A("a.ipaddr.skydns.test. 300 A 172.16.1.1"), - }, - }, - // Txt - { - Qname: "a1.txt.skydns.test.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT("a1.txt.skydns.test. 300 IN TXT \"abc\""), - }, - }, - { - Qname: "a2.txt.skydns.test.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT("a2.txt.skydns.test. 300 IN TXT \"abc abc\""), - }, - }, - // Large txt less than 512 - { - Qname: "large400.skydns.test.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT(fmt.Sprintf("large400.skydns.test. 300 IN TXT \"%s\"", strings.Repeat("0", 400))), - }, - }, - // Large txt greater than 512 (UDP) - { - Qname: "large600.skydns.test.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT(fmt.Sprintf("large600.skydns.test. 300 IN TXT \"%s\"", strings.Repeat("0", 600))), - }, - }, - // Large txt greater than 1500 (typical Ethernet) - { - Qname: "large2000.skydns.test.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT(fmt.Sprintf("large2000.skydns.test. 300 IN TXT \"%s\"", strings.Repeat("0", 2000))), - }, - }, - // Duplicate IP address test - { - Qname: "multiport.http.skydns.test.", Qtype: dns.TypeA, - Answer: []dns.RR{test.A("multiport.http.skydns.test. 300 IN A 10.11.11.10")}, - }, -} diff --git a/middleware/etcd/setup.go b/middleware/etcd/setup.go deleted file mode 100644 index 7cbcfd2ea..000000000 --- a/middleware/etcd/setup.go +++ /dev/null @@ -1,144 +0,0 @@ -package etcd - -import ( - "crypto/tls" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/pkg/singleflight" - mwtls "github.com/coredns/coredns/middleware/pkg/tls" - "github.com/coredns/coredns/middleware/proxy" - - etcdc "github.com/coreos/etcd/client" - "github.com/mholt/caddy" - "golang.org/x/net/context" -) - -func init() { - caddy.RegisterPlugin("etcd", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - e, stubzones, err := etcdParse(c) - if err != nil { - return middleware.Error("etcd", err) - } - - if stubzones { - c.OnStartup(func() error { - e.UpdateStubZones() - return nil - }) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - e.Next = next - return e - }) - - return nil -} - -func etcdParse(c *caddy.Controller) (*Etcd, bool, error) { - stub := make(map[string]proxy.Proxy) - etc := Etcd{ - // Don't default to a proxy for lookups. - // Proxy: proxy.NewLookup([]string{"8.8.8.8:53", "8.8.4.4:53"}), - PathPrefix: "skydns", - Ctx: context.Background(), - Inflight: &singleflight.Group{}, - Stubmap: &stub, - } - var ( - tlsConfig *tls.Config - err error - endpoints = []string{defaultEndpoint} - stubzones = false - ) - for c.Next() { - etc.Zones = c.RemainingArgs() - if len(etc.Zones) == 0 { - etc.Zones = make([]string, len(c.ServerBlockKeys)) - copy(etc.Zones, c.ServerBlockKeys) - } - for i, str := range etc.Zones { - etc.Zones[i] = middleware.Host(str).Normalize() - } - - if c.NextBlock() { - for { - switch c.Val() { - case "stubzones": - stubzones = true - case "fallthrough": - etc.Fallthrough = true - case "debug": - /* it is a noop now */ - case "path": - if !c.NextArg() { - return &Etcd{}, false, c.ArgErr() - } - etc.PathPrefix = c.Val() - case "endpoint": - args := c.RemainingArgs() - if len(args) == 0 { - return &Etcd{}, false, c.ArgErr() - } - endpoints = args - case "upstream": - args := c.RemainingArgs() - if len(args) == 0 { - return &Etcd{}, false, c.ArgErr() - } - ups, err := dnsutil.ParseHostPortOrFile(args...) - if err != nil { - return &Etcd{}, false, err - } - etc.Proxy = proxy.NewLookup(ups) - case "tls": // cert key cacertfile - args := c.RemainingArgs() - tlsConfig, err = mwtls.NewTLSConfigFromArgs(args...) - if err != nil { - return &Etcd{}, false, err - } - default: - if c.Val() != "}" { - return &Etcd{}, false, c.Errf("unknown property '%s'", c.Val()) - } - } - - if !c.Next() { - break - } - } - - } - client, err := newEtcdClient(endpoints, tlsConfig) - if err != nil { - return &Etcd{}, false, err - } - etc.Client = client - etc.endpoints = endpoints - - return &etc, stubzones, nil - } - return &Etcd{}, false, nil -} - -func newEtcdClient(endpoints []string, cc *tls.Config) (etcdc.KeysAPI, error) { - etcdCfg := etcdc.Config{ - Endpoints: endpoints, - Transport: mwtls.NewHTTPSTransport(cc), - } - cli, err := etcdc.New(etcdCfg) - if err != nil { - return nil, err - } - return etcdc.NewKeysAPI(cli), nil -} - -const defaultEndpoint = "http://localhost:2379" diff --git a/middleware/etcd/setup_test.go b/middleware/etcd/setup_test.go deleted file mode 100644 index 833e2ba4c..000000000 --- a/middleware/etcd/setup_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package etcd - -import ( - "strings" - "testing" - - "github.com/mholt/caddy" -) - -func TestSetupEtcd(t *testing.T) { - tests := []struct { - input string - shouldErr bool - expectedPath string - expectedEndpoint string - expectedErrContent string // substring from the expected error. Empty for positive cases. - }{ - // positive - { - `etcd`, false, "skydns", "http://localhost:2379", "", - }, - { - `etcd skydns.local { - endpoint localhost:300 -} -`, false, "skydns", "localhost:300", "", - }, - // negative - { - `etcd { - endpoints localhost:300 -} -`, true, "", "", "unknown property 'endpoints'", - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - etcd, _ /*stubzones*/, err := etcdParse(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - continue - } - - if !strings.Contains(err.Error(), test.expectedErrContent) { - t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) - continue - } - } - - if !test.shouldErr && etcd.PathPrefix != test.expectedPath { - t.Errorf("Etcd not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedPath, etcd.PathPrefix) - } - if !test.shouldErr && etcd.endpoints[0] != test.expectedEndpoint { // only checks the first - t.Errorf("Etcd not correctly set for input %s. Expected: '%s', actual: '%s'", test.input, test.expectedEndpoint, etcd.endpoints[0]) - } - } -} diff --git a/middleware/etcd/stub.go b/middleware/etcd/stub.go deleted file mode 100644 index b40af6341..000000000 --- a/middleware/etcd/stub.go +++ /dev/null @@ -1,82 +0,0 @@ -package etcd - -import ( - "log" - "net" - "strconv" - "time" - - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/proxy" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// UpdateStubZones checks etcd for an update on the stubzones. -func (e *Etcd) UpdateStubZones() { - go func() { - for { - e.updateStubZones() - time.Sleep(15 * time.Second) - } - }() -} - -// Look in .../dns/stub/<zone>/xx for msg.Services. Loop through them -// extract <zone> and add them as forwarders (ip:port-combos) for -// the stub zones. Only numeric (i.e. IP address) hosts are used. -// Only the first zone configured on e is used for the lookup. -func (e *Etcd) updateStubZones() { - zone := e.Zones[0] - - fakeState := request.Request{W: nil, Req: new(dns.Msg)} - fakeState.Req.SetQuestion(stubDomain+"."+zone, dns.TypeA) - - services, err := e.Records(fakeState, false) - if err != nil { - return - } - - stubmap := make(map[string]proxy.Proxy) - // track the nameservers on a per domain basis, but allow a list on the domain. - nameservers := map[string][]string{} - -Services: - for _, serv := range services { - if serv.Port == 0 { - serv.Port = 53 - } - ip := net.ParseIP(serv.Host) - if ip == nil { - log.Printf("[WARNING] Non IP address stub nameserver: %s", serv.Host) - continue - } - - domain := msg.Domain(serv.Key) - labels := dns.SplitDomainName(domain) - - // If the remaining name equals any of the zones we have, we ignore it. - for _, z := range e.Zones { - // Chop of left most label, because that is used as the nameserver place holder - // and drop the right most labels that belong to zone. - // We must *also* chop of dns.stub. which means cutting two more labels. - domain = dnsutil.Join(labels[1 : len(labels)-dns.CountLabel(z)-2]) - if domain == z { - log.Printf("[WARNING] Skipping nameserver for domain we are authoritative for: %s", domain) - continue Services - } - } - nameservers[domain] = append(nameservers[domain], net.JoinHostPort(serv.Host, strconv.Itoa(serv.Port))) - } - - for domain, nss := range nameservers { - stubmap[domain] = proxy.NewLookup(nss) - } - // atomic swap (at least that's what we hope it is) - if len(stubmap) > 0 { - e.Stubmap = &stubmap - } - return -} diff --git a/middleware/etcd/stub_handler.go b/middleware/etcd/stub_handler.go deleted file mode 100644 index a6b752177..000000000 --- a/middleware/etcd/stub_handler.go +++ /dev/null @@ -1,86 +0,0 @@ -package etcd - -import ( - "errors" - "log" - - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Stub wraps an Etcd. We have this type so that it can have a ServeDNS method. -type Stub struct { - *Etcd - Zone string // for what zone (and thus what nameservers are we called) -} - -// ServeDNS implements the middleware.Handler interface. -func (s Stub) ServeDNS(ctx context.Context, w dns.ResponseWriter, req *dns.Msg) (int, error) { - if hasStubEdns0(req) { - log.Printf("[WARNING] Forwarding cycle detected, refusing msg: %s", req.Question[0].Name) - return dns.RcodeRefused, errors.New("stub forward cycle") - } - req = addStubEdns0(req) - proxy, ok := (*s.Etcd.Stubmap)[s.Zone] - if !ok { // somebody made a mistake.. - return dns.RcodeServerFailure, nil - } - - state := request.Request{W: w, Req: req} - m, e := proxy.Forward(state) - if e != nil { - return dns.RcodeServerFailure, e - } - m.RecursionAvailable, m.Compress = true, true - state.SizeAndDo(m) - w.WriteMsg(m) - return dns.RcodeSuccess, nil -} - -// hasStubEdns0 checks if the message is carrying our special edns0 zero option. -func hasStubEdns0(m *dns.Msg) bool { - option := m.IsEdns0() - if option == nil { - return false - } - for _, o := range option.Option { - if o.Option() == ednsStubCode && len(o.(*dns.EDNS0_LOCAL).Data) == 1 && - o.(*dns.EDNS0_LOCAL).Data[0] == 1 { - return true - } - } - return false -} - -// addStubEdns0 adds our special option to the message's OPT record. -func addStubEdns0(m *dns.Msg) *dns.Msg { - option := m.IsEdns0() - // Add a custom EDNS0 option to the packet, so we can detect loops when 2 stubs are forwarding to each other. - if option != nil { - option.Option = append(option.Option, &dns.EDNS0_LOCAL{Code: ednsStubCode, Data: []byte{1}}) - return m - } - - m.Extra = append(m.Extra, ednsStub) - return m -} - -const ( - ednsStubCode = dns.EDNS0LOCALSTART + 10 - stubDomain = "stub.dns" -) - -var ednsStub = func() *dns.OPT { - o := new(dns.OPT) - o.Hdr.Name = "." - o.Hdr.Rrtype = dns.TypeOPT - o.SetUDPSize(4096) - - e := new(dns.EDNS0_LOCAL) - e.Code = ednsStubCode - e.Data = []byte{1} - o.Option = append(o.Option, e) - return o -}() diff --git a/middleware/etcd/stub_test.go b/middleware/etcd/stub_test.go deleted file mode 100644 index 5f22f42c9..000000000 --- a/middleware/etcd/stub_test.go +++ /dev/null @@ -1,88 +0,0 @@ -// +build etcd - -package etcd - -import ( - "net" - "strconv" - "testing" - - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" -) - -func fakeStubServerExampleNet(t *testing.T) (*dns.Server, string) { - server, addr, err := test.UDPServer("127.0.0.1:0") - if err != nil { - t.Fatalf("failed to create a UDP server: %s", err) - } - // add handler for example.net - dns.HandleFunc("example.net.", func(w dns.ResponseWriter, r *dns.Msg) { - m := new(dns.Msg) - m.SetReply(r) - m.Answer = []dns.RR{test.A("example.net. 86400 IN A 93.184.216.34")} - w.WriteMsg(m) - }) - - return server, addr -} - -func TestStubLookup(t *testing.T) { - server, addr := fakeStubServerExampleNet(t) - defer server.Shutdown() - - host, p, _ := net.SplitHostPort(addr) - port, _ := strconv.Atoi(p) - exampleNetStub := &msg.Service{Host: host, Port: port, Key: "a.example.net.stub.dns.skydns.test."} - servicesStub = append(servicesStub, exampleNetStub) - - etc := newEtcdMiddleware() - - for _, serv := range servicesStub { - set(t, etc, serv.Key, 0, serv) - defer delete(t, etc, serv.Key) - } - - etc.updateStubZones() - - for _, tc := range dnsTestCasesStub { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := etc.ServeDNS(ctxt, rec, m) - if err != nil && m.Question[0].Name == "example.org." { - // This is OK, we expect this backend to *not* work. - continue - } - if err != nil { - t.Errorf("expected no error, got %v for %s\n", err, m.Question[0].Name) - } - resp := rec.Msg - if resp == nil { - // etcd not running? - continue - } - - test.SortAndCheck(t, resp, tc) - } -} - -var servicesStub = []*msg.Service{ - // Two tests, ask a question that should return servfail because remote it no accessible - // and one with edns0 option added, that should return refused. - {Host: "127.0.0.1", Port: 666, Key: "b.example.org.stub.dns.skydns.test."}, -} - -var dnsTestCasesStub = []test.Case{ - { - Qname: "example.org.", Qtype: dns.TypeA, Rcode: dns.RcodeServerFailure, - }, - { - Qname: "example.net.", Qtype: dns.TypeA, - Answer: []dns.RR{test.A("example.net. 86400 IN A 93.184.216.34")}, - Extra: []dns.RR{test.OPT(4096, false)}, // This will have an EDNS0 section, because *we* added our local stub forward to detect loops. - }, -} diff --git a/middleware/federation/README.md b/middleware/federation/README.md deleted file mode 100644 index c22a909f5..000000000 --- a/middleware/federation/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# federation - -The *federation* middleware enables -[federated](https://kubernetes.io/docs/tasks/federation/federation-service-discovery/) queries to be -resolved via the kubernetes middleware. - -Enabling *federation* without also having *kubernetes* is a noop. - -## Syntax - -~~~ -federation [ZONES...] { - NAME DOMAIN -~~~ - -* Each **NAME** and **DOMAIN** defines federation membership. One entry for each. A duplicate - **NAME** will silently overwrite any previous value. - -## Examples - -Here we handle all service requests in the `prod` and `stage` federations. - -~~~ txt -. { - kubernetes cluster.local - federation cluster.local { - prod prod.feddomain.com - staging staging.feddomain.com - } -} -~~~ - -Or slightly shorter: - -~~~ txt -cluster.local { - kubernetes - federation { - prod prod.feddomain.com - staging staging.feddomain.com - } -} -~~~ diff --git a/middleware/federation/federation.go b/middleware/federation/federation.go deleted file mode 100644 index bc74d9fc9..000000000 --- a/middleware/federation/federation.go +++ /dev/null @@ -1,141 +0,0 @@ -/* -Package federation implements kubernetes federation. It checks if the qname matches -a possible federation. If this is the case and the captured answer is an NXDOMAIN, -federation is performed. If this is not the case the original answer is returned. - -The federation label is always the 2nd to last once the zone is chopped of. For -instance "nginx.mynamespace.myfederation.svc.example.com" has "myfederation" as -the federation label. For federation to work we do a normal k8s lookup -*without* that label, if that comes back with NXDOMAIN or NODATA(??) we create -a federation record and return that. - -Federation is only useful in conjunction with the kubernetes middleware, without it is a noop. -*/ -package federation - -import ( - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/pkg/nonwriter" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Federation contains the name to zone mapping used for federation in kubernetes. -type Federation struct { - f map[string]string - zones []string - - Next middleware.Handler - Federations Func -} - -// Func needs to be implemented by any middleware that implements -// federation. Right now this is only the kubernetes middleware. -type Func func(state request.Request, fname, fzone string) (msg.Service, error) - -// New returns a new federation. -func New() *Federation { - return &Federation{f: make(map[string]string)} -} - -// ServeDNS implements the middleware.Handle interface. -func (f *Federation) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - if f.Federations == nil { - return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r) - } - - state := request.Request{W: w, Req: r} - zone := middleware.Zones(f.zones).Matches(state.Name()) - if zone == "" { - return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r) - } - - state.Zone = zone - - // Remove the federation label from the qname to see if something exists. - without, label := f.isNameFederation(state.Name(), state.Zone) - if without == "" { - return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r) - } - - qname := r.Question[0].Name - r.Question[0].Name = without - state.Clear() - - // Start the next middleware, but with a nowriter, capture the result, if NXDOMAIN - // perform federation, otherwise just write the result. - nw := nonwriter.New(w) - ret, err := middleware.NextOrFailure(f.Name(), f.Next, ctx, nw, r) - - if !middleware.ClientWrite(ret) { - // something went wrong - r.Question[0].Name = qname - return ret, err - } - - if m := nw.Msg; m.Rcode != dns.RcodeNameError { - // If positive answer we need to substitute the original qname in the answer. - m.Question[0].Name = qname - for _, a := range m.Answer { - a.Header().Name = qname - } - - state.SizeAndDo(m) - m, _ = state.Scrub(m) - w.WriteMsg(m) - - return dns.RcodeSuccess, nil - } - - // Still here, we've seen NXDOMAIN and need to perform federation. - service, err := f.Federations(state, label, f.f[label]) // state references Req which has updated qname - if err != nil { - r.Question[0].Name = qname - return dns.RcodeServerFailure, err - } - - r.Question[0].Name = qname - - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - - m.Answer = []dns.RR{service.NewCNAME(state.QName(), service.Host)} - - state.SizeAndDo(m) - m, _ = state.Scrub(m) - w.WriteMsg(m) - - return dns.RcodeSuccess, nil -} - -// Name implements the middleware.Handle interface. -func (f *Federation) Name() string { return "federation" } - -// IsNameFederation checks the qname to see if it is a potential federation. The federation -// label is always the 2nd to last once the zone is chopped of. For instance -// "nginx.mynamespace.myfederation.svc.example.com" has "myfederation" as the federation label. -// IsNameFederation returns a new qname with the federation label and the label itself or two -// empty strings if there wasn't a hit. -func (f *Federation) isNameFederation(name, zone string) (string, string) { - base, _ := dnsutil.TrimZone(name, zone) - - // TODO(miek): dns.PrevLabel is better for memory, or dns.Split. - labels := dns.SplitDomainName(base) - ll := len(labels) - if ll < 2 { - return "", "" - } - - fed := labels[ll-2] - - if _, ok := f.f[fed]; ok { - without := dnsutil.Join(labels[:ll-2]) + labels[ll-1] + "." + zone - return without, fed - } - return "", "" -} diff --git a/middleware/federation/federation_test.go b/middleware/federation/federation_test.go deleted file mode 100644 index 6f27cabad..000000000 --- a/middleware/federation/federation_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package federation - -import ( - "testing" - - "github.com/coredns/coredns/middleware/kubernetes" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestIsNameFederation(t *testing.T) { - tests := []struct { - fed string - qname string - expectedZone string - }{ - {"prod", "nginx.mynamespace.prod.svc.example.com.", "nginx.mynamespace.svc.example.com."}, - {"prod", "nginx.mynamespace.staging.svc.example.com.", ""}, - {"prod", "nginx.mynamespace.example.com.", ""}, - {"prod", "example.com.", ""}, - {"prod", "com.", ""}, - } - - fed := New() - for i, tc := range tests { - fed.f[tc.fed] = "test-name" - if x, _ := fed.isNameFederation(tc.qname, "example.com."); x != tc.expectedZone { - t.Errorf("Test %d, failed to get zone, expected %s, got %s", i, tc.expectedZone, x) - } - } -} - -func TestFederationKubernetes(t *testing.T) { - tests := []test.Case{ - { - // service exists so we return the IP address associated with it. - Qname: "svc1.testns.prod.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("svc1.testns.prod.svc.cluster.local. 303 IN A 10.0.0.1"), - }, - }, - { - // service does not exist, do the federation dance. - Qname: "svc0.testns.prod.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.CNAME("svc0.testns.prod.svc.cluster.local. 303 IN CNAME svc0.testns.prod.svc.fd-az.fd-r.federal.example."), - }, - }, - } - - k := kubernetes.New([]string{"cluster.local."}) - k.APIConn = &APIConnFederationTest{} - - fed := New() - fed.zones = []string{"cluster.local."} - fed.Federations = k.Federations - fed.Next = k - fed.f = map[string]string{ - "prod": "federal.example.", - } - - ctx := context.TODO() - for i, tc := range tests { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fed.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Test %d, expected no error, got %v\n", i, err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/federation/kubernetes_api_test.go b/middleware/federation/kubernetes_api_test.go deleted file mode 100644 index 9e7056e49..000000000 --- a/middleware/federation/kubernetes_api_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package federation - -import ( - "github.com/coredns/coredns/middleware/kubernetes" - - "k8s.io/client-go/1.5/pkg/api" -) - -type APIConnFederationTest struct{} - -func (APIConnFederationTest) Run() { return } -func (APIConnFederationTest) Stop() error { return nil } - -func (APIConnFederationTest) PodIndex(string) []interface{} { - a := make([]interface{}, 1) - a[0] = &api.Pod{ - ObjectMeta: api.ObjectMeta{ - Namespace: "podns", - }, - Status: api.PodStatus{ - PodIP: "10.240.0.1", // Remote IP set in test.ResponseWriter - }, - } - return a -} - -func (APIConnFederationTest) ServiceList() []*api.Service { - svcs := []*api.Service{ - { - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: api.ClusterIPNone, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "external", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ExternalName: "ext.interwebs.test", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - } - return svcs - -} - -func (APIConnFederationTest) EndpointsList() api.EndpointsList { - return api.EndpointsList{ - Items: []api.Endpoints{ - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.1", - Hostname: "ep1a", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - }, - }, - } -} - -func (APIConnFederationTest) GetNodeByName(name string) (api.Node, error) { - return api.Node{ - ObjectMeta: api.ObjectMeta{ - Name: "test.node.foo.bar", - Labels: map[string]string{ - kubernetes.LabelRegion: "fd-r", - kubernetes.LabelZone: "fd-az", - }, - }, - }, nil -} diff --git a/middleware/federation/setup.go b/middleware/federation/setup.go deleted file mode 100644 index 1ef635603..000000000 --- a/middleware/federation/setup.go +++ /dev/null @@ -1,89 +0,0 @@ -package federation - -import ( - "fmt" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/kubernetes" - "github.com/miekg/dns" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("federation", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - fed, err := federationParse(c) - if err != nil { - return middleware.Error("federation", err) - } - - // Do this in OnStartup, so all middleware has been initialized. - c.OnStartup(func() error { - m := dnsserver.GetConfig(c).Handler("kubernetes") - if m == nil { - return nil - } - if x, ok := m.(*kubernetes.Kubernetes); ok { - fed.Federations = x.Federations - } - return nil - }) - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - fed.Next = next - return fed - }) - - return nil -} - -func federationParse(c *caddy.Controller) (*Federation, error) { - fed := New() - - for c.Next() { - // federation [zones..] - zones := c.RemainingArgs() - origins := []string{} - if len(zones) > 0 { - origins = make([]string, len(zones)) - copy(origins, zones) - } else { - origins = make([]string, len(c.ServerBlockKeys)) - copy(origins, c.ServerBlockKeys) - } - - for c.NextBlock() { - x := c.Val() - switch x { - default: - args := c.RemainingArgs() - if x := len(args); x != 1 { - return fed, fmt.Errorf("need two arguments for federation, got %d", x) - } - - fed.f[x] = dns.Fqdn(args[0]) - } - } - - for i := range origins { - origins[i] = middleware.Host(origins[i]).Normalize() - } - - fed.zones = origins - - if len(fed.f) == 0 { - return fed, fmt.Errorf("at least one name to zone federation expected") - } - - return fed, nil - } - - return fed, nil -} diff --git a/middleware/federation/setup_test.go b/middleware/federation/setup_test.go deleted file mode 100644 index e85b01772..000000000 --- a/middleware/federation/setup_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package federation - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestSetup(t *testing.T) { - tests := []struct { - input string - shouldErr bool - expectedLen int - expectedNameZone []string // contains only entry for now - }{ - // ok - {`federation { - prod prod.example.org - }`, false, 1, []string{"prod", "prod.example.org."}}, - - {`federation { - staging staging.example.org - prod prod.example.org - }`, false, 2, []string{"prod", "prod.example.org."}}, - {`federation { - staging staging.example.org - prod prod.example.org - }`, false, 2, []string{"staging", "staging.example.org."}}, - {`federation example.com { - staging staging.example.org - prod prod.example.org - }`, false, 2, []string{"staging", "staging.example.org."}}, - // errors - {`federation { - }`, true, 0, []string{}}, - {`federation { - staging - }`, true, 0, []string{}}, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - fed, err := federationParse(c) - if test.shouldErr && err == nil { - t.Errorf("Test %v: Expected error but found nil", i) - continue - } else if !test.shouldErr && err != nil { - t.Errorf("Test %v: Expected no error but found error: %v", i, err) - continue - } - if test.shouldErr && err != nil { - continue - } - - if x := len(fed.f); x != test.expectedLen { - t.Errorf("Test %v: Expected map length of %d, got: %d", i, test.expectedLen, x) - } - if x, ok := fed.f[test.expectedNameZone[0]]; !ok { - t.Errorf("Test %v: Expected name for %s, got nothing", i, test.expectedNameZone[0]) - } else { - if x != test.expectedNameZone[1] { - t.Errorf("Test %v: Expected zone: %s, got %s", i, test.expectedNameZone[1], x) - } - } - } -} diff --git a/middleware/file/README.md b/middleware/file/README.md deleted file mode 100644 index 29656d161..000000000 --- a/middleware/file/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# file - -*file* enables serving zone data from an RFC 1035-style master file. - -The file middleware is used for an "old-style" DNS server. It serves from a preloaded file that exists -on disk. If the zone file contains signatures (i.e. is signed, i.e. DNSSEC) correct DNSSEC answers -are returned. Only NSEC is supported! If you use this setup *you* are responsible for resigning the -zonefile. - -## Syntax - -~~~ -file DBFILE [ZONES...] -~~~ - -* **DBFILE** the database file to read and parse. If the path is relative the path from the *root* - directive will be prepended to it. -* **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block - are used. - -If you want to round robin A and AAAA responses look at the *loadbalance* middleware. - -TSIG key configuration is TODO; directive format for transfer will probably be extended with -TSIG key information, something like `transfer out [ADDRESS...] key [NAME[:ALG]] [BASE64]` - -~~~ -file DBFILE [ZONES... ] { - transfer to ADDRESS... - no_reload - upstream ADDRESS... -} -~~~ - -* `transfer` enables zone transfers. It may be specified multiples times. `To` or `from` signals - the direction. **ADDRESS** must be denoted in CIDR notation (127.0.0.1/32 etc.) or just as plain - addresses. The special wildcard `*` means: the entire internet (only valid for 'transfer to'). - When an address is specified a notify message will be send whenever the zone is reloaded. -* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the - file. This option disables that behavior. -* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs) - pointing to external names. This is only really useful when CoreDNS is configured as a proxy, for - normal authoritative serving you don't need *or* want to use this. **ADDRESS** can be an IP - address, and IP:port or a string pointing to a file that is structured as /etc/resolv.conf. - -## Examples - -Load the `example.org` zone from `example.org.signed` and allow transfers to the internet, but send -notifies to 10.240.1.1 - -~~~ -file example.org.signed example.org { - transfer to * - transfer to 10.240.1.1 -} -~~~ diff --git a/middleware/file/closest.go b/middleware/file/closest.go deleted file mode 100644 index ea201671f..000000000 --- a/middleware/file/closest.go +++ /dev/null @@ -1,24 +0,0 @@ -package file - -import ( - "github.com/coredns/coredns/middleware/file/tree" - - "github.com/miekg/dns" -) - -// ClosestEncloser returns the closest encloser for qname. -func (z *Zone) ClosestEncloser(qname string) (*tree.Elem, bool) { - - offset, end := dns.NextLabel(qname, 0) - for !end { - elem, _ := z.Tree.Search(qname) - if elem != nil { - return elem, true - } - qname = qname[offset:] - - offset, end = dns.NextLabel(qname, offset) - } - - return z.Tree.Search(z.origin) -} diff --git a/middleware/file/closest_test.go b/middleware/file/closest_test.go deleted file mode 100644 index b37495493..000000000 --- a/middleware/file/closest_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package file - -import ( - "strings" - "testing" -) - -func TestClosestEncloser(t *testing.T) { - z, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0) - if err != nil { - t.Fatalf("expect no error when reading zone, got %q", err) - } - - tests := []struct { - in, out string - }{ - {"miek.nl.", "miek.nl."}, - {"www.miek.nl.", "www.miek.nl."}, - - {"blaat.miek.nl.", "miek.nl."}, - {"blaat.www.miek.nl.", "www.miek.nl."}, - {"www.blaat.miek.nl.", "miek.nl."}, - {"blaat.a.miek.nl.", "a.miek.nl."}, - } - - for _, tc := range tests { - ce, _ := z.ClosestEncloser(tc.in) - if ce == nil { - if z.origin != tc.out { - t.Errorf("Expected ce to be %s for %s, got %s", tc.out, tc.in, ce.Name()) - } - continue - } - if ce.Name() != tc.out { - t.Errorf("Expected ce to be %s for %s, got %s", tc.out, tc.in, ce.Name()) - } - } -} diff --git a/middleware/file/cname_test.go b/middleware/file/cname_test.go deleted file mode 100644 index 90e6f05a9..000000000 --- a/middleware/file/cname_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/proxy" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestLookupCNAMEChain(t *testing.T) { - name := "example.org." - zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin", 0) - if err != nil { - t.Fatalf("Expected no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}} - ctx := context.TODO() - - for _, tc := range cnameTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -var cnameTestCases = []test.Case{ - { - Qname: "a.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("a.example.org. 1800 IN A 127.0.0.1"), - }, - }, - { - Qname: "www3.example.org.", Qtype: dns.TypeCNAME, - Answer: []dns.RR{ - test.CNAME("www3.example.org. 1800 IN CNAME www2.example.org."), - }, - }, - { - Qname: "dangling.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.CNAME("dangling.example.org. 1800 IN CNAME foo.example.org."), - }, - }, - { - Qname: "www3.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("a.example.org. 1800 IN A 127.0.0.1"), - test.CNAME("www.example.org. 1800 IN CNAME a.example.org."), - test.CNAME("www1.example.org. 1800 IN CNAME www.example.org."), - test.CNAME("www2.example.org. 1800 IN CNAME www1.example.org."), - test.CNAME("www3.example.org. 1800 IN CNAME www2.example.org."), - }, - }, -} - -func TestLookupCNAMEExternal(t *testing.T) { - name := "example.org." - zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin", 0) - if err != nil { - t.Fatalf("Expected no error when reading zone, got %q", err) - } - zone.Proxy = proxy.NewLookup([]string{"8.8.8.8:53"}) // TODO(miek): point to local instance - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}} - ctx := context.TODO() - - for _, tc := range exernalTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -var exernalTestCases = []test.Case{ - { - Qname: "external.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.CNAME("external.example.org. 1800 CNAME www.example.net."), - // magic 303 TTL that says: don't check TTL. - test.A("www.example.net. 303 IN A 93.184.216.34"), - }, - }, -} - -const dbExampleCNAME = ` -$TTL 30M -$ORIGIN example.org. -@ IN SOA linode.atoom.net. miek.miek.nl. ( - 1282630057 ; Serial - 4H ; Refresh - 1H ; Retry - 7D ; Expire - 4H ) ; Negative Cache TTL - -a IN A 127.0.0.1 -www3 IN CNAME www2 -www2 IN CNAME www1 -www1 IN CNAME www -www IN CNAME a -dangling IN CNAME foo -external IN CNAME www.example.net.` diff --git a/middleware/file/delegation_test.go b/middleware/file/delegation_test.go deleted file mode 100644 index 49510944f..000000000 --- a/middleware/file/delegation_test.go +++ /dev/null @@ -1,207 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var delegationTestCases = []test.Case{ - { - Qname: "a.delegated.miek.nl.", Qtype: dns.TypeTXT, - Ns: []dns.RR{ - test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."), - test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - }, - Extra: []dns.RR{ - test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, - { - Qname: "delegated.miek.nl.", Qtype: dns.TypeNS, - Answer: []dns.RR{ - test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."), - test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - }, - Extra: []dns.RR{ - test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, - { - Qname: "foo.delegated.miek.nl.", Qtype: dns.TypeA, - Ns: []dns.RR{ - test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."), - test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - }, - Extra: []dns.RR{ - test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, - { - Qname: "foo.delegated.miek.nl.", Qtype: dns.TypeTXT, - Ns: []dns.RR{ - test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."), - test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - }, - Extra: []dns.RR{ - test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeSOA, - Answer: []dns.RR{ - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - Ns: miekAuth, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeAAAA, - Ns: []dns.RR{ - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - }, -} - -var secureDelegationTestCases = []test.Case{ - { - Qname: "a.delegated.example.org.", Qtype: dns.TypeTXT, - Do: true, - Ns: []dns.RR{ - test.DS("delegated.example.org. 1800 IN DS 10056 5 1 EE72CABD1927759CDDA92A10DBF431504B9E1F13"), - test.DS("delegated.example.org. 1800 IN DS 10056 5 2 E4B05F87725FA86D9A64F1E53C3D0E6250946599DFE639C45955B0ED416CDDFA"), - test.NS("delegated.example.org. 1800 IN NS a.delegated.example.org."), - test.NS("delegated.example.org. 1800 IN NS ns-ext.nlnetlabs.nl."), - test.RRSIG("delegated.example.org. 1800 IN RRSIG DS 13 3 1800 20161129153240 20161030153240 49035 example.org. rlNNzcUmtbjLSl02ZzQGUbWX75yCUx0Mug1jHtKVqRq1hpPE2S3863tIWSlz+W9wz4o19OI4jbznKKqk+DGKog=="), - }, - Extra: []dns.RR{ - test.OPT(4096, true), - test.A("a.delegated.example.org. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.example.org. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, - { - Qname: "delegated.example.org.", Qtype: dns.TypeNS, - Do: true, - Answer: []dns.RR{ - test.NS("delegated.example.org. 1800 IN NS a.delegated.example.org."), - test.NS("delegated.example.org. 1800 IN NS ns-ext.nlnetlabs.nl."), - }, - Extra: []dns.RR{ - test.OPT(4096, true), - test.A("a.delegated.example.org. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.example.org. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, - { - Qname: "foo.delegated.example.org.", Qtype: dns.TypeA, - Do: true, - Ns: []dns.RR{ - test.DS("delegated.example.org. 1800 IN DS 10056 5 1 EE72CABD1927759CDDA92A10DBF431504B9E1F13"), - test.DS("delegated.example.org. 1800 IN DS 10056 5 2 E4B05F87725FA86D9A64F1E53C3D0E6250946599DFE639C45955B0ED416CDDFA"), - test.NS("delegated.example.org. 1800 IN NS a.delegated.example.org."), - test.NS("delegated.example.org. 1800 IN NS ns-ext.nlnetlabs.nl."), - test.RRSIG("delegated.example.org. 1800 IN RRSIG DS 13 3 1800 20161129153240 20161030153240 49035 example.org. rlNNzcUmtbjLSl02ZzQGUbWX75yCUx0Mug1jHtKVqRq1hpPE2S3863tIWSlz+W9wz4o19OI4jbznKKqk+DGKog=="), - }, - Extra: []dns.RR{ - test.OPT(4096, true), - test.A("a.delegated.example.org. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.example.org. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, - { - Qname: "foo.delegated.example.org.", Qtype: dns.TypeTXT, - Do: true, - Ns: []dns.RR{ - test.DS("delegated.example.org. 1800 IN DS 10056 5 1 EE72CABD1927759CDDA92A10DBF431504B9E1F13"), - test.DS("delegated.example.org. 1800 IN DS 10056 5 2 E4B05F87725FA86D9A64F1E53C3D0E6250946599DFE639C45955B0ED416CDDFA"), - test.NS("delegated.example.org. 1800 IN NS a.delegated.example.org."), - test.NS("delegated.example.org. 1800 IN NS ns-ext.nlnetlabs.nl."), - test.RRSIG("delegated.example.org. 1800 IN RRSIG DS 13 3 1800 20161129153240 20161030153240 49035 example.org. rlNNzcUmtbjLSl02ZzQGUbWX75yCUx0Mug1jHtKVqRq1hpPE2S3863tIWSlz+W9wz4o19OI4jbznKKqk+DGKog=="), - }, - Extra: []dns.RR{ - test.OPT(4096, true), - test.A("a.delegated.example.org. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.example.org. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, -} - -var miekAuth = []dns.RR{ - test.NS("miek.nl. 1800 IN NS ext.ns.whyscream.net."), - test.NS("miek.nl. 1800 IN NS linode.atoom.net."), - test.NS("miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - test.NS("miek.nl. 1800 IN NS omval.tednet.nl."), -} - -func TestLookupDelegation(t *testing.T) { - testDelegation(t, dbMiekNLDelegation, testzone, delegationTestCases) -} - -func TestLookupSecureDelegation(t *testing.T) { - testDelegation(t, exampleOrgSigned, "example.org.", secureDelegationTestCases) -} - -func testDelegation(t *testing.T, z, origin string, testcases []test.Case) { - zone, err := Parse(strings.NewReader(z), origin, "stdin", 0) - if err != nil { - t.Fatalf("Expect no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{origin: zone}, Names: []string{origin}}} - ctx := context.TODO() - - for _, tc := range testcases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %q\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -const dbMiekNLDelegation = ` -$TTL 30M -$ORIGIN miek.nl. -@ IN SOA linode.atoom.net. miek.miek.nl. ( - 1282630057 ; Serial - 4H ; Refresh - 1H ; Retry - 7D ; Expire - 4H ) ; Negative Cache TTL - IN NS linode.atoom.net. - IN NS ns-ext.nlnetlabs.nl. - IN NS omval.tednet.nl. - IN NS ext.ns.whyscream.net. - - IN MX 1 aspmx.l.google.com. - IN MX 5 alt1.aspmx.l.google.com. - IN MX 5 alt2.aspmx.l.google.com. - IN MX 10 aspmx2.googlemail.com. - IN MX 10 aspmx3.googlemail.com. - -delegated IN NS a.delegated - IN NS ns-ext.nlnetlabs.nl. - -a.delegated IN TXT "obscured" - IN A 139.162.196.78 - IN AAAA 2a01:7e00::f03c:91ff:fef1:6735 - -a IN A 139.162.196.78 - IN AAAA 2a01:7e00::f03c:91ff:fef1:6735 -www IN CNAME a -archive IN CNAME a` diff --git a/middleware/file/dname.go b/middleware/file/dname.go deleted file mode 100644 index 6bbfd9377..000000000 --- a/middleware/file/dname.go +++ /dev/null @@ -1,44 +0,0 @@ -package file - -import ( - "github.com/coredns/coredns/middleware/pkg/dnsutil" - - "github.com/miekg/dns" -) - -// substituteDNAME performs the DNAME substitution defined by RFC 6672, -// assuming the QTYPE of the query is not DNAME. It returns an empty -// string if there is no match. -func substituteDNAME(qname, owner, target string) string { - if dns.IsSubDomain(owner, qname) && qname != owner { - labels := dns.SplitDomainName(qname) - labels = append(labels[0:len(labels)-dns.CountLabel(owner)], dns.SplitDomainName(target)...) - - return dnsutil.Join(labels) - } - - return "" -} - -// synthesizeCNAME returns a CNAME RR pointing to the resulting name of -// the DNAME substitution. The owner name of the CNAME is the QNAME of -// the query and the TTL is the same as the corresponding DNAME RR. -// -// It returns nil if the DNAME substitution has no match. -func synthesizeCNAME(qname string, d *dns.DNAME) *dns.CNAME { - target := substituteDNAME(qname, d.Header().Name, d.Target) - if target == "" { - return nil - } - - r := new(dns.CNAME) - r.Hdr = dns.RR_Header{ - Name: qname, - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - Ttl: d.Header().Ttl, - } - r.Target = target - - return r -} diff --git a/middleware/file/dname_test.go b/middleware/file/dname_test.go deleted file mode 100644 index 27890a78c..000000000 --- a/middleware/file/dname_test.go +++ /dev/null @@ -1,300 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// RFC 6672, Section 2.2. Assuming QTYPE != DNAME. -var dnameSubstitutionTestCases = []struct { - qname string - owner string - target string - expected string -}{ - {"com.", "example.com.", "example.net.", ""}, - {"example.com.", "example.com.", "example.net.", ""}, - {"a.example.com.", "example.com.", "example.net.", "a.example.net."}, - {"a.b.example.com.", "example.com.", "example.net.", "a.b.example.net."}, - {"ab.example.com.", "b.example.com.", "example.net.", ""}, - {"foo.example.com.", "example.com.", "example.net.", "foo.example.net."}, - {"a.x.example.com.", "x.example.com.", "example.net.", "a.example.net."}, - {"a.example.com.", "example.com.", "y.example.net.", "a.y.example.net."}, - {"cyc.example.com.", "example.com.", "example.com.", "cyc.example.com."}, - {"cyc.example.com.", "example.com.", "c.example.com.", "cyc.c.example.com."}, - {"shortloop.x.x.", "x.", ".", "shortloop.x."}, - {"shortloop.x.", "x.", ".", "shortloop."}, -} - -func TestDNAMESubstitution(t *testing.T) { - for i, tc := range dnameSubstitutionTestCases { - result := substituteDNAME(tc.qname, tc.owner, tc.target) - if result != tc.expected { - if result == "" { - result = "<no match>" - } - - t.Errorf("Case %d: Expected %s -> %s, got %v", i, tc.qname, tc.expected, result) - return - } - } -} - -var dnameTestCases = []test.Case{ - { - Qname: "dname.miek.nl.", Qtype: dns.TypeDNAME, - Answer: []dns.RR{ - test.DNAME("dname.miek.nl. 1800 IN DNAME test.miek.nl."), - }, - Ns: miekAuth, - }, - { - Qname: "dname.miek.nl.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("dname.miek.nl. 1800 IN A 127.0.0.1"), - }, - Ns: miekAuth, - }, - { - Qname: "dname.miek.nl.", Qtype: dns.TypeMX, - Answer: []dns.RR{}, - Ns: []dns.RR{ - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - }, - { - Qname: "a.dname.miek.nl.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.CNAME("a.dname.miek.nl. 1800 IN CNAME a.test.miek.nl."), - test.A("a.test.miek.nl. 1800 IN A 139.162.196.78"), - test.DNAME("dname.miek.nl. 1800 IN DNAME test.miek.nl."), - }, - Ns: miekAuth, - }, - { - Qname: "www.dname.miek.nl.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("a.test.miek.nl. 1800 IN A 139.162.196.78"), - test.DNAME("dname.miek.nl. 1800 IN DNAME test.miek.nl."), - test.CNAME("www.dname.miek.nl. 1800 IN CNAME www.test.miek.nl."), - test.CNAME("www.test.miek.nl. 1800 IN CNAME a.test.miek.nl."), - }, - Ns: miekAuth, - }, -} - -func TestLookupDNAME(t *testing.T) { - zone, err := Parse(strings.NewReader(dbMiekNLDNAME), testzone, "stdin", 0) - if err != nil { - t.Fatalf("Expect no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} - ctx := context.TODO() - - for _, tc := range dnameTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -var dnameDnssecTestCases = []test.Case{ - { - // We have no auth section, because the test zone does not have nameservers. - Qname: "ns.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("ns.example.org. 1800 IN A 127.0.0.1"), - }, - }, - { - Qname: "dname.example.org.", Qtype: dns.TypeDNAME, - Do: true, - Answer: []dns.RR{ - test.DNAME("dname.example.org. 1800 IN DNAME test.example.org."), - test.RRSIG("dname.example.org. 1800 IN RRSIG DNAME 5 3 1800 20170702091734 20170602091734 54282 example.org. HvXtiBM="), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "a.dname.example.org.", Qtype: dns.TypeA, - Do: true, - Answer: []dns.RR{ - test.CNAME("a.dname.example.org. 1800 IN CNAME a.test.example.org."), - test.DNAME("dname.example.org. 1800 IN DNAME test.example.org."), - test.RRSIG("dname.example.org. 1800 IN RRSIG DNAME 5 3 1800 20170702091734 20170602091734 54282 example.org. HvXtiBM="), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, -} - -func TestLookupDNAMEDNSSEC(t *testing.T) { - zone, err := Parse(strings.NewReader(dbExampleDNAMESigned), testzone, "stdin", 0) - if err != nil { - t.Fatalf("Expect no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{"example.org.": zone}, Names: []string{"example.org."}}} - ctx := context.TODO() - - for _, tc := range dnameDnssecTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -const dbMiekNLDNAME = ` -$TTL 30M -$ORIGIN miek.nl. -@ IN SOA linode.atoom.net. miek.miek.nl. ( - 1282630057 ; Serial - 4H ; Refresh - 1H ; Retry - 7D ; Expire - 4H ) ; Negative Cache TTL - IN NS linode.atoom.net. - IN NS ns-ext.nlnetlabs.nl. - IN NS omval.tednet.nl. - IN NS ext.ns.whyscream.net. - -test IN MX 1 aspmx.l.google.com. - IN MX 5 alt1.aspmx.l.google.com. - IN MX 5 alt2.aspmx.l.google.com. - IN MX 10 aspmx2.googlemail.com. - IN MX 10 aspmx3.googlemail.com. -a.test IN A 139.162.196.78 - IN AAAA 2a01:7e00::f03c:91ff:fef1:6735 -www.test IN CNAME a.test - -dname IN DNAME test -dname IN A 127.0.0.1 -a.dname IN A 127.0.0.1 -` - -const dbExampleDNAMESigned = ` -; File written on Fri Jun 2 10:17:34 2017 -; dnssec_signzone version 9.10.3-P4-Debian -example.org. 1800 IN SOA a.example.org. b.example.org. ( - 1282630057 ; serial - 14400 ; refresh (4 hours) - 3600 ; retry (1 hour) - 604800 ; expire (1 week) - 14400 ; minimum (4 hours) - ) - 1800 RRSIG SOA 5 2 1800 ( - 20170702091734 20170602091734 54282 example.org. - mr5eQtFs1GubgwaCcqrpiF6Cgi822OkESPeV - X0OJYq3JzthJjHw8TfYAJWQ2yGqhlePHir9h - FT/uFZdYyytHq+qgIUbJ9IVCrq0gZISZdHML - Ry1DNffMR9CpD77KocOAUABfopcvH/3UGOHn - TFxkAr447zPaaoC68JYGxYLfZk8= ) - 1800 NS ns.example.org. - 1800 RRSIG NS 5 2 1800 ( - 20170702091734 20170602091734 54282 example.org. - McM4UdMxkscVQkJnnEbdqwyjpPgq5a/EuOLA - r2MvG43/cwOaWULiZoNzLi5Rjzhf+GTeVTan - jw6EsL3gEuYI1nznwlLQ04/G0XAHjbq5VvJc - rlscBD+dzf774yfaTjRNoeo2xTem6S7nyYPW - Y+1f6xkrsQPLYJfZ6VZ9QqyupBw= ) - 14400 NSEC dname.example.org. NS SOA RRSIG NSEC DNSKEY - 14400 RRSIG NSEC 5 2 14400 ( - 20170702091734 20170602091734 54282 example.org. - VT+IbjDFajM0doMKFipdX3+UXfCn3iHIxg5x - LElp4Q/YddTbX+6tZf53+EO+G8Kye3JDLwEl - o8VceijNeF3igZ+LiZuXCei5Qg/TJ7IAUnAO - xd85IWwEYwyKkKd6Z2kXbAN2pdcHE8EmboQd - wfTr9oyWhpZk1Z+pN8vdejPrG0M= ) - 1800 DNSKEY 256 3 5 ( - AwEAAczLlmTk5bMXUzpBo/Jta6MWSZYy3Nfw - gz8t/pkfSh4IlFF6vyXZhEqCeQsCBdD7ltkD - h5qd4A+nFrYOMwsi5XIjoHMlJN15xwFS9EgS - ZrZmuxePIEiYB5KccEf9JQMgM1t07Iu1FnrY - 02OuAqGWcO4tuyTLaK3QP4MLQOfAgKqf - ) ; ZSK; alg = RSASHA1; key id = 54282 - 1800 RRSIG DNSKEY 5 2 1800 ( - 20170702091734 20170602091734 54282 example.org. - MBgSRtZ6idJblLIHxZWpWL/1oqIwImb1mkl7 - hDFxqV6Hw19yLX06P7gcJEWiisdZBkVEfcOK - LeMJly05vgKfrMzLgIu2Ry4bL8AMKc8NMXBG - b1VDCEBW69P2omogj2KnORHDCZQr/BX9+wBU - 5rIMTTKlMSI5sT6ecJHHEymtiac= ) -dname.example.org. 1800 IN A 127.0.0.1 - 1800 RRSIG A 5 3 1800 ( - 20170702091734 20170602091734 54282 example.org. - LPCK2nLyDdGwvmzGLkUO2atEUjoc+aEspkC3 - keZCdXZaLnAwBH7dNAjvvXzzy0WrgWeiyDb4 - +rJ2N0oaKEZicM4QQDHKhugJblKbU5G4qTey - LSEaV3vvQnzGd0S6dCqnwfPj9czagFN7Zlf5 - DmLtdxx0aiDPCUpqT0+H/vuGPfk= ) - 1800 DNAME test.example.org. - 1800 RRSIG DNAME 5 3 1800 ( - 20170702091734 20170602091734 54282 example.org. - HvX79T1flWJ8H9/1XZjX6gz8rP/o2jbfPXJ9 - vC7ids/ZJilSReabLru4DCqcw1IV2DM/CZdE - tBnED/T2PJXvMut9tnYMrz+ZFPxoV6XyA3Z7 - bok3B0OuxizzAN2EXdol04VdbMHoWUzjQCzi - 0Ri12zLGRPzDepZ7FolgD+JtiBM= ) - 14400 NSEC a.dname.example.org. A DNAME RRSIG NSEC - 14400 RRSIG NSEC 5 3 14400 ( - 20170702091734 20170602091734 54282 example.org. - U3ZPYMUBJl3wF2SazQv/kBf6ec0CH+7n0Hr9 - w6lBKkiXz7P9WQzJDVnTHEZOrbDI6UetFGyC - 6qcaADCASZ9Wxc+riyK1Hl4ox+Y/CHJ97WHy - oS2X//vEf6qmbHQXin0WQtFdU/VCRYF40X5v - 8VfqOmrr8iKiEqXND8XNVf58mTw= ) -a.dname.example.org. 1800 IN A 127.0.0.1 - 1800 RRSIG A 5 4 1800 ( - 20170702091734 20170602091734 54282 example.org. - y7RHBWZwli8SJQ4BgTmdXmYS3KGHZ7AitJCx - zXFksMQtNoOfVEQBwnFqjAb8ezcV5u92h1gN - i1EcuxCFiElML1XFT8dK2GnlPAga9w3oIwd5 - wzW/YHcnR0P9lF56Sl7RoIt6+jJqOdRfixS6 - TDoLoXsNbOxQ+qV3B8pU2Tam204= ) - 14400 NSEC ns.example.org. A RRSIG NSEC - 14400 RRSIG NSEC 5 4 14400 ( - 20170702091734 20170602091734 54282 example.org. - Tmu27q3+xfONSZZtZLhejBUVtEw+83ZU1AFb - Rsxctjry/x5r2JSxw/sgSAExxX/7tx/okZ8J - oJqtChpsr91Kiw3eEBgINi2lCYIpMJlW4cWz - 8bYlHfR81VsKYgy/cRgrq1RRvBoJnw+nwSty - mKPIvUtt67LAvLxJheSCEMZLCKI= ) -ns.example.org. 1800 IN A 127.0.0.1 - 1800 RRSIG A 5 3 1800 ( - 20170702091734 20170602091734 54282 example.org. - mhi1SGaaAt+ndQEg5uKWKCH0HMzaqh/9dUK3 - p2wWMBrLbTZrcWyz10zRnvehicXDCasbBrer - ZpDQnz5AgxYYBURvdPfUzx1XbNuRJRE4l5PN - CEUTlTWcqCXnlSoPKEJE5HRf7v0xg2BrBUfM - 4mZnW2bFLwjrRQ5mm/mAmHmTROk= ) - 14400 NSEC example.org. A RRSIG NSEC - 14400 RRSIG NSEC 5 3 14400 ( - 20170702091734 20170602091734 54282 example.org. - loHcdjX+NIWLAkUDfPSy2371wrfUvrBQTfMO - 17eO2Y9E/6PE935NF5bjQtZBRRghyxzrFJhm - vY1Ad5ZTb+NLHvdSWbJQJog+eCc7QWp64WzR - RXpMdvaE6ZDwalWldLjC3h8QDywDoFdndoRY - eHOsmTvvtWWqtO6Fa5A8gmHT5HA= ) -` diff --git a/middleware/file/dnssec_test.go b/middleware/file/dnssec_test.go deleted file mode 100644 index 136a9dfee..000000000 --- a/middleware/file/dnssec_test.go +++ /dev/null @@ -1,358 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var dnssecTestCases = []test.Case{ - { - Qname: "miek.nl.", Qtype: dns.TypeSOA, Do: true, - Answer: []dns.RR{ - test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - Ns: auth, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeAAAA, Do: true, - Answer: []dns.RR{ - test.AAAA("miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - test.RRSIG("miek.nl. 1800 IN RRSIG AAAA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. SsRT="), - }, - Ns: auth, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeNS, Do: true, - Answer: []dns.RR{ - test.NS("miek.nl. 1800 IN NS ext.ns.whyscream.net."), - test.NS("miek.nl. 1800 IN NS linode.atoom.net."), - test.NS("miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - test.NS("miek.nl. 1800 IN NS omval.tednet.nl."), - test.RRSIG("miek.nl. 1800 IN RRSIG NS 8 2 1800 20160426031301 20160327031301 12051 miek.nl. ZLtsQhwaz+lHfNpztFoR1Vxs="), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeMX, Do: true, - Answer: []dns.RR{ - test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."), - test.MX("miek.nl. 1800 IN MX 10 aspmx2.googlemail.com."), - test.MX("miek.nl. 1800 IN MX 10 aspmx3.googlemail.com."), - test.MX("miek.nl. 1800 IN MX 5 alt1.aspmx.l.google.com."), - test.MX("miek.nl. 1800 IN MX 5 alt2.aspmx.l.google.com."), - test.RRSIG("miek.nl. 1800 IN RRSIG MX 8 2 1800 20160426031301 20160327031301 12051 miek.nl. kLqG+iOr="), - }, - Ns: auth, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "www.miek.nl.", Qtype: dns.TypeA, Do: true, - Answer: []dns.RR{ - test.A("a.miek.nl. 1800 IN A 139.162.196.78"), - test.RRSIG("a.miek.nl. 1800 IN RRSIG A 8 3 1800 20160426031301 20160327031301 12051 miek.nl. lxLotCjWZ3kihTxk="), - test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."), - test.RRSIG("www.miek.nl. 1800 RRSIG CNAME 8 3 1800 20160426031301 20160327031301 12051 miek.nl. NVZmMJaypS+wDL2Lar4Zw1zF"), - }, - Ns: auth, - Extra: []dns.RR{ - test.OPT(4096, true), - }, - }, - { - // NoData - Qname: "a.miek.nl.", Qtype: dns.TypeSRV, Do: true, - Ns: []dns.RR{ - test.NSEC("a.miek.nl. 14400 IN NSEC archive.miek.nl. A AAAA RRSIG NSEC"), - test.RRSIG("a.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. GqnF6cutipmSHEao="), - test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "b.miek.nl.", Qtype: dns.TypeA, Do: true, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.NSEC("archive.miek.nl. 14400 IN NSEC go.dns.miek.nl. CNAME RRSIG NSEC"), - test.RRSIG("archive.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. jEpx8lcp4do5fWXg="), - test.NSEC("miek.nl. 14400 IN NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY"), - test.RRSIG("miek.nl. 14400 IN RRSIG NSEC 8 2 14400 20160426031301 20160327031301 12051 miek.nl. mFfc3r/9PSC1H6oSpdC"), - test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "b.blaat.miek.nl.", Qtype: dns.TypeA, Do: true, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.NSEC("archive.miek.nl. 14400 IN NSEC go.dns.miek.nl. CNAME RRSIG NSEC"), - test.RRSIG("archive.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. jEpx8lcp4do5fWXg="), - test.NSEC("miek.nl. 14400 IN NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY"), - test.RRSIG("miek.nl. 14400 IN RRSIG NSEC 8 2 14400 20160426031301 20160327031301 12051 miek.nl. mFfc3r/9PSC1H6oSpdC"), - test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "b.a.miek.nl.", Qtype: dns.TypeA, Do: true, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - // dedupped NSEC, because 1 nsec tells all - test.NSEC("a.miek.nl. 14400 IN NSEC archive.miek.nl. A AAAA RRSIG NSEC"), - test.RRSIG("a.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. GqnF6cut/RRGPQ1QGQE1ipmSHEao="), - test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, -} - -var auth = []dns.RR{ - test.NS("miek.nl. 1800 IN NS ext.ns.whyscream.net."), - test.NS("miek.nl. 1800 IN NS linode.atoom.net."), - test.NS("miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - test.NS("miek.nl. 1800 IN NS omval.tednet.nl."), - test.RRSIG("miek.nl. 1800 IN RRSIG NS 8 2 1800 20160426031301 20160327031301 12051 miek.nl. ZLtsQhwazbqSpztFoR1Vxs="), -} - -func TestLookupDNSSEC(t *testing.T) { - zone, err := Parse(strings.NewReader(dbMiekNLSigned), testzone, "stdin", 0) - if err != nil { - t.Fatalf("Expected no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} - ctx := context.TODO() - - for _, tc := range dnssecTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -func BenchmarkFileLookupDNSSEC(b *testing.B) { - zone, err := Parse(strings.NewReader(dbMiekNLSigned), testzone, "stdin", 0) - if err != nil { - return - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} - ctx := context.TODO() - rec := dnsrecorder.New(&test.ResponseWriter{}) - - tc := test.Case{ - Qname: "b.miek.nl.", Qtype: dns.TypeA, Do: true, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.NSEC("archive.miek.nl. 14400 IN NSEC go.dns.miek.nl. CNAME RRSIG NSEC"), - test.RRSIG("archive.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. jEpx8lcp4do5fWXg="), - test.NSEC("miek.nl. 14400 IN NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY"), - test.RRSIG("miek.nl. 14400 IN RRSIG NSEC 8 2 14400 20160426031301 20160327031301 12051 miek.nl. mFfc3r/9PSC1H6oSpdC"), - test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - } - - m := tc.Msg() - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - fm.ServeDNS(ctx, rec, m) - } -} - -const dbMiekNLSigned = ` -; File written on Sun Mar 27 04:13:01 2016 -; dnssec_signzone version 9.10.3-P4-Ubuntu -miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. ( - 1459051981 ; serial - 14400 ; refresh (4 hours) - 3600 ; retry (1 hour) - 604800 ; expire (1 week) - 14400 ; minimum (4 hours) - ) - 1800 RRSIG SOA 8 2 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - FIrzy07acBzrf6kNW13Ypmq/ahojoMqOj0qJ - ixTevTvwOEcVuw9GlJoYIHTYg+hm1sZHtx9K - RiVmYsm8SHKsJA1WzixtT4K7vQvM+T+qbeOJ - xA6YTivKUcGRWRXQlOTUAlHS/KqBEfmxKgRS - 68G4oOEClFDSJKh7RbtyQczy1dc= ) - 1800 NS ext.ns.whyscream.net. - 1800 NS omval.tednet.nl. - 1800 NS linode.atoom.net. - 1800 NS ns-ext.nlnetlabs.nl. - 1800 RRSIG NS 8 2 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - ZLtsQhwaz+CwrgzgFiEAqbqS/JH65MYjziA3 - 6EXwlGDy41lcfGm71PpxA7cDzFhWNkJNk4QF - q48wtpP4IGPPpHbnJHKDUXj6se7S+ylAGbS+ - VgVJ4YaVcE6xA9ZVhVpz8CSSjeH34vmqq9xj - zmFjofuDvraZflHfNpztFoR1Vxs= ) - 1800 A 139.162.196.78 - 1800 RRSIG A 8 2 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - hl+6Q075tsCkxIqbop8zZ6U8rlFvooz7Izzx - MgCZYVLcg75El28EXKIhBfRb1dPaKbd+v+AD - wrJMHL131pY5sU2Ly05K+7CqmmyaXgDaVsKS - rSw/TbhGDIItBemeseeuXGAKAbY2+gE7kNN9 - mZoQ9hRB3SrxE2jhctv66DzYYQQ= ) - 1800 MX 1 aspmx.l.google.com. - 1800 MX 5 alt1.aspmx.l.google.com. - 1800 MX 5 alt2.aspmx.l.google.com. - 1800 MX 10 aspmx2.googlemail.com. - 1800 MX 10 aspmx3.googlemail.com. - 1800 RRSIG MX 8 2 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - kLqG+iOrKSzms1H9Et9me8Zts1rbyeCFSVQD - G9is/u6ec3Lqg2vwJddf/yRsjVpVgadWSAkc - GSDuD2dK8oBeP24axWc3Z1OY2gdMI7w+PKWT - Z+pjHVjbjM47Ii/a6jk5SYeOwpGMsdEwhtTP - vk2O2WGljifqV3uE7GshF5WNR10= ) - 1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735 - 1800 RRSIG AAAA 8 2 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - SsRTHytW4YTAuHovHQgfIMhNwMtMp4gaAU/Z - lgTO+IkBb9y9F8uHrf25gG6RqA1bnGV/gezV - NU5negXm50bf1BNcyn3aCwEbA0rCGYIL+nLJ - szlBVbBu6me/Ym9bbJlfgfHRDfsVy2ZkNL+B - jfNQtGCSDoJwshjcqJlfIVSardo= ) - 14400 NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY - 14400 RRSIG NSEC 8 2 14400 ( - 20160426031301 20160327031301 12051 miek.nl. - mFfc3r/9PSC1H6oSpdC+FDy/Iu02W2Tf0x+b - n6Lpe1gCC1uvcSUrrmBNlyAWRr5Zm+ZXssEb - cKddRGiu/5sf0bUWrs4tqokL/HUl10X/sBxb - HfwNAeD7R7+CkpMv67li5AhsDgmQzpX2r3P6 - /6oZyLvODGobysbmzeWM6ckE8IE= ) - 1800 DNSKEY 256 3 8 ( - AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6 - E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5EC - IoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb - 2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXH - Py7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz - ) ; ZSK; alg = RSASHA256; key id = 12051 - 1800 DNSKEY 257 3 8 ( - AwEAAcWdjBl4W4wh/hPxMDcBytmNCvEngIgB - 9Ut3C2+QI0oVz78/WK9KPoQF7B74JQ/mjO4f - vIncBmPp6mFNxs9/WQX0IXf7oKviEVOXLjct - R4D1KQLX0wprvtUIsQFIGdXaO6suTT5eDbSd - 6tTwu5xIkGkDmQhhH8OQydoEuCwV245ZwF/8 - AIsqBYDNQtQ6zhd6jDC+uZJXg/9LuPOxFHbi - MTjp6j3CCW0kHbfM/YHZErWWtjPj3U3Z7knQ - SIm5PO5FRKBEYDdr5UxWJ/1/20SrzI3iztvP - wHDsA2rdHm/4YRzq7CvG4N0t9ac/T0a0Sxba - /BUX2UVPWaIVBdTRBtgHi0s= - ) ; KSK; alg = RSASHA256; key id = 33694 - 1800 RRSIG DNSKEY 8 2 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - o/D6o8+/bNGQyyRvwZ2hM0BJ+3HirvNjZoko - yGhGe9sPSrYU39WF3JVIQvNJFK6W3/iwlKir - TPOeYlN6QilnztFq1vpCxwj2kxJaIJhZecig - LsKxY/fOHwZlIbBLZZadQG6JoGRLHnImSzpf - xtyVaXQtfnJFC07HHt9np3kICfE= ) - 1800 RRSIG DNSKEY 8 2 1800 ( - 20160426031301 20160327031301 33694 miek.nl. - Ak/mbbQVQV+nUgw5Sw/c+TSoYqIwbLARzuNE - QJvJNoRR4tKVOY6qSxQv+j5S7vzyORZ+yeDp - NlEa1T9kxZVBMABoOtLX5kRqZncgijuH8fxb - L57Sv2IzINI9+DOcy9Q9p9ygtwYzQKrYoNi1 - 0hwHi6emGkVG2gGghruMinwOJASGgQy487Yd - eIpcEKJRw73nxd2le/4/Vafy+mBpKWOczfYi - 5m9MSSxcK56NFYjPG7TvdIw0m70F/smY9KBP - pGWEdzRQDlqfZ4fpDaTAFGyRX0mPFzMbs1DD - 3hQ4LHUSi/NgQakdH9eF42EVEDeL4cI69K98 - 6NNk6X9TRslO694HKw== ) -a.miek.nl. 1800 IN A 139.162.196.78 - 1800 RRSIG A 8 3 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - lxLotCjWZ3kikNNcePu6HOCqMHDINKFRJRD8 - laz2KQ9DKtgXPdnRw5RJvVITSj8GUVzw1ec1 - CYVEKu/eMw/rc953Zns528QBypGPeMNLe2vu - C6a6UhZnGHA48dSd9EX33eSJs0MP9xsC9csv - LGdzYmv++eslkKxkhSOk2j/hTxk= ) - 1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735 - 1800 RRSIG AAAA 8 3 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - ji3QMlaUzlK85ppB5Pc+y2WnfqOi6qrm6dm1 - bXgsEov/5UV1Lmcv8+Y5NBbTbBlXGlWcpqNp - uWpf9z3lbguDWznpnasN2MM8t7yxo/Cr7WRf - QCzui7ewpWiA5hq7j0kVbM4nnDc6cO+U93hO - mMhVbeVI70HM2m0HaHkziEyzVZk= ) - 14400 NSEC archive.miek.nl. A AAAA RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20160426031301 20160327031301 12051 miek.nl. - GqnF6cut/KCxbnJj27MCjjVGkjObV0hLhHOP - E1/GXAUTEKG6BWxJq8hidS3p/yrOmP5PEL9T - 4FjBp0/REdVmGpuLaiHyMselES82p/uMMdY5 - QqRM6LHhZdO1zsRbyzOZbm5MsW6GR7K2kHlX - 9TdBIULiRRGPQ1QGQE1ipmSHEao= ) -archive.miek.nl. 1800 IN CNAME a.miek.nl. - 1800 RRSIG CNAME 8 3 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - s4zVJiDrVuUiUFr8CNQLuXYYfpqpl8rovL50 - BYsub/xK756NENiOTAOjYH6KYg7RSzsygJjV - YQwXolZly2/KXAr48SCtxzkGFxLexxiKcFaj - vm7ZDl7Btoa5l68qmBcxOX5E/W0IKITi4PNK - mhBs7dlaf0IbPGNgMxae72RosxM= ) - 14400 NSEC go.dns.miek.nl. CNAME RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20160426031301 20160327031301 12051 miek.nl. - jEp7LsoK++/PRFh2HieLzasA1jXBpp90NyDf - RfpfOxdM69yRKfvXMc2bazIiMuDhxht79dGI - Gj02cn1cvX60SlaHkeFtqTdJcHdK9rbI65EK - YHFZFzGh9XVnuMJKpUsm/xS1dnUSAnXN8q+0 - xBlUDlQpsAFv/cx8lcp4do5fWXg= ) -go.dns.miek.nl. 1800 IN TXT "Hello!" - 1800 RRSIG TXT 8 4 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - O0uo1NsXTq2TTfgOmGbHQQEchrcpllaDAMMX - dTDizw3t+vZ5SR32qJ8W7y6VXLgUqJgcdRxS - Fou1pp+t5juRZSQ0LKgxMpZAgHorkzPvRf1b - E9eBKrDSuLGagsQRwHeldFGFgsXtCbf07vVH - zoKR8ynuG4/cAoY0JzMhCts+56U= ) - 14400 NSEC www.miek.nl. TXT RRSIG NSEC - 14400 RRSIG NSEC 8 4 14400 ( - 20160426031301 20160327031301 12051 miek.nl. - BW6qo7kYe3Z+Y0ebaVTWTy1c3bpdf8WUEoXq - WDQxLDEj2fFiuEBDaSN5lTWRg3wj8kZmr6Uk - LvX0P29lbATFarIgkyiAdbOEdaf88nMfqBW8 - z2T5xrPQcN0F13uehmv395yAJs4tebRxErMl - KdkVF0dskaDvw8Wo3YgjHUf6TXM= ) -www.miek.nl. 1800 IN CNAME a.miek.nl. - 1800 RRSIG CNAME 8 3 1800 ( - 20160426031301 20160327031301 12051 miek.nl. - MiQQh2lScoNiNVZmMJaypS+wDL2Lar4Zw1zF - Uo4tL16BfQOt7yl8gXdAH2JMFqoKAoIdM2K6 - XwFOwKTOGSW0oNCOcaE7ts+1Z1U0H3O2tHfq - FAzfg1s9pQ5zxk8J/bJgkVIkw2/cyB0y1/PK - EmIqvChBSb4NchTuMCSqo63LJM8= ) - 14400 NSEC miek.nl. CNAME RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20160426031301 20160327031301 12051 miek.nl. - OPPZ8iaUPrVKEP4cqeCiiv1WLRAY30GRIhc/ - me0gBwFkbmTEnvB+rUp831OJZDZBNKv4QdZj - Uyc26wKUOQeUyMJqv4IRDgxH7nq9GB5JRjYZ - IVxtGD1aqWLXz+8aMaf9ARJjtYUd3K4lt8Wz - LbJSo5Wdq7GOWqhgkY5n3XD0/FA= )` diff --git a/middleware/file/dnssex_test.go b/middleware/file/dnssex_test.go deleted file mode 100644 index d9a0a4568..000000000 --- a/middleware/file/dnssex_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package file - -const dbDnssexNLSigned = ` -; File written on Tue Mar 29 21:02:24 2016 -; dnssec_signzone version 9.10.3-P4-Ubuntu -dnssex.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. ( - 1459281744 ; serial - 14400 ; refresh (4 hours) - 3600 ; retry (1 hour) - 604800 ; expire (1 week) - 14400 ; minimum (4 hours) - ) - 1800 RRSIG SOA 8 2 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - CA/Y3m9hCOiKC/8ieSOv8SeP964BUdG/8MC3 - WtKljUosK9Z9bBGrVizDjjqgq++lyH8BZJcT - aabAsERs4xj5PRtcxicwQXZACX5VYjXHQeZm - CyytFU5wq2gcXSmvUH86zZzftx3RGPvn1aOo - TlcvoC3iF8fYUCpROlUS0YR8Cdw= ) - 1800 NS omval.tednet.nl. - 1800 NS linode.atoom.net. - 1800 NS ns-ext.nlnetlabs.nl. - 1800 RRSIG NS 8 2 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - dLIeEvP86jj5nd3orv9bH7hTvkblF4Na0sbl - k6fJA6ha+FPN1d6Pig3NNEEVQ/+wlOp/JTs2 - v07L7roEEUCbBprI8gMSld2gFDwNLW3DAB4M - WD/oayYdAnumekcLzhgvWixTABjWAGRTGQsP - sVDFXsGMf9TGGC9FEomgkCVeNC0= ) - 1800 A 139.162.196.78 - 1800 RRSIG A 8 2 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - LKJKLzPiSEDWOLAag2YpfD5EJCuDcEAJu+FZ - Xy+4VyOv9YvRHCTL4vbrevOo5+XymY2RxU1q - j+6leR/Fe7nlreSj2wzAAk2bIYn4m6r7hqeO - aKZsUFfpX8cNcFtGEywfHndCPELbRxFeEziP - utqHFLPNMX5nYCpS28w4oJ5sAnM= ) - 1800 TXT "Doing It Safe Is Better" - 1800 RRSIG TXT 8 2 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - f6S+DUfJK1UYdOb3AHgUXzFTTtu+yLp/Fv7S - Hv0CAGhXAVw+nBbK719igFvBtObS33WKwzxD - 1pQNMaJcS6zeevtD+4PKB1KDC4fyJffeEZT6 - E30jGR8Y29/xA+Fa4lqDNnj9zP3b8TiABCle - ascY5abkgWCALLocFAzFJQ/27YQ= ) - 1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735 - 1800 RRSIG AAAA 8 2 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - PWcPSawEUBAfCuv0liEOQ8RYe7tfNW4rubIJ - LE+dbrub1DUer3cWrDoCYFtOufvcbkYJQ2CQ - AGjJmAQ5J2aqYDOPMrKa615V0KT3ifbZJcGC - gkIic4U/EXjaQpRoLdDzR9MyVXOmbA6sKYzj - ju1cNkLqM8D7Uunjl4pIr6rdSFo= ) - 14400 NSEC *.dnssex.nl. A NS SOA TXT AAAA RRSIG NSEC DNSKEY - 14400 RRSIG NSEC 8 2 14400 ( - 20160428190224 20160329190224 14460 dnssex.nl. - oIvM6JZIlNc1aNKGTxv58ApSnDr1nDPPgnD9 - 9oJZRIn7eb5WnpeDz2H3z5+x6Bhlp5hJJaUp - KJ3Ss6Jg/IDnrmIvKmgq6L6gHj1Y1IiHmmU8 - VeZTRzdTsDx/27OsN23roIvsytjveNSEMfIm - iLZ23x5kg1kBdJ9p3xjYHm5lR+8= ) - 1800 DNSKEY 256 3 8 ( - AwEAAazSO6uvLPEVknDA8yxjFe8nnAMU7txp - wb19k55hQ81WV3G4bpBM1NdN6sbYHrkXaTNx - 2bQWAkvX6pz0XFx3z/MPhW+vkakIWFYpyQ7R - AT5LIJfToVfiCDiyhhF0zVobKBInO9eoGjd9 - BAW3TUt+LmNAO/Ak5D5BX7R3CuA7v9k7 - ) ; ZSK; alg = RSASHA256; key id = 14460 - 1800 DNSKEY 257 3 8 ( - AwEAAbyeaV9zg0IqdtgYoqK5jJ239anzwG2i - gvH1DxSazLyaoNvEkCIvPgMLW/JWfy7Z1mQp - SMy9DtzL5pzRyQgw7kIeXLbi6jufUFd9pxN+ - xnzKLf9mY5AcnGToTrbSL+jnMT67wG+c34+Q - PeVfucHNUePBxsbz2+4xbXiViSQyCQGv - ) ; KSK; alg = RSASHA256; key id = 18772 - 1800 RRSIG DNSKEY 8 2 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - cFSFtJE+DBGNxb52AweFaVHBe5Ue5MDpqNdC - TIneUnEhP2m+vK4zJ/TraK0WdQFpsX63pod8 - PZ9y03vHUfewivyonCCBD3DcNdoU9subhN22 - tez9Ct8Z5/9E4RAz7orXal4M1VUEhRcXSEH8 - SJW20mfVsqJAiKqqNeGB/pAj23I= ) - 1800 RRSIG DNSKEY 8 2 1800 ( - 20160428190224 20160329190224 18772 dnssex.nl. - oiiwo/7NYacePqohEp50261elhm6Dieh4j2S - VZGAHU5gqLIQeW9CxKJKtSCkBVgUo4cvO4Rn - 2tzArAuclDvBrMXRIoct8u7f96moeFE+x5FI - DYqICiV6k449ljj9o4t/5G7q2CRsEfxZKpTI - A/L0+uDk0RwVVzL45+TnilcsmZs= ) -*.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better" - 1800 RRSIG TXT 8 2 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - FUZSTyvZfeuuOpCmNzVKOfITRHJ6/ygjmnnb - XGBxVUyQjoLuYXwD5XqZWGw4iKH6QeSDfGCx - 4MPqA4qQmW7Wwth7mat9yMfA4+p2sO84bysl - 7/BG9+W2G+q1uQiM9bX9V42P2X/XuW5Y/t9Y - 8u1sljQ7D8WwS6naH/vbaJxnDBw= ) - 14400 NSEC a.dnssex.nl. TXT RRSIG NSEC - 14400 RRSIG NSEC 8 2 14400 ( - 20160428190224 20160329190224 14460 dnssex.nl. - os6INm6q2eXknD5z8TpfbK00uxVbQefMvHcR - /RNX/kh0xXvzAaaDOV+Ge/Ko+2dXnKP+J1LY - G9ffXNpdbaQy5ygzH5F041GJst4566GdG/jt - 7Z7vLHYxEBTpZfxo+PLsXQXH3VTemZyuWyDf - qJzafXJVH1F0nDrcXmMlR6jlBHA= ) -www.dnssex.nl. 1800 IN CNAME a.dnssex.nl. - 1800 RRSIG CNAME 8 3 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - Omv42q/uVvdNsWQoSrQ6m6w6U7r7Abga7uF4 - 25b3gZlse0C+WyMyGFMGUbapQm7azvBpreeo - uKJHjzd+ufoG+Oul6vU9vyoj+ejgHzGLGbJQ - HftfP+UqP5SWvAaipP/LULTWKPuiBcLDLiBI - PGTfsq0DB6R+qCDTV0fNnkgxEBQ= ) - 14400 NSEC dnssex.nl. CNAME RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20160428190224 20160329190224 14460 dnssex.nl. - TBN3ddfZW+kC84/g3QlNNJMeLZoyCalPQylt - KXXLPGuxfGpl3RYRY8KaHbP+5a8MnHjqjuMB - Lofb7yKMFxpSzMh8E36vnOqry1mvkSakNj9y - 9jM8PwDjcpYUwn/ql76MsmNgEV5CLeQ7lyH4 - AOrL79yOSQVI3JHJIjKSiz88iSw= ) -a.dnssex.nl. 1800 IN A 139.162.196.78 - 1800 RRSIG A 8 3 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - OXHpFj9nSpKi5yA/ULH7MOpGAWfyJ2yC/2xa - Pw0fqSY4QvcRt+V3adcFA4H9+P1b32GpxEjB - lXmCJID+H4lYkhUR4r4IOZBVtKG2SJEBZXip - pH00UkOIBiXxbGzfX8VL04v2G/YxUgLW57kA - aknaeTOkJsO20Y+8wmR9EtzaRFI= ) - 1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735 - 1800 RRSIG AAAA 8 3 1800 ( - 20160428190224 20160329190224 14460 dnssex.nl. - jrepc/VnRzJypnrG0WDEqaAr3HMjWrPxJNX0 - 86gbFjZG07QxBmrA1rj0jM9YEWTjjyWb2tT7 - lQhzKDYX/0XdOVUeeOM4FoSks80V+pWR8fvj - AZ5HmX69g36tLosMDKNR4lXcrpv89QovG4Hr - /r58fxEKEFJqrLDjMo6aOrg+uKA= ) - 14400 NSEC www.dnssex.nl. A AAAA RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20160428190224 20160329190224 14460 dnssex.nl. - S+UM62wXRNNFN3QDWK5YFWUbHBXC4aqaqinZ - A2ZDeC+IQgyw7vazPz7cLI5T0YXXks0HTMlr - soEjKnnRZsqSO9EuUavPNE1hh11Jjm0fB+5+ - +Uro0EmA5Dhgc0Z2VpbXVQEhNDf/pI1gem15 - RffN2tBYNykZn4Has2ySgRaaRYQ= )` diff --git a/middleware/file/ds_test.go b/middleware/file/ds_test.go deleted file mode 100644 index f5c9bb332..000000000 --- a/middleware/file/ds_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var dsTestCases = []test.Case{ - { - Qname: "a.delegated.miek.nl.", Qtype: dns.TypeDS, - Ns: []dns.RR{ - test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."), - test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - }, - Extra: []dns.RR{ - test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, - { - Qname: "_udp.delegated.miek.nl.", Qtype: dns.TypeDS, - Ns: []dns.RR{ - test.NS("delegated.miek.nl. 1800 IN NS a.delegated.miek.nl."), - test.NS("delegated.miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - }, - Extra: []dns.RR{ - test.A("a.delegated.miek.nl. 1800 IN A 139.162.196.78"), - test.AAAA("a.delegated.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - }, - { - // This works *here* because we skip the server routing for DS in core/dnsserver/server.go - Qname: "_udp.miek.nl.", Qtype: dns.TypeDS, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeDS, - Ns: []dns.RR{ - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - }, -} - -func TestLookupDS(t *testing.T) { - zone, err := Parse(strings.NewReader(dbMiekNLDelegation), testzone, "stdin", 0) - if err != nil { - t.Fatalf("Expected no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} - ctx := context.TODO() - - for _, tc := range dsTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/file/ent_test.go b/middleware/file/ent_test.go deleted file mode 100644 index 433aa182d..000000000 --- a/middleware/file/ent_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var entTestCases = []test.Case{ - { - Qname: "b.c.miek.nl.", Qtype: dns.TypeA, - Ns: []dns.RR{ - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - }, - { - Qname: "b.c.miek.nl.", Qtype: dns.TypeA, Do: true, - Ns: []dns.RR{ - test.NSEC("a.miek.nl. 14400 IN NSEC a.b.c.miek.nl. A RRSIG NSEC"), - test.RRSIG("a.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160502144311 20160402144311 12051 miek.nl. d5XZEy6SUpq98ZKUlzqhAfkLI9pQPc="), - test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160502144311 20160402144311 12051 miek.nl. KegoBxA3Tbrhlc4cEdkRiteIkOfsq"), - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, -} - -func TestLookupEnt(t *testing.T) { - zone, err := Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin", 0) - if err != nil { - t.Fatalf("expect no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} - ctx := context.TODO() - - for _, tc := range entTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -// fdjfdjkf -const dbMiekENTNL = `; File written on Sat Apr 2 16:43:11 2016 -; dnssec_signzone version 9.10.3-P4-Ubuntu -miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. ( - 1282630057 ; serial - 14400 ; refresh (4 hours) - 3600 ; retry (1 hour) - 604800 ; expire (1 week) - 14400 ; minimum (4 hours) - ) - 1800 RRSIG SOA 8 2 1800 ( - 20160502144311 20160402144311 12051 miek.nl. - KegoBxA3Tbrhlc4cEdkRiteIkOfsqD4oCLLM - ISJ5bChWy00LGHUlAnHVu5Ti96hUjVNmGSxa - xtGSuAAMFCr52W8pAB8LBIlu9B6QZUPHMccr - SuzxAX3ioawk2uTjm+k8AGPT4RoQdXemGLAp - zJTASolTVmeMTh5J0sZTZJrtvZ0= ) - 1800 NS linode.atoom.net. - 1800 RRSIG NS 8 2 1800 ( - 20160502144311 20160402144311 12051 miek.nl. - m0cOHL6Rre/0jZPXe+0IUjs/8AFASRCvDbSx - ZQsRDSlZgS6RoMP3OC77cnrKDVlfZ2Vhq3Ce - nYPoGe0/atB92XXsilmstx4HTSU64gsV9iLN - Xkzk36617t7zGOl/qumqfaUXeA9tihItzEim - 6SGnufVZI4o8xeyaVCNDDuN0bvY= ) - 14400 NSEC a.miek.nl. NS SOA RRSIG NSEC DNSKEY - 14400 RRSIG NSEC 8 2 14400 ( - 20160502144311 20160402144311 12051 miek.nl. - BCWVgwxWrs4tBjS9QXKkftCUbiLi40NyH1yA - nbFy1wCKQ2jDH00810+ia4b66QrjlAKgxE9z - 9U7MKSMV86sNkyAtlCi+2OnjtWF6sxPdJO7k - CHeg46XBjrQuiJRY8CneQX56+IEPdufLeqPR - l+ocBQ2UkGhXmQdWp3CFDn2/eqU= ) - 1800 DNSKEY 256 3 8 ( - AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6 - E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5EC - IoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb - 2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXH - Py7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz - ) ; ZSK; alg = RSASHA256; key id = 12051 - 1800 DNSKEY 257 3 8 ( - AwEAAcWdjBl4W4wh/hPxMDcBytmNCvEngIgB - 9Ut3C2+QI0oVz78/WK9KPoQF7B74JQ/mjO4f - vIncBmPp6mFNxs9/WQX0IXf7oKviEVOXLjct - R4D1KQLX0wprvtUIsQFIGdXaO6suTT5eDbSd - 6tTwu5xIkGkDmQhhH8OQydoEuCwV245ZwF/8 - AIsqBYDNQtQ6zhd6jDC+uZJXg/9LuPOxFHbi - MTjp6j3CCW0kHbfM/YHZErWWtjPj3U3Z7knQ - SIm5PO5FRKBEYDdr5UxWJ/1/20SrzI3iztvP - wHDsA2rdHm/4YRzq7CvG4N0t9ac/T0a0Sxba - /BUX2UVPWaIVBdTRBtgHi0s= - ) ; KSK; alg = RSASHA256; key id = 33694 - 1800 RRSIG DNSKEY 8 2 1800 ( - 20160502144311 20160402144311 12051 miek.nl. - YNpi1jRDQKpnsQEjIjxqy+kJGaYnV16e8Iug - 40c82y4pee7kIojFUllSKP44qiJpCArxF557 - tfjfwBd6c4hkqCScGPZXJ06LMyG4u//rhVMh - 4hyKcxzQFKxmrFlj3oQGksCI8lxGX6RxiZuR - qv2ol2lUWrqetpAL+Zzwt71884E= ) - 1800 RRSIG DNSKEY 8 2 1800 ( - 20160502144311 20160402144311 33694 miek.nl. - jKpLDEeyadgM0wDgzEk6sBBdWr2/aCrkAOU/ - w6dYIafN98f21oIYQfscV1gc7CTsA0vwzzUu - x0QgwxoNLMvSxxjOiW/2MzF8eozczImeCWbl - ad/pVCYH6Jn5UBrZ5RCWMVcs2RP5KDXWeXKs - jEN/0EmQg5qNd4zqtlPIQinA9I1HquJAnS56 - pFvYyGIbZmGEbhR18sXVBeTWYr+zOMHn2quX - 0kkrx2udz+sPg7i4yRsLdhw138gPRy1qvbaC - 8ELs1xo1mC9pTlDOhz24Q3iXpVAU1lXLYOh9 - nUP1/4UvZEYXHBUQk/XPRciojniWjAF825x3 - QoSivMHblBwRdAKJSg== ) -a.miek.nl. 1800 IN A 127.0.0.1 - 1800 RRSIG A 8 3 1800 ( - 20160502144311 20160402144311 12051 miek.nl. - lUOYdSxScjyYz+Ebc+nb6iTNgCohqj7K+Dat - 97KE7haV2nP3LxdYuDCJYZpeyhsXDLHd4bFI - bInYPwJiC6DUCxPCuCWy0KYlZOWW8KCLX3Ia - BOPQbvIwLsJhnX+/tyMD9mXortoqATO79/6p - nNxvFeM8pFDwaih17fXMuFR/BsI= ) - 14400 NSEC a.b.c.miek.nl. A RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20160502144311 20160402144311 12051 miek.nl. - d5XZEy6SUp+TPRJQED+0R65zf2Yeo/1dlEA2 - jYYvkXGSHXke4sg9nH8U3nr1rLcuqA1DsQgH - uMIjdENvXuZ+WCSwvIbhC+JEI6AyQ6Gfaf/D - I3mfu60C730IRByTrKM5C2rt11lwRQlbdaUY - h23/nn/q98ZKUlzqhAfkLI9pQPc= ) -a.b.c.miek.nl. 1800 IN A 127.0.0.1 - 1800 RRSIG A 8 5 1800 ( - 20160502144311 20160402144311 12051 miek.nl. - FwgU5+fFD4hEebco3gvKQt3PXfY+dcOJr8dl - Ky4WLsONIdhP+4e9oprPisSLxImErY21BcrW - xzu1IZrYDsS8XBVV44lBx5WXEKvAOrUcut/S - OWhFZW7ncdIQCp32ZBIatiLRJEqXUjx+guHs - noFLiHix35wJWsRKwjGLIhH1fbs= ) - 14400 NSEC miek.nl. A RRSIG NSEC - 14400 RRSIG NSEC 8 5 14400 ( - 20160502144311 20160402144311 12051 miek.nl. - lXgOqm9/jRRYvaG5jC1CDvTtGYxMroTzf4t4 - jeYGb60+qI0q9sHQKfAJvoQ5o8o1qfR7OuiF - f544ipYT9eTcJRyGAOoJ37yMie7ZIoVJ91tB - r8YdzZ9Q6x3v1cbwTaQiacwhPZhGYOw63qIs - q5IQErIPos2sNk+y9D8BEce2DO4= )` diff --git a/middleware/file/example_org.go b/middleware/file/example_org.go deleted file mode 100644 index eba18e0e4..000000000 --- a/middleware/file/example_org.go +++ /dev/null @@ -1,113 +0,0 @@ -package file - -// exampleOrgSigned is a fake signed example.org zone with two delegations, -// one signed (with DSs) and one "normal". -const exampleOrgSigned = ` -example.org. 1800 IN SOA a.iana-servers.net. devnull.example.org. ( - 1282630057 ; serial - 14400 ; refresh (4 hours) - 3600 ; retry (1 hour) - 604800 ; expire (1 week) - 14400 ; minimum (4 hours) - ) - 1800 RRSIG SOA 13 2 1800 ( - 20161129153240 20161030153240 49035 example.org. - GVnMpFmN+6PDdgCtlYDEYBsnBNDgYmEJNvos - Bk9+PNTPNWNst+BXCpDadTeqRwrr1RHEAQ7j - YWzNwqn81pN+IA== ) - 1800 NS a.iana-servers.net. - 1800 NS b.iana-servers.net. - 1800 RRSIG NS 13 2 1800 ( - 20161129153240 20161030153240 49035 example.org. - llrHoIuwjnbo28LOt4p5zWAs98XGqrXicKVI - Qxyaf/ORM8boJvW2XrKr3nj6Y8FKMhzd287D - 5PBzVCL6MZyjQg== ) - 14400 NSEC a.example.org. NS SOA RRSIG NSEC DNSKEY - 14400 RRSIG NSEC 13 2 14400 ( - 20161129153240 20161030153240 49035 example.org. - BQROf1swrmYi3GqpP5M/h5vTB8jmJ/RFnlaX - 7fjxvV7aMvXCsr3ekWeB2S7L6wWFihDYcKJg - 9BxVPqxzBKeaqg== ) - 1800 DNSKEY 256 3 13 ( - UNTqlHbC51EbXuY0rshW19Iz8SkCuGVS+L0e - bQj53dvtNlaKfWmtTauC797FoyVLbQwoMy/P - G68SXgLCx8g+9g== - ) ; ZSK; alg = ECDSAP256SHA256; key id = 49035 - 1800 RRSIG DNSKEY 13 2 1800 ( - 20161129153240 20161030153240 49035 example.org. - LnLHyqYJaCMOt7EHB4GZxzAzWLwEGCTFiEhC - jj1X1VuQSjJcN42Zd3yF+jihSW6huknrig0Z - Mqv0FM6mJ/qPKg== ) -a.delegated.example.org. 1800 IN A 139.162.196.78 - 1800 TXT "obscured" - 1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735 -archive.example.org. 1800 IN CNAME a.example.org. - 1800 RRSIG CNAME 13 3 1800 ( - 20161129153240 20161030153240 49035 example.org. - SDFW1z/PN9knzH8BwBvmWK0qdIwMVtGrMgRw - 7lgy4utRrdrRdCSLZy3xpkmkh1wehuGc4R0S - 05Z3DPhB0Fg5BA== ) - 14400 NSEC delegated.example.org. CNAME RRSIG NSEC - 14400 RRSIG NSEC 13 3 14400 ( - 20161129153240 20161030153240 49035 example.org. - DQqLSVNl8F6v1K09wRU6/M6hbHy2VUddnOwn - JusJjMlrAOmoOctCZ/N/BwqCXXBA+d9yFGdH - knYumXp+BVPBAQ== ) -www.example.org. 1800 IN CNAME a.example.org. - 1800 RRSIG CNAME 13 3 1800 ( - 20161129153240 20161030153240 49035 example.org. - adzujOxCV0uBV4OayPGfR11iWBLiiSAnZB1R - slmhBFaDKOKSNYijGtiVPeaF+EuZs63pzd4y - 6Nm2Iq9cQhAwAA== ) - 14400 NSEC example.org. CNAME RRSIG NSEC - 14400 RRSIG NSEC 13 3 14400 ( - 20161129153240 20161030153240 49035 example.org. - jy3f96GZGBaRuQQjuqsoP1YN8ObZF37o+WkV - PL7TruzI7iNl0AjrUDy9FplP8Mqk/HWyvlPe - N3cU+W8NYlfDDQ== ) -a.example.org. 1800 IN A 139.162.196.78 - 1800 RRSIG A 13 3 1800 ( - 20161129153240 20161030153240 49035 example.org. - 41jFz0Dr8tZBN4Kv25S5dD4vTmviFiLx7xSA - qMIuLFm0qibKL07perKpxqgLqM0H1wreT4xz - I9Y4Dgp1nsOuMA== ) - 1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735 - 1800 RRSIG AAAA 13 3 1800 ( - 20161129153240 20161030153240 49035 example.org. - brHizDxYCxCHrSKIu+J+XQbodRcb7KNRdN4q - VOWw8wHqeBsFNRzvFF6jwPQYphGP7kZh1KAb - VuY5ZVVhM2kHjw== ) - 14400 NSEC archive.example.org. A AAAA RRSIG NSEC - 14400 RRSIG NSEC 13 3 14400 ( - 20161129153240 20161030153240 49035 example.org. - zIenVlg5ScLr157EWigrTGUgrv7W/1s49Fic - i2k+OVjZfT50zw+q5X6DPKkzfAiUhIuqs53r - hZUzZwV/1Wew9Q== ) -delegated.example.org. 1800 IN NS a.delegated.example.org. - 1800 IN NS ns-ext.nlnetlabs.nl. - 1800 DS 10056 5 1 ( - EE72CABD1927759CDDA92A10DBF431504B9E - 1F13 ) - 1800 DS 10056 5 2 ( - E4B05F87725FA86D9A64F1E53C3D0E625094 - 6599DFE639C45955B0ED416CDDFA ) - 1800 RRSIG DS 13 3 1800 ( - 20161129153240 20161030153240 49035 example.org. - rlNNzcUmtbjLSl02ZzQGUbWX75yCUx0Mug1j - HtKVqRq1hpPE2S3863tIWSlz+W9wz4o19OI4 - jbznKKqk+DGKog== ) - 14400 NSEC sub.example.org. NS DS RRSIG NSEC - 14400 RRSIG NSEC 13 3 14400 ( - 20161129153240 20161030153240 49035 example.org. - lNQ5kRTB26yvZU5bFn84LYFCjwWTmBcRCDbD - cqWZvCSw4LFOcqbz1/wJKIRjIXIqnWIrfIHe - fZ9QD5xZsrPgUQ== ) -sub.example.org. 1800 IN NS sub1.example.net. - 1800 IN NS sub2.example.net. - 14400 NSEC www.example.org. NS RRSIG NSEC - 14400 RRSIG NSEC 13 3 14400 ( - 20161129153240 20161030153240 49035 example.org. - VYjahdV+TTkA3RBdnUI0hwXDm6U5k/weeZZr - ix1znORpOELbeLBMJW56cnaG+LGwOQfw9qqj - bOuULDst84s4+g== ) -` diff --git a/middleware/file/file.go b/middleware/file/file.go deleted file mode 100644 index 27ab0cd1b..000000000 --- a/middleware/file/file.go +++ /dev/null @@ -1,138 +0,0 @@ -// Package file implements a file backend. -package file - -import ( - "fmt" - "io" - "log" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -type ( - // File is the middleware that reads zone data from disk. - File struct { - Next middleware.Handler - Zones Zones - } - - // Zones maps zone names to a *Zone. - Zones struct { - Z map[string]*Zone // A map mapping zone (origin) to the Zone's data - Names []string // All the keys from the map Z as a string slice. - } -) - -// ServeDNS implements the middleware.Handle interface. -func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - - qname := state.Name() - // TODO(miek): match the qname better in the map - zone := middleware.Zones(f.Zones.Names).Matches(qname) - if zone == "" { - return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r) - } - - z, ok := f.Zones.Z[zone] - if !ok || z == nil { - return dns.RcodeServerFailure, nil - } - - // This is only for when we are a secondary zones. - if r.Opcode == dns.OpcodeNotify { - if z.isNotify(state) { - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - state.SizeAndDo(m) - w.WriteMsg(m) - - log.Printf("[INFO] Notify from %s for %s: checking transfer", state.IP(), zone) - ok, err := z.shouldTransfer() - if ok { - z.TransferIn() - } else { - log.Printf("[INFO] Notify from %s for %s: no serial increase seen", state.IP(), zone) - } - if err != nil { - log.Printf("[WARNING] Notify from %s for %s: failed primary check: %s", state.IP(), zone, err) - } - return dns.RcodeSuccess, nil - } - log.Printf("[INFO] Dropping notify from %s for %s", state.IP(), zone) - return dns.RcodeSuccess, nil - } - - if z.Expired != nil && *z.Expired { - log.Printf("[ERROR] Zone %s is expired", zone) - return dns.RcodeServerFailure, nil - } - - if state.QType() == dns.TypeAXFR || state.QType() == dns.TypeIXFR { - xfr := Xfr{z} - return xfr.ServeDNS(ctx, w, r) - } - - answer, ns, extra, result := z.Lookup(state, qname) - - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - m.Answer, m.Ns, m.Extra = answer, ns, extra - - switch result { - case Success: - case NoData: - case NameError: - m.Rcode = dns.RcodeNameError - case Delegation: - m.Authoritative = false - case ServerFailure: - return dns.RcodeServerFailure, nil - } - - state.SizeAndDo(m) - m, _ = state.Scrub(m) - w.WriteMsg(m) - return dns.RcodeSuccess, nil -} - -// Name implements the Handler interface. -func (f File) Name() string { return "file" } - -// Parse parses the zone in filename and returns a new Zone or an error. -// If serial >= 0 it will reload the zone, if the SOA hasn't changed -// it returns an error indicating nothing was read. -func Parse(f io.Reader, origin, fileName string, serial int64) (*Zone, error) { - tokens := dns.ParseZone(f, dns.Fqdn(origin), fileName) - z := NewZone(origin, fileName) - seenSOA := false - for x := range tokens { - if x.Error != nil { - return nil, x.Error - } - - if !seenSOA && serial >= 0 { - if s, ok := x.RR.(*dns.SOA); ok { - if s.Serial == uint32(serial) { // same zone - return nil, fmt.Errorf("no change in serial: %d", serial) - } - seenSOA = true - } - } - - if err := z.Insert(x.RR); err != nil { - return nil, err - } - } - if !seenSOA { - return nil, fmt.Errorf("file %q has no SOA record", fileName) - } - - return z, nil -} diff --git a/middleware/file/file_test.go b/middleware/file/file_test.go deleted file mode 100644 index 02668785b..000000000 --- a/middleware/file/file_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package file - -import ( - "strings" - "testing" -) - -func BenchmarkFileParseInsert(b *testing.B) { - for i := 0; i < b.N; i++ { - Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin", 0) - } -} - -func TestParseNoSOA(t *testing.T) { - _, err := Parse(strings.NewReader(dbNoSOA), "example.org.", "stdin", 0) - if err == nil { - t.Fatalf("zone %q should have failed to load", "example.org.") - } - if !strings.Contains(err.Error(), "no SOA record") { - t.Fatalf("zone %q should have failed to load with no soa error: %s", "example.org.", err) - } -} - -const dbNoSOA = ` -$TTL 1M -$ORIGIN example.org. - -www IN A 192.168.0.14 -mail IN A 192.168.0.15 -imap IN CNAME mail -` diff --git a/middleware/file/glue_test.go b/middleware/file/glue_test.go deleted file mode 100644 index 14716ae33..000000000 --- a/middleware/file/glue_test.go +++ /dev/null @@ -1,253 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// another personal zone (helps in testing as my secondary is NSD -// atoom = atom in English. -var atoomTestCases = []test.Case{ - { - Qname: atoom, Qtype: dns.TypeNS, Do: true, - Answer: []dns.RR{ - test.NS("atoom.net. 1800 IN NS linode.atoom.net."), - test.NS("atoom.net. 1800 IN NS ns-ext.nlnetlabs.nl."), - test.NS("atoom.net. 1800 IN NS omval.tednet.nl."), - test.RRSIG("atoom.net. 1800 IN RRSIG NS 8 2 1800 20170112031301 20161213031301 53289 atoom.net. DLe+G1 jlw="), - }, - Extra: []dns.RR{ - test.OPT(4096, true), - test.A("linode.atoom.net. 1800 IN A 176.58.119.54"), - test.AAAA("linode.atoom.net. 1800 IN AAAA 2a01:7e00::f03c:91ff:fe79:234c"), - test.RRSIG("linode.atoom.net. 1800 IN RRSIG A 8 3 1800 20170112031301 20161213031301 53289 atoom.net. Z4Ka4OLDoyxj72CL vkI="), - test.RRSIG("linode.atoom.net. 1800 IN RRSIG AAAA 8 3 1800 20170112031301 20161213031301 53289 atoom.net. l+9Qc914zFH/okG2fzJ1q olQ="), - }, - }, -} - -func TestLookupGlue(t *testing.T) { - zone, err := Parse(strings.NewReader(dbAtoomNetSigned), atoom, "stdin", 0) - if err != nil { - t.Fatalf("Expected no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{atoom: zone}, Names: []string{atoom}}} - ctx := context.TODO() - - for _, tc := range atoomTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -const dbAtoomNetSigned = ` -; File written on Tue Dec 13 04:13:01 2016 -; dnssec_signzone version 9.10.3-P4-Debian -atoom.net. 1800 IN SOA linode.atoom.net. miek.miek.nl. ( - 1481602381 ; serial - 14400 ; refresh (4 hours) - 3600 ; retry (1 hour) - 604800 ; expire (1 week) - 14400 ; minimum (4 hours) - ) - 1800 RRSIG SOA 8 2 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - GZ30uFuGATKzwHXgpEwK70qjdXSAqmbB5d4z - e7WTibvJDPLa1ptZBI7Zuod2KMOkT1ocSvhL - U7makhdv0BQx+5RSaP25mAmPIzfU7/T7R+DJ - 5q1GLlDSvOprfyMUlwOgZKZinesSdUa9gRmu - 8E+XnPNJ/jcTrGzzaDjn1/irrM0= ) - 1800 NS omval.tednet.nl. - 1800 NS linode.atoom.net. - 1800 NS ns-ext.nlnetlabs.nl. - 1800 RRSIG NS 8 2 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - D8Sd9JpXIOxOrUF5Hi1ASutyQwP7JNu8XZxA - rse86A6L01O8H8sCNib2VEoJjHuZ/dDEogng - OgmfqeFy04cpSX19GAk3bkx8Lr6aEat3nqIC - XA/xsCCfXy0NKZpI05zntHPbbP5tF/NvpE7n - 0+oLtlHSPEg1ZnEgwNoLe+G1jlw= ) - 1800 A 176.58.119.54 - 1800 RRSIG A 8 2 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - mrjiUFNCqDgCW8TuhjzcMh0V841uC224QvwH - 0+OvYhcve9twbX3Y12PSFmz77Xz3Jg9WAj4I - qhh3iHUac4dzUXyC702DT62yMF/9CMUO0+Ee - b6wRtvPHr2Tt0i/xV/BTbArInIvurXJrvKvo - LsZHOfsg7dZs6Mvdpe/CgwRExpk= ) - 1800 AAAA 2a01:7e00::f03c:91ff:fe79:234c - 1800 RRSIG AAAA 8 2 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - EkMxX2vUaP4h0qbWlHaT4yNhm8MrPMZTn/3R - zNw+i3oF2cLMWKh6GCfuIX/x5ID706o8kfum - bxTYwuTe1LJ+GoZHWEiH8VCa1laTlh8l3qSi - PZKU8339rr5cCYluk6p9PbAuRkYYOEruNg42 - wPOx46dsAlvp2XpOaOeJtU64QGQ= ) - 14400 NSEC deb.atoom.net. A NS SOA AAAA RRSIG NSEC DNSKEY - 14400 RRSIG NSEC 8 2 14400 ( - 20170112031301 20161213031301 53289 atoom.net. - P7Stx7lqRKl8tbTAAaJ0W6UhgJwZz3cjpM8z - eplbhXEVohKtyJ9xgptKt1vreH6lkhzciar5 - EB9Nj0VOmcthiht/+As8aEKmf8UlcJ2EbLII - NT7NUaasxsrLE2rjjX5mEtzOZ1uQAGiU8Hnk - XdGweTgIVFuiCcMCgaKpC2TRrMw= ) - 1800 DNSKEY 256 3 8 ( - AwEAAeDZTH9YT9qLMPlq4VrxX7H3GbWcqCrC - tXc9RT/hf96GN+ttnnEQVaJY8Gbly3IZpYQW - MwaCi0t30UULXE3s9FUQtl4AMbplyiz9EF8L - /XoBS1yhGm5WV5u608ihoPaRkYNyVV3egb5Y - hA5EXWy2vfsa1XWPpxvSAhlqM0YENtP3 - ) ; ZSK; alg = RSASHA256; key id = 53289 - 1800 DNSKEY 257 3 8 ( - AwEAAepN7Vo8enDCruVduVlGxTDIv7QG0wJQ - fTL1hMy4k0Yf/7dXzrn5bZT4ytBvH1hoBImH - mtTrQo6DQlBBVXDJXTyQjQozaHpN1HhTJJTz - IXl8UrdbkLWvz6QSeJPmBBYQRAqylUA2KE29 - nxyiNboheDLiIWyQ7Q/Op7lYaKMdb555kQAs - b/XT4Tb3/3BhAjcofNofNBjDjPq2i8pAo8HU - 5mW5/Pl+ZT/S0aqQPnCkHk/iofSRu3ZdBzkH - 54eoC+BdyXb7gTbPGRr+1gMbf/rzhRiZ4vnX - NoEzGAXmorKzJHANNb6KQ/932V9UDHm9wbln - 6y3s7IBvsMX5KF8vo81Stkc= - ) ; KSK; alg = RSASHA256; key id = 19114 - 1800 RRSIG DNSKEY 8 2 1800 ( - 20170112031301 20161213031301 19114 atoom.net. - IEjViubKdef8RWB5bcnirqVcqDk16irkywJZ - sBjMyNs03/a+sl0UHEGAB7qCC+Rn+RDaM5It - WF+Gha6BwRIN9NuSg3BwB2h1nJtHw61pMVU9 - 2j9Q3pq7X1xoTBAcwY95t5a1xlw0iTCaLu1L - Iu/PbVp1gj1o8BF/PiYilvZJGUjaTgsi+YNi - 2kiWpp6afO78/W4nfVx+lQBmpyfX1lwL5PEC - 9f5PMbzRmOapvUBc2XdddGywLdmlNsLHimGV - t7kkHZHOWQR1TvvMbU3dsC0bFCrBVGDhEuxC - hATR+X5YV0AyDSyrew7fOGJKrapwMWS3yRLr - FAt0Vcxno5lwQImbCQ== ) - 1800 RRSIG DNSKEY 8 2 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - sSxdgPT+gFZPN0ot6lZRGqOwvONUEsg0uEbf - kh19JlWHu/qvq5HOOK2VOW/UnswpVmtpFk0W - z/jiCNHifjpCCVn5tfCMZDLGekmPOjdobw24 - swBuGjnn0NHvxHoN6S+mb+AR6V/dLjquNUda - yzBc2Ua+XtQ7SCLKIvEhcNg9H3o= ) -deb.atoom.net. 1800 IN A 176.58.119.54 - 1800 RRSIG A 8 3 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - ZW7jm/VDa/I9DxWlE7Cm+HHymiVv4Wk5UGYI - Uf/g0EfxLCBR6SwL5QKuV1z7xoWKaiNqqrmc - gg35xgskKyS8QHgCCODhDzcIKe+MSsBXbY04 - AtrC5dV3JJQoA65Ng/48hwcyghAjXKrA2Yyq - GXf2DSvWeIV9Jmk0CsOELP24dpk= ) - 1800 TXT "v=spf1 a ip6:2a01:7e00::f03c:91ff:fe79:234c ~all" - 1800 RRSIG TXT 8 3 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - fpvVJ+Z6tzSd9yETn/PhLSCRISwRD1c3ET80 - 8twnx3XfAPQfV2R8dw7pz8Vw4TSxvf19bAZc - PWRjW682gb7gAxoJshCXBYabMfqExrBc9V1S - ezwm3D93xNMyegxzHx2b/H8qp3ZWdsMLTvvN - Azu7P4iyO+WRWT0R7bJGrdTwRz8= ) - 1800 AAAA 2a01:7e00::f03c:91ff:fe79:234c - 1800 RRSIG AAAA 8 3 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - aaPF6NqXfWamzi+xUDVeYa7StJUVM1tDsL34 - w5uozFRZ0f4K/Z88Kk5CgztxmtpNNKGdLWa0 - iryUJsbVWAbSQfrZNkNckBtczMNxGgjqn97A - 2//F6ajH/qrR3dWcCm+VJMgu3UPqAxLiCaYO - GQUx6Y8JA1VIM/RJAM6BhgNxjD0= ) - 14400 NSEC lafhart.atoom.net. A TXT AAAA RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20170112031301 20161213031301 53289 atoom.net. - 1Llad64NDWcz8CyBu2TsyANrJ9Tpfm5257sY - FPYF579p3c9Imwp9kYEO1zMEKgNoXBN/sQnd - YCugq3r2GAI6bfJj8sV5bt6GKuZcGHMESug4 - uh2gU0NDcCA4GPdBYGdusePwV0RNpcRnVCFA - fsACp+22j3uwRUbCh0re0ufbAs4= ) -lafhart.atoom.net. 1800 IN A 178.79.160.171 - 1800 RRSIG A 8 3 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - fruP6cvMVICXEV8NcheS73NWLCEKlO1FgW6B - 35D2GhtfYZe+M23V5YBRtlVCCrAdS0etdCOf - xH9yt3u2kVvDXuMRiQr1zJPRDEq3cScYumpd - bOO8cjHiCic5lEcRVWNNHXyGtpqTvrp9CxOu - IQw1WgAlZyKj43zGg3WZi6OTKLg= ) - 14400 NSEC linode.atoom.net. A RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20170112031301 20161213031301 53289 atoom.net. - 2AUWXbScL0jIJ7G6UsJAlUs+bgSprZ1zY6v/ - iVB5BAYwZD6pPky7LZdzvPEHh0aNLGIFbbU8 - SDJI7u/e4RUTlE+8yyjl6obZNfNKyJFqE5xN - 1BJ8sjFrVn6KaHIDKEOZunNb1MlMfCRkLg9O - 94zg04XEgVUfaYCPxvLs3fCEgzw= ) -voordeur.atoom.net. 1800 IN A 77.249.87.46 - 1800 RRSIG A 8 3 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - SzJz0NaKLRA/lW4CxgMHgeuQLp5QqFEjQv3I - zfPtY4joQsZn8RN8RLECcpcPKjbC8Dj6mxIJ - dd2vwhsCVlZKMNcZUOfpB7eGx1TR9HnzMkY9 - OdTt30a9+tktagrJEoy31vAhj1hJqLbSgvOa - pRr1P4ZpQ53/qH8JX/LOmqfWTdg= ) - 14400 NSEC www.atoom.net. A RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20170112031301 20161213031301 53289 atoom.net. - CETJhUJy1rKjVj9wsW1549gth+/Z37//BI6S - nxJ+2Oq63jEjlbznmyo5hvFW54DbVUod+cLo - N9PdlNQDr1XsRBgWhkKW37RkuoRVEPwqRykv - xzn9i7CgYKAAHFyWMGihBLkV9ByPp8GDR8Zr - DEkrG3ErDlBcwi3FqGZFsSOW2xg= ) -www.atoom.net. 1800 IN CNAME deb.atoom.net. - 1800 RRSIG CNAME 8 3 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - 1lhG6iTtbeesBCVOrA8a7+V2gogCuXzKgSi8 - 6K0Pzq2CwqTScdNcZvcDOIbLq45Am5p09PIj - lXnd2fw6WAxphwvRhmwCve3uTZMUt5STw7oi - 0rED7GMuFUSC/BX0XVly7NET3ECa1vaK6RhO - hDSsKPWFI7to4d1z6tQ9j9Kvm4Y= ) - 14400 NSEC atoom.net. CNAME RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20170112031301 20161213031301 53289 atoom.net. - CC4yCYP1q75/gTmPz+mVM6Lam2foPP5oTccY - RtROuTkgbt8DtAoPe304vmNazWBlGidnWJeD - YyAAe3znIHP0CgrxjD/hRL9FUzMnVrvB3mnx - 4W13wP1rE97RqJxV1kk22Wl3uCkVGy7LCjb0 - JLFvzCe2fuMe7YcTzI+t1rioTP0= ) -linode.atoom.net. 1800 IN A 176.58.119.54 - 1800 RRSIG A 8 3 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - Z4Ka4OLDha4eQNWs3GtUd1Cumr48RUnH523I - nZzGXtpQNou70qsm5Jt8n/HmsZ4L5DoxomRz - rgZTGnrqj43+A16UUGfVEk6SfUUHOgxgspQW - zoaqk5/5mQO1ROsLKY8RqaRqzvbToHvqeZEh - VkTPVA02JK9UFlKqoyxj72CLvkI= ) - 1800 AAAA 2a01:7e00::f03c:91ff:fe79:234c - 1800 RRSIG AAAA 8 3 1800 ( - 20170112031301 20161213031301 53289 atoom.net. - l+9Qce/EQyKrTJVKLv7iatjuCO285ckd5Oie - P2LzWVsL4tW04oHzieKZwIuNBRE+px8g5qrT - LIK2TikCGL1xHAd7CT7gbCtDcZ7jHmSTmMTJ - 405nOV3G3xWelreLI5Fn5ck8noEsF64kiw1y - XfkyQn2B914zFH/okG2fzJ1qolQ= ) - 14400 NSEC voordeur.atoom.net. A AAAA RRSIG NSEC - 14400 RRSIG NSEC 8 3 14400 ( - 20170112031301 20161213031301 53289 atoom.net. - Owzmz7QrVL2Gw2njEsUVEknMl2amx1HG9X3K - tO+Ihyy4tApiUFxUjAu3P/30QdqbB85h7s// - ipwX/AmQJNoxTScR3nHt9qDqJ044DPmiuh0l - NuIjguyZRANApmKCTA6AoxXIUqToIIjfVzi/ - PxXE6T3YIPlK7Bxgv1lcCBJ1fmE= )` - -const atoom = "atoom.net." diff --git a/middleware/file/include_test.go b/middleware/file/include_test.go deleted file mode 100644 index f07a02e03..000000000 --- a/middleware/file/include_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/test" -) - -// Make sure the external miekg/dns dependency is up to date - -func TestInclude(t *testing.T) { - - name, rm, err := test.TempFile(".", "foo\tIN\tA\t127.0.0.1\n") - if err != nil { - t.Fatalf("Unable to create tmpfile %q: %s", name, err) - } - defer rm() - - zone := `$ORIGIN example.org. -@ IN SOA sns.dns.icann.org. noc.dns.icann.org. 2017042766 7200 3600 1209600 3600 -$INCLUDE ` + name + "\n" - - z, err := Parse(strings.NewReader(zone), "example.org.", "test", 0) - if err != nil { - t.Errorf("Unable to parse zone %q: %s", "example.org.", err) - } - - if _, ok := z.Search("foo.example.org."); !ok { - t.Errorf("Failed to find %q in parsed zone", "foo.example.org.") - } -} diff --git a/middleware/file/lookup.go b/middleware/file/lookup.go deleted file mode 100644 index 89e0f5a47..000000000 --- a/middleware/file/lookup.go +++ /dev/null @@ -1,467 +0,0 @@ -package file - -import ( - "github.com/coredns/coredns/middleware/file/tree" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// Result is the result of a Lookup -type Result int - -const ( - // Success is a successful lookup. - Success Result = iota - // NameError indicates a nameerror - NameError - // Delegation indicates the lookup resulted in a delegation. - Delegation - // NoData indicates the lookup resulted in a NODATA. - NoData - // ServerFailure indicates a server failure during the lookup. - ServerFailure -) - -// Lookup looks up qname and qtype in the zone. When do is true DNSSEC records are included. -// Three sets of records are returned, one for the answer, one for authority and one for the additional section. -func (z *Zone) Lookup(state request.Request, qname string) ([]dns.RR, []dns.RR, []dns.RR, Result) { - - qtype := state.QType() - do := state.Do() - - if !z.NoReload { - z.reloadMu.RLock() - } - defer func() { - if !z.NoReload { - z.reloadMu.RUnlock() - } - }() - - // If z is a secondary zone we might not have transferred it, meaning we have - // all zone context setup, except the actual record. This means (for one thing) the apex - // is empty and we don't have a SOA record. - soa := z.Apex.SOA - if soa == nil { - return nil, nil, nil, ServerFailure - } - - if qtype == dns.TypeSOA { - return z.soa(do), z.ns(do), nil, Success - } - if qtype == dns.TypeNS && qname == z.origin { - nsrrs := z.ns(do) - glue := z.Glue(nsrrs, do) - return nsrrs, nil, glue, Success - } - - var ( - found, shot bool - parts string - i int - elem, wildElem *tree.Elem - ) - - // Lookup: - // * Per label from the right, look if it exists. We do this to find potential - // delegation records. - // * If the per-label search finds nothing, we will look for the wildcard at the - // level. If found we keep it around. If we don't find the complete name we will - // use the wildcard. - // - // Main for-loop handles delegation and finding or not finding the qname. - // If found we check if it is a CNAME/DNAME and do CNAME processing - // We also check if we have type and do a nodata resposne. - // - // If not found, we check the potential wildcard, and use that for further processing. - // If not found and no wildcard we will process this as an NXDOMAIN response. - for { - parts, shot = z.nameFromRight(qname, i) - // We overshot the name, break and check if we previously found something. - if shot { - break - } - - elem, found = z.Tree.Search(parts) - if !found { - // Apex will always be found, when we are here we can search for a wildcard - // and save the result of that search. So when nothing match, but we have a - // wildcard we should expand the wildcard. - - wildcard := replaceWithAsteriskLabel(parts) - if wild, found := z.Tree.Search(wildcard); found { - wildElem = wild - } - - // Keep on searching, because maybe we hit an empty-non-terminal (which aren't - // stored in the tree. Only when we have match the full qname (and possible wildcard - // we can be confident that we didn't find anything. - i++ - continue - } - - // If we see DNAME records, we should return those. - if dnamerrs := elem.Types(dns.TypeDNAME); dnamerrs != nil { - // Only one DNAME is allowed per name. We just pick the first one to synthesize from. - dname := dnamerrs[0] - if cname := synthesizeCNAME(state.Name(), dname.(*dns.DNAME)); cname != nil { - answer, ns, extra, rcode := z.searchCNAME(state, elem, []dns.RR{cname}) - - if do { - sigs := elem.Types(dns.TypeRRSIG) - sigs = signatureForSubType(sigs, dns.TypeDNAME) - dnamerrs = append(dnamerrs, sigs...) - } - - // The relevant DNAME RR should be included in the answer section, - // if the DNAME is being employed as a substitution instruction. - answer = append(dnamerrs, answer...) - - return answer, ns, extra, rcode - } - // The domain name that owns a DNAME record is allowed to have other RR types - // at that domain name, except those have restrictions on what they can coexist - // with (e.g. another DNAME). So there is nothing special left here. - } - - // If we see NS records, it means the name as been delegated, and we should return the delegation. - if nsrrs := elem.Types(dns.TypeNS); nsrrs != nil { - glue := z.Glue(nsrrs, do) - // If qtype == NS, we should returns success to put RRs in answer. - if qtype == dns.TypeNS { - return nsrrs, nil, glue, Success - } - - if do { - dss := z.typeFromElem(elem, dns.TypeDS, do) - nsrrs = append(nsrrs, dss...) - } - - return nil, nsrrs, glue, Delegation - } - - i++ - } - - // What does found and !shot mean - do we ever hit it? - if found && !shot { - return nil, nil, nil, ServerFailure - } - - // Found entire name. - if found && shot { - - if rrs := elem.Types(dns.TypeCNAME); len(rrs) > 0 && qtype != dns.TypeCNAME { - return z.searchCNAME(state, elem, rrs) - } - - rrs := elem.Types(qtype, qname) - - // NODATA - if len(rrs) == 0 { - ret := z.soa(do) - if do { - nsec := z.typeFromElem(elem, dns.TypeNSEC, do) - ret = append(ret, nsec...) - } - return nil, ret, nil, NoData - } - - // Additional section processing for MX, SRV. Check response and see if any of the names are in baliwick - - // if so add IP addresses to the additional section. - additional := additionalProcessing(z, rrs, do) - - if do { - sigs := elem.Types(dns.TypeRRSIG) - sigs = signatureForSubType(sigs, qtype) - rrs = append(rrs, sigs...) - } - - return rrs, z.ns(do), additional, Success - - } - - // Haven't found the original name. - - // Found wildcard. - if wildElem != nil { - auth := z.ns(do) - - if rrs := wildElem.Types(dns.TypeCNAME, qname); len(rrs) > 0 { - return z.searchCNAME(state, wildElem, rrs) - } - - rrs := wildElem.Types(qtype, qname) - - // NODATA response. - if len(rrs) == 0 { - ret := z.soa(do) - if do { - nsec := z.typeFromElem(wildElem, dns.TypeNSEC, do) - ret = append(ret, nsec...) - } - return nil, ret, nil, Success - } - - if do { - // An NSEC is needed to say no longer name exists under this wildcard. - if deny, found := z.Tree.Prev(qname); found { - nsec := z.typeFromElem(deny, dns.TypeNSEC, do) - auth = append(auth, nsec...) - } - - sigs := wildElem.Types(dns.TypeRRSIG, qname) - sigs = signatureForSubType(sigs, qtype) - rrs = append(rrs, sigs...) - - } - return rrs, auth, nil, Success - } - - rcode := NameError - - // Hacky way to get around empty-non-terminals. If a longer name does exist, but this qname, does not, it - // must be an empty-non-terminal. If so, we do the proper NXDOMAIN handling, but set the rcode to be success. - if x, found := z.Tree.Next(qname); found { - if dns.IsSubDomain(qname, x.Name()) { - rcode = Success - } - } - - ret := z.soa(do) - if do { - deny, _ := z.Tree.Prev(qname) // TODO(miek): *found* was not used here. - nsec := z.typeFromElem(deny, dns.TypeNSEC, do) - ret = append(ret, nsec...) - - if rcode != NameError { - goto Out - } - - ce, found := z.ClosestEncloser(qname) - - // wildcard denial only for NXDOMAIN - if found { - // wildcard denial - wildcard := "*." + ce.Name() - if ss, found := z.Tree.Prev(wildcard); found { - // Only add this nsec if it is different than the one already added - if ss.Name() != deny.Name() { - nsec := z.typeFromElem(ss, dns.TypeNSEC, do) - ret = append(ret, nsec...) - } - } - } - - } -Out: - return nil, ret, nil, rcode -} - -// Return type tp from e and add signatures (if they exists) and do is true. -func (z *Zone) typeFromElem(elem *tree.Elem, tp uint16, do bool) []dns.RR { - rrs := elem.Types(tp) - if do { - sigs := elem.Types(dns.TypeRRSIG) - sigs = signatureForSubType(sigs, tp) - if len(sigs) > 0 { - rrs = append(rrs, sigs...) - } - } - return rrs -} - -func (z *Zone) soa(do bool) []dns.RR { - if do { - ret := append([]dns.RR{z.Apex.SOA}, z.Apex.SIGSOA...) - return ret - } - return []dns.RR{z.Apex.SOA} -} - -func (z *Zone) ns(do bool) []dns.RR { - if do { - ret := append(z.Apex.NS, z.Apex.SIGNS...) - return ret - } - return z.Apex.NS -} - -// TODO(miek): should be better named, like aditionalProcessing? -func (z *Zone) searchCNAME(state request.Request, elem *tree.Elem, rrs []dns.RR) ([]dns.RR, []dns.RR, []dns.RR, Result) { - - qtype := state.QType() - do := state.Do() - - if do { - sigs := elem.Types(dns.TypeRRSIG) - sigs = signatureForSubType(sigs, dns.TypeCNAME) - if len(sigs) > 0 { - rrs = append(rrs, sigs...) - } - } - - targetName := rrs[0].(*dns.CNAME).Target - elem, _ = z.Tree.Search(targetName) - if elem == nil { - if !dns.IsSubDomain(z.origin, targetName) { - rrs = append(rrs, z.externalLookup(state, targetName, qtype)...) - } - return rrs, z.ns(do), nil, Success - } - - i := 0 - -Redo: - cname := elem.Types(dns.TypeCNAME) - if len(cname) > 0 { - rrs = append(rrs, cname...) - - if do { - sigs := elem.Types(dns.TypeRRSIG) - sigs = signatureForSubType(sigs, dns.TypeCNAME) - if len(sigs) > 0 { - rrs = append(rrs, sigs...) - } - } - targetName := cname[0].(*dns.CNAME).Target - elem, _ = z.Tree.Search(targetName) - if elem == nil { - if !dns.IsSubDomain(z.origin, targetName) { - if !dns.IsSubDomain(z.origin, targetName) { - rrs = append(rrs, z.externalLookup(state, targetName, qtype)...) - } - } - return rrs, z.ns(do), nil, Success - } - - i++ - if i > maxChain { - return rrs, z.ns(do), nil, Success - } - - goto Redo - } - - targets := cnameForType(elem.All(), qtype) - if len(targets) > 0 { - rrs = append(rrs, targets...) - - if do { - sigs := elem.Types(dns.TypeRRSIG) - sigs = signatureForSubType(sigs, qtype) - if len(sigs) > 0 { - rrs = append(rrs, sigs...) - } - } - } - - return rrs, z.ns(do), nil, Success -} - -func cnameForType(targets []dns.RR, origQtype uint16) []dns.RR { - ret := []dns.RR{} - for _, target := range targets { - if target.Header().Rrtype == origQtype { - ret = append(ret, target) - } - } - return ret -} - -func (z *Zone) externalLookup(state request.Request, target string, qtype uint16) []dns.RR { - m, e := z.Proxy.Lookup(state, target, qtype) - if e != nil { - // TODO(miek): debugMsg for this as well? Log? - return nil - } - return m.Answer -} - -// signatureForSubType range through the signature and return the correct ones for the subtype. -func signatureForSubType(rrs []dns.RR, subtype uint16) []dns.RR { - sigs := []dns.RR{} - for _, sig := range rrs { - if s, ok := sig.(*dns.RRSIG); ok { - if s.TypeCovered == subtype { - sigs = append(sigs, s) - } - } - } - return sigs -} - -// Glue returns any potential glue records for nsrrs. -func (z *Zone) Glue(nsrrs []dns.RR, do bool) []dns.RR { - glue := []dns.RR{} - for _, rr := range nsrrs { - if ns, ok := rr.(*dns.NS); ok && dns.IsSubDomain(ns.Header().Name, ns.Ns) { - glue = append(glue, z.searchGlue(ns.Ns, do)...) - } - } - return glue -} - -// searchGlue looks up A and AAAA for name. -func (z *Zone) searchGlue(name string, do bool) []dns.RR { - glue := []dns.RR{} - - // A - if elem, found := z.Tree.Search(name); found { - glue = append(glue, elem.Types(dns.TypeA)...) - if do { - sigs := elem.Types(dns.TypeRRSIG) - sigs = signatureForSubType(sigs, dns.TypeA) - glue = append(glue, sigs...) - } - } - - // AAAA - if elem, found := z.Tree.Search(name); found { - glue = append(glue, elem.Types(dns.TypeAAAA)...) - if do { - sigs := elem.Types(dns.TypeRRSIG) - sigs = signatureForSubType(sigs, dns.TypeAAAA) - glue = append(glue, sigs...) - } - } - return glue -} - -// additionalProcessing checks the current answer section and retrieves A or AAAA records -// (and possible SIGs) to need to be put in the additional section. -func additionalProcessing(z *Zone, answer []dns.RR, do bool) (extra []dns.RR) { - for _, rr := range answer { - name := "" - switch x := rr.(type) { - case *dns.SRV: - name = x.Target - case *dns.MX: - name = x.Mx - } - if !dns.IsSubDomain(z.origin, name) { - continue - } - - elem, _ := z.Tree.Search(name) - if elem == nil { - continue - } - - sigs := elem.Types(dns.TypeRRSIG) - for _, addr := range []uint16{dns.TypeA, dns.TypeAAAA} { - if a := elem.Types(addr); a != nil { - extra = append(extra, a...) - if do { - sig := signatureForSubType(sigs, addr) - extra = append(extra, sig...) - } - } - } - } - - return extra -} - -const maxChain = 8 diff --git a/middleware/file/lookup_test.go b/middleware/file/lookup_test.go deleted file mode 100644 index 82d8f77ff..000000000 --- a/middleware/file/lookup_test.go +++ /dev/null @@ -1,194 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var dnsTestCases = []test.Case{ - { - Qname: "www.miek.nl.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("a.miek.nl. 1800 IN A 139.162.196.78"), - test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."), - }, - Ns: miekAuth, - }, - { - Qname: "www.miek.nl.", Qtype: dns.TypeAAAA, - Answer: []dns.RR{ - test.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."), - }, - Ns: miekAuth, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeSOA, - Answer: []dns.RR{ - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - Ns: miekAuth, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeAAAA, - Answer: []dns.RR{ - test.AAAA("miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - Ns: miekAuth, - }, - { - Qname: "mIeK.NL.", Qtype: dns.TypeAAAA, - Answer: []dns.RR{ - test.AAAA("miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - Ns: miekAuth, - }, - { - Qname: "miek.nl.", Qtype: dns.TypeMX, - Answer: []dns.RR{ - test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."), - test.MX("miek.nl. 1800 IN MX 10 aspmx2.googlemail.com."), - test.MX("miek.nl. 1800 IN MX 10 aspmx3.googlemail.com."), - test.MX("miek.nl. 1800 IN MX 5 alt1.aspmx.l.google.com."), - test.MX("miek.nl. 1800 IN MX 5 alt2.aspmx.l.google.com."), - }, - Ns: miekAuth, - }, - { - Qname: "a.miek.nl.", Qtype: dns.TypeSRV, - Ns: []dns.RR{ - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - }, - { - Qname: "b.miek.nl.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), - }, - }, - { - Qname: "srv.miek.nl.", Qtype: dns.TypeSRV, - Answer: []dns.RR{ - test.SRV("srv.miek.nl. 1800 IN SRV 10 10 8080 a.miek.nl."), - }, - Extra: []dns.RR{ - test.A("a.miek.nl. 1800 IN A 139.162.196.78"), - test.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - Ns: miekAuth, - }, - { - Qname: "mx.miek.nl.", Qtype: dns.TypeMX, - Answer: []dns.RR{ - test.MX("mx.miek.nl. 1800 IN MX 10 a.miek.nl."), - }, - Extra: []dns.RR{ - test.A("a.miek.nl. 1800 IN A 139.162.196.78"), - test.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), - }, - Ns: miekAuth, - }, -} - -const ( - testzone = "miek.nl." - testzone1 = "dnssex.nl." -) - -func TestLookup(t *testing.T) { - zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0) - if err != nil { - t.Fatalf("expect no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} - ctx := context.TODO() - - for _, tc := range dnsTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -func TestLookupNil(t *testing.T) { - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: nil}, Names: []string{testzone}}} - ctx := context.TODO() - - m := dnsTestCases[0].Msg() - rec := dnsrecorder.New(&test.ResponseWriter{}) - fm.ServeDNS(ctx, rec, m) -} - -func BenchmarkFileLookup(b *testing.B) { - zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0) - if err != nil { - return - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} - ctx := context.TODO() - rec := dnsrecorder.New(&test.ResponseWriter{}) - - tc := test.Case{ - Qname: "www.miek.nl.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."), - test.A("a.miek.nl. 1800 IN A 139.162.196.78"), - }, - } - - m := tc.Msg() - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - fm.ServeDNS(ctx, rec, m) - } -} - -const dbMiekNL = ` -$TTL 30M -$ORIGIN miek.nl. -@ IN SOA linode.atoom.net. miek.miek.nl. ( - 1282630057 ; Serial - 4H ; Refresh - 1H ; Retry - 7D ; Expire - 4H ) ; Negative Cache TTL - IN NS linode.atoom.net. - IN NS ns-ext.nlnetlabs.nl. - IN NS omval.tednet.nl. - IN NS ext.ns.whyscream.net. - - IN MX 1 aspmx.l.google.com. - IN MX 5 alt1.aspmx.l.google.com. - IN MX 5 alt2.aspmx.l.google.com. - IN MX 10 aspmx2.googlemail.com. - IN MX 10 aspmx3.googlemail.com. - - IN A 139.162.196.78 - IN AAAA 2a01:7e00::f03c:91ff:fef1:6735 - -a IN A 139.162.196.78 - IN AAAA 2a01:7e00::f03c:91ff:fef1:6735 -www IN CNAME a -archive IN CNAME a - -srv IN SRV 10 10 8080 a.miek.nl. -mx IN MX 10 a.miek.nl.` diff --git a/middleware/file/notify.go b/middleware/file/notify.go deleted file mode 100644 index b1628c3a6..000000000 --- a/middleware/file/notify.go +++ /dev/null @@ -1,82 +0,0 @@ -package file - -import ( - "fmt" - "log" - "net" - - "github.com/coredns/coredns/middleware/pkg/rcode" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// isNotify checks if state is a notify message and if so, will *also* check if it -// is from one of the configured masters. If not it will not be a valid notify -// message. If the zone z is not a secondary zone the message will also be ignored. -func (z *Zone) isNotify(state request.Request) bool { - if state.Req.Opcode != dns.OpcodeNotify { - return false - } - if len(z.TransferFrom) == 0 { - return false - } - // If remote IP matches we accept. - remote := state.IP() - for _, f := range z.TransferFrom { - from, _, err := net.SplitHostPort(f) - if err != nil { - continue - } - if from == remote { - return true - } - } - return false -} - -// Notify will send notifies to all configured TransferTo IP addresses. -func (z *Zone) Notify() { - go notify(z.origin, z.TransferTo) -} - -// notify sends notifies to the configured remote servers. It will try up to three times -// before giving up on a specific remote. We will sequentially loop through "to" -// until they all have replied (or have 3 failed attempts). -func notify(zone string, to []string) error { - m := new(dns.Msg) - m.SetNotify(zone) - c := new(dns.Client) - - for _, t := range to { - if t == "*" { - continue - } - if err := notifyAddr(c, m, t); err != nil { - log.Printf("[ERROR] " + err.Error()) - } else { - log.Printf("[INFO] Sent notify for zone %q to %q", zone, t) - } - } - return nil -} - -func notifyAddr(c *dns.Client, m *dns.Msg, s string) error { - var err error - - code := dns.RcodeServerFailure - for i := 0; i < 3; i++ { - ret, _, err := c.Exchange(m, s) - if err != nil { - continue - } - code = ret.Rcode - if code == dns.RcodeSuccess { - return nil - } - } - if err != nil { - return fmt.Errorf("notify for zone %q was not accepted by %q: %q", m.Question[0].Name, s, err) - } - return fmt.Errorf("notify for zone %q was not accepted by %q: rcode was %q", m.Question[0].Name, s, rcode.ToString(code)) -} diff --git a/middleware/file/nsec3_test.go b/middleware/file/nsec3_test.go deleted file mode 100644 index 6611056cb..000000000 --- a/middleware/file/nsec3_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package file - -import ( - "strings" - "testing" -) - -func TestParseNSEC3PARAM(t *testing.T) { - _, err := Parse(strings.NewReader(nsec3paramTest), "miek.nl", "stdin", 0) - if err == nil { - t.Fatalf("expected error when reading zone, got nothing") - } -} - -func TestParseNSEC3(t *testing.T) { - _, err := Parse(strings.NewReader(nsec3Test), "miek.nl", "stdin", 0) - if err == nil { - t.Fatalf("expected error when reading zone, got nothing") - } -} - -const nsec3paramTest = `miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1460175181 14400 3600 604800 14400 -miek.nl. 1800 IN NS omval.tednet.nl. -miek.nl. 0 IN NSEC3PARAM 1 0 5 A3DEBC9CC4F695C7` - -const nsec3Test = `example.org. 1800 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016082508 7200 3600 1209600 3600 -aub8v9ce95ie18spjubsr058h41n7pa5.example.org. 284 IN NSEC3 1 1 5 D0CBEAAF0AC77314 AUB95P93VPKP55G6U5S4SGS7LS61ND85 NS SOA TXT RRSIG DNSKEY NSEC3PARAM -aub8v9ce95ie18spjubsr058h41n7pa5.example.org. 284 IN RRSIG NSEC3 8 2 600 20160910232502 20160827231002 14028 example.org. XBNpA7KAIjorPbXvTinOHrc1f630aHic2U716GHLHA4QMx9cl9ss4QjR Wj2UpDM9zBW/jNYb1xb0yjQoez/Jv200w0taSWjRci5aUnRpOi9bmcrz STHb6wIUjUsbJ+NstQsUwVkj6679UviF1FqNwr4GlJnWG3ZrhYhE+NI6 s0k=` diff --git a/middleware/file/reload.go b/middleware/file/reload.go deleted file mode 100644 index 18e949a94..000000000 --- a/middleware/file/reload.go +++ /dev/null @@ -1,72 +0,0 @@ -package file - -import ( - "log" - "os" - "path" - - "github.com/fsnotify/fsnotify" -) - -// Reload reloads a zone when it is changed on disk. If z.NoRoload is true, no reloading will be done. -func (z *Zone) Reload() error { - if z.NoReload { - return nil - } - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } - err = watcher.Add(path.Dir(z.file)) - if err != nil { - return err - } - - go func() { - // TODO(miek): needs to be killed on reload. - for { - select { - case event := <-watcher.Events: - if path.Clean(event.Name) == z.file { - - reader, err := os.Open(z.file) - if err != nil { - log.Printf("[ERROR] Failed to open `%s' for `%s': %v", z.file, z.origin, err) - continue - } - - serial := z.SOASerialIfDefined() - zone, err := Parse(reader, z.origin, z.file, serial) - if err != nil { - log.Printf("[WARNING] Parsing zone `%s': %v", z.origin, err) - continue - } - - // copy elements we need - z.reloadMu.Lock() - z.Apex = zone.Apex - z.Tree = zone.Tree - z.reloadMu.Unlock() - - log.Printf("[INFO] Successfully reloaded zone `%s'", z.origin) - z.Notify() - } - case <-z.ReloadShutdown: - watcher.Close() - return - } - } - }() - return nil -} - -// SOASerialIfDefined returns the SOA's serial if the zone has a SOA record in the Apex, or -// -1 otherwise. -func (z *Zone) SOASerialIfDefined() int64 { - z.reloadMu.Lock() - defer z.reloadMu.Unlock() - if z.Apex.SOA != nil { - return int64(z.Apex.SOA.Serial) - } - return -1 -} diff --git a/middleware/file/reload_test.go b/middleware/file/reload_test.go deleted file mode 100644 index c4d065155..000000000 --- a/middleware/file/reload_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package file - -import ( - "io/ioutil" - "log" - "os" - "strings" - "testing" - "time" - - "github.com/coredns/coredns/middleware/test" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -func TestZoneReload(t *testing.T) { - log.SetOutput(ioutil.Discard) - - fileName, rm, err := test.TempFile(".", reloadZoneTest) - if err != nil { - t.Fatalf("failed to create zone: %s", err) - } - defer rm() - reader, err := os.Open(fileName) - if err != nil { - t.Fatalf("failed to open zone: %s", err) - } - z, err := Parse(reader, "miek.nl", fileName, 0) - if err != nil { - t.Fatalf("failed to parse zone: %s", err) - } - - z.Reload() - - r := new(dns.Msg) - r.SetQuestion("miek.nl", dns.TypeSOA) - state := request.Request{W: &test.ResponseWriter{}, Req: r} - if _, _, _, res := z.Lookup(state, "miek.nl."); res != Success { - t.Fatalf("failed to lookup, got %d", res) - } - - r = new(dns.Msg) - r.SetQuestion("miek.nl", dns.TypeNS) - state = request.Request{W: &test.ResponseWriter{}, Req: r} - if _, _, _, res := z.Lookup(state, "miek.nl."); res != Success { - t.Fatalf("failed to lookup, got %d", res) - } - - if len(z.All()) != 5 { - t.Fatalf("expected 5 RRs, got %d", len(z.All())) - } - if err := ioutil.WriteFile(fileName, []byte(reloadZone2Test), 0644); err != nil { - t.Fatalf("failed to write new zone data: %s", err) - } - // Could still be racy, but we need to wait a bit for the event to be seen - time.Sleep(1 * time.Second) - - if len(z.All()) != 3 { - t.Fatalf("expected 3 RRs, got %d", len(z.All())) - } -} - -func TestZoneReloadSOAChange(t *testing.T) { - _, err := Parse(strings.NewReader(reloadZoneTest), "miek.nl.", "stdin", 1460175181) - if err == nil { - t.Fatalf("zone should not have been re-parsed") - } - -} - -const reloadZoneTest = `miek.nl. 1627 IN SOA linode.atoom.net. miek.miek.nl. 1460175181 14400 3600 604800 14400 -miek.nl. 1627 IN NS ext.ns.whyscream.net. -miek.nl. 1627 IN NS omval.tednet.nl. -miek.nl. 1627 IN NS linode.atoom.net. -miek.nl. 1627 IN NS ns-ext.nlnetlabs.nl. -` - -const reloadZone2Test = `miek.nl. 1627 IN SOA linode.atoom.net. miek.miek.nl. 1460175182 14400 3600 604800 14400 -miek.nl. 1627 IN NS ext.ns.whyscream.net. -miek.nl. 1627 IN NS omval.tednet.nl. -` diff --git a/middleware/file/secondary.go b/middleware/file/secondary.go deleted file mode 100644 index a37d62442..000000000 --- a/middleware/file/secondary.go +++ /dev/null @@ -1,199 +0,0 @@ -package file - -import ( - "log" - "math/rand" - "time" - - "github.com/miekg/dns" -) - -// TransferIn retrieves the zone from the masters, parses it and sets it live. -func (z *Zone) TransferIn() error { - if len(z.TransferFrom) == 0 { - return nil - } - m := new(dns.Msg) - m.SetAxfr(z.origin) - - z1 := z.Copy() - var ( - Err error - tr string - ) - -Transfer: - for _, tr = range z.TransferFrom { - t := new(dns.Transfer) - c, err := t.In(m, tr) - if err != nil { - log.Printf("[ERROR] Failed to setup transfer `%s' with `%q': %v", z.origin, tr, err) - Err = err - continue Transfer - } - for env := range c { - if env.Error != nil { - log.Printf("[ERROR] Failed to transfer `%s' from %q: %v", z.origin, tr, env.Error) - Err = env.Error - continue Transfer - } - for _, rr := range env.RR { - if err := z1.Insert(rr); err != nil { - log.Printf("[ERROR] Failed to parse transfer `%s' from: %q: %v", z.origin, tr, err) - Err = err - continue Transfer - } - } - } - Err = nil - break - } - if Err != nil { - return Err - } - - z.Tree = z1.Tree - z.Apex = z1.Apex - *z.Expired = false - log.Printf("[INFO] Transferred: %s from %s", z.origin, tr) - return nil -} - -// shouldTransfer checks the primaries of zone, retrieves the SOA record, checks the current serial -// and the remote serial and will return true if the remote one is higher than the locally configured one. -func (z *Zone) shouldTransfer() (bool, error) { - c := new(dns.Client) - c.Net = "tcp" // do this query over TCP to minimize spoofing - m := new(dns.Msg) - m.SetQuestion(z.origin, dns.TypeSOA) - - var Err error - serial := -1 - -Transfer: - for _, tr := range z.TransferFrom { - Err = nil - ret, _, err := c.Exchange(m, tr) - if err != nil || ret.Rcode != dns.RcodeSuccess { - Err = err - continue - } - for _, a := range ret.Answer { - if a.Header().Rrtype == dns.TypeSOA { - serial = int(a.(*dns.SOA).Serial) - break Transfer - } - } - } - if serial == -1 { - return false, Err - } - if z.Apex.SOA == nil { - return true, Err - } - return less(z.Apex.SOA.Serial, uint32(serial)), Err -} - -// less return true of a is smaller than b when taking RFC 1982 serial arithmetic into account. -func less(a, b uint32) bool { - if a < b { - return (b - a) <= MaxSerialIncrement - } - return (a - b) > MaxSerialIncrement -} - -// Update updates the secondary zone according to its SOA. It will run for the life time of the server -// and uses the SOA parameters. Every refresh it will check for a new SOA number. If that fails (for all -// server) it wil retry every retry interval. If the zone failed to transfer before the expire, the zone -// will be marked expired. -func (z *Zone) Update() error { - // If we don't have a SOA, we don't have a zone, wait for it to appear. - for z.Apex.SOA == nil { - time.Sleep(1 * time.Second) - } - retryActive := false - -Restart: - refresh := time.Second * time.Duration(z.Apex.SOA.Refresh) - retry := time.Second * time.Duration(z.Apex.SOA.Retry) - expire := time.Second * time.Duration(z.Apex.SOA.Expire) - - if refresh < time.Hour { - refresh = time.Hour - } - if retry < time.Hour { - retry = time.Hour - } - if refresh > 24*time.Hour { - refresh = 24 * time.Hour - } - if retry > 12*time.Hour { - retry = 12 * time.Hour - } - - refreshTicker := time.NewTicker(refresh) - retryTicker := time.NewTicker(retry) - expireTicker := time.NewTicker(expire) - - for { - select { - case <-expireTicker.C: - if !retryActive { - break - } - *z.Expired = true - - case <-retryTicker.C: - if !retryActive { - break - } - - time.Sleep(jitter(2000)) // 2s randomize - - ok, err := z.shouldTransfer() - if err != nil && ok { - if err := z.TransferIn(); err != nil { - // transfer failed, leave retryActive true - break - } - retryActive = false - // transfer OK, possible new SOA, stop timers and redo - refreshTicker.Stop() - retryTicker.Stop() - expireTicker.Stop() - goto Restart - } - - case <-refreshTicker.C: - - time.Sleep(jitter(5000)) // 5s randomize - - ok, err := z.shouldTransfer() - retryActive = err != nil - if err != nil && ok { - if err := z.TransferIn(); err != nil { - // transfer failed - retryActive = true - break - } - retryActive = false - // transfer OK, possible new SOA, stop timers and redo - refreshTicker.Stop() - retryTicker.Stop() - expireTicker.Stop() - goto Restart - } - } - } -} - -// jitter returns a random duration between [0,n) * time.Millisecond -func jitter(n int) time.Duration { - r := rand.Intn(n) - return time.Duration(r) * time.Millisecond - -} - -// MaxSerialIncrement is the maximum difference between two serial numbers. If the difference between -// two serials is greater than this number, the smaller one is considered greater. -const MaxSerialIncrement uint32 = 2147483647 diff --git a/middleware/file/secondary_test.go b/middleware/file/secondary_test.go deleted file mode 100644 index cdb051d26..000000000 --- a/middleware/file/secondary_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package file - -import ( - "fmt" - "io/ioutil" - "log" - "testing" - - "github.com/coredns/coredns/middleware/test" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// TODO(miek): should test notifies as well, ie start test server (a real coredns one)... -// setup other test server that sends notify, see if CoreDNS comes calling for a zone -// tranfer - -func TestLess(t *testing.T) { - const ( - min = 0 - max = 4294967295 - low = 12345 - high = 4000000000 - ) - - if less(min, max) { - t.Fatalf("less: should be false") - } - if !less(max, min) { - t.Fatalf("less: should be true") - } - if !less(high, low) { - t.Fatalf("less: should be true") - } - if !less(7, 9) { - t.Fatalf("less; should be true") - } -} - -type soa struct { - serial uint32 -} - -func (s *soa) Handler(w dns.ResponseWriter, req *dns.Msg) { - m := new(dns.Msg) - m.SetReply(req) - switch req.Question[0].Qtype { - case dns.TypeSOA: - m.Answer = make([]dns.RR, 1) - m.Answer[0] = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, s.serial)) - w.WriteMsg(m) - case dns.TypeAXFR: - m.Answer = make([]dns.RR, 4) - m.Answer[0] = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, s.serial)) - m.Answer[1] = test.A(fmt.Sprintf("%s IN A 127.0.0.1", testZone)) - m.Answer[2] = test.A(fmt.Sprintf("%s IN A 127.0.0.1", testZone)) - m.Answer[3] = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, s.serial)) - w.WriteMsg(m) - } -} - -func (s *soa) TransferHandler(w dns.ResponseWriter, req *dns.Msg) { - m := new(dns.Msg) - m.SetReply(req) - m.Answer = make([]dns.RR, 1) - m.Answer[0] = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, s.serial)) - w.WriteMsg(m) -} - -const testZone = "secondary.miek.nl." - -func TestShouldTransfer(t *testing.T) { - soa := soa{250} - log.SetOutput(ioutil.Discard) - - dns.HandleFunc(testZone, soa.Handler) - defer dns.HandleRemove(testZone) - - s, addrstr, err := test.TCPServer("127.0.0.1:0") - if err != nil { - t.Fatalf("unable to run test server: %v", err) - } - defer s.Shutdown() - - z := new(Zone) - z.origin = testZone - z.TransferFrom = []string{addrstr} - - // when we have a nil SOA (initial state) - should, err := z.shouldTransfer() - if err != nil { - t.Fatalf("unable to run shouldTransfer: %v", err) - } - if !should { - t.Fatalf("shouldTransfer should return true for serial: %d", soa.serial) - } - // Serial smaller - z.Apex.SOA = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, soa.serial-1)) - should, err = z.shouldTransfer() - if err != nil { - t.Fatalf("unable to run shouldTransfer: %v", err) - } - if !should { - t.Fatalf("shouldTransfer should return true for serial: %q", soa.serial-1) - } - // Serial equal - z.Apex.SOA = test.SOA(fmt.Sprintf("%s IN SOA bla. bla. %d 0 0 0 0 ", testZone, soa.serial)) - should, err = z.shouldTransfer() - if err != nil { - t.Fatalf("unable to run shouldTransfer: %v", err) - } - if should { - t.Fatalf("shouldTransfer should return false for serial: %d", soa.serial) - } -} - -func TestTransferIn(t *testing.T) { - soa := soa{250} - log.SetOutput(ioutil.Discard) - - dns.HandleFunc(testZone, soa.Handler) - defer dns.HandleRemove(testZone) - - s, addrstr, err := test.TCPServer("127.0.0.1:0") - if err != nil { - t.Fatalf("unable to run test server: %v", err) - } - defer s.Shutdown() - - z := new(Zone) - z.Expired = new(bool) - z.origin = testZone - z.TransferFrom = []string{addrstr} - - err = z.TransferIn() - if err != nil { - t.Fatalf("unable to run TransferIn: %v", err) - } - if z.Apex.SOA.String() != fmt.Sprintf("%s 3600 IN SOA bla. bla. 250 0 0 0 0", testZone) { - t.Fatalf("unknown SOA transferred") - } -} - -func TestIsNotify(t *testing.T) { - z := new(Zone) - z.Expired = new(bool) - z.origin = testZone - state := newRequest(testZone, dns.TypeSOA) - // need to set opcode - state.Req.Opcode = dns.OpcodeNotify - - z.TransferFrom = []string{"10.240.0.1:53"} // IP from from testing/responseWriter - if !z.isNotify(state) { - t.Fatal("should have been valid notify") - } - z.TransferFrom = []string{"10.240.0.2:53"} - if z.isNotify(state) { - t.Fatal("should have been invalid notify") - } -} - -func newRequest(zone string, qtype uint16) request.Request { - m := new(dns.Msg) - m.SetQuestion("example.com.", dns.TypeA) - m.SetEdns0(4097, true) - return request.Request{W: &test.ResponseWriter{}, Req: m} -} diff --git a/middleware/file/setup.go b/middleware/file/setup.go deleted file mode 100644 index 2ac7610c5..000000000 --- a/middleware/file/setup.go +++ /dev/null @@ -1,171 +0,0 @@ -package file - -import ( - "fmt" - "os" - "path" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/proxy" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("file", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - zones, err := fileParse(c) - if err != nil { - return middleware.Error("file", err) - } - - // Add startup functions to notify the master(s). - for _, n := range zones.Names { - z := zones.Z[n] - c.OnStartup(func() error { - z.StartupOnce.Do(func() { - if len(z.TransferTo) > 0 { - z.Notify() - } - z.Reload() - }) - return nil - }) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return File{Next: next, Zones: zones} - }) - - return nil -} - -func fileParse(c *caddy.Controller) (Zones, error) { - z := make(map[string]*Zone) - names := []string{} - origins := []string{} - - config := dnsserver.GetConfig(c) - - for c.Next() { - // file db.file [zones...] - if !c.NextArg() { - return Zones{}, c.ArgErr() - } - fileName := c.Val() - - origins = make([]string, len(c.ServerBlockKeys)) - copy(origins, c.ServerBlockKeys) - args := c.RemainingArgs() - if len(args) > 0 { - origins = args - } - - if !path.IsAbs(fileName) && config.Root != "" { - fileName = path.Join(config.Root, fileName) - } - - reader, err := os.Open(fileName) - if err != nil { - // bail out - return Zones{}, err - } - - for i := range origins { - origins[i] = middleware.Host(origins[i]).Normalize() - zone, err := Parse(reader, origins[i], fileName, 0) - if err == nil { - z[origins[i]] = zone - } else { - return Zones{}, err - } - names = append(names, origins[i]) - } - - noReload := false - prxy := proxy.Proxy{} - t := []string{} - var e error - - for c.NextBlock() { - switch c.Val() { - case "transfer": - t, _, e = TransferParse(c, false) - if e != nil { - return Zones{}, e - } - - case "no_reload": - noReload = true - - case "upstream": - args := c.RemainingArgs() - if len(args) == 0 { - return Zones{}, c.ArgErr() - } - ups, err := dnsutil.ParseHostPortOrFile(args...) - if err != nil { - return Zones{}, err - } - prxy = proxy.NewLookup(ups) - default: - return Zones{}, c.Errf("unknown property '%s'", c.Val()) - } - - for _, origin := range origins { - if t != nil { - z[origin].TransferTo = append(z[origin].TransferTo, t...) - } - z[origin].NoReload = noReload - z[origin].Proxy = prxy - } - } - } - return Zones{Z: z, Names: names}, nil -} - -// TransferParse parses transfer statements: 'transfer to [address...]'. -func TransferParse(c *caddy.Controller, secondary bool) (tos, froms []string, err error) { - if !c.NextArg() { - return nil, nil, c.ArgErr() - } - value := c.Val() - switch value { - case "to": - tos = c.RemainingArgs() - for i := range tos { - if tos[i] != "*" { - normalized, err := dnsutil.ParseHostPort(tos[i], "53") - if err != nil { - return nil, nil, err - } - tos[i] = normalized - } - } - - case "from": - if !secondary { - return nil, nil, fmt.Errorf("can't use `transfer from` when not being a secondary") - } - froms = c.RemainingArgs() - for i := range froms { - if froms[i] != "*" { - normalized, err := dnsutil.ParseHostPort(froms[i], "53") - if err != nil { - return nil, nil, err - } - froms[i] = normalized - } else { - return nil, nil, fmt.Errorf("can't use '*' in transfer from") - } - } - } - return -} diff --git a/middleware/file/setup_test.go b/middleware/file/setup_test.go deleted file mode 100644 index 111be1261..000000000 --- a/middleware/file/setup_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package file - -import ( - "testing" - - "github.com/coredns/coredns/middleware/test" - - "github.com/mholt/caddy" -) - -func TestFileParse(t *testing.T) { - zoneFileName1, rm, err := test.TempFile(".", dbMiekNL) - if err != nil { - t.Fatal(err) - } - defer rm() - - zoneFileName2, rm, err := test.TempFile(".", dbDnssexNLSigned) - if err != nil { - t.Fatal(err) - } - defer rm() - - tests := []struct { - inputFileRules string - shouldErr bool - expectedZones Zones - }{ - { - `file ` + zoneFileName1 + ` miek.nl { - transfer from 127.0.0.1 - }`, - true, - Zones{}, - }, - { - `file`, - true, - Zones{}, - }, - { - `file ` + zoneFileName1 + ` miek.nl.`, - false, - Zones{Names: []string{"miek.nl."}}, - }, - { - `file ` + zoneFileName2 + ` dnssex.nl.`, - false, - Zones{Names: []string{"dnssex.nl."}}, - }, - { - `file ` + zoneFileName2 + ` 10.0.0.0/8`, - false, - Zones{Names: []string{"10.in-addr.arpa."}}, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.inputFileRules) - actualZones, err := fileParse(c) - - if err == nil && test.shouldErr { - t.Fatalf("Test %d expected errors, but got no error", i) - } else if err != nil && !test.shouldErr { - t.Fatalf("Test %d expected no errors, but got '%v'", i, err) - } else { - if len(actualZones.Names) != len(test.expectedZones.Names) { - t.Fatalf("Test %d expected %v, got %v", i, test.expectedZones.Names, actualZones.Names) - } - for j, name := range test.expectedZones.Names { - if actualZones.Names[j] != name { - t.Fatalf("Test %d expected %v for %d th zone, got %v", i, name, j, actualZones.Names[j]) - } - } - } - } -} diff --git a/middleware/file/tree/all.go b/middleware/file/tree/all.go deleted file mode 100644 index fd806365f..000000000 --- a/middleware/file/tree/all.go +++ /dev/null @@ -1,48 +0,0 @@ -package tree - -// All traverses tree and returns all elements -func (t *Tree) All() []*Elem { - if t.Root == nil { - return nil - } - found := t.Root.all(nil) - return found -} - -func (n *Node) all(found []*Elem) []*Elem { - if n.Left != nil { - found = n.Left.all(found) - } - found = append(found, n.Elem) - if n.Right != nil { - found = n.Right.all(found) - } - return found -} - -// Do performs fn on all values stored in the tree. A boolean is returned indicating whether the -// Do traversal was interrupted by an Operation returning true. If fn alters stored values' sort -// relationships, future tree operation behaviors are undefined. -func (t *Tree) Do(fn func(e *Elem) bool) bool { - if t.Root == nil { - return false - } - return t.Root.do(fn) -} - -func (n *Node) do(fn func(e *Elem) bool) (done bool) { - if n.Left != nil { - done = n.Left.do(fn) - if done { - return - } - } - done = fn(n.Elem) - if done { - return - } - if n.Right != nil { - done = n.Right.do(fn) - } - return -} diff --git a/middleware/file/tree/elem.go b/middleware/file/tree/elem.go deleted file mode 100644 index 6317cc912..000000000 --- a/middleware/file/tree/elem.go +++ /dev/null @@ -1,136 +0,0 @@ -package tree - -import "github.com/miekg/dns" - -// Elem is an element in the tree. -type Elem struct { - m map[uint16][]dns.RR - name string // owner name -} - -// newElem returns a new elem. -func newElem(rr dns.RR) *Elem { - e := Elem{m: make(map[uint16][]dns.RR)} - e.m[rr.Header().Rrtype] = []dns.RR{rr} - return &e -} - -// Types returns the RRs with type qtype from e. If qname is given (only the -// first one is used), the RR are copied and the owner is replaced with qname[0]. -func (e *Elem) Types(qtype uint16, qname ...string) []dns.RR { - rrs := e.m[qtype] - - if rrs != nil && len(qname) > 0 { - copied := make([]dns.RR, len(rrs)) - for i := range rrs { - copied[i] = dns.Copy(rrs[i]) - copied[i].Header().Name = qname[0] - } - return copied - } - return rrs -} - -// All returns all RRs from e, regardless of type. -func (e *Elem) All() []dns.RR { - list := []dns.RR{} - for _, rrs := range e.m { - list = append(list, rrs...) - } - return list -} - -// Name returns the name for this node. -func (e *Elem) Name() string { - if e.name != "" { - return e.name - } - for _, rrs := range e.m { - e.name = rrs[0].Header().Name - return e.name - } - return "" -} - -// Empty returns true is e does not contain any RRs, i.e. is an -// empty-non-terminal. -func (e *Elem) Empty() bool { - return len(e.m) == 0 -} - -// Insert inserts rr into e. If rr is equal to existing rrs this is a noop. -func (e *Elem) Insert(rr dns.RR) { - t := rr.Header().Rrtype - if e.m == nil { - e.m = make(map[uint16][]dns.RR) - e.m[t] = []dns.RR{rr} - return - } - rrs, ok := e.m[t] - if !ok { - e.m[t] = []dns.RR{rr} - return - } - for _, er := range rrs { - if equalRdata(er, rr) { - return - } - } - - rrs = append(rrs, rr) - e.m[t] = rrs -} - -// Delete removes rr from e. When e is empty after the removal the returned bool is true. -func (e *Elem) Delete(rr dns.RR) (empty bool) { - if e.m == nil { - return true - } - - t := rr.Header().Rrtype - rrs, ok := e.m[t] - if !ok { - return - } - - for i, er := range rrs { - if equalRdata(er, rr) { - rrs = removeFromSlice(rrs, i) - e.m[t] = rrs - empty = len(rrs) == 0 - if empty { - delete(e.m, t) - } - return - } - } - return -} - -// Less is a tree helper function that calls less. -func Less(a *Elem, name string) int { return less(name, a.Name()) } - -// Assuming the same type and name this will check if the rdata is equal as well. -func equalRdata(a, b dns.RR) bool { - switch x := a.(type) { - // TODO(miek): more types, i.e. all types. + tests for this. - case *dns.A: - return x.A.Equal(b.(*dns.A).A) - case *dns.AAAA: - return x.AAAA.Equal(b.(*dns.AAAA).AAAA) - case *dns.MX: - if x.Mx == b.(*dns.MX).Mx && x.Preference == b.(*dns.MX).Preference { - return true - } - } - return false -} - -// removeFromSlice removes index i from the slice. -func removeFromSlice(rrs []dns.RR, i int) []dns.RR { - if i >= len(rrs) { - return rrs - } - rrs = append(rrs[:i], rrs[i+1:]...) - return rrs -} diff --git a/middleware/file/tree/less.go b/middleware/file/tree/less.go deleted file mode 100644 index 3b8340088..000000000 --- a/middleware/file/tree/less.go +++ /dev/null @@ -1,59 +0,0 @@ -package tree - -import ( - "bytes" - - "github.com/miekg/dns" -) - -// less returns <0 when a is less than b, 0 when they are equal and -// >0 when a is larger than b. -// The function orders names in DNSSEC canonical order: RFC 4034s section-6.1 -// -// See http://bert-hubert.blogspot.co.uk/2015/10/how-to-do-fast-canonical-ordering-of.html -// for a blog article on this implementation, although here we still go label by label. -// -// The values of a and b are *not* lowercased before the comparison! -func less(a, b string) int { - i := 1 - aj := len(a) - bj := len(b) - for { - ai, oka := dns.PrevLabel(a, i) - bi, okb := dns.PrevLabel(b, i) - if oka && okb { - return 0 - } - - // sadly this []byte will allocate... TODO(miek): check if this is needed - // for a name, otherwise compare the strings. - ab := []byte(a[ai:aj]) - bb := []byte(b[bi:bj]) - doDDD(ab) - doDDD(bb) - - res := bytes.Compare(ab, bb) - if res != 0 { - return res - } - - i++ - aj, bj = ai, bi - } -} - -func doDDD(b []byte) { - lb := len(b) - for i := 0; i < lb; i++ { - if i+3 < lb && b[i] == '\\' && isDigit(b[i+1]) && isDigit(b[i+2]) && isDigit(b[i+3]) { - b[i] = dddToByte(b[i:]) - for j := i + 1; j < lb-3; j++ { - b[j] = b[j+3] - } - lb -= 3 - } - } -} - -func isDigit(b byte) bool { return b >= '0' && b <= '9' } -func dddToByte(s []byte) byte { return (s[1]-'0')*100 + (s[2]-'0')*10 + (s[3] - '0') } diff --git a/middleware/file/tree/less_test.go b/middleware/file/tree/less_test.go deleted file mode 100644 index ed021b66f..000000000 --- a/middleware/file/tree/less_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package tree - -import ( - "sort" - "strings" - "testing" -) - -type set []string - -func (p set) Len() int { return len(p) } -func (p set) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p set) Less(i, j int) bool { d := less(p[i], p[j]); return d <= 0 } - -func TestLess(t *testing.T) { - tests := []struct { - in []string - out []string - }{ - { - []string{"aaa.powerdns.de", "bbb.powerdns.net.", "xxx.powerdns.com."}, - []string{"xxx.powerdns.com.", "aaa.powerdns.de", "bbb.powerdns.net."}, - }, - { - []string{"aaa.POWERDNS.de", "bbb.PoweRdnS.net.", "xxx.powerdns.com."}, - []string{"xxx.powerdns.com.", "aaa.POWERDNS.de", "bbb.PoweRdnS.net."}, - }, - { - []string{"aaa.aaaa.aa.", "aa.aaa.a.", "bbb.bbbb.bb."}, - []string{"aa.aaa.a.", "aaa.aaaa.aa.", "bbb.bbbb.bb."}, - }, - { - []string{"aaaaa.", "aaa.", "bbb."}, - []string{"aaa.", "aaaaa.", "bbb."}, - }, - { - []string{"a.a.a.a.", "a.a.", "a.a.a."}, - []string{"a.a.", "a.a.a.", "a.a.a.a."}, - }, - { - []string{"example.", "z.example.", "a.example."}, - []string{"example.", "a.example.", "z.example."}, - }, - { - []string{"a.example.", "Z.a.example.", "z.example.", "yljkjljk.a.example.", "\\001.z.example.", "example.", "*.z.example.", "\\200.z.example.", "zABC.a.EXAMPLE."}, - []string{"example.", "a.example.", "yljkjljk.a.example.", "Z.a.example.", "zABC.a.EXAMPLE.", "z.example.", "\\001.z.example.", "*.z.example.", "\\200.z.example."}, - }, - { - // RFC3034 example. - []string{"a.example.", "Z.a.example.", "z.example.", "yljkjljk.a.example.", "example.", "*.z.example.", "zABC.a.EXAMPLE."}, - []string{"example.", "a.example.", "yljkjljk.a.example.", "Z.a.example.", "zABC.a.EXAMPLE.", "z.example.", "*.z.example."}, - }, - } - -Tests: - for j, test := range tests { - // Need to lowercase these example as the Less function does lowercase for us anymore. - for i, b := range test.in { - test.in[i] = strings.ToLower(b) - } - for i, b := range test.out { - test.out[i] = strings.ToLower(b) - } - - sort.Sort(set(test.in)) - for i := 0; i < len(test.in); i++ { - if test.in[i] != test.out[i] { - t.Errorf("Test %d: expected %s, got %s\n", j, test.out[i], test.in[i]) - n := "" - for k, in := range test.in { - if k+1 == len(test.in) { - n = "\n" - } - t.Logf("%s <-> %s\n%s", in, test.out[k], n) - } - continue Tests - } - - } - } -} diff --git a/middleware/file/tree/print.go b/middleware/file/tree/print.go deleted file mode 100644 index bd86ef690..000000000 --- a/middleware/file/tree/print.go +++ /dev/null @@ -1,62 +0,0 @@ -package tree - -import "fmt" - -// Print prints a Tree. Main use is to aid in debugging. -func (t *Tree) Print() { - if t.Root == nil { - fmt.Println("<nil>") - } - t.Root.print() -} - -func (n *Node) print() { - q := newQueue() - q.push(n) - - nodesInCurrentLevel := 1 - nodesInNextLevel := 0 - - for !q.empty() { - do := q.pop() - nodesInCurrentLevel-- - - if do != nil { - fmt.Print(do.Elem.Name(), " ") - q.push(do.Left) - q.push(do.Right) - nodesInNextLevel += 2 - } - if nodesInCurrentLevel == 0 { - fmt.Println() - } - nodesInCurrentLevel = nodesInNextLevel - nodesInNextLevel = 0 - } - fmt.Println() -} - -type queue []*Node - -// newQueue returns a new queue. -func newQueue() queue { - q := queue([]*Node{}) - return q -} - -// push pushes n to the end of the queue. -func (q *queue) push(n *Node) { - *q = append(*q, n) -} - -// pop pops the first element off the queue. -func (q *queue) pop() *Node { - n := (*q)[0] - *q = (*q)[1:] - return n -} - -// empty returns true when the queue contains zero nodes. -func (q *queue) empty() bool { - return len(*q) == 0 -} diff --git a/middleware/file/tree/tree.go b/middleware/file/tree/tree.go deleted file mode 100644 index ed33c09a4..000000000 --- a/middleware/file/tree/tree.go +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright ©2012 The bÃogo Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found at the end of this file. - -// Package tree implements Left-Leaning Red Black trees as described by Robert Sedgewick. -// -// More details relating to the implementation are available at the following locations: -// -// http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf -// http://www.cs.princeton.edu/~rs/talks/LLRB/Java/RedBlackBST.java -// http://www.teachsolaisgames.com/articles/balanced_left_leaning.html -// -// Heavily modified by Miek Gieben for use in DNS zones. -package tree - -import "github.com/miekg/dns" - -const ( - td234 = iota - bu23 -) - -// Operation mode of the LLRB tree. -const mode = bu23 - -func init() { - if mode != td234 && mode != bu23 { - panic("tree: unknown mode") - } -} - -// A Color represents the color of a Node. -type Color bool - -const ( - // Red as false give us the defined behaviour that new nodes are red. Although this - // is incorrect for the root node, that is resolved on the first insertion. - red Color = false - black Color = true -) - -// A Node represents a node in the LLRB tree. -type Node struct { - Elem *Elem - Left, Right *Node - Color Color -} - -// A Tree manages the root node of an LLRB tree. Public methods are exposed through this type. -type Tree struct { - Root *Node // Root node of the tree. - Count int // Number of elements stored. -} - -// Helper methods - -// color returns the effect color of a Node. A nil node returns black. -func (n *Node) color() Color { - if n == nil { - return black - } - return n.Color -} - -// (a,c)b -rotL-> ((a,)b,)c -func (n *Node) rotateLeft() (root *Node) { - // Assumes: n has two children. - root = n.Right - n.Right = root.Left - root.Left = n - root.Color = n.Color - n.Color = red - return -} - -// (a,c)b -rotR-> (,(,c)b)a -func (n *Node) rotateRight() (root *Node) { - // Assumes: n has two children. - root = n.Left - n.Left = root.Right - root.Right = n - root.Color = n.Color - n.Color = red - return -} - -// (aR,cR)bB -flipC-> (aB,cB)bR | (aB,cB)bR -flipC-> (aR,cR)bB -func (n *Node) flipColors() { - // Assumes: n has two children. - n.Color = !n.Color - n.Left.Color = !n.Left.Color - n.Right.Color = !n.Right.Color -} - -// fixUp ensures that black link balance is correct, that red nodes lean left, -// and that 4 nodes are split in the case of BU23 and properly balanced in TD234. -func (n *Node) fixUp() *Node { - if n.Right.color() == red { - if mode == td234 && n.Right.Left.color() == red { - n.Right = n.Right.rotateRight() - } - n = n.rotateLeft() - } - if n.Left.color() == red && n.Left.Left.color() == red { - n = n.rotateRight() - } - if mode == bu23 && n.Left.color() == red && n.Right.color() == red { - n.flipColors() - } - return n -} - -func (n *Node) moveRedLeft() *Node { - n.flipColors() - if n.Right.Left.color() == red { - n.Right = n.Right.rotateRight() - n = n.rotateLeft() - n.flipColors() - if mode == td234 && n.Right.Right.color() == red { - n.Right = n.Right.rotateLeft() - } - } - return n -} - -func (n *Node) moveRedRight() *Node { - n.flipColors() - if n.Left.Left.color() == red { - n = n.rotateRight() - n.flipColors() - } - return n -} - -// Len returns the number of elements stored in the Tree. -func (t *Tree) Len() int { - return t.Count -} - -// Search returns the first match of qname in the Tree. -func (t *Tree) Search(qname string) (*Elem, bool) { - if t.Root == nil { - return nil, false - } - n, res := t.Root.search(qname) - if n == nil { - return nil, res - } - return n.Elem, res -} - -// search searches the tree for qname and type. -func (n *Node) search(qname string) (*Node, bool) { - for n != nil { - switch c := Less(n.Elem, qname); { - case c == 0: - return n, true - case c < 0: - n = n.Left - default: - n = n.Right - } - } - - return n, false -} - -// Insert inserts rr into the Tree at the first match found -// with e or when a nil node is reached. -func (t *Tree) Insert(rr dns.RR) { - var d int - t.Root, d = t.Root.insert(rr) - t.Count += d - t.Root.Color = black -} - -// insert inserts rr in to the tree. -func (n *Node) insert(rr dns.RR) (root *Node, d int) { - if n == nil { - return &Node{Elem: newElem(rr)}, 1 - } else if n.Elem == nil { - n.Elem = newElem(rr) - return n, 1 - } - - if mode == td234 { - if n.Left.color() == red && n.Right.color() == red { - n.flipColors() - } - } - - switch c := Less(n.Elem, rr.Header().Name); { - case c == 0: - n.Elem.Insert(rr) - case c < 0: - n.Left, d = n.Left.insert(rr) - default: - n.Right, d = n.Right.insert(rr) - } - - if n.Right.color() == red && n.Left.color() == black { - n = n.rotateLeft() - } - if n.Left.color() == red && n.Left.Left.color() == red { - n = n.rotateRight() - } - - if mode == bu23 { - if n.Left.color() == red && n.Right.color() == red { - n.flipColors() - } - } - - root = n - - return -} - -// DeleteMin deletes the node with the minimum value in the tree. -func (t *Tree) DeleteMin() { - if t.Root == nil { - return - } - var d int - t.Root, d = t.Root.deleteMin() - t.Count += d - if t.Root == nil { - return - } - t.Root.Color = black -} - -func (n *Node) deleteMin() (root *Node, d int) { - if n.Left == nil { - return nil, -1 - } - if n.Left.color() == black && n.Left.Left.color() == black { - n = n.moveRedLeft() - } - n.Left, d = n.Left.deleteMin() - - root = n.fixUp() - - return -} - -// DeleteMax deletes the node with the maximum value in the tree. -func (t *Tree) DeleteMax() { - if t.Root == nil { - return - } - var d int - t.Root, d = t.Root.deleteMax() - t.Count += d - if t.Root == nil { - return - } - t.Root.Color = black -} - -func (n *Node) deleteMax() (root *Node, d int) { - if n.Left != nil && n.Left.color() == red { - n = n.rotateRight() - } - if n.Right == nil { - return nil, -1 - } - if n.Right.color() == black && n.Right.Left.color() == black { - n = n.moveRedRight() - } - n.Right, d = n.Right.deleteMax() - - root = n.fixUp() - - return -} - -// Delete removes rr from the tree, is the node turns empty, that node is deleted with DeleteNode. -func (t *Tree) Delete(rr dns.RR) { - if t.Root == nil { - return - } - - el, _ := t.Search(rr.Header().Name) - if el == nil { - t.deleteNode(rr) - return - } - // Delete from this element. - empty := el.Delete(rr) - if empty { - t.deleteNode(rr) - return - } -} - -// DeleteNode deletes the node that matches rr according to Less(). -func (t *Tree) deleteNode(rr dns.RR) { - if t.Root == nil { - return - } - var d int - t.Root, d = t.Root.delete(rr) - t.Count += d - if t.Root == nil { - return - } - t.Root.Color = black -} - -func (n *Node) delete(rr dns.RR) (root *Node, d int) { - if Less(n.Elem, rr.Header().Name) < 0 { - if n.Left != nil { - if n.Left.color() == black && n.Left.Left.color() == black { - n = n.moveRedLeft() - } - n.Left, d = n.Left.delete(rr) - } - } else { - if n.Left.color() == red { - n = n.rotateRight() - } - if n.Right == nil && Less(n.Elem, rr.Header().Name) == 0 { - return nil, -1 - } - if n.Right != nil { - if n.Right.color() == black && n.Right.Left.color() == black { - n = n.moveRedRight() - } - if Less(n.Elem, rr.Header().Name) == 0 { - n.Elem = n.Right.min().Elem - n.Right, d = n.Right.deleteMin() - } else { - n.Right, d = n.Right.delete(rr) - } - } - } - - root = n.fixUp() - return -} - -// Min returns the minimum value stored in the tree. -func (t *Tree) Min() *Elem { - if t.Root == nil { - return nil - } - return t.Root.min().Elem -} - -func (n *Node) min() *Node { - for ; n.Left != nil; n = n.Left { - } - return n -} - -// Max returns the maximum value stored in the tree. -func (t *Tree) Max() *Elem { - if t.Root == nil { - return nil - } - return t.Root.max().Elem -} - -func (n *Node) max() *Node { - for ; n.Right != nil; n = n.Right { - } - return n -} - -// Prev returns the greatest value equal to or less than the qname according to Less(). -func (t *Tree) Prev(qname string) (*Elem, bool) { - if t.Root == nil { - return nil, false - } - - n := t.Root.floor(qname) - if n == nil { - return nil, false - } - return n.Elem, true -} - -func (n *Node) floor(qname string) *Node { - if n == nil { - return nil - } - switch c := Less(n.Elem, qname); { - case c == 0: - return n - case c <= 0: - return n.Left.floor(qname) - default: - if r := n.Right.floor(qname); r != nil { - return r - } - } - return n -} - -// Next returns the smallest value equal to or greater than the qname according to Less(). -func (t *Tree) Next(qname string) (*Elem, bool) { - if t.Root == nil { - return nil, false - } - n := t.Root.ceil(qname) - if n == nil { - return nil, false - } - return n.Elem, true -} - -func (n *Node) ceil(qname string) *Node { - if n == nil { - return nil - } - switch c := Less(n.Elem, qname); { - case c == 0: - return n - case c > 0: - return n.Right.ceil(qname) - default: - if l := n.Left.ceil(qname); l != nil { - return l - } - } - return n -} - -/* -Copyright ©2012 The bÃogo Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -* Neither the name of the bÃogo project nor the names of its authors and - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ diff --git a/middleware/file/wildcard.go b/middleware/file/wildcard.go deleted file mode 100644 index 9526cb53f..000000000 --- a/middleware/file/wildcard.go +++ /dev/null @@ -1,13 +0,0 @@ -package file - -import "github.com/miekg/dns" - -// replaceWithWildcard replaces the left most label with '*'. -func replaceWithAsteriskLabel(qname string) (wildcard string) { - i, shot := dns.NextLabel(qname, 0) - if shot { - return "" - } - - return "*." + qname[i:] -} diff --git a/middleware/file/wildcard_test.go b/middleware/file/wildcard_test.go deleted file mode 100644 index 7f1a2fed3..000000000 --- a/middleware/file/wildcard_test.go +++ /dev/null @@ -1,289 +0,0 @@ -package file - -import ( - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var wildcardTestCases = []test.Case{ - { - Qname: "wild.dnssex.nl.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT(`wild.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better"`), - }, - Ns: dnssexAuth[:len(dnssexAuth)-1], // remove RRSIG on the end - }, - { - Qname: "a.wild.dnssex.nl.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT(`a.wild.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better"`), - }, - Ns: dnssexAuth[:len(dnssexAuth)-1], // remove RRSIG on the end - }, - { - Qname: "wild.dnssex.nl.", Qtype: dns.TypeTXT, Do: true, - Answer: []dns.RR{ - test.RRSIG("wild.dnssex.nl. 1800 IN RRSIG TXT 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. FUZSTyvZfeuuOpCm"), - test.TXT(`wild.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better"`), - }, - Ns: append([]dns.RR{ - test.NSEC("a.dnssex.nl. 14400 IN NSEC www.dnssex.nl. A AAAA RRSIG NSEC"), - test.RRSIG("a.dnssex.nl. 14400 IN RRSIG NSEC 8 3 14400 20160428190224 20160329190224 14460 dnssex.nl. S+UMs2ySgRaaRY"), - }, dnssexAuth...), - Extra: []dns.RR{test.OPT(4096, true)}, - }, - { - Qname: "a.wild.dnssex.nl.", Qtype: dns.TypeTXT, Do: true, - Answer: []dns.RR{ - test.RRSIG("a.wild.dnssex.nl. 1800 IN RRSIG TXT 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. FUZSTyvZfeuuOpCm"), - test.TXT(`a.wild.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better"`), - }, - Ns: append([]dns.RR{ - test.NSEC("a.dnssex.nl. 14400 IN NSEC www.dnssex.nl. A AAAA RRSIG NSEC"), - test.RRSIG("a.dnssex.nl. 14400 IN RRSIG NSEC 8 3 14400 20160428190224 20160329190224 14460 dnssex.nl. S+UMs2ySgRaaRY"), - }, dnssexAuth...), - Extra: []dns.RR{test.OPT(4096, true)}, - }, - // nodata responses - { - Qname: "wild.dnssex.nl.", Qtype: dns.TypeSRV, - Ns: []dns.RR{ - test.SOA(`dnssex.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1459281744 14400 3600 604800 14400`), - }, - }, - { - Qname: "wild.dnssex.nl.", Qtype: dns.TypeSRV, Do: true, - Ns: []dns.RR{ - // TODO(miek): needs closest encloser proof as well? This is the wrong answer - test.NSEC(`*.dnssex.nl. 14400 IN NSEC a.dnssex.nl. TXT RRSIG NSEC`), - test.RRSIG(`*.dnssex.nl. 14400 IN RRSIG NSEC 8 2 14400 20160428190224 20160329190224 14460 dnssex.nl. os6INm6q2eXknD5z8TaaDOV+Ge/Ko+2dXnKP+J1fqJzafXJVH1F0nDrcXmMlR6jlBHA=`), - test.RRSIG(`dnssex.nl. 1800 IN RRSIG SOA 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. CA/Y3m9hCOiKC/8ieSOv8SeP964Bq++lyH8BZJcTaabAsERs4xj5PRtcxicwQXZiF8fYUCpROlUS0YR8Cdw=`), - test.SOA(`dnssex.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1459281744 14400 3600 604800 14400`), - }, - Extra: []dns.RR{test.OPT(4096, true)}, - }, -} - -var dnssexAuth = []dns.RR{ - test.NS("dnssex.nl. 1800 IN NS linode.atoom.net."), - test.NS("dnssex.nl. 1800 IN NS ns-ext.nlnetlabs.nl."), - test.NS("dnssex.nl. 1800 IN NS omval.tednet.nl."), - test.RRSIG("dnssex.nl. 1800 IN RRSIG NS 8 2 1800 20160428190224 20160329190224 14460 dnssex.nl. dLIeEvP86jj5ndkcLzhgvWixTABjWAGRTGQsPsVDFXsGMf9TGGC9FEomgkCVeNC0="), -} - -func TestLookupWildcard(t *testing.T) { - zone, err := Parse(strings.NewReader(dbDnssexNLSigned), testzone1, "stdin", 0) - if err != nil { - t.Fatalf("Expect no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone1: zone}, Names: []string{testzone1}}} - ctx := context.TODO() - - for _, tc := range wildcardTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -var wildcardDoubleTestCases = []test.Case{ - { - Qname: "wild.w.example.org.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT(`wild.w.example.org. IN TXT "Wildcard"`), - }, - Ns: exampleAuth, - }, - { - Qname: "wild.c.example.org.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT(`wild.c.example.org. IN TXT "c Wildcard"`), - }, - Ns: exampleAuth, - }, - { - Qname: "wild.d.example.org.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT(`alias.example.org. IN TXT "Wildcard CNAME expansion"`), - test.CNAME(`wild.d.example.org. IN CNAME alias.example.org`), - }, - Ns: exampleAuth, - }, - { - Qname: "alias.example.org.", Qtype: dns.TypeTXT, - Answer: []dns.RR{ - test.TXT(`alias.example.org. IN TXT "Wildcard CNAME expansion"`), - }, - Ns: exampleAuth, - }, -} - -var exampleAuth = []dns.RR{ - test.NS("example.org. 3600 IN NS a.iana-servers.net."), - test.NS("example.org. 3600 IN NS b.iana-servers.net."), -} - -func TestLookupDoubleWildcard(t *testing.T) { - zone, err := Parse(strings.NewReader(exampleOrg), "example.org.", "stdin", 0) - if err != nil { - t.Fatalf("Expect no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{"example.org.": zone}, Names: []string{"example.org."}}} - ctx := context.TODO() - - for _, tc := range wildcardDoubleTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -func TestReplaceWithAsteriskLabel(t *testing.T) { - tests := []struct { - in, out string - }{ - {".", ""}, - {"miek.nl.", "*.nl."}, - {"www.miek.nl.", "*.miek.nl."}, - } - - for _, tc := range tests { - got := replaceWithAsteriskLabel(tc.in) - if got != tc.out { - t.Errorf("Expected to be %s, got %s", tc.out, got) - } - } -} - -var apexWildcardTestCases = []test.Case{ - { - Qname: "foo.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{test.A(`foo.example.org. 3600 IN A 127.0.0.54`)}, - Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)}, - }, - { - Qname: "bar.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{test.A(`bar.example.org. 3600 IN A 127.0.0.53`)}, - Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)}, - }, -} - -func TestLookupApexWildcard(t *testing.T) { - const name = "example.org." - zone, err := Parse(strings.NewReader(apexWildcard), name, "stdin", 0) - if err != nil { - t.Fatalf("Expect no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}} - ctx := context.TODO() - - for _, tc := range apexWildcardTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -var multiWildcardTestCases = []test.Case{ - { - Qname: "foo.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{test.A(`foo.example.org. 3600 IN A 127.0.0.54`)}, - Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)}, - }, - { - Qname: "bar.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{test.A(`bar.example.org. 3600 IN A 127.0.0.53`)}, - Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)}, - }, - { - Qname: "bar.intern.example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{test.A(`bar.intern.example.org. 3600 IN A 127.0.1.52`)}, - Ns: []dns.RR{test.NS(`example.org. 3600 IN NS b.iana-servers.net.`)}, - }, -} - -func TestLookupMultiWildcard(t *testing.T) { - const name = "example.org." - zone, err := Parse(strings.NewReader(doubleWildcard), name, "stdin", 0) - if err != nil { - t.Fatalf("Expect no error when reading zone, got %q", err) - } - - fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}} - ctx := context.TODO() - - for _, tc := range multiWildcardTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := fm.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -const exampleOrg = `; example.org test file -example.org. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600 -example.org. IN NS b.iana-servers.net. -example.org. IN NS a.iana-servers.net. -example.org. IN A 127.0.0.1 -example.org. IN A 127.0.0.2 -*.w.example.org. IN TXT "Wildcard" -a.b.c.w.example.org. IN TXT "Not a wildcard" -*.c.example.org. IN TXT "c Wildcard" -*.d.example.org. IN CNAME alias.example.org. -alias.example.org. IN TXT "Wildcard CNAME expansion" -` - -const apexWildcard = `; example.org test file with wildcard at apex -example.org. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600 -example.org. IN NS b.iana-servers.net. -*.example.org. IN A 127.0.0.53 -foo.example.org. IN A 127.0.0.54 -` - -const doubleWildcard = `; example.org test file with wildcard at apex -example.org. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600 -example.org. IN NS b.iana-servers.net. -*.example.org. IN A 127.0.0.53 -*.intern.example.org. IN A 127.0.1.52 -foo.example.org. IN A 127.0.0.54 -` diff --git a/middleware/file/xfr.go b/middleware/file/xfr.go deleted file mode 100644 index 54f7b71f8..000000000 --- a/middleware/file/xfr.go +++ /dev/null @@ -1,62 +0,0 @@ -package file - -import ( - "fmt" - "log" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Xfr serves up an AXFR. -type Xfr struct { - *Zone -} - -// ServeDNS implements the middleware.Handler interface. -func (x Xfr) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - if !x.TransferAllowed(state) { - return dns.RcodeServerFailure, nil - } - if state.QType() != dns.TypeAXFR && state.QType() != dns.TypeIXFR { - return 0, middleware.Error(x.Name(), fmt.Errorf("xfr called with non transfer type: %d", state.QType())) - } - - records := x.All() - if len(records) == 0 { - return dns.RcodeServerFailure, nil - } - - ch := make(chan *dns.Envelope) - defer close(ch) - tr := new(dns.Transfer) - go tr.Out(w, r, ch) - - j, l := 0, 0 - records = append(records, records[0]) // add closing SOA to the end - log.Printf("[INFO] Outgoing transfer of %d records of zone %s to %s started", len(records), x.origin, state.IP()) - for i, r := range records { - l += dns.Len(r) - if l > transferLength { - ch <- &dns.Envelope{RR: records[j:i]} - l = 0 - j = i - } - } - if j < len(records) { - ch <- &dns.Envelope{RR: records[j:]} - } - - w.Hijack() - // w.Close() // Client closes connection - return dns.RcodeSuccess, nil -} - -// Name implements the middleware.Hander interface. -func (x Xfr) Name() string { return "xfr" } - -const transferLength = 1000 // Start a new envelop after message reaches this size in bytes. Intentionally small to test multi envelope parsing. diff --git a/middleware/file/xfr_test.go b/middleware/file/xfr_test.go deleted file mode 100644 index 69ad68e64..000000000 --- a/middleware/file/xfr_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package file - -import ( - "fmt" - "strings" -) - -func ExampleZone_All() { - zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin", 0) - if err != nil { - return - } - records := zone.All() - for _, r := range records { - fmt.Printf("%+v\n", r) - } - // Output - // xfr_test.go:15: miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400 - // xfr_test.go:15: www.miek.nl. 1800 IN CNAME a.miek.nl. - // xfr_test.go:15: miek.nl. 1800 IN NS linode.atoom.net. - // xfr_test.go:15: miek.nl. 1800 IN NS ns-ext.nlnetlabs.nl. - // xfr_test.go:15: miek.nl. 1800 IN NS omval.tednet.nl. - // xfr_test.go:15: miek.nl. 1800 IN NS ext.ns.whyscream.net. - // xfr_test.go:15: miek.nl. 1800 IN MX 1 aspmx.l.google.com. - // xfr_test.go:15: miek.nl. 1800 IN MX 5 alt1.aspmx.l.google.com. - // xfr_test.go:15: miek.nl. 1800 IN MX 5 alt2.aspmx.l.google.com. - // xfr_test.go:15: miek.nl. 1800 IN MX 10 aspmx2.googlemail.com. - // xfr_test.go:15: miek.nl. 1800 IN MX 10 aspmx3.googlemail.com. - // xfr_test.go:15: miek.nl. 1800 IN A 139.162.196.78 - // xfr_test.go:15: miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735 - // xfr_test.go:15: archive.miek.nl. 1800 IN CNAME a.miek.nl. - // xfr_test.go:15: a.miek.nl. 1800 IN A 139.162.196.78 - // xfr_test.go:15: a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735 -} diff --git a/middleware/file/zone.go b/middleware/file/zone.go deleted file mode 100644 index 3f3bcf0f7..000000000 --- a/middleware/file/zone.go +++ /dev/null @@ -1,190 +0,0 @@ -package file - -import ( - "fmt" - "net" - "path" - "strings" - "sync" - - "github.com/coredns/coredns/middleware/file/tree" - "github.com/coredns/coredns/middleware/proxy" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// Zone defines a structure that contains all data related to a DNS zone. -type Zone struct { - origin string - origLen int - file string - *tree.Tree - Apex Apex - - TransferTo []string - StartupOnce sync.Once - TransferFrom []string - Expired *bool - - NoReload bool - reloadMu sync.RWMutex - ReloadShutdown chan bool - Proxy proxy.Proxy // Proxy for looking up names during the resolution process -} - -// Apex contains the apex records of a zone: SOA, NS and their potential signatures. -type Apex struct { - SOA *dns.SOA - NS []dns.RR - SIGSOA []dns.RR - SIGNS []dns.RR -} - -// NewZone returns a new zone. -func NewZone(name, file string) *Zone { - z := &Zone{ - origin: dns.Fqdn(name), - origLen: dns.CountLabel(dns.Fqdn(name)), - file: path.Clean(file), - Tree: &tree.Tree{}, - Expired: new(bool), - ReloadShutdown: make(chan bool), - } - *z.Expired = false - - return z -} - -// Copy copies a zone. -func (z *Zone) Copy() *Zone { - z1 := NewZone(z.origin, z.file) - z1.TransferTo = z.TransferTo - z1.TransferFrom = z.TransferFrom - z1.Expired = z.Expired - - z1.Apex = z.Apex - return z1 -} - -// Insert inserts r into z. -func (z *Zone) Insert(r dns.RR) error { - r.Header().Name = strings.ToLower(r.Header().Name) - - switch h := r.Header().Rrtype; h { - case dns.TypeNS: - r.(*dns.NS).Ns = strings.ToLower(r.(*dns.NS).Ns) - - if r.Header().Name == z.origin { - z.Apex.NS = append(z.Apex.NS, r) - return nil - } - case dns.TypeSOA: - r.(*dns.SOA).Ns = strings.ToLower(r.(*dns.SOA).Ns) - r.(*dns.SOA).Mbox = strings.ToLower(r.(*dns.SOA).Mbox) - - z.Apex.SOA = r.(*dns.SOA) - return nil - case dns.TypeNSEC3, dns.TypeNSEC3PARAM: - return fmt.Errorf("NSEC3 zone is not supported, dropping RR: %s for zone: %s", r.Header().Name, z.origin) - case dns.TypeRRSIG: - x := r.(*dns.RRSIG) - switch x.TypeCovered { - case dns.TypeSOA: - z.Apex.SIGSOA = append(z.Apex.SIGSOA, x) - return nil - case dns.TypeNS: - if r.Header().Name == z.origin { - z.Apex.SIGNS = append(z.Apex.SIGNS, x) - return nil - } - } - case dns.TypeCNAME: - r.(*dns.CNAME).Target = strings.ToLower(r.(*dns.CNAME).Target) - case dns.TypeMX: - r.(*dns.MX).Mx = strings.ToLower(r.(*dns.MX).Mx) - case dns.TypeSRV: - r.(*dns.SRV).Target = strings.ToLower(r.(*dns.SRV).Target) - } - - z.Tree.Insert(r) - return nil -} - -// Delete deletes r from z. -func (z *Zone) Delete(r dns.RR) { z.Tree.Delete(r) } - -// TransferAllowed checks if incoming request for transferring the zone is allowed according to the ACLs. -func (z *Zone) TransferAllowed(state request.Request) bool { - for _, t := range z.TransferTo { - if t == "*" { - return true - } - // If remote IP matches we accept. - remote := state.IP() - to, _, err := net.SplitHostPort(t) - if err != nil { - continue - } - if to == remote { - return true - } - } - // TODO(miek): future matching against IP/CIDR notations - return false -} - -// All returns all records from the zone, the first record will be the SOA record, -// otionally followed by all RRSIG(SOA)s. -func (z *Zone) All() []dns.RR { - if !z.NoReload { - z.reloadMu.RLock() - defer z.reloadMu.RUnlock() - } - - records := []dns.RR{} - allNodes := z.Tree.All() - for _, a := range allNodes { - records = append(records, a.All()...) - } - - if len(z.Apex.SIGNS) > 0 { - records = append(z.Apex.SIGNS, records...) - } - records = append(z.Apex.NS, records...) - - if len(z.Apex.SIGSOA) > 0 { - records = append(z.Apex.SIGSOA, records...) - } - return append([]dns.RR{z.Apex.SOA}, records...) -} - -// Print prints the zone's tree to stdout. -func (z *Zone) Print() { - z.Tree.Print() -} - -// NameFromRight returns the labels from the right, staring with the -// origin and then i labels extra. When we are overshooting the name -// the returned boolean is set to true. -func (z *Zone) nameFromRight(qname string, i int) (string, bool) { - if i <= 0 { - return z.origin, false - } - - for j := 1; j <= z.origLen; j++ { - if _, shot := dns.PrevLabel(qname, j); shot { - return qname, shot - } - } - - k := 0 - shot := false - for j := 1; j <= i; j++ { - k, shot = dns.PrevLabel(qname, j+z.origLen) - if shot { - return qname, shot - } - } - return qname[k:], false -} diff --git a/middleware/file/zone_test.go b/middleware/file/zone_test.go deleted file mode 100644 index c9ff174db..000000000 --- a/middleware/file/zone_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package file - -import "testing" - -func TestNameFromRight(t *testing.T) { - z := NewZone("example.org.", "stdin") - - tests := []struct { - in string - labels int - shot bool - expected string - }{ - {"example.org.", 0, false, "example.org."}, - {"a.example.org.", 0, false, "example.org."}, - {"a.example.org.", 1, false, "a.example.org."}, - {"a.example.org.", 2, true, "a.example.org."}, - {"a.b.example.org.", 2, false, "a.b.example.org."}, - } - - for i, tc := range tests { - got, shot := z.nameFromRight(tc.in, tc.labels) - if got != tc.expected { - t.Errorf("Test %d: expected %s, got %s\n", i, tc.expected, got) - } - if shot != tc.shot { - t.Errorf("Test %d: expected shot to be %t, got %t\n", i, tc.shot, shot) - } - } -} diff --git a/middleware/health/README.md b/middleware/health/README.md deleted file mode 100644 index 195a480c0..000000000 --- a/middleware/health/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# health - -This module enables a simple health check endpoint. By default it will listen on port 8080. - -## Syntax - -~~~ -health [ADDRESS] -~~~ - -Optionally takes an address; the default is `:8080`. The health path is fixed to `/health`. The -health endpoint returns a 200 response code and the word "OK" when CoreDNS is healthy. It returns -a 503. *health* periodically (1s) polls middleware that exports health information. If any of the -middleware signals that it is unhealthy, the server will go unhealthy too. Each middleware that -supports health checks has a section "Health" in their README. - -## Examples - -Run another health endpoint on http://localhost:8091. - -~~~ -health localhost:8091 -~~~ diff --git a/middleware/health/health.go b/middleware/health/health.go deleted file mode 100644 index 5e5c4c32f..000000000 --- a/middleware/health/health.go +++ /dev/null @@ -1,69 +0,0 @@ -// Package health implements an HTTP handler that responds to health checks. -package health - -import ( - "io" - "log" - "net" - "net/http" - "sync" -) - -var once sync.Once - -type health struct { - Addr string - - ln net.Listener - mux *http.ServeMux - - // A slice of Healthers that the health middleware will poll every second for their health status. - h []Healther - sync.RWMutex - ok bool // ok is the global boolean indicating an all healthy middleware stack -} - -func (h *health) Startup() error { - if h.Addr == "" { - h.Addr = defAddr - } - - once.Do(func() { - ln, err := net.Listen("tcp", h.Addr) - if err != nil { - log.Printf("[ERROR] Failed to start health handler: %s", err) - return - } - - h.ln = ln - - h.mux = http.NewServeMux() - - h.mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { - if h.Ok() { - w.WriteHeader(http.StatusOK) - io.WriteString(w, ok) - return - } - w.WriteHeader(http.StatusServiceUnavailable) - }) - - go func() { - http.Serve(h.ln, h.mux) - }() - }) - return nil -} - -func (h *health) Shutdown() error { - if h.ln != nil { - return h.ln.Close() - } - return nil -} - -const ( - ok = "OK" - defAddr = ":8080" - path = "/health" -) diff --git a/middleware/health/health_test.go b/middleware/health/health_test.go deleted file mode 100644 index ea22ad8b6..000000000 --- a/middleware/health/health_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package health - -// TODO(miek): enable again if middleware gets health check. -/* -func TestHealth(t *testing.T) { - h := health{Addr: ":0"} - h.h = append(h.h, &erratic.Erratic{}) - - if err := h.Startup(); err != nil { - t.Fatalf("Unable to startup the health server: %v", err) - } - defer h.Shutdown() - - // Reconstruct the http address based on the port allocated by operating system. - address := fmt.Sprintf("http://%s%s", h.ln.Addr().String(), path) - - // Norhing set should be unhealthy - response, err := http.Get(address) - if err != nil { - t.Fatalf("Unable to query %s: %v", address, err) - } - if response.StatusCode != 503 { - t.Errorf("Invalid status code: expecting '503', got '%d'", response.StatusCode) - } - response.Body.Close() - - // Make healthy - h.Poll() - - response, err = http.Get(address) - if err != nil { - t.Fatalf("Unable to query %s: %v", address, err) - } - if response.StatusCode != 200 { - t.Errorf("Invalid status code: expecting '200', got '%d'", response.StatusCode) - } - content, err := ioutil.ReadAll(response.Body) - if err != nil { - t.Fatalf("Unable to get response body from %s: %v", address, err) - } - response.Body.Close() - - if string(content) != ok { - t.Errorf("Invalid response body: expecting 'OK', got '%s'", string(content)) - } -} -*/ diff --git a/middleware/health/healther.go b/middleware/health/healther.go deleted file mode 100644 index 1b794e03b..000000000 --- a/middleware/health/healther.go +++ /dev/null @@ -1,42 +0,0 @@ -package health - -// Healther interface needs to be implemented by each middleware willing to -// provide healthhceck information to the health middleware. As a second step -// the middleware needs to registered against the health middleware, by addding -// it to healthers map. Note this method should return quickly, i.e. just -// checking a boolean status, as it is called every second from the health -// middleware. -type Healther interface { - // Health returns a boolean indicating the health status of a middleware. - // False indicates unhealthy. - Health() bool -} - -// Ok returns the global health status of all middleware configured in this server. -func (h *health) Ok() bool { - h.RLock() - defer h.RUnlock() - return h.ok -} - -// SetOk sets the global health status of all middleware configured in this server. -func (h *health) SetOk(ok bool) { - h.Lock() - defer h.Unlock() - h.ok = ok -} - -// poll polls all healthers and sets the global state. -func (h *health) poll() { - for _, m := range h.h { - if !m.Health() { - h.SetOk(false) - return - } - } - h.SetOk(true) -} - -// Middleware that implements the Healther interface. -// TODO(miek): none yet. -var healthers = map[string]bool{} diff --git a/middleware/health/setup.go b/middleware/health/setup.go deleted file mode 100644 index bf465bd74..000000000 --- a/middleware/health/setup.go +++ /dev/null @@ -1,73 +0,0 @@ -package health - -import ( - "net" - "time" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("health", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - addr, err := healthParse(c) - if err != nil { - return middleware.Error("health", err) - } - - h := &health{Addr: addr} - - c.OnStartup(func() error { - for he := range healthers { - m := dnsserver.GetConfig(c).Handler(he) - if x, ok := m.(Healther); ok { - h.h = append(h.h, x) - } - } - return nil - }) - - c.OnStartup(func() error { - h.poll() - go func() { - for { - <-time.After(1 * time.Second) - h.poll() - } - }() - return nil - }) - - c.OnStartup(h.Startup) - c.OnFinalShutdown(h.Shutdown) - - // Don't do AddMiddleware, as health is not *really* a middleware just a separate webserver running. - return nil -} - -func healthParse(c *caddy.Controller) (string, error) { - addr := "" - for c.Next() { - args := c.RemainingArgs() - - switch len(args) { - case 0: - case 1: - addr = args[0] - if _, _, e := net.SplitHostPort(addr); e != nil { - return "", e - } - default: - return "", c.ArgErr() - } - } - return addr, nil -} diff --git a/middleware/health/setup_test.go b/middleware/health/setup_test.go deleted file mode 100644 index 87f4fc5fd..000000000 --- a/middleware/health/setup_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package health - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestSetupHealth(t *testing.T) { - tests := []struct { - input string - shouldErr bool - }{ - {`health`, false}, - {`health localhost:1234`, false}, - {`health bla:a`, false}, - {`health bla`, true}, - {`health bla bla`, true}, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - _, err := healthParse(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - } - } - } -} diff --git a/middleware/hosts/README.md b/middleware/hosts/README.md deleted file mode 100644 index 91d340cb5..000000000 --- a/middleware/hosts/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# hosts - -*hosts* enables serving zone data from a `/etc/hosts` style file. - -The hosts middleware is useful for serving zones from a /etc/hosts file. It serves from a preloaded -file that exists on disk. It checks the file for changes and updates the zones accordingly. This -middleware only supports A, AAAA, and PTR records. The hosts middleware can be used with readily -available hosts files that block access to advertising servers. - -## Syntax - -~~~ -hosts [FILE [ZONES...]] { - fallthrough -} -~~~ - -* **FILE** the hosts file to read and parse. If the path is relative the path from the *root* - directive will be prepended to it. Defaults to /etc/hosts if omitted -* **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block - are used. -* `fallthrough` If zone matches and no record can be generated, pass request to the next middleware. - -## Examples - -Load `/etc/hosts` file. - -~~~ -hosts -~~~ - -Load `example.hosts` file in the current directory. - -~~~ -hosts example.hosts -~~~ - -Load example.hosts file and only serve example.org and example.net from it and fall through to the -next middleware if query doesn't match. - -~~~ -hosts example.hosts example.org example.net { - fallthrough -} -~~~ diff --git a/middleware/hosts/hosts.go b/middleware/hosts/hosts.go deleted file mode 100644 index 28efc47ec..000000000 --- a/middleware/hosts/hosts.go +++ /dev/null @@ -1,136 +0,0 @@ -package hosts - -import ( - "net" - - "golang.org/x/net/context" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" - "github.com/miekg/dns" -) - -// Hosts is the middleware handler -type Hosts struct { - Next middleware.Handler - *Hostsfile - - Fallthrough bool -} - -// ServeDNS implements the middleware.Handle interface. -func (h Hosts) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - qname := state.Name() - - answers := []dns.RR{} - - zone := middleware.Zones(h.Origins).Matches(qname) - if zone == "" { - // PTR zones don't need to be specified in Origins - if state.Type() != "PTR" { - // If this doesn't match we need to fall through regardless of h.Fallthrough - return middleware.NextOrFailure(h.Name(), h.Next, ctx, w, r) - } - } - - switch state.QType() { - case dns.TypePTR: - names := h.LookupStaticAddr(dnsutil.ExtractAddressFromReverse(qname)) - if len(names) == 0 { - // If this doesn't match we need to fall through regardless of h.Fallthrough - return middleware.NextOrFailure(h.Name(), h.Next, ctx, w, r) - } - answers = h.ptr(qname, names) - case dns.TypeA: - ips := h.LookupStaticHostV4(qname) - answers = a(qname, ips) - case dns.TypeAAAA: - ips := h.LookupStaticHostV6(qname) - answers = aaaa(qname, ips) - } - - if len(answers) == 0 { - if h.Fallthrough { - return middleware.NextOrFailure(h.Name(), h.Next, ctx, w, r) - } - if !h.otherRecordsExist(state.QType(), qname) { - return dns.RcodeNameError, nil - } - } - - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - m.Answer = answers - - state.SizeAndDo(m) - m, _ = state.Scrub(m) - w.WriteMsg(m) - return dns.RcodeSuccess, nil -} - -func (h Hosts) otherRecordsExist(qtype uint16, qname string) bool { - switch qtype { - case dns.TypeA: - if len(h.LookupStaticHostV6(qname)) > 0 { - return true - } - case dns.TypeAAAA: - if len(h.LookupStaticHostV4(qname)) > 0 { - return true - } - default: - if len(h.LookupStaticHostV4(qname)) > 0 { - return true - } - if len(h.LookupStaticHostV6(qname)) > 0 { - return true - } - } - return false - -} - -// Name implements the middleware.Handle interface. -func (h Hosts) Name() string { return "hosts" } - -// a takes a slice of net.IPs and returns a slice of A RRs. -func a(zone string, ips []net.IP) []dns.RR { - answers := []dns.RR{} - for _, ip := range ips { - r := new(dns.A) - r.Hdr = dns.RR_Header{Name: zone, Rrtype: dns.TypeA, - Class: dns.ClassINET, Ttl: 3600} - r.A = ip - answers = append(answers, r) - } - return answers -} - -// aaaa takes a slice of net.IPs and returns a slice of AAAA RRs. -func aaaa(zone string, ips []net.IP) []dns.RR { - answers := []dns.RR{} - for _, ip := range ips { - r := new(dns.AAAA) - r.Hdr = dns.RR_Header{Name: zone, Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, Ttl: 3600} - r.AAAA = ip - answers = append(answers, r) - } - return answers -} - -// ptr takes a slice of host names and filters out the ones that aren't in Origins, if specified, and returns a slice of PTR RRs. -func (h *Hosts) ptr(zone string, names []string) []dns.RR { - answers := []dns.RR{} - for _, n := range names { - r := new(dns.PTR) - r.Hdr = dns.RR_Header{Name: zone, Rrtype: dns.TypePTR, - Class: dns.ClassINET, Ttl: 3600} - r.Ptr = dns.Fqdn(n) - answers = append(answers, r) - } - return answers -} diff --git a/middleware/hosts/hosts_test.go b/middleware/hosts/hosts_test.go deleted file mode 100644 index b6ebaa43a..000000000 --- a/middleware/hosts/hosts_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package hosts - -import ( - "strings" - "testing" - "time" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestLookupA(t *testing.T) { - h := Hosts{Next: test.ErrorHandler(), Hostsfile: &Hostsfile{expire: time.Now().Add(1 * time.Hour), Origins: []string{"."}}} - h.Parse(strings.NewReader(hostsExample)) - - ctx := context.TODO() - - for _, tc := range hostsTestCases { - m := tc.Msg() - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := h.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("Expected no error, got %v\n", err) - return - } - - resp := rec.Msg - test.SortAndCheck(t, resp, tc) - } -} - -var hostsTestCases = []test.Case{ - { - Qname: "example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - test.A("example.org. 3600 IN A 10.0.0.1"), - }, - }, - { - Qname: "localhost.", Qtype: dns.TypeAAAA, - Answer: []dns.RR{ - test.AAAA("localhost. 3600 IN AAAA ::1"), - }, - }, - { - Qname: "1.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR, - Answer: []dns.RR{ - test.PTR("1.0.0.10.in-addr.arpa. 3600 PTR example.org."), - }, - }, - { - Qname: "1.0.0.127.in-addr.arpa.", Qtype: dns.TypePTR, - Answer: []dns.RR{ - test.PTR("1.0.0.127.in-addr.arpa. 3600 PTR localhost."), - test.PTR("1.0.0.127.in-addr.arpa. 3600 PTR localhost.domain."), - }, - }, - { - Qname: "example.org.", Qtype: dns.TypeAAAA, - Answer: []dns.RR{}, - }, - { - Qname: "example.org.", Qtype: dns.TypeMX, - Answer: []dns.RR{}, - }, -} - -const hostsExample = ` -127.0.0.1 localhost localhost.domain -::1 localhost localhost.domain -10.0.0.1 example.org` diff --git a/middleware/hosts/hostsfile.go b/middleware/hosts/hostsfile.go deleted file mode 100644 index a4f4a017d..000000000 --- a/middleware/hosts/hostsfile.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file is a modified version of net/hosts.go from the golang repo - -package hosts - -import ( - "bufio" - "bytes" - "io" - "net" - "os" - "strings" - "sync" - "time" - - "github.com/coredns/coredns/middleware" -) - -const cacheMaxAge = 5 * time.Second - -func parseLiteralIP(addr string) net.IP { - if i := strings.Index(addr, "%"); i >= 0 { - // discard ipv6 zone - addr = addr[0:i] - } - - return net.ParseIP(addr) -} - -func absDomainName(b string) string { - return middleware.Name(b).Normalize() -} - -// Hostsfile contains known host entries. -type Hostsfile struct { - sync.Mutex - - // list of zones we are authoritive for - Origins []string - - // Key for the list of literal IP addresses must be a host - // name. It would be part of DNS labels, a FQDN or an absolute - // FQDN. - // For now the key is converted to lower case for convenience. - byNameV4 map[string][]net.IP - byNameV6 map[string][]net.IP - - // Key for the list of host names must be a literal IP address - // including IPv6 address with zone identifier. - // We don't support old-classful IP address notation. - byAddr map[string][]string - - expire time.Time - path string - mtime time.Time - size int64 -} - -// ReadHosts determines if the cached data needs to be updated based on the size and modification time of the hostsfile. -func (h *Hostsfile) ReadHosts() { - now := time.Now() - - if now.Before(h.expire) && len(h.byAddr) > 0 { - return - } - stat, err := os.Stat(h.path) - if err == nil && h.mtime.Equal(stat.ModTime()) && h.size == stat.Size() { - h.expire = now.Add(cacheMaxAge) - return - } - - var file *os.File - if file, _ = os.Open(h.path); file == nil { - return - } - defer file.Close() - - h.Parse(file) - - // Update the data cache. - h.expire = now.Add(cacheMaxAge) - h.mtime = stat.ModTime() - h.size = stat.Size() -} - -// Parse reads the hostsfile and populates the byName and byAddr maps. -func (h *Hostsfile) Parse(file io.Reader) { - hsv4 := make(map[string][]net.IP) - hsv6 := make(map[string][]net.IP) - is := make(map[string][]string) - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Bytes() - if i := bytes.Index(line, []byte{'#'}); i >= 0 { - // Discard comments. - line = line[0:i] - } - f := bytes.Fields(line) - if len(f) < 2 { - continue - } - addr := parseLiteralIP(string(f[0])) - if addr == nil { - continue - } - ver := ipVersion(string(f[0])) - for i := 1; i < len(f); i++ { - name := absDomainName(string(f[i])) - if middleware.Zones(h.Origins).Matches(name) == "" { - // name is not in Origins - continue - } - switch ver { - case 4: - hsv4[name] = append(hsv4[name], addr) - case 6: - hsv6[name] = append(hsv6[name], addr) - default: - continue - } - is[addr.String()] = append(is[addr.String()], name) - } - } - h.byNameV4 = hsv4 - h.byNameV6 = hsv6 - h.byAddr = is -} - -// ipVersion returns what IP version was used textually -func ipVersion(s string) int { - for i := 0; i < len(s); i++ { - switch s[i] { - case '.': - return 4 - case ':': - return 6 - } - } - return 0 -} - -// LookupStaticHostV4 looks up the IPv4 addresses for the given host from the hosts file. -func (h *Hostsfile) LookupStaticHostV4(host string) []net.IP { - h.Lock() - defer h.Unlock() - h.ReadHosts() - if len(h.byNameV4) != 0 { - if ips, ok := h.byNameV4[absDomainName(host)]; ok { - ipsCp := make([]net.IP, len(ips)) - copy(ipsCp, ips) - return ipsCp - } - } - return nil -} - -// LookupStaticHostV6 looks up the IPv6 addresses for the given host from the hosts file. -func (h *Hostsfile) LookupStaticHostV6(host string) []net.IP { - h.Lock() - defer h.Unlock() - h.ReadHosts() - if len(h.byNameV6) != 0 { - if ips, ok := h.byNameV6[absDomainName(host)]; ok { - ipsCp := make([]net.IP, len(ips)) - copy(ipsCp, ips) - return ipsCp - } - } - return nil -} - -// LookupStaticAddr looks up the hosts for the given address from the hosts file. -func (h *Hostsfile) LookupStaticAddr(addr string) []string { - h.Lock() - defer h.Unlock() - h.ReadHosts() - addr = parseLiteralIP(addr).String() - if addr == "" { - return nil - } - if len(h.byAddr) != 0 { - if hosts, ok := h.byAddr[addr]; ok { - hostsCp := make([]string, len(hosts)) - copy(hostsCp, hosts) - return hostsCp - } - } - return nil -} diff --git a/middleware/hosts/hostsfile_test.go b/middleware/hosts/hostsfile_test.go deleted file mode 100644 index 65841fa42..000000000 --- a/middleware/hosts/hostsfile_test.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package hosts - -import ( - "net" - "reflect" - "strings" - "testing" - "time" -) - -func testHostsfile(file string) *Hostsfile { - h := &Hostsfile{expire: time.Now().Add(1 * time.Hour), Origins: []string{"."}} - h.Parse(strings.NewReader(file)) - return h -} - -type staticHostEntry struct { - in string - v4 []string - v6 []string -} - -var ( - hosts = `255.255.255.255 broadcasthost - 127.0.0.2 odin - 127.0.0.3 odin # inline comment - ::2 odin - 127.1.1.1 thor - # aliases - 127.1.1.2 ullr ullrhost - fe80::1%lo0 localhost - # Bogus entries that must be ignored. - 123.123.123 loki - 321.321.321.321` - singlelinehosts = `127.0.0.2 odin` - ipv4hosts = `# See https://tools.ietf.org/html/rfc1123. - # - # The literal IPv4 address parser in the net package is a relaxed - # one. It may accept a literal IPv4 address in dotted-decimal notation - # with leading zeros such as "001.2.003.4". - - # internet address and host name - 127.0.0.1 localhost # inline comment separated by tab - 127.000.000.002 localhost # inline comment separated by space - - # internet address, host name and aliases - 127.000.000.003 localhost localhost.localdomain` - ipv6hosts = `# See https://tools.ietf.org/html/rfc5952, https://tools.ietf.org/html/rfc4007. - - # internet address and host name - ::1 localhost # inline comment separated by tab - fe80:0000:0000:0000:0000:0000:0000:0001 localhost # inline comment separated by space - - # internet address with zone identifier and host name - fe80:0000:0000:0000:0000:0000:0000:0002%lo0 localhost - - # internet address, host name and aliases - fe80::3%lo0 localhost localhost.localdomain` - casehosts = `127.0.0.1 PreserveMe PreserveMe.local - ::1 PreserveMe PreserveMe.local` -) - -var lookupStaticHostTests = []struct { - file string - ents []staticHostEntry -}{ - { - hosts, - []staticHostEntry{ - {"odin", []string{"127.0.0.2", "127.0.0.3"}, []string{"::2"}}, - {"thor", []string{"127.1.1.1"}, []string{}}, - {"ullr", []string{"127.1.1.2"}, []string{}}, - {"ullrhost", []string{"127.1.1.2"}, []string{}}, - {"localhost", []string{}, []string{"fe80::1"}}, - }, - }, - { - singlelinehosts, // see golang.org/issue/6646 - []staticHostEntry{ - {"odin", []string{"127.0.0.2"}, []string{}}, - }, - }, - { - ipv4hosts, - []staticHostEntry{ - {"localhost", []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, []string{}}, - {"localhost.localdomain", []string{"127.0.0.3"}, []string{}}, - }, - }, - { - ipv6hosts, - []staticHostEntry{ - {"localhost", []string{}, []string{"::1", "fe80::1", "fe80::2", "fe80::3"}}, - {"localhost.localdomain", []string{}, []string{"fe80::3"}}, - }, - }, - { - casehosts, - []staticHostEntry{ - {"PreserveMe", []string{"127.0.0.1"}, []string{"::1"}}, - {"PreserveMe.local", []string{"127.0.0.1"}, []string{"::1"}}, - }, - }, -} - -func TestLookupStaticHost(t *testing.T) { - - for _, tt := range lookupStaticHostTests { - h := testHostsfile(tt.file) - for _, ent := range tt.ents { - testStaticHost(t, ent, h) - } - } -} - -func testStaticHost(t *testing.T, ent staticHostEntry, h *Hostsfile) { - ins := []string{ent.in, absDomainName(ent.in), strings.ToLower(ent.in), strings.ToUpper(ent.in)} - for k, in := range ins { - addrsV4 := h.LookupStaticHostV4(in) - if len(addrsV4) != len(ent.v4) { - t.Fatalf("%d, lookupStaticHostV4(%s) = %v; want %v", k, in, addrsV4, ent.v4) - } - for i, v4 := range addrsV4 { - if v4.String() != ent.v4[i] { - t.Fatalf("%d, lookupStaticHostV4(%s) = %v; want %v", k, in, addrsV4, ent.v4) - } - } - addrsV6 := h.LookupStaticHostV6(in) - if len(addrsV6) != len(ent.v6) { - t.Fatalf("%d, lookupStaticHostV6(%s) = %v; want %v", k, in, addrsV6, ent.v6) - } - for i, v6 := range addrsV6 { - if v6.String() != ent.v6[i] { - t.Fatalf("%d, lookupStaticHostV6(%s) = %v; want %v", k, in, addrsV6, ent.v6) - } - } - } -} - -type staticIPEntry struct { - in string - out []string -} - -var lookupStaticAddrTests = []struct { - file string - ents []staticIPEntry -}{ - { - hosts, - []staticIPEntry{ - {"255.255.255.255", []string{"broadcasthost"}}, - {"127.0.0.2", []string{"odin"}}, - {"127.0.0.3", []string{"odin"}}, - {"::2", []string{"odin"}}, - {"127.1.1.1", []string{"thor"}}, - {"127.1.1.2", []string{"ullr", "ullrhost"}}, - {"fe80::1", []string{"localhost"}}, - }, - }, - { - singlelinehosts, // see golang.org/issue/6646 - []staticIPEntry{ - {"127.0.0.2", []string{"odin"}}, - }, - }, - { - ipv4hosts, // see golang.org/issue/8996 - []staticIPEntry{ - {"127.0.0.1", []string{"localhost"}}, - {"127.0.0.2", []string{"localhost"}}, - {"127.0.0.3", []string{"localhost", "localhost.localdomain"}}, - }, - }, - { - ipv6hosts, // see golang.org/issue/8996 - []staticIPEntry{ - {"::1", []string{"localhost"}}, - {"fe80::1", []string{"localhost"}}, - {"fe80::2", []string{"localhost"}}, - {"fe80::3", []string{"localhost", "localhost.localdomain"}}, - }, - }, - { - casehosts, // see golang.org/issue/12806 - []staticIPEntry{ - {"127.0.0.1", []string{"PreserveMe", "PreserveMe.local"}}, - {"::1", []string{"PreserveMe", "PreserveMe.local"}}, - }, - }, -} - -func TestLookupStaticAddr(t *testing.T) { - for _, tt := range lookupStaticAddrTests { - h := testHostsfile(tt.file) - for _, ent := range tt.ents { - testStaticAddr(t, ent, h) - } - } -} - -func testStaticAddr(t *testing.T, ent staticIPEntry, h *Hostsfile) { - hosts := h.LookupStaticAddr(ent.in) - for i := range ent.out { - ent.out[i] = absDomainName(ent.out[i]) - } - if !reflect.DeepEqual(hosts, ent.out) { - t.Errorf("%s, lookupStaticAddr(%s) = %v; want %v", h.path, ent.in, hosts, h) - } -} - -func TestHostCacheModification(t *testing.T) { - // Ensure that programs can't modify the internals of the host cache. - // See https://github.com/golang/go/issues/14212. - - h := testHostsfile(ipv4hosts) - ent := staticHostEntry{"localhost", []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, []string{}} - testStaticHost(t, ent, h) - // Modify the addresses return by lookupStaticHost. - addrs := h.LookupStaticHostV6(ent.in) - for i := range addrs { - addrs[i] = net.IPv4zero - } - testStaticHost(t, ent, h) - - h = testHostsfile(ipv6hosts) - entip := staticIPEntry{"::1", []string{"localhost"}} - testStaticAddr(t, entip, h) - // Modify the hosts return by lookupStaticAddr. - hosts := h.LookupStaticAddr(entip.in) - for i := range hosts { - hosts[i] += "junk" - } - testStaticAddr(t, entip, h) -} diff --git a/middleware/hosts/setup.go b/middleware/hosts/setup.go deleted file mode 100644 index 38458541c..000000000 --- a/middleware/hosts/setup.go +++ /dev/null @@ -1,88 +0,0 @@ -package hosts - -import ( - "log" - "os" - "path" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("hosts", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - h, err := hostsParse(c) - if err != nil { - return middleware.Error("hosts", err) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - h.Next = next - return h - }) - - return nil -} - -func hostsParse(c *caddy.Controller) (Hosts, error) { - var h = Hosts{ - Hostsfile: &Hostsfile{path: "/etc/hosts"}, - } - defer h.ReadHosts() - - config := dnsserver.GetConfig(c) - - for c.Next() { - args := c.RemainingArgs() - if len(args) >= 1 { - h.path = args[0] - args = args[1:] - - if !path.IsAbs(h.path) && config.Root != "" { - h.path = path.Join(config.Root, h.path) - } - _, err := os.Stat(h.path) - if err != nil { - if os.IsNotExist(err) { - log.Printf("[WARNING] File does not exist: %s", h.path) - } else { - return h, c.Errf("unable to access hosts file '%s': %v", h.path, err) - } - } - } - - origins := make([]string, len(c.ServerBlockKeys)) - copy(origins, c.ServerBlockKeys) - if len(args) > 0 { - origins = args - } - - for i := range origins { - origins[i] = middleware.Host(origins[i]).Normalize() - } - h.Origins = origins - - for c.NextBlock() { - switch c.Val() { - case "fallthrough": - args := c.RemainingArgs() - if len(args) == 0 { - h.Fallthrough = true - continue - } - return h, c.ArgErr() - default: - return h, c.Errf("unknown property '%s'", c.Val()) - } - } - } - return h, nil -} diff --git a/middleware/hosts/setup_test.go b/middleware/hosts/setup_test.go deleted file mode 100644 index a4c95b1c6..000000000 --- a/middleware/hosts/setup_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package hosts - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestHostsParse(t *testing.T) { - tests := []struct { - inputFileRules string - shouldErr bool - expectedPath string - expectedOrigins []string - expectedFallthrough bool - }{ - { - `hosts -`, - false, "/etc/hosts", nil, false, - }, - { - `hosts /tmp`, - false, "/tmp", nil, false, - }, - { - `hosts /etc/hosts miek.nl.`, - false, "/etc/hosts", []string{"miek.nl."}, false, - }, - { - `hosts /etc/hosts miek.nl. pun.gent.`, - false, "/etc/hosts", []string{"miek.nl.", "pun.gent."}, false, - }, - { - `hosts { - fallthrough - }`, - false, "/etc/hosts", nil, true, - }, - { - `hosts /tmp { - fallthrough - }`, - false, "/tmp", nil, true, - }, - { - `hosts /etc/hosts miek.nl. { - fallthrough - }`, - false, "/etc/hosts", []string{"miek.nl."}, true, - }, - { - `hosts /etc/hosts miek.nl 10.0.0.9/8 { - fallthrough - }`, - false, "/etc/hosts", []string{"miek.nl.", "10.in-addr.arpa."}, true, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.inputFileRules) - h, err := hostsParse(c) - - if err == nil && test.shouldErr { - t.Fatalf("Test %d expected errors, but got no error", i) - } else if err != nil && !test.shouldErr { - t.Fatalf("Test %d expected no errors, but got '%v'", i, err) - } else if !test.shouldErr { - if h.path != test.expectedPath { - t.Fatalf("Test %d expected %v, got %v", i, test.expectedPath, h.path) - } - } else { - if h.Fallthrough != test.expectedFallthrough { - t.Fatalf("Test %d expected fallthrough of %v, got %v", i, test.expectedFallthrough, h.Fallthrough) - } - if len(h.Origins) != len(test.expectedOrigins) { - t.Fatalf("Test %d expected %v, got %v", i, test.expectedOrigins, h.Origins) - } - for j, name := range test.expectedOrigins { - if h.Origins[j] != name { - t.Fatalf("Test %d expected %v for %d th zone, got %v", i, name, j, h.Origins[j]) - } - } - } - } -} diff --git a/middleware/kubernetes/DEV-README.md b/middleware/kubernetes/DEV-README.md deleted file mode 100644 index 4f652b578..000000000 --- a/middleware/kubernetes/DEV-README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Basic Setup for Development and Testing - -## Launch Kubernetes - -To run the tests, you'll need a private, live Kubernetes cluster. If you don't have one, -you can try out [minikube](https://github.com/kubernetes/minikube), which is -also available via Homebrew for OS X users. - -## Configure Test Data - -The test data is all in [this manifest](https://github.com/coredns/coredns/blob/master/.travis/kubernetes/dns-test.yaml) -and you can load it with `kubectl apply -f`. It will create a couple namespaces and some services. -For the tests to pass, you should not create anything else in the cluster. - -## Proxy the API Server - -Assuming your Kuberentes API server isn't running on http://localhost:8080, you will need to proxy from that -port to your cluster. You can do this with `kubectl proxy --port 8080`. - -## Run CoreDNS Kubernetes Tests - -Now you can run the tests locally, for example: - -~~~ -$ cd $GOPATH/src/github.com/coredns/coredns/test -$ go test -v -tags k8s -~~~ - -# Implementation Notes/Ideas - -* Additional features: - * Implement IP selection and ordering (internal/external). Related to - wildcards and SkyDNS use of CNAMES. - * Expose arbitrary kubernetes repository data as TXT records? -* DNS Correctness - * Do we need to generate synthetic zone records for namespaces? - * Do we need to generate synthetic zone records for the skydns synthetic zones? -* Test cases - * Test with CoreDNS caching. CoreDNS caching for DNS response is working - using the `cache` directive. Tested working using 20s cache timeout - and A-record queries. Automate testing with cache in place. - * Automate CoreDNS performance tests. Initially for zone files, and for - pre-loaded k8s API cache. With and without CoreDNS response caching. diff --git a/middleware/kubernetes/README.md b/middleware/kubernetes/README.md deleted file mode 100644 index 3e199c93a..000000000 --- a/middleware/kubernetes/README.md +++ /dev/null @@ -1,167 +0,0 @@ -# kubernetes - -The *kubernetes* middleware enables the reading zone data from a Kubernetes cluster. It implements -the [Kubernetes DNS-Based Service Discovery -Specification](https://github.com/kubernetes/dns/blob/master/docs/specification.md). - -CoreDNS running the kubernetes middleware can be used as a replacement of kube-dns in a kubernetes -cluster. See the [deployment](https://github.com/coredns/deployment) repository for details on [how -to deploy CoreDNS in Kubernetes](https://github.com/coredns/deployment/tree/master/kubernetes). - -[stubDomains](http://blog.kubernetes.io/2017/04/configuring-private-dns-zones-upstream-nameservers-kubernetes.html) -are implemented via the *proxy* middleware. - -## Syntax - -~~~ -kubernetes [ZONES...] -~~~ - -With only the directive specified, the *kubernetes* middleware will default to the zone specified in -the server's block. It will handle all queries in that zone and connect to Kubernetes in-cluster. It -will not provide PTR records for services, or A records for pods. If **ZONES** is used it specifies -all the zones the middleware should be authoritative for. - -``` -kubernetes [ZONES...] { - resyncperiod DURATION - endpoint URL - tls CERT KEY CACERT - namespaces NAMESPACE... - labels EXPRESSION - pods POD-MODE - upstream ADDRESS... - ttl TTL - fallthrough -} -``` -* `resyncperiod` specifies the Kubernetes data API **DURATION** period. -* `endpoint` specifies the **URL** for a remove k8s API endpoint. - If omitted, it will connect to k8s in-cluster using the cluster service account. - Multiple k8s API endpoints could be specified, separated by `,`s, e.g. - `endpoint http://k8s-endpoint1:8080,http://k8s-endpoint2:8080`. CoreDNS - will automatically perform a healthcheck and proxy to the healthy k8s API endpoint. -* `tls` **CERT** **KEY** **CACERT** are the TLS cert, key and the CA cert file names for remote k8s connection. - This option is ignored if connecting in-cluster (i.e. endpoint is not specified). -* `namespaces` **NAMESPACE [NAMESPACE...]**, exposed only the k8s namespaces listed. - If this option is omitted all namespaces are exposed -* `labels` **EXPRESSION** only exposes the records for Kubernetes objects that match this label selector. - The label selector syntax is described in the - [Kubernetes User Guide - Labels](http://kubernetes.io/docs/user-guide/labels/). An example that - only exposes objects labeled as "application=nginx" in the "staging" or "qa" environments, would - use: `labels environment in (staging, qa),application=nginx`. -* `pods` **POD-MODE** sets the mode for handling IP-based pod A records, e.g. - `1-2-3-4.ns.pod.cluster.local. in A 1.2.3.4`. - This option is provided to facilitate use of SSL certs when connecting directly to pods. Valid - values for **POD-MODE**: - - * `disabled`: Default. Do not process pod requests, always returning `NXDOMAIN` - * `insecure`: Always return an A record with IP from request (without checking k8s). This option - is is vulnerable to abuse if used maliciously in conjunction with wildcard SSL certs. This - option is provided for backward compatibility with kube-dns. - * `verified`: Return an A record if there exists a pod in same namespace with matching IP. This - option requires substantially more memory than in insecure mode, since it will maintain a watch - on all pods. - -* `upstream` **ADDRESS [ADDRESS...]** defines the upstream resolvers used for resolving services - that point to external hosts (External Services). **ADDRESS** can be an ip, an ip:port, or a path - to a file structured like resolv.conf. -* `ttl` allows you to set a custom TTL for responses. The default (and allowed minimum) is to use - 5 seconds, the maximum is capped at 3600 seconds. -* `fallthrough` If a query for a record in the cluster zone results in NXDOMAIN, normally that is - what the response will be. However, if you specify this option, the query will instead be passed - on down the middleware chain, which can include another middleware to handle the query. - -## Examples - -Handle all queries in the `cluster.local` zone. Connect to Kubernetes in-cluster. -Also handle all `PTR` requests for `10.0.0.0/16` . Verify the existence of pods when answering pod -requests. Resolve upstream records against `10.102.3.10`. Note we show the entire server block -here: - -~~~ txt -10.0.0.0/16 cluster.local { - kubernetes { - pods verified - upstream 10.102.3.10:53 - } -} -~~~ - -Or you can selectively expose some namespaces: - -~~~ txt -kubernetes cluster.local { - namespaces test staging -} -~~~ - -Connect to Kubernetes with CoreDNS running outside the cluster: - -~~~ txt -kubernetes cluster.local { - endpoint https://k8s-endpoint:8443 - tls cert key cacert -} -~~~ - -Here we use the *proxy* middleware to implement stubDomains that forwards `example.org` and -`example.com` to another nameserver. - -~~~ txt -cluster.local { - kubernetes { - endpoint https://k8s-endpoint:8443 - tls cert key cacert - } -} -example.org { - proxy . 8.8.8.8:53 -} -example.com { - proxy . 8.8.8.8:53 -} -~~~ - -## AutoPath - -The *kubernetes* middleware can be used in conjunction with the *autopath* middleware. Using this -feature enables server-side domain search path completion in kubernetes clusters. Note: `pods` must -be set to `verified` for this to function properly. - - cluster.local { - autopath @kubernetes - kubernetes { - pods verified - } - } - -## Federation - -The *kubernetes* middleware can be used in conjunction with the *federation* middleware. Using this -feature enables serving federated domains from the kubernetes clusters. - - cluster.local { - federation { - fallthrough - prod prod.example.org - staging staging.example.org - - } - kubernetes - } - - -## Wildcards - -Some query labels accept a wildcard value to match any value. If a label is a valid wildcard (\*, -or the word "any"), then that label will match all values. The labels that accept wildcards are: - - * _service_ in an `A` record request: _service_.namespace.svc.zone. - * e.g. `*.ns.svc.myzone.local` - * _namespace_ in an `A` record request: service._namespace_.svc.zone. - * e.g. `nginx.*.svc.myzone.local` - * _port and/or protocol_ in an `SRV` request: __port_.__protocol_.service.namespace.svc.zone. - * e.g. `_http.*.service.ns.svc.` - * multiple wild cards are allowed in a single query. - * e.g. `A` Request `*.*.svc.zone.` or `SRV` request `*.*.*.*.svc.zone.` diff --git a/middleware/kubernetes/apiproxy.go b/middleware/kubernetes/apiproxy.go deleted file mode 100644 index f6d2c00fa..000000000 --- a/middleware/kubernetes/apiproxy.go +++ /dev/null @@ -1,76 +0,0 @@ -package kubernetes - -import ( - "fmt" - "io" - "log" - "net" - "net/http" - - "github.com/coredns/coredns/middleware/pkg/healthcheck" -) - -type proxyHandler struct { - healthcheck.HealthCheck -} - -type apiProxy struct { - http.Server - listener net.Listener - handler proxyHandler -} - -func (p *proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - upstream := p.Select() - network := "tcp" - if upstream.Network != "" { - network = upstream.Network - } - address := upstream.Name - d, err := net.Dial(network, address) - if err != nil { - log.Printf("[ERROR] Unable to establish connection to upstream %s://%s: %s", network, address, err) - http.Error(w, fmt.Sprintf("Unable to establish connection to upstream %s://%s: %s", network, address, err), 500) - return - } - hj, ok := w.(http.Hijacker) - if !ok { - log.Printf("[ERROR] Unable to establish connection: no hijacker") - http.Error(w, "Unable to establish connection: no hijacker", 500) - return - } - nc, _, err := hj.Hijack() - if err != nil { - log.Printf("[ERROR] Unable to hijack connection: %s", err) - http.Error(w, fmt.Sprintf("Unable to hijack connection: %s", err), 500) - return - } - defer nc.Close() - defer d.Close() - - err = r.Write(d) - if err != nil { - log.Printf("[ERROR] Unable to copy connection to upstream %s://%s: %s", network, address, err) - http.Error(w, fmt.Sprintf("Unable to copy connection to upstream %s://%s: %s", network, address, err), 500) - return - } - - errChan := make(chan error, 2) - cp := func(dst io.Writer, src io.Reader) { - _, err := io.Copy(dst, src) - errChan <- err - } - go cp(d, nc) - go cp(nc, d) - <-errChan -} - -func (p *apiProxy) Run() { - p.handler.Start() - p.Serve(p.listener) -} - -func (p *apiProxy) Stop() { - p.handler.Stop() - p.listener.Close() -} diff --git a/middleware/kubernetes/autopath.go b/middleware/kubernetes/autopath.go deleted file mode 100644 index be71ce326..000000000 --- a/middleware/kubernetes/autopath.go +++ /dev/null @@ -1,53 +0,0 @@ -package kubernetes - -import ( - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "k8s.io/client-go/1.5/pkg/api" -) - -// AutoPath implements the AutoPathFunc call from the autopath middleware. -// It returns a per-query search path or nil indicating no searchpathing should happen. -func (k *Kubernetes) AutoPath(state request.Request) []string { - // Check if the query falls in a zone we are actually authoriative for and thus if we want autopath. - zone := middleware.Zones(k.Zones).Matches(state.Name()) - if zone == "" { - return nil - } - - ip := state.IP() - - pod := k.podWithIP(ip) - if pod == nil { - return nil - } - - search := make([]string, 3) - if zone == "." { - search[0] = pod.Namespace + ".svc." - search[1] = "svc." - search[2] = "." - } else { - search[0] = pod.Namespace + ".svc." + zone - search[1] = "svc." + zone - search[2] = zone - } - - search = append(search, k.autoPathSearch...) - search = append(search, "") // sentinal - return search -} - -// podWithIP return the api.Pod for source IP ip. It returns nil if nothing can be found. -func (k *Kubernetes) podWithIP(ip string) (p *api.Pod) { - objList := k.APIConn.PodIndex(ip) - for _, o := range objList { - p, ok := o.(*api.Pod) - if !ok { - return nil - } - return p - } - return nil -} diff --git a/middleware/kubernetes/controller.go b/middleware/kubernetes/controller.go deleted file mode 100644 index b809264e1..000000000 --- a/middleware/kubernetes/controller.go +++ /dev/null @@ -1,399 +0,0 @@ -package kubernetes - -import ( - "errors" - "fmt" - "log" - "sync" - "time" - - "k8s.io/client-go/1.5/kubernetes" - "k8s.io/client-go/1.5/pkg/api" - unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" - "k8s.io/client-go/1.5/pkg/api/v1" - "k8s.io/client-go/1.5/pkg/labels" - "k8s.io/client-go/1.5/pkg/runtime" - "k8s.io/client-go/1.5/pkg/watch" - "k8s.io/client-go/1.5/tools/cache" -) - -var ( - namespace = api.NamespaceAll -) - -// storeToNamespaceLister makes a Store that lists Namespaces. -type storeToNamespaceLister struct { - cache.Store -} - -const podIPIndex = "PodIP" - -// List lists all Namespaces in the store. -func (s *storeToNamespaceLister) List() (ns api.NamespaceList, err error) { - for _, m := range s.Store.List() { - ns.Items = append(ns.Items, *(m.(*api.Namespace))) - } - return ns, nil -} - -type dnsController interface { - ServiceList() []*api.Service - PodIndex(string) []interface{} - EndpointsList() api.EndpointsList - - GetNodeByName(string) (api.Node, error) - - Run() - Stop() error -} - -type dnsControl struct { - client *kubernetes.Clientset - - selector *labels.Selector - - svcController *cache.Controller - podController *cache.Controller - nsController *cache.Controller - epController *cache.Controller - - svcLister cache.StoreToServiceLister - podLister cache.StoreToPodLister - nsLister storeToNamespaceLister - epLister cache.StoreToEndpointsLister - - // stopLock is used to enforce only a single call to Stop is active. - // Needed because we allow stopping through an http endpoint and - // allowing concurrent stoppers leads to stack traces. - stopLock sync.Mutex - shutdown bool - stopCh chan struct{} -} - -type dnsControlOpts struct { - initPodCache bool - resyncPeriod time.Duration - // Label handling. - labelSelector *unversionedapi.LabelSelector - selector *labels.Selector -} - -// newDNSController creates a controller for CoreDNS. -func newdnsController(kubeClient *kubernetes.Clientset, opts dnsControlOpts) *dnsControl { - dns := dnsControl{ - client: kubeClient, - selector: opts.selector, - stopCh: make(chan struct{}), - } - - dns.svcLister.Indexer, dns.svcController = cache.NewIndexerInformer( - &cache.ListWatch{ - ListFunc: serviceListFunc(dns.client, namespace, dns.selector), - WatchFunc: serviceWatchFunc(dns.client, namespace, dns.selector), - }, - &api.Service{}, - opts.resyncPeriod, - cache.ResourceEventHandlerFuncs{}, - cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - - if opts.initPodCache { - dns.podLister.Indexer, dns.podController = cache.NewIndexerInformer( - &cache.ListWatch{ - ListFunc: podListFunc(dns.client, namespace, dns.selector), - WatchFunc: podWatchFunc(dns.client, namespace, dns.selector), - }, - &api.Pod{}, // TODO replace with a lighter-weight custom struct - opts.resyncPeriod, - cache.ResourceEventHandlerFuncs{}, - cache.Indexers{podIPIndex: podIPIndexFunc}) - } - - dns.nsLister.Store, dns.nsController = cache.NewInformer( - &cache.ListWatch{ - ListFunc: namespaceListFunc(dns.client, dns.selector), - WatchFunc: namespaceWatchFunc(dns.client, dns.selector), - }, - &api.Namespace{}, - opts.resyncPeriod, - cache.ResourceEventHandlerFuncs{}) - - dns.epLister.Store, dns.epController = cache.NewInformer( - &cache.ListWatch{ - ListFunc: endpointsListFunc(dns.client, namespace, dns.selector), - WatchFunc: endpointsWatchFunc(dns.client, namespace, dns.selector), - }, - &api.Endpoints{}, - opts.resyncPeriod, - cache.ResourceEventHandlerFuncs{}) - - return &dns -} - -func podIPIndexFunc(obj interface{}) ([]string, error) { - p, ok := obj.(*api.Pod) - if !ok { - return nil, errors.New("obj was not an *api.Pod") - } - return []string{p.Status.PodIP}, nil -} - -func serviceListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) { - return func(opts api.ListOptions) (runtime.Object, error) { - if s != nil { - opts.LabelSelector = *s - } - listV1, err := c.Core().Services(ns).List(opts) - - if err != nil { - return nil, err - } - var listAPI api.ServiceList - err = v1.Convert_v1_ServiceList_To_api_ServiceList(listV1, &listAPI, nil) - if err != nil { - return nil, err - } - return &listAPI, err - } -} - -func podListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) { - return func(opts api.ListOptions) (runtime.Object, error) { - if s != nil { - opts.LabelSelector = *s - } - listV1, err := c.Core().Pods(ns).List(opts) - - if err != nil { - return nil, err - } - var listAPI api.PodList - err = v1.Convert_v1_PodList_To_api_PodList(listV1, &listAPI, nil) - if err != nil { - return nil, err - } - - return &listAPI, err - } -} - -func v1ToAPIFilter(in watch.Event) (out watch.Event, keep bool) { - if in.Type == watch.Error { - return in, true - } - - switch v1Obj := in.Object.(type) { - case *v1.Service: - var apiObj api.Service - err := v1.Convert_v1_Service_To_api_Service(v1Obj, &apiObj, nil) - if err != nil { - log.Printf("[ERROR] Could not convert v1.Service: %s", err) - return in, true - } - return watch.Event{Type: in.Type, Object: &apiObj}, true - case *v1.Pod: - var apiObj api.Pod - err := v1.Convert_v1_Pod_To_api_Pod(v1Obj, &apiObj, nil) - if err != nil { - log.Printf("[ERROR] Could not convert v1.Pod: %s", err) - return in, true - } - return watch.Event{Type: in.Type, Object: &apiObj}, true - case *v1.Namespace: - var apiObj api.Namespace - err := v1.Convert_v1_Namespace_To_api_Namespace(v1Obj, &apiObj, nil) - if err != nil { - log.Printf("[ERROR] Could not convert v1.Namespace: %s", err) - return in, true - } - return watch.Event{Type: in.Type, Object: &apiObj}, true - case *v1.Endpoints: - var apiObj api.Endpoints - err := v1.Convert_v1_Endpoints_To_api_Endpoints(v1Obj, &apiObj, nil) - if err != nil { - log.Printf("[ERROR] Could not convert v1.Endpoint: %s", err) - return in, true - } - return watch.Event{Type: in.Type, Object: &apiObj}, true - } - - log.Printf("[WARN] Unhandled v1 type in event: %v", in) - return in, true -} - -func serviceWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) { - return func(options api.ListOptions) (watch.Interface, error) { - if s != nil { - options.LabelSelector = *s - } - w, err := c.Core().Services(ns).Watch(options) - if err != nil { - return nil, err - } - return watch.Filter(w, v1ToAPIFilter), nil - } -} - -func podWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) { - return func(options api.ListOptions) (watch.Interface, error) { - if s != nil { - options.LabelSelector = *s - } - w, err := c.Core().Pods(ns).Watch(options) - - if err != nil { - return nil, err - } - return watch.Filter(w, v1ToAPIFilter), nil - } -} - -func namespaceListFunc(c *kubernetes.Clientset, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) { - return func(opts api.ListOptions) (runtime.Object, error) { - if s != nil { - opts.LabelSelector = *s - } - listV1, err := c.Core().Namespaces().List(opts) - if err != nil { - return nil, err - } - var listAPI api.NamespaceList - err = v1.Convert_v1_NamespaceList_To_api_NamespaceList(listV1, &listAPI, nil) - if err != nil { - return nil, err - } - return &listAPI, err - } -} - -func namespaceWatchFunc(c *kubernetes.Clientset, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) { - return func(options api.ListOptions) (watch.Interface, error) { - if s != nil { - options.LabelSelector = *s - } - w, err := c.Core().Namespaces().Watch(options) - if err != nil { - return nil, err - } - return watch.Filter(w, v1ToAPIFilter), nil - } -} - -func endpointsListFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) { - return func(opts api.ListOptions) (runtime.Object, error) { - if s != nil { - opts.LabelSelector = *s - } - listV1, err := c.Core().Endpoints(ns).List(opts) - - if err != nil { - return nil, err - } - var listAPI api.EndpointsList - err = v1.Convert_v1_EndpointsList_To_api_EndpointsList(listV1, &listAPI, nil) - if err != nil { - return nil, err - } - return &listAPI, err - } -} - -func endpointsWatchFunc(c *kubernetes.Clientset, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) { - return func(options api.ListOptions) (watch.Interface, error) { - if s != nil { - options.LabelSelector = *s - } - w, err := c.Core().Endpoints(ns).Watch(options) - if err != nil { - return nil, err - } - return watch.Filter(w, v1ToAPIFilter), nil - } -} - -func (dns *dnsControl) controllersInSync() bool { - hs := dns.svcController.HasSynced() && - dns.nsController.HasSynced() && - dns.epController.HasSynced() - - if dns.podController != nil { - hs = hs && dns.podController.HasSynced() - } - - return hs -} - -// Stop stops the controller. -func (dns *dnsControl) Stop() error { - dns.stopLock.Lock() - defer dns.stopLock.Unlock() - - // Only try draining the workqueue if we haven't already. - if !dns.shutdown { - close(dns.stopCh) - dns.shutdown = true - - return nil - } - - return fmt.Errorf("shutdown already in progress") -} - -// Run starts the controller. -func (dns *dnsControl) Run() { - go dns.svcController.Run(dns.stopCh) - go dns.nsController.Run(dns.stopCh) - go dns.epController.Run(dns.stopCh) - if dns.podController != nil { - go dns.podController.Run(dns.stopCh) - } - <-dns.stopCh -} - -func (dns *dnsControl) NamespaceList() *api.NamespaceList { - nsList, err := dns.nsLister.List() - if err != nil { - return &api.NamespaceList{} - } - - return &nsList -} - -func (dns *dnsControl) ServiceList() []*api.Service { - svcs, err := dns.svcLister.List(labels.Everything()) - if err != nil { - return []*api.Service{} - } - - return svcs -} - -func (dns *dnsControl) PodIndex(ip string) []interface{} { - pods, err := dns.podLister.Indexer.ByIndex(podIPIndex, ip) - if err != nil { - return nil - } - - return pods -} - -func (dns *dnsControl) EndpointsList() api.EndpointsList { - epl, err := dns.epLister.List() - if err != nil { - return api.EndpointsList{} - } - - return epl -} - -func (dns *dnsControl) GetNodeByName(name string) (api.Node, error) { - v1node, err := dns.client.Core().Nodes().Get(name) - if err != nil { - return api.Node{}, err - } - var apinode api.Node - err = v1.Convert_v1_Node_To_api_Node(v1node, &apinode, nil) - if err != nil { - return api.Node{}, err - } - return apinode, nil -} diff --git a/middleware/kubernetes/federation.go b/middleware/kubernetes/federation.go deleted file mode 100644 index f192a4a76..000000000 --- a/middleware/kubernetes/federation.go +++ /dev/null @@ -1,45 +0,0 @@ -package kubernetes - -import ( - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" -) - -// The federation node.Labels keys used. -const ( - // TODO: Do not hardcode these labels. Pull them out of the API instead. - // - // We can get them via .... - // import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - // metav1.LabelZoneFailureDomain - // metav1.LabelZoneRegion - // - // But importing above breaks coredns with flag collision of 'log_dir' - - LabelZone = "failure-domain.beta.kubernetes.io/zone" - LabelRegion = "failure-domain.beta.kubernetes.io/region" -) - -// Federations is used from the federations middleware to return the service that should be -// returned as a CNAME for federation(s) to work. -func (k *Kubernetes) Federations(state request.Request, fname, fzone string) (msg.Service, error) { - nodeName := k.localNodeName() - node, err := k.APIConn.GetNodeByName(nodeName) - if err != nil { - return msg.Service{}, err - } - r, err := parseRequest(state) - if err != nil { - return msg.Service{}, err - } - - lz := node.Labels[LabelZone] - lr := node.Labels[LabelRegion] - - if r.endpoint == "" { - return msg.Service{Host: dnsutil.Join([]string{r.service, r.namespace, fname, r.podOrSvc, lz, lr, fzone})}, nil - } - - return msg.Service{Host: dnsutil.Join([]string{r.endpoint, r.service, r.namespace, fname, r.podOrSvc, lz, lr, fzone})}, nil -} diff --git a/middleware/kubernetes/handler.go b/middleware/kubernetes/handler.go deleted file mode 100644 index e83ab3d7d..000000000 --- a/middleware/kubernetes/handler.go +++ /dev/null @@ -1,86 +0,0 @@ -package kubernetes - -import ( - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// ServeDNS implements the middleware.Handler interface. -func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - - zone := middleware.Zones(k.Zones).Matches(state.Name()) - if zone == "" { - return middleware.NextOrFailure(k.Name(), k.Next, ctx, w, r) - } - - state.Zone = zone - - var ( - records []dns.RR - extra []dns.RR - err error - ) - - switch state.Type() { - case "A": - records, err = middleware.A(&k, zone, state, nil, middleware.Options{}) - case "AAAA": - records, err = middleware.AAAA(&k, zone, state, nil, middleware.Options{}) - case "TXT": - records, err = middleware.TXT(&k, zone, state, middleware.Options{}) - case "CNAME": - records, err = middleware.CNAME(&k, zone, state, middleware.Options{}) - case "PTR": - records, err = middleware.PTR(&k, zone, state, middleware.Options{}) - case "MX": - records, extra, err = middleware.MX(&k, zone, state, middleware.Options{}) - case "SRV": - records, extra, err = middleware.SRV(&k, zone, state, middleware.Options{}) - case "SOA": - records, err = middleware.SOA(&k, zone, state, middleware.Options{}) - case "NS": - if state.Name() == zone { - records, extra, err = middleware.NS(&k, zone, state, middleware.Options{}) - break - } - fallthrough - default: - // Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN - _, err = middleware.A(&k, zone, state, nil, middleware.Options{}) - } - - if k.IsNameError(err) { - if k.Fallthrough { - return middleware.NextOrFailure(k.Name(), k.Next, ctx, w, r) - } - return middleware.BackendError(&k, zone, dns.RcodeNameError, state, nil /* err */, middleware.Options{}) - } - if err != nil { - return dns.RcodeServerFailure, err - } - - if len(records) == 0 { - return middleware.BackendError(&k, zone, dns.RcodeSuccess, state, nil, middleware.Options{}) - } - - m.Answer = append(m.Answer, records...) - m.Extra = append(m.Extra, extra...) - - m = dnsutil.Dedup(m) - state.SizeAndDo(m) - m, _ = state.Scrub(m) - w.WriteMsg(m) - return dns.RcodeSuccess, nil -} - -// Name implements the Handler interface. -func (k Kubernetes) Name() string { return "kubernetes" } diff --git a/middleware/kubernetes/handler_pod_disabled_test.go b/middleware/kubernetes/handler_pod_disabled_test.go deleted file mode 100644 index b1fb7604c..000000000 --- a/middleware/kubernetes/handler_pod_disabled_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var podModeDisabledCases = []test.Case{ - { - Qname: "10-240-0-1.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Error: errPodsDisabled, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - { - Qname: "172-0-0-2.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Error: errPodsDisabled, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, -} - -func TestServeDNSModeDisabled(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - k.podMode = podModeDisabled - ctx := context.TODO() - - for i, tc := range podModeDisabledCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d expected no error, got %v", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error for %q", i, r.Question[0].Name) - } - - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/handler_pod_insecure_test.go b/middleware/kubernetes/handler_pod_insecure_test.go deleted file mode 100644 index bc09f3c4f..000000000 --- a/middleware/kubernetes/handler_pod_insecure_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var podModeInsecureCases = []test.Case{ - { - Qname: "10-240-0-1.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("10-240-0-1.podns.pod.cluster.local. 0 IN A 10.240.0.1"), - }, - }, - { - Qname: "172-0-0-2.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("172-0-0-2.podns.pod.cluster.local. 0 IN A 172.0.0.2"), - }, - }, -} - -func TestServeDNSModeInsecure(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - ctx := context.TODO() - k.podMode = podModeInsecure - - for i, tc := range podModeInsecureCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d expected no error, got %v", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error for %q", i, r.Question[0].Name) - } - - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/handler_pod_verified_test.go b/middleware/kubernetes/handler_pod_verified_test.go deleted file mode 100644 index 879e785af..000000000 --- a/middleware/kubernetes/handler_pod_verified_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var podModeVerifiedCases = []test.Case{ - { - Qname: "10-240-0-1.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("10-240-0-1.podns.pod.cluster.local. 0 IN A 10.240.0.1"), - }, - }, - { - Qname: "172-0-0-2.podns.pod.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, -} - -func TestServeDNSModeVerified(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - ctx := context.TODO() - k.podMode = podModeVerified - - for i, tc := range podModeVerifiedCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d expected no error, got %v", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error for %q", i, r.Question[0].Name) - } - - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/handler_test.go b/middleware/kubernetes/handler_test.go deleted file mode 100644 index 8ab51aff8..000000000 --- a/middleware/kubernetes/handler_test.go +++ /dev/null @@ -1,347 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" - "k8s.io/client-go/1.5/pkg/api" -) - -var dnsTestCases = []test.Case{ - // A Service - { - Qname: "svc1.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("svc1.testns.svc.cluster.local. 5 IN A 10.0.0.1"), - }, - }, - // A Service (wildcard) - { - Qname: "svc1.*.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("svc1.*.svc.cluster.local. 5 IN A 10.0.0.1"), - }, - }, - { - Qname: "svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{test.SRV("svc1.testns.svc.cluster.local. 303 IN SRV 0 100 80 svc1.testns.svc.cluster.local.")}, - Extra: []dns.RR{test.A("svc1.testns.svc.cluster.local. 303 IN A 10.0.0.1")}, - }, - // SRV Service (wildcard) - { - Qname: "svc1.*.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{test.SRV("svc1.*.svc.cluster.local. 303 IN SRV 0 100 80 svc1.testns.svc.cluster.local.")}, - Extra: []dns.RR{test.A("svc1.testns.svc.cluster.local. 303 IN A 10.0.0.1")}, - }, - // SRV Service (wildcards) - { - Qname: "*.any.svc1.*.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{test.SRV("*.any.svc1.*.svc.cluster.local. 303 IN SRV 0 100 80 svc1.testns.svc.cluster.local.")}, - Extra: []dns.RR{test.A("svc1.testns.svc.cluster.local. 303 IN A 10.0.0.1")}, - }, - // A Service (wildcards) - { - Qname: "*.any.svc1.*.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("*.any.svc1.*.svc.cluster.local. 303 IN A 10.0.0.1"), - }, - }, - // SRV Service Not udp/tcp - { - Qname: "*._not-udp-or-tcp.svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // SRV Service - { - Qname: "_http._tcp.svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.SRV("_http._tcp.svc1.testns.svc.cluster.local. 303 IN SRV 0 100 80 svc1.testns.svc.cluster.local."), - }, - Extra: []dns.RR{ - test.A("svc1.testns.svc.cluster.local. 303 IN A 10.0.0.1"), - }, - }, - // A Service (Headless) - { - Qname: "hdls1.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.A("hdls1.testns.svc.cluster.local. 303 IN A 172.0.0.2"), - test.A("hdls1.testns.svc.cluster.local. 303 IN A 172.0.0.3"), - }, - }, - // SRV Service (Headless) - { - Qname: "_http._tcp.hdls1.testns.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.SRV("_http._tcp.hdls1.testns.svc.cluster.local. 303 IN SRV 0 50 80 172-0-0-2.hdls1.testns.svc.cluster.local."), - test.SRV("_http._tcp.hdls1.testns.svc.cluster.local. 303 IN SRV 0 50 80 172-0-0-3.hdls1.testns.svc.cluster.local."), - }, - Extra: []dns.RR{ - test.A("172-0-0-2.hdls1.testns.svc.cluster.local. 303 IN A 172.0.0.2"), - test.A("172-0-0-3.hdls1.testns.svc.cluster.local. 303 IN A 172.0.0.3"), - }, - }, - // CNAME External - { - Qname: "external.testns.svc.cluster.local.", Qtype: dns.TypeCNAME, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.CNAME("external.testns.svc.cluster.local. 303 IN CNAME ext.interwebs.test."), - }, - }, - // AAAA Service (existing service) - { - Qname: "svc1.testns.svc.cluster.local.", Qtype: dns.TypeAAAA, - Rcode: dns.RcodeSuccess, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // AAAA Service (non-existing service) - { - Qname: "svc0.testns.svc.cluster.local.", Qtype: dns.TypeAAAA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // A Service (non-existing service) - { - Qname: "svc0.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // TXT Schema - { - Qname: "dns-version.cluster.local.", Qtype: dns.TypeTXT, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.TXT("dns-version.cluster.local 28800 IN TXT 1.0.1"), - }, - }, - // A Service (Headless) does not exist - { - Qname: "bogusendpoint.hdls1.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - // A Service does not exist - { - Qname: "bogusendpoint.svc0.testns.svc.cluster.local.", Qtype: dns.TypeA, - Rcode: dns.RcodeNameError, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, -} - -func TestServeDNS(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - ctx := context.TODO() - - for i, tc := range dnsTestCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d expected no error, got %v", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error for %q", i, r.Question[0].Name) - } - - // Before sorting, make sure that CNAMES do not appear after their target records - test.CNAMEOrder(t, resp) - - test.SortAndCheck(t, resp, tc) - } -} - -type APIConnServeTest struct{} - -func (APIConnServeTest) Run() { return } -func (APIConnServeTest) Stop() error { return nil } - -func (APIConnServeTest) PodIndex(string) []interface{} { - a := make([]interface{}, 1) - a[0] = &api.Pod{ - ObjectMeta: api.ObjectMeta{ - Namespace: "podns", - }, - Status: api.PodStatus{ - PodIP: "10.240.0.1", // Remote IP set in test.ResponseWriter - }, - } - return a -} - -func (APIConnServeTest) ServiceList() []*api.Service { - svcs := []*api.Service{ - { - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: api.ClusterIPNone, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "external", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ExternalName: "ext.interwebs.test", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - } - return svcs - -} - -func (APIConnServeTest) EndpointsList() api.EndpointsList { - n := "test.node.foo.bar" - - return api.EndpointsList{ - Items: []api.Endpoints{ - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.1", - Hostname: "ep1a", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.2", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.3", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "10.9.8.7", - NodeName: &n, - }, - }, - }, - }, - }, - }, - } -} - -func (APIConnServeTest) GetNodeByName(name string) (api.Node, error) { - return api.Node{ - ObjectMeta: api.ObjectMeta{ - Name: "test.node.foo.bar", - }, - }, nil -} diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go deleted file mode 100644 index fe58df74d..000000000 --- a/middleware/kubernetes/kubernetes.go +++ /dev/null @@ -1,457 +0,0 @@ -// Package kubernetes provides the kubernetes backend. -package kubernetes - -import ( - "errors" - "fmt" - "net" - "strings" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/coredns/middleware/proxy" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "k8s.io/client-go/1.5/kubernetes" - "k8s.io/client-go/1.5/pkg/api" - unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" - "k8s.io/client-go/1.5/pkg/labels" - "k8s.io/client-go/1.5/rest" - "k8s.io/client-go/1.5/tools/clientcmd" - clientcmdapi "k8s.io/client-go/1.5/tools/clientcmd/api" -) - -// Kubernetes implements a middleware that connects to a Kubernetes cluster. -type Kubernetes struct { - Next middleware.Handler - Zones []string - Proxy proxy.Proxy // Proxy for looking up names during the resolution process - APIServerList []string - APIProxy *apiProxy - APICertAuth string - APIClientCert string - APIClientKey string - APIConn dnsController - Namespaces map[string]bool - podMode string - Fallthrough bool - ttl uint32 - - primaryZoneIndex int - interfaceAddrsFunc func() net.IP - autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath. -} - -// New returns a intialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other -// values default to their zero value, primaryZoneIndex will thus point to the first zone. -func New(zones []string) *Kubernetes { - k := new(Kubernetes) - k.Zones = zones - k.Namespaces = make(map[string]bool) - k.interfaceAddrsFunc = func() net.IP { return net.ParseIP("127.0.0.1") } - k.podMode = podModeDisabled - k.Proxy = proxy.Proxy{} - k.ttl = defaultTTL - - return k -} - -const ( - // podModeDisabled is the default value where pod requests are ignored - podModeDisabled = "disabled" - // podModeVerified is where Pod requests are answered only if they exist - podModeVerified = "verified" - // podModeInsecure is where pod requests are answered without verfying they exist - podModeInsecure = "insecure" - // DNSSchemaVersion is the schema version: https://github.com/kubernetes/dns/blob/master/docs/specification.md - DNSSchemaVersion = "1.0.1" -) - -var ( - errNoItems = errors.New("no items found") - errNsNotExposed = errors.New("namespace is not exposed") - errInvalidRequest = errors.New("invalid query name") - errAPIBadPodType = errors.New("expected type *api.Pod") - errPodsDisabled = errors.New("pod records disabled") -) - -// Services implements the ServiceBackend interface. -func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) (svcs []msg.Service, err error) { - - // We're looking again at types, which we've already done in ServeDNS, but there are some types k8s just can't answer. - switch state.QType() { - - case dns.TypeTXT: - // 1 label + zone, label must be "dns-version". - t, _ := dnsutil.TrimZone(state.Name(), state.Zone) - - segs := dns.SplitDomainName(t) - if len(segs) != 1 { - return nil, fmt.Errorf("kubernetes: TXT query can only be for dns-version: %s", state.QName()) - } - if segs[0] != "dns-version" { - return nil, nil - } - svc := msg.Service{Text: DNSSchemaVersion, TTL: 28800, Key: msg.Path(state.QName(), "coredns")} - return []msg.Service{svc}, nil - - case dns.TypeNS: - // We can only get here if the qname equal the zone, see ServeDNS in handler.go. - ns := k.nsAddr() - svc := msg.Service{Host: ns.A.String(), Key: msg.Path(state.QName(), "coredns")} - return []msg.Service{svc}, nil - } - - if state.QType() == dns.TypeA && isDefaultNS(state.Name(), state.Zone) { - // If this is an A request for "ns.dns", respond with a "fake" record for coredns. - // SOA records always use this hardcoded name - ns := k.nsAddr() - svc := msg.Service{Host: ns.A.String(), Key: msg.Path(state.QName(), "coredns")} - return []msg.Service{svc}, nil - } - - s, e := k.Records(state, false) - - // SRV for external services is not yet implemented, so remove those records. - - if state.QType() != dns.TypeSRV { - return s, e - } - - internal := []msg.Service{} - for _, svc := range s { - if t, _ := svc.HostType(); t != dns.TypeCNAME { - internal = append(internal, svc) - } - } - - return internal, e -} - -// primaryZone will return the first non-reverse zone being handled by this middleware -func (k *Kubernetes) primaryZone() string { return k.Zones[k.primaryZoneIndex] } - -// Lookup implements the ServiceBackend interface. -func (k *Kubernetes) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) { - return k.Proxy.Lookup(state, name, typ) -} - -// IsNameError implements the ServiceBackend interface. -func (k *Kubernetes) IsNameError(err error) bool { - return err == errNoItems || err == errNsNotExposed || err == errInvalidRequest -} - -func (k *Kubernetes) getClientConfig() (*rest.Config, error) { - loadingRules := &clientcmd.ClientConfigLoadingRules{} - overrides := &clientcmd.ConfigOverrides{} - clusterinfo := clientcmdapi.Cluster{} - authinfo := clientcmdapi.AuthInfo{} - - if len(k.APIServerList) == 0 { - cc, err := rest.InClusterConfig() - if err != nil { - return nil, err - } - return cc, err - } - - endpoint := k.APIServerList[0] - if len(k.APIServerList) > 1 { - // Use a random port for api proxy, will get the value later through listener.Addr() - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return nil, fmt.Errorf("failed to create kubernetes api proxy: %v", err) - } - k.APIProxy = &apiProxy{ - listener: listener, - handler: proxyHandler{ - HealthCheck: healthcheck.HealthCheck{ - FailTimeout: 3 * time.Second, - MaxFails: 1, - Future: 10 * time.Second, - Path: "/", - Interval: 5 * time.Second, - }, - }, - } - k.APIProxy.handler.Hosts = make([]*healthcheck.UpstreamHost, len(k.APIServerList)) - for i, entry := range k.APIServerList { - - uh := &healthcheck.UpstreamHost{ - Name: strings.TrimPrefix(entry, "http://"), - - CheckDown: func(upstream *proxyHandler) healthcheck.UpstreamHostDownFunc { - return func(uh *healthcheck.UpstreamHost) bool { - - down := false - - uh.CheckMu.Lock() - until := uh.OkUntil - uh.CheckMu.Unlock() - - if !until.IsZero() && time.Now().After(until) { - down = true - } - - fails := atomic.LoadInt32(&uh.Fails) - if fails >= upstream.MaxFails && upstream.MaxFails != 0 { - down = true - } - return down - } - }(&k.APIProxy.handler), - } - - k.APIProxy.handler.Hosts[i] = uh - } - k.APIProxy.Handler = &k.APIProxy.handler - - // Find the random port used for api proxy - endpoint = fmt.Sprintf("http://%s", listener.Addr()) - } - clusterinfo.Server = endpoint - - if len(k.APICertAuth) > 0 { - clusterinfo.CertificateAuthority = k.APICertAuth - } - if len(k.APIClientCert) > 0 { - authinfo.ClientCertificate = k.APIClientCert - } - if len(k.APIClientKey) > 0 { - authinfo.ClientKey = k.APIClientKey - } - - overrides.ClusterInfo = clusterinfo - overrides.AuthInfo = authinfo - clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides) - - return clientConfig.ClientConfig() -} - -// initKubeCache initializes a new Kubernetes cache. -func (k *Kubernetes) initKubeCache(opts dnsControlOpts) (err error) { - - config, err := k.getClientConfig() - if err != nil { - return err - } - - kubeClient, err := kubernetes.NewForConfig(config) - if err != nil { - return fmt.Errorf("failed to create kubernetes notification controller: %q", err) - } - - if opts.labelSelector != nil { - var selector labels.Selector - selector, err = unversionedapi.LabelSelectorAsSelector(opts.labelSelector) - if err != nil { - return fmt.Errorf("unable to create Selector for LabelSelector '%s': %q", opts.labelSelector, err) - } - opts.selector = &selector - } - - opts.initPodCache = k.podMode == podModeVerified - - k.APIConn = newdnsController(kubeClient, opts) - - return err -} - -// Records looks up services in kubernetes. -func (k *Kubernetes) Records(state request.Request, exact bool) ([]msg.Service, error) { - r, e := parseRequest(state) - if e != nil { - return nil, e - } - - if !wildcard(r.namespace) && !k.namespaceExposed(r.namespace) { - return nil, errNsNotExposed - } - - if r.podOrSvc == Pod { - pods, err := k.findPods(r, state.Zone) - return pods, err - } - - services, err := k.findServices(r, state.Zone) - return services, err -} - -func endpointHostname(addr api.EndpointAddress) string { - if addr.Hostname != "" { - return strings.ToLower(addr.Hostname) - } - if strings.Contains(addr.IP, ".") { - return strings.Replace(addr.IP, ".", "-", -1) - } - if strings.Contains(addr.IP, ":") { - return strings.ToLower(strings.Replace(addr.IP, ":", "-", -1)) - } - return "" -} - -func (k *Kubernetes) findPods(r recordRequest, zone string) (pods []msg.Service, err error) { - if k.podMode == podModeDisabled { - return nil, errPodsDisabled - } - - namespace := r.namespace - podname := r.service - zonePath := msg.Path(zone, "coredns") - ip := "" - err = errNoItems - - if strings.Count(podname, "-") == 3 && !strings.Contains(podname, "--") { - ip = strings.Replace(podname, "-", ".", -1) - } else { - ip = strings.Replace(podname, "-", ":", -1) - } - - if k.podMode == podModeInsecure { - return []msg.Service{{Key: strings.Join([]string{zonePath, Pod, namespace, podname}, "/"), Host: ip}}, nil - } - - // PodModeVerified - objList := k.APIConn.PodIndex(ip) - - for _, o := range objList { - p, ok := o.(*api.Pod) - if !ok { - return nil, errAPIBadPodType - } - // If namespace has a wildcard, filter results against Corefile namespace list. - if wildcard(namespace) && !k.namespaceExposed(p.Namespace) { - continue - } - // check for matching ip and namespace - if ip == p.Status.PodIP && match(namespace, p.Namespace) { - s := msg.Service{Key: strings.Join([]string{zonePath, Pod, namespace, podname}, "/"), Host: ip} - pods = append(pods, s) - - err = nil - } - } - return pods, err -} - -// findServices returns the services matching r from the cache. -func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.Service, err error) { - serviceList := k.APIConn.ServiceList() - zonePath := msg.Path(zone, "coredns") - err = errNoItems // Set to errNoItems to signal really nothing found, gets reset when name is matched. - - for _, svc := range serviceList { - if !(match(r.namespace, svc.Namespace) && match(r.service, svc.Name)) { - continue - } - - // If namespace has a wildcard, filter results against Corefile namespace list. - // (Namespaces without a wildcard were filtered before the call to this function.) - if wildcard(r.namespace) && !k.namespaceExposed(svc.Namespace) { - continue - } - - // Endpoint query or headless service - if svc.Spec.ClusterIP == api.ClusterIPNone || r.endpoint != "" { - endpointsList := k.APIConn.EndpointsList() - for _, ep := range endpointsList.Items { - if ep.ObjectMeta.Name != svc.Name || ep.ObjectMeta.Namespace != svc.Namespace { - continue - } - - for _, eps := range ep.Subsets { - for _, addr := range eps.Addresses { - - // See comments in parse.go parseRequest about the endpoint handling. - - if r.endpoint != "" { - if !match(r.endpoint, endpointHostname(addr)) { - continue - } - } - - for _, p := range eps.Ports { - if !(match(r.port, p.Name) && match(r.protocol, string(p.Protocol))) { - continue - } - s := msg.Service{Host: addr.IP, Port: int(p.Port), TTL: k.ttl} - s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name, endpointHostname(addr)}, "/") - - err = nil - - services = append(services, s) - } - } - } - } - continue - } - - // External service - if svc.Spec.ExternalName != "" { - s := msg.Service{Key: strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/"), Host: svc.Spec.ExternalName, TTL: k.ttl} - if t, _ := s.HostType(); t == dns.TypeCNAME { - s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") - services = append(services, s) - - err = nil - - continue - } - } - - // ClusterIP service - for _, p := range svc.Spec.Ports { - if !(match(r.port, p.Name) && match(r.protocol, string(p.Protocol))) { - continue - } - - err = nil - - s := msg.Service{Host: svc.Spec.ClusterIP, Port: int(p.Port), TTL: k.ttl} - s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") - - services = append(services, s) - } - } - return services, err -} - -// match checks if a and b are equal taking wildcards into account. -func match(a, b string) bool { - if wildcard(a) { - return true - } - if wildcard(b) { - return true - } - return strings.EqualFold(a, b) -} - -// wildcard checks whether s contains a wildcard value defined as "*" or "any". -func wildcard(s string) bool { - return s == "*" || s == "any" -} - -// namespaceExposed returns true when the namespace is exposed. -func (k *Kubernetes) namespaceExposed(namespace string) bool { - _, ok := k.Namespaces[namespace] - if len(k.Namespaces) > 0 && !ok { - return false - } - return true -} - -const ( - // Svc is the DNS schema for kubernetes services - Svc = "svc" - // Pod is the DNS schema for kubernetes pods - Pod = "pod" - // defaultTTL to apply to all answers. - defaultTTL = 5 -) diff --git a/middleware/kubernetes/kubernetes_apex_test.go b/middleware/kubernetes/kubernetes_apex_test.go deleted file mode 100644 index 9a87a790a..000000000 --- a/middleware/kubernetes/kubernetes_apex_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -var kubeApexCases = [](test.Case){ - { - Qname: "cluster.local.", Qtype: dns.TypeSOA, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.SOA("cluster.local. 303 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - { - Qname: "cluster.local.", Qtype: dns.TypeHINFO, - Rcode: dns.RcodeSuccess, - Ns: []dns.RR{ - test.SOA("cluster.local. 303 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"), - }, - }, - { - Qname: "cluster.local.", Qtype: dns.TypeNS, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.NS("cluster.local. 303 IN NS ns.dns.cluster.local."), - }, - Extra: []dns.RR{ - test.A("ns.dns.cluster.local. 303 IN A 127.0.0.1"), - }, - }, -} - -func TestServeDNSApex(t *testing.T) { - - k := New([]string{"cluster.local."}) - k.APIConn = &APIConnServeTest{} - k.Next = test.NextHandler(dns.RcodeSuccess, nil) - ctx := context.TODO() - - for i, tc := range kubeApexCases { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d, expected no error, got %v\n", i, err) - return - } - if tc.Error != nil { - continue - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d, got nil message and no error ford", i) - } - - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/kubernetes_test.go b/middleware/kubernetes/kubernetes_test.go deleted file mode 100644 index 573a517b1..000000000 --- a/middleware/kubernetes/kubernetes_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "k8s.io/client-go/1.5/pkg/api" -) - -func TestWildcard(t *testing.T) { - var tests = []struct { - s string - expected bool - }{ - {"mynamespace", false}, - {"*", true}, - {"any", true}, - {"my*space", false}, - {"*space", false}, - {"myname*", false}, - } - - for _, te := range tests { - got := wildcard(te.s) - if got != te.expected { - t.Errorf("Expected Wildcard result '%v' for example '%v', got '%v'.", te.expected, te.s, got) - } - } -} - -func TestEndpointHostname(t *testing.T) { - var tests = []struct { - ip string - hostname string - expected string - }{ - {"10.11.12.13", "", "10-11-12-13"}, - {"10.11.12.13", "epname", "epname"}, - } - for _, test := range tests { - result := endpointHostname(api.EndpointAddress{IP: test.ip, Hostname: test.hostname}) - if result != test.expected { - t.Errorf("Expected endpoint name for (ip:%v hostname:%v) to be '%v', but got '%v'", test.ip, test.hostname, test.expected, result) - } - } -} - -type APIConnServiceTest struct{} - -func (APIConnServiceTest) Run() { return } -func (APIConnServiceTest) Stop() error { return nil } -func (APIConnServiceTest) PodIndex(string) []interface{} { return nil } - -func (APIConnServiceTest) ServiceList() []*api.Service { - svcs := []*api.Service{ - { - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: "10.0.0.1", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: api.ClusterIPNone, - }, - }, - { - ObjectMeta: api.ObjectMeta{ - Name: "external", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ExternalName: "coredns.io", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - } - return svcs -} - -func (APIConnServiceTest) EndpointsList() api.EndpointsList { - n := "test.node.foo.bar" - - return api.EndpointsList{ - Items: []api.Endpoints{ - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.1", - Hostname: "ep1a", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.2", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "172.0.0.3", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "hdls1", - Namespace: "testns", - }, - }, - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "10.9.8.7", - NodeName: &n, - }, - }, - }, - }, - }, - }, - } -} - -func (APIConnServiceTest) GetNodeByName(name string) (api.Node, error) { - return api.Node{ - ObjectMeta: api.ObjectMeta{ - Name: "test.node.foo.bar", - }, - }, nil -} - -func TestServices(t *testing.T) { - - k := New([]string{"interwebs.test."}) - k.APIConn = &APIConnServiceTest{} - - type svcAns struct { - host string - key string - } - type svcTest struct { - qname string - qtype uint16 - answer svcAns - } - tests := []svcTest{ - // Cluster IP Services - {qname: "svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "10.0.0.1", key: "/coredns/test/interwebs/svc/testns/svc1"}}, - {qname: "_http._tcp.svc1.testns.svc.interwebs.test.", qtype: dns.TypeSRV, answer: svcAns{host: "10.0.0.1", key: "/coredns/test/interwebs/svc/testns/svc1"}}, - {qname: "ep1a.svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "172.0.0.1", key: "/coredns/test/interwebs/svc/testns/svc1/ep1a"}}, - - // External Services - {qname: "external.testns.svc.interwebs.test.", qtype: dns.TypeCNAME, answer: svcAns{host: "coredns.io", key: "/coredns/test/interwebs/svc/testns/external"}}, - } - - for i, test := range tests { - state := request.Request{ - Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}}, - Zone: "interwebs.test.", // must match from k.Zones[0] - } - svcs, e := k.Services(state, false, middleware.Options{}) - if e != nil { - t.Errorf("Test %d: got error '%v'", i, e) - continue - } - if len(svcs) != 1 { - t.Errorf("Test %d, expected expected 1 answer, got %v", i, len(svcs)) - continue - } - - if test.answer.host != svcs[0].Host { - t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer.host, svcs[0].Host) - } - if test.answer.key != svcs[0].Key { - t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer.key, svcs[0].Key) - } - } -} diff --git a/middleware/kubernetes/local.go b/middleware/kubernetes/local.go deleted file mode 100644 index e5b7f1e0f..000000000 --- a/middleware/kubernetes/local.go +++ /dev/null @@ -1,40 +0,0 @@ -package kubernetes - -import "net" - -func localPodIP() net.IP { - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil - } - - for _, addr := range addrs { - ip, _, _ := net.ParseCIDR(addr.String()) - ip = ip.To4() - if ip == nil || ip.IsLoopback() { - continue - } - return ip - } - return nil -} - -func (k *Kubernetes) localNodeName() string { - localIP := k.interfaceAddrsFunc() - if localIP == nil { - return "" - } - - // Find endpoint matching localIP - endpointsList := k.APIConn.EndpointsList() - for _, ep := range endpointsList.Items { - for _, eps := range ep.Subsets { - for _, addr := range eps.Addresses { - if localIP.Equal(net.ParseIP(addr.IP)) { - return *addr.NodeName - } - } - } - } - return "" -} diff --git a/middleware/kubernetes/ns.go b/middleware/kubernetes/ns.go deleted file mode 100644 index 4cacc382f..000000000 --- a/middleware/kubernetes/ns.go +++ /dev/null @@ -1,65 +0,0 @@ -package kubernetes - -import ( - "net" - "strings" - - "github.com/miekg/dns" - "k8s.io/client-go/1.5/pkg/api" -) - -func isDefaultNS(name, zone string) bool { - return strings.Index(name, defaultNSName) == 0 && strings.Index(name, zone) == len(defaultNSName) -} - -func (k *Kubernetes) nsAddr() *dns.A { - var ( - svcName string - svcNamespace string - ) - - rr := new(dns.A) - localIP := k.interfaceAddrsFunc() - endpointsList := k.APIConn.EndpointsList() - - rr.A = localIP - -FindEndpoint: - for _, ep := range endpointsList.Items { - for _, eps := range ep.Subsets { - for _, addr := range eps.Addresses { - if localIP.Equal(net.ParseIP(addr.IP)) { - svcNamespace = ep.ObjectMeta.Namespace - svcName = ep.ObjectMeta.Name - break FindEndpoint - } - } - } - } - - if len(svcName) == 0 { - rr.Hdr.Name = defaultNSName - rr.A = localIP - return rr - } - // Find service to get ClusterIP - serviceList := k.APIConn.ServiceList() - -FindService: - for _, svc := range serviceList { - if svcName == svc.Name && svcNamespace == svc.Namespace { - if svc.Spec.ClusterIP == api.ClusterIPNone { - rr.A = localIP - } else { - rr.A = net.ParseIP(svc.Spec.ClusterIP) - } - break FindService - } - } - - rr.Hdr.Name = strings.Join([]string{svcName, svcNamespace, "svc."}, ".") - - return rr -} - -const defaultNSName = "ns.dns." diff --git a/middleware/kubernetes/ns_test.go b/middleware/kubernetes/ns_test.go deleted file mode 100644 index 8e9e80c71..000000000 --- a/middleware/kubernetes/ns_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package kubernetes - -import ( - "testing" - - "k8s.io/client-go/1.5/pkg/api" -) - -type APIConnTest struct{} - -func (APIConnTest) Run() { return } -func (APIConnTest) Stop() error { return nil } -func (APIConnTest) PodIndex(string) []interface{} { return nil } - -func (APIConnTest) ServiceList() []*api.Service { - svc := api.Service{ - ObjectMeta: api.ObjectMeta{ - Name: "dns-service", - Namespace: "kube-system", - }, - Spec: api.ServiceSpec{ - ClusterIP: "10.0.0.111", - }, - } - - return []*api.Service{&svc} - -} - -func (APIConnTest) EndpointsList() api.EndpointsList { - return api.EndpointsList{ - Items: []api.Endpoints{ - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "127.0.0.1", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "dns-service", - Namespace: "kube-system", - }, - }, - }, - } -} - -func (APIConnTest) GetNodeByName(name string) (api.Node, error) { return api.Node{}, nil } - -func TestNsAddr(t *testing.T) { - - k := New([]string{"inter.webs.test."}) - k.APIConn = &APIConnTest{} - - cdr := k.nsAddr() - expected := "10.0.0.111" - - if cdr.A.String() != expected { - t.Errorf("Expected A to be %q, got %q", expected, cdr.A.String()) - } - expected = "dns-service.kube-system.svc." - if cdr.Hdr.Name != expected { - t.Errorf("Expected Hdr.Name to be %q, got %q", expected, cdr.Hdr.Name) - } -} diff --git a/middleware/kubernetes/parse.go b/middleware/kubernetes/parse.go deleted file mode 100644 index bf8adfec3..000000000 --- a/middleware/kubernetes/parse.go +++ /dev/null @@ -1,112 +0,0 @@ -package kubernetes - -import ( - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -type recordRequest struct { - // The named port from the kubernetes DNS spec, this is the service part (think _https) from a well formed - // SRV record. - port string - // The protocol is usually _udp or _tcp (if set), and comes from the protocol part of a well formed - // SRV record. - protocol string - endpoint string - // The servicename used in Kubernetes. - service string - // The namespace used in Kubernetes. - namespace string - // A each name can be for a pod or a service, here we track what we've seen, either "pod" or "service". - podOrSvc string -} - -// parseRequest parses the qname to find all the elements we need for querying k8s. Anything -// that is not parsed will have the wildcard "*" value (except r.endpoint). -// Potential underscores are stripped from _port and _protocol. -func parseRequest(state request.Request) (r recordRequest, err error) { - // 3 Possible cases: - // 1. _port._protocol.service.namespace.pod|svc.zone - // 2. (endpoint): endpoint.service.namespace.pod|svc.zone - // 3. (service): service.namespace.pod|svc.zone - // - // Federations are handled in the federation middleware. And aren't parsed here. - - base, _ := dnsutil.TrimZone(state.Name(), state.Zone) - segs := dns.SplitDomainName(base) - - r.port = "*" - r.protocol = "*" - r.service = "*" - r.namespace = "*" - // r.endpoint is the odd one out, we need to know if it has been set or not. If it is - // empty we should skip the endpoint check in k.get(). Hence we cannot set if to "*". - - // start at the right and fill out recordRequest with the bits we find, so we look for - // pod|svc.namespace.service and then either - // * endpoint - // *_protocol._port - - last := len(segs) - 1 - if last < 0 { - return r, nil - } - r.podOrSvc = segs[last] - if r.podOrSvc != Pod && r.podOrSvc != Svc { - return r, errInvalidRequest - } - last-- - if last < 0 { - return r, nil - } - - r.namespace = segs[last] - last-- - if last < 0 { - return r, nil - } - - r.service = segs[last] - last-- - if last < 0 { - return r, nil - } - - // Because of ambiquity we check the labels left: 1: an endpoint. 2: port and protocol. - // Anything else is a query that is too long to answer and can safely be delegated to return an nxdomain. - switch last { - - case 0: // endpoint only - r.endpoint = segs[last] - case 1: // service and port - r.protocol = stripUnderscore(segs[last]) - r.port = stripUnderscore(segs[last-1]) - - default: // too long - return r, errInvalidRequest - } - - return r, nil -} - -// stripUnderscore removes a prefixed underscore from s. -func stripUnderscore(s string) string { - if s[0] != '_' { - return s - } - return s[1:] -} - -// String return a string representation of r, it just returns all fields concatenated with dots. -// This is mostly used in tests. -func (r recordRequest) String() string { - s := r.port - s += "." + r.protocol - s += "." + r.endpoint - s += "." + r.service - s += "." + r.namespace - s += "." + r.podOrSvc - return s -} diff --git a/middleware/kubernetes/parse_test.go b/middleware/kubernetes/parse_test.go deleted file mode 100644 index 06d5a2aaa..000000000 --- a/middleware/kubernetes/parse_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -func TestParseRequest(t *testing.T) { - tests := []struct { - query string - expected string // output from r.String() - }{ - // valid SRV request - {"_http._tcp.webs.mynamespace.svc.inter.webs.test.", "http.tcp..webs.mynamespace.svc"}, - // wildcard acceptance - {"*.any.*.any.svc.inter.webs.test.", "*.any..*.any.svc"}, - // A request of endpoint - {"1-2-3-4.webs.mynamespace.svc.inter.webs.test.", "*.*.1-2-3-4.webs.mynamespace.svc"}, - } - for i, tc := range tests { - m := new(dns.Msg) - m.SetQuestion(tc.query, dns.TypeA) - state := request.Request{Zone: zone, Req: m} - - r, e := parseRequest(state) - if e != nil { - t.Errorf("Test %d, expected no error, got '%v'.", i, e) - } - rs := r.String() - if rs != tc.expected { - t.Errorf("Test %d, expected (stringyfied) recordRequest: %s, got %s", i, tc.expected, rs) - } - } -} - -func TestParseInvalidRequest(t *testing.T) { - invalid := []string{ - "webs.mynamespace.pood.inter.webs.test.", // Request must be for pod or svc subdomain. - "too.long.for.what.I.am.trying.to.pod.inter.webs.tests.", // Too long. - } - - for i, query := range invalid { - m := new(dns.Msg) - m.SetQuestion(query, dns.TypeA) - state := request.Request{Zone: zone, Req: m} - - if _, e := parseRequest(state); e == nil { - t.Errorf("Test %d: expected error from %s, got none", i, query) - } - } -} - -const zone = "intern.webs.tests." diff --git a/middleware/kubernetes/reverse.go b/middleware/kubernetes/reverse.go deleted file mode 100644 index 25ece8035..000000000 --- a/middleware/kubernetes/reverse.go +++ /dev/null @@ -1,55 +0,0 @@ -package kubernetes - -import ( - "strings" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/etcd/msg" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" -) - -// Reverse implements the ServiceBackend interface. -func (k *Kubernetes) Reverse(state request.Request, exact bool, opt middleware.Options) ([]msg.Service, error) { - - ip := dnsutil.ExtractAddressFromReverse(state.Name()) - if ip == "" { - return nil, nil - } - - records := k.serviceRecordForIP(ip, state.Name()) - return records, nil -} - -// serviceRecordForIP gets a service record with a cluster ip matching the ip argument -// If a service cluster ip does not match, it checks all endpoints -func (k *Kubernetes) serviceRecordForIP(ip, name string) []msg.Service { - // First check services with cluster ips - svcList := k.APIConn.ServiceList() - - for _, service := range svcList { - if (len(k.Namespaces) > 0) && !k.namespaceExposed(service.Namespace) { - continue - } - if service.Spec.ClusterIP == ip { - domain := strings.Join([]string{service.Name, service.Namespace, Svc, k.primaryZone()}, ".") - return []msg.Service{{Host: domain}} - } - } - // If no cluster ips match, search endpoints - epList := k.APIConn.EndpointsList() - for _, ep := range epList.Items { - if (len(k.Namespaces) > 0) && !k.namespaceExposed(ep.ObjectMeta.Namespace) { - continue - } - for _, eps := range ep.Subsets { - for _, addr := range eps.Addresses { - if addr.IP == ip { - domain := strings.Join([]string{endpointHostname(addr), ep.ObjectMeta.Name, ep.ObjectMeta.Namespace, Svc, k.primaryZone()}, ".") - return []msg.Service{{Host: domain}} - } - } - } - } - return nil -} diff --git a/middleware/kubernetes/reverse_test.go b/middleware/kubernetes/reverse_test.go deleted file mode 100644 index aaf0907e8..000000000 --- a/middleware/kubernetes/reverse_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" - "k8s.io/client-go/1.5/pkg/api" -) - -type APIConnReverseTest struct{} - -func (APIConnReverseTest) Run() { return } -func (APIConnReverseTest) Stop() error { return nil } -func (APIConnReverseTest) PodIndex(string) []interface{} { return nil } - -func (APIConnReverseTest) ServiceList() []*api.Service { - svcs := []*api.Service{ - { - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - Spec: api.ServiceSpec{ - ClusterIP: "192.168.1.100", - Ports: []api.ServicePort{{ - Name: "http", - Protocol: "tcp", - Port: 80, - }}, - }, - }, - } - return svcs -} - -func (APIConnReverseTest) EndpointsList() api.EndpointsList { - return api.EndpointsList{ - Items: []api.Endpoints{ - { - Subsets: []api.EndpointSubset{ - { - Addresses: []api.EndpointAddress{ - { - IP: "10.0.0.100", - Hostname: "ep1a", - }, - }, - Ports: []api.EndpointPort{ - { - Port: 80, - Protocol: "tcp", - Name: "http", - }, - }, - }, - }, - ObjectMeta: api.ObjectMeta{ - Name: "svc1", - Namespace: "testns", - }, - }, - }, - } -} - -func (APIConnReverseTest) GetNodeByName(name string) (api.Node, error) { - return api.Node{ - ObjectMeta: api.ObjectMeta{ - Name: "test.node.foo.bar", - }, - }, nil -} - -func TestReverse(t *testing.T) { - - k := New([]string{"cluster.local.", "0.10.in-addr.arpa."}) - k.APIConn = &APIConnReverseTest{} - - tests := []test.Case{ - { - Qname: "100.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR, - Rcode: dns.RcodeSuccess, - Answer: []dns.RR{ - test.PTR("100.0.0.10.in-addr.arpa. 303 IN PTR ep1a.svc1.testns.svc.cluster.local."), - }, - }, - { - Qname: "101.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR, - Rcode: dns.RcodeSuccess, - Ns: []dns.RR{ - test.SOA("0.10.in-addr.arpa. 300 IN SOA ns.dns.0.10.in-addr.arpa. hostmaster.0.10.in-addr.arpa. 1502782828 7200 1800 86400 60"), - }, - }, - { - Qname: "example.org.cluster.local.", Qtype: dns.TypePTR, - Rcode: dns.RcodeSuccess, - Ns: []dns.RR{ - test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1502989566 7200 1800 86400 60"), - }, - }, - } - - ctx := context.TODO() - for i, tc := range tests { - r := tc.Msg() - - w := dnsrecorder.New(&test.ResponseWriter{}) - - _, err := k.ServeDNS(ctx, w, r) - if err != tc.Error { - t.Errorf("Test %d: expected no error, got %v", i, err) - return - } - - resp := w.Msg - if resp == nil { - t.Fatalf("Test %d: got nil message and no error for: %s %d", i, r.Question[0].Name, r.Question[0].Qtype) - } - test.SortAndCheck(t, resp, tc) - } -} diff --git a/middleware/kubernetes/setup.go b/middleware/kubernetes/setup.go deleted file mode 100644 index 15b87c2cd..000000000 --- a/middleware/kubernetes/setup.go +++ /dev/null @@ -1,208 +0,0 @@ -package kubernetes - -import ( - "errors" - "fmt" - "strconv" - "strings" - "time" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/proxy" - "github.com/miekg/dns" - - "github.com/mholt/caddy" - unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" -) - -func init() { - caddy.RegisterPlugin("kubernetes", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - kubernetes, initOpts, err := kubernetesParse(c) - if err != nil { - return middleware.Error("kubernetes", err) - } - - err = kubernetes.initKubeCache(initOpts) - if err != nil { - return middleware.Error("kubernetes", err) - } - - // Register KubeCache start and stop functions with Caddy - c.OnStartup(func() error { - go kubernetes.APIConn.Run() - if kubernetes.APIProxy != nil { - go kubernetes.APIProxy.Run() - } - return nil - }) - - c.OnShutdown(func() error { - if kubernetes.APIProxy != nil { - kubernetes.APIProxy.Stop() - } - return kubernetes.APIConn.Stop() - }) - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - kubernetes.Next = next - return kubernetes - }) - - return nil -} - -func kubernetesParse(c *caddy.Controller) (*Kubernetes, dnsControlOpts, error) { - k8s := New([]string{""}) - k8s.interfaceAddrsFunc = localPodIP - k8s.autoPathSearch = searchFromResolvConf() - - opts := dnsControlOpts{ - resyncPeriod: defaultResyncPeriod, - } - - for c.Next() { - zones := c.RemainingArgs() - - if len(zones) != 0 { - k8s.Zones = zones - for i := 0; i < len(k8s.Zones); i++ { - k8s.Zones[i] = middleware.Host(k8s.Zones[i]).Normalize() - } - } else { - k8s.Zones = make([]string, len(c.ServerBlockKeys)) - for i := 0; i < len(c.ServerBlockKeys); i++ { - k8s.Zones[i] = middleware.Host(c.ServerBlockKeys[i]).Normalize() - } - } - - k8s.primaryZoneIndex = -1 - for i, z := range k8s.Zones { - if strings.HasSuffix(z, "in-addr.arpa.") || strings.HasSuffix(z, "ip6.arpa.") { - continue - } - k8s.primaryZoneIndex = i - break - } - - if k8s.primaryZoneIndex == -1 { - return nil, opts, errors.New("non-reverse zone name must be used") - } - - for c.NextBlock() { - switch c.Val() { - case "pods": - args := c.RemainingArgs() - if len(args) == 1 { - switch args[0] { - case podModeDisabled, podModeInsecure, podModeVerified: - k8s.podMode = args[0] - default: - return nil, opts, fmt.Errorf("wrong value for pods: %s, must be one of: disabled, verified, insecure", args[0]) - } - continue - } - return nil, opts, c.ArgErr() - case "namespaces": - args := c.RemainingArgs() - if len(args) > 0 { - for _, a := range args { - k8s.Namespaces[a] = true - } - continue - } - return nil, opts, c.ArgErr() - case "endpoint": - args := c.RemainingArgs() - if len(args) > 0 { - for _, endpoint := range strings.Split(args[0], ",") { - k8s.APIServerList = append(k8s.APIServerList, strings.TrimSpace(endpoint)) - } - continue - } - return nil, opts, c.ArgErr() - case "tls": // cert key cacertfile - args := c.RemainingArgs() - if len(args) == 3 { - k8s.APIClientCert, k8s.APIClientKey, k8s.APICertAuth = args[0], args[1], args[2] - continue - } - return nil, opts, c.ArgErr() - case "resyncperiod": - args := c.RemainingArgs() - if len(args) > 0 { - rp, err := time.ParseDuration(args[0]) - if err != nil { - return nil, opts, fmt.Errorf("unable to parse resync duration value: '%v': %v", args[0], err) - } - opts.resyncPeriod = rp - continue - } - return nil, opts, c.ArgErr() - case "labels": - args := c.RemainingArgs() - if len(args) > 0 { - labelSelectorString := strings.Join(args, " ") - ls, err := unversionedapi.ParseToLabelSelector(labelSelectorString) - if err != nil { - return nil, opts, fmt.Errorf("unable to parse label selector value: '%v': %v", labelSelectorString, err) - } - opts.labelSelector = ls - continue - } - return nil, opts, c.ArgErr() - case "fallthrough": - args := c.RemainingArgs() - if len(args) == 0 { - k8s.Fallthrough = true - continue - } - return nil, opts, c.ArgErr() - case "upstream": - args := c.RemainingArgs() - if len(args) == 0 { - return nil, opts, c.ArgErr() - } - ups, err := dnsutil.ParseHostPortOrFile(args...) - if err != nil { - return nil, opts, err - } - k8s.Proxy = proxy.NewLookup(ups) - case "ttl": - args := c.RemainingArgs() - if len(args) == 0 { - return nil, opts, c.ArgErr() - } - t, err := strconv.Atoi(args[0]) - if err != nil { - return nil, opts, err - } - if t < 5 || t > 3600 { - return nil, opts, c.Errf("ttl must be in range [5, 3600]: %d", t) - } - k8s.ttl = uint32(t) - default: - return nil, opts, c.Errf("unknown property '%s'", c.Val()) - } - } - } - return k8s, opts, nil -} - -func searchFromResolvConf() []string { - rc, err := dns.ClientConfigFromFile("/etc/resolv.conf") - if err != nil { - return nil - } - middleware.Zones(rc.Search).Normalize() - return rc.Search -} - -const defaultResyncPeriod = 5 * time.Minute diff --git a/middleware/kubernetes/setup_reverse_test.go b/middleware/kubernetes/setup_reverse_test.go deleted file mode 100644 index ed51a7410..000000000 --- a/middleware/kubernetes/setup_reverse_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestKubernetesParseReverseZone(t *testing.T) { - tests := []struct { - input string // Corefile data as string - expectedZones []string // expected count of defined zones. - }{ - {`kubernetes coredns.local 10.0.0.0/16`, []string{"coredns.local.", "0.10.in-addr.arpa."}}, - {`kubernetes coredns.local 10.0.0.0/17`, []string{"coredns.local.", "10.0.0.0/17."}}, - } - - for i, tc := range tests { - c := caddy.NewTestController("dns", tc.input) - k, _, err := kubernetesParse(c) - if err != nil { - t.Fatalf("Test %d: Expected no error, got %q", i, err) - } - - zl := len(k.Zones) - if zl != len(tc.expectedZones) { - t.Errorf("Test %d: Expected kubernetes to be initialized with %d zones, found %d zones", i, len(tc.expectedZones), zl) - } - for i, z := range tc.expectedZones { - if k.Zones[i] != z { - t.Errorf("Test %d: Expected zones to be %q, got %q", i, z, k.Zones[i]) - } - } - } -} diff --git a/middleware/kubernetes/setup_test.go b/middleware/kubernetes/setup_test.go deleted file mode 100644 index 2fdc38a9c..000000000 --- a/middleware/kubernetes/setup_test.go +++ /dev/null @@ -1,473 +0,0 @@ -package kubernetes - -import ( - "strings" - "testing" - "time" - - "github.com/mholt/caddy" - "k8s.io/client-go/1.5/pkg/api/unversioned" -) - -func TestKubernetesParse(t *testing.T) { - tests := []struct { - input string // Corefile data as string - shouldErr bool // true if test case is exected to produce an error. - expectedErrContent string // substring from the expected error. Empty for positive cases. - expectedZoneCount int // expected count of defined zones. - expectedNSCount int // expected count of namespaces. - expectedResyncPeriod time.Duration // expected resync period value - expectedLabelSelector string // expected label selector value - expectedPodMode string - expectedFallthrough bool - expectedUpstreams []string - }{ - // positive - { - `kubernetes coredns.local`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local test.local`, - false, - "", - 2, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - endpoint http://localhost:9090 -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - namespaces demo -}`, - false, - "", - 1, - 1, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - namespaces demo test -}`, - false, - "", - 1, - 2, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod 30s -}`, - false, - "", - 1, - 0, - 30 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod 15m -}`, - false, - "", - 1, - 0, - 15 * time.Minute, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - labels environment=prod -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "environment=prod", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - labels environment in (production, staging, qa),application=nginx -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "application=nginx,environment in (production,qa,staging)", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local test.local { - resyncperiod 15m - endpoint http://localhost:8080 - namespaces demo test - labels environment in (production, staging, qa),application=nginx - fallthrough -}`, - false, - "", - 2, - 2, - 15 * time.Minute, - "application=nginx,environment in (production,qa,staging)", - podModeDisabled, - true, - nil, - }, - // negative - { - `kubernetes coredns.local { - endpoint -}`, - true, - "rong argument count or unexpected line ending", - -1, - -1, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - namespaces -}`, - true, - "rong argument count or unexpected line ending", - -1, - -1, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod -}`, - true, - "rong argument count or unexpected line ending", - -1, - 0, - 0 * time.Minute, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod 15 -}`, - true, - "unable to parse resync duration value", - -1, - 0, - 0 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - resyncperiod abc -}`, - true, - "unable to parse resync duration value", - -1, - 0, - 0 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - labels -}`, - true, - "rong argument count or unexpected line ending", - -1, - 0, - 0 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - { - `kubernetes coredns.local { - labels environment in (production, qa -}`, - true, - "unable to parse label selector", - -1, - 0, - 0 * time.Second, - "", - podModeDisabled, - false, - nil, - }, - // pods disabled - { - `kubernetes coredns.local { - pods disabled -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - // pods insecure - { - `kubernetes coredns.local { - pods insecure -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeInsecure, - false, - nil, - }, - // pods verified - { - `kubernetes coredns.local { - pods verified -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeVerified, - false, - nil, - }, - // pods invalid - { - `kubernetes coredns.local { - pods giant_seed -}`, - true, - "rong value for pods", - -1, - 0, - defaultResyncPeriod, - "", - podModeVerified, - false, - nil, - }, - // fallthrough invalid - { - `kubernetes coredns.local { - fallthrough junk -}`, - true, - "rong argument count", - -1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - // Valid upstream - { - `kubernetes coredns.local { - upstream 13.14.15.16:53 -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - []string{"13.14.15.16:53"}, - }, - // Invalid upstream - { - `kubernetes coredns.local { - upstream 13.14.15.16orange -}`, - true, - "not an IP address or file: \"13.14.15.16orange\"", - -1, - 0, - defaultResyncPeriod, - "", - podModeDisabled, - false, - nil, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - k8sController, opts, err := kubernetesParse(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error, but did not find error for input '%s'. Error was: '%v'", i, test.input, err) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - continue - } - - if test.shouldErr && (len(test.expectedErrContent) < 1) { - t.Fatalf("Test %d: Test marked as expecting an error, but no expectedErrContent provided for input '%s'. Error was: '%v'", i, test.input, err) - } - - if test.shouldErr && (test.expectedZoneCount >= 0) { - t.Errorf("Test %d: Test marked as expecting an error, but provides value for expectedZoneCount!=-1 for input '%s'. Error was: '%v'", i, test.input, err) - } - - if !strings.Contains(err.Error(), test.expectedErrContent) { - t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) - } - continue - } - - // No error was raised, so validate initialization of k8sController - // Zones - foundZoneCount := len(k8sController.Zones) - if foundZoneCount != test.expectedZoneCount { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d zones, instead found %d zones: '%v' for input '%s'", i, test.expectedZoneCount, foundZoneCount, k8sController.Zones, test.input) - } - - // Namespaces - foundNSCount := len(k8sController.Namespaces) - if foundNSCount != test.expectedNSCount { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d namespaces. Instead found %d namespaces: '%v' for input '%s'", i, test.expectedNSCount, foundNSCount, k8sController.Namespaces, test.input) - } - - // ResyncPeriod - foundResyncPeriod := opts.resyncPeriod - if foundResyncPeriod != test.expectedResyncPeriod { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with resync period '%s'. Instead found period '%s' for input '%s'", i, test.expectedResyncPeriod, foundResyncPeriod, test.input) - } - - // Labels - if opts.labelSelector != nil { - foundLabelSelectorString := unversioned.FormatLabelSelector(opts.labelSelector) - if foundLabelSelectorString != test.expectedLabelSelector { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with label selector '%s'. Instead found selector '%s' for input '%s'", i, test.expectedLabelSelector, foundLabelSelectorString, test.input) - } - } - // Pods - foundPodMode := k8sController.podMode - if foundPodMode != test.expectedPodMode { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with pod mode '%s'. Instead found pod mode '%s' for input '%s'", i, test.expectedPodMode, foundPodMode, test.input) - } - - // fallthrough - foundFallthrough := k8sController.Fallthrough - if foundFallthrough != test.expectedFallthrough { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, foundFallthrough, test.input) - } - // upstream - foundUpstreams := k8sController.Proxy.Upstreams - if test.expectedUpstreams == nil { - if foundUpstreams != nil { - t.Errorf("Test %d: Expected kubernetes controller to not be initialized with upstreams for input '%s'", i, test.input) - } - } else { - if foundUpstreams == nil { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with upstreams for input '%s'", i, test.input) - } else { - if len(*foundUpstreams) != len(test.expectedUpstreams) { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d upstreams. Instead found %d upstreams for input '%s'", i, len(test.expectedUpstreams), len(*foundUpstreams), test.input) - } - for j, want := range test.expectedUpstreams { - got := (*foundUpstreams)[j].Select().Name - if got != want { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with upstream '%s'. Instead found upstream '%s' for input '%s'", i, want, got, test.input) - } - } - - } - } - } -} diff --git a/middleware/kubernetes/setup_ttl_test.go b/middleware/kubernetes/setup_ttl_test.go deleted file mode 100644 index d58f91576..000000000 --- a/middleware/kubernetes/setup_ttl_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package kubernetes - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestKubernetesParseTTL(t *testing.T) { - tests := []struct { - input string // Corefile data as string - expectedTTL uint32 // expected count of defined zones. - shouldErr bool - }{ - {`kubernetes cluster.local { - ttl 56 - }`, 56, false}, - {`kubernetes cluster.local`, defaultTTL, false}, - {`kubernetes cluster.local { - ttl -1 - }`, 0, true}, - {`kubernetes cluster.local { - ttl 3601 - }`, 0, true}, - } - - for i, tc := range tests { - c := caddy.NewTestController("dns", tc.input) - k, _, err := kubernetesParse(c) - if err != nil && !tc.shouldErr { - t.Fatalf("Test %d: Expected no error, got %q", i, err) - } - if err == nil && tc.shouldErr { - t.Fatalf("Test %d: Expected error, got none", i) - } - if err != nil && tc.shouldErr { - // input should error - continue - } - - if k.ttl != tc.expectedTTL { - t.Errorf("Test %d: Expected TTl to be %d, got %d", i, tc.expectedTTL, k.ttl) - } - } -} diff --git a/middleware/loadbalance/README.md b/middleware/loadbalance/README.md deleted file mode 100644 index 1cce54ebf..000000000 --- a/middleware/loadbalance/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# loadbalance - -*loadbalance* acts as a round-robin DNS loadbalancer by randomizing the order of A and AAAA records - in the answer. - - See [Wikipedia](https://en.wikipedia.org/wiki/Round-robin_DNS) about the pros and cons on this - setup. It will take care to sort any CNAMEs before any address records, because some stub resolver - implementations (like glibc) are particular about that. - -## Syntax - -~~~ -loadbalance [POLICY] -~~~ - -* **POLICY** is how to balance, the default is "round_robin" - -## Examples - -~~~ -loadbalance round_robin -~~~ diff --git a/middleware/loadbalance/handler.go b/middleware/loadbalance/handler.go deleted file mode 100644 index 3d3e4aa26..000000000 --- a/middleware/loadbalance/handler.go +++ /dev/null @@ -1,23 +0,0 @@ -// Package loadbalance is middleware for rewriting responses to do "load balancing" -package loadbalance - -import ( - "github.com/coredns/coredns/middleware" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// RoundRobin is middleware to rewrite responses for "load balancing". -type RoundRobin struct { - Next middleware.Handler -} - -// ServeDNS implements the middleware.Handler interface. -func (rr RoundRobin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - wrr := &RoundRobinResponseWriter{w} - return middleware.NextOrFailure(rr.Name(), rr.Next, ctx, wrr, r) -} - -// Name implements the Handler interface. -func (rr RoundRobin) Name() string { return "loadbalance" } diff --git a/middleware/loadbalance/loadbalance.go b/middleware/loadbalance/loadbalance.go deleted file mode 100644 index 7df0b31c6..000000000 --- a/middleware/loadbalance/loadbalance.go +++ /dev/null @@ -1,87 +0,0 @@ -// Package loadbalance shuffles A and AAAA records. -package loadbalance - -import ( - "log" - - "github.com/miekg/dns" -) - -// RoundRobinResponseWriter is a response writer that shuffles A and AAAA records. -type RoundRobinResponseWriter struct { - dns.ResponseWriter -} - -// WriteMsg implements the dns.ResponseWriter interface. -func (r *RoundRobinResponseWriter) WriteMsg(res *dns.Msg) error { - if res.Rcode != dns.RcodeSuccess { - return r.ResponseWriter.WriteMsg(res) - } - - res.Answer = roundRobin(res.Answer) - res.Ns = roundRobin(res.Ns) - res.Extra = roundRobin(res.Extra) - - return r.ResponseWriter.WriteMsg(res) -} - -func roundRobin(in []dns.RR) []dns.RR { - cname := []dns.RR{} - address := []dns.RR{} - mx := []dns.RR{} - rest := []dns.RR{} - for _, r := range in { - switch r.Header().Rrtype { - case dns.TypeCNAME: - cname = append(cname, r) - case dns.TypeA, dns.TypeAAAA: - address = append(address, r) - case dns.TypeMX: - mx = append(mx, r) - default: - rest = append(rest, r) - } - } - - roundRobinShuffle(address) - roundRobinShuffle(mx) - - out := append(cname, rest...) - out = append(out, address...) - out = append(out, mx...) - return out -} - -func roundRobinShuffle(records []dns.RR) { - switch l := len(records); l { - case 0, 1: - break - case 2: - if dns.Id()%2 == 0 { - records[0], records[1] = records[1], records[0] - } - default: - for j := 0; j < l*(int(dns.Id())%4+1); j++ { - q := int(dns.Id()) % l - p := int(dns.Id()) % l - if q == p { - p = (p + 1) % l - } - records[q], records[p] = records[p], records[q] - } - } -} - -// Write implements the dns.ResponseWriter interface. -func (r *RoundRobinResponseWriter) Write(buf []byte) (int, error) { - // Should we pack and unpack here to fiddle with the packet... Not likely. - log.Printf("[WARNING] RoundRobin called with Write: no shuffling records") - n, err := r.ResponseWriter.Write(buf) - return n, err -} - -// Hijack implements the dns.ResponseWriter interface. -func (r *RoundRobinResponseWriter) Hijack() { - r.ResponseWriter.Hijack() - return -} diff --git a/middleware/loadbalance/loadbalance_test.go b/middleware/loadbalance/loadbalance_test.go deleted file mode 100644 index 8c9205ca9..000000000 --- a/middleware/loadbalance/loadbalance_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package loadbalance - -import ( - "testing" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestLoadBalance(t *testing.T) { - rm := RoundRobin{Next: handler()} - - // the first X records must be cnames after this test - tests := []struct { - answer []dns.RR - extra []dns.RR - cnameAnswer int - cnameExtra int - addressAnswer int - addressExtra int - mxAnswer int - mxExtra int - }{ - { - answer: []dns.RR{ - test.CNAME("cname1.region2.skydns.test. 300 IN CNAME cname2.region2.skydns.test."), - test.CNAME("cname2.region2.skydns.test. 300 IN CNAME cname3.region2.skydns.test."), - test.CNAME("cname5.region2.skydns.test. 300 IN CNAME cname6.region2.skydns.test."), - test.CNAME("cname6.region2.skydns.test. 300 IN CNAME endpoint.region2.skydns.test."), - test.A("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"), - test.MX("mx.region2.skydns.test. 300 IN MX 1 mx1.region2.skydns.test."), - test.MX("mx.region2.skydns.test. 300 IN MX 2 mx2.region2.skydns.test."), - test.MX("mx.region2.skydns.test. 300 IN MX 3 mx3.region2.skydns.test."), - }, - cnameAnswer: 4, - addressAnswer: 1, - mxAnswer: 3, - }, - { - answer: []dns.RR{ - test.A("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"), - test.MX("mx.region2.skydns.test. 300 IN MX 1 mx1.region2.skydns.test."), - test.CNAME("cname.region2.skydns.test. 300 IN CNAME endpoint.region2.skydns.test."), - }, - cnameAnswer: 1, - addressAnswer: 1, - mxAnswer: 1, - }, - { - answer: []dns.RR{ - test.MX("mx.region2.skydns.test. 300 IN MX 1 mx1.region2.skydns.test."), - test.A("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"), - test.A("endpoint.region2.skydns.test. 300 IN A 10.240.0.2"), - test.MX("mx.region2.skydns.test. 300 IN MX 1 mx2.region2.skydns.test."), - test.CNAME("cname2.region2.skydns.test. 300 IN CNAME cname3.region2.skydns.test."), - test.A("endpoint.region2.skydns.test. 300 IN A 10.240.0.3"), - test.MX("mx.region2.skydns.test. 300 IN MX 1 mx3.region2.skydns.test."), - }, - extra: []dns.RR{ - test.A("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"), - test.AAAA("endpoint.region2.skydns.test. 300 IN AAAA ::1"), - test.MX("mx.region2.skydns.test. 300 IN MX 1 mx1.region2.skydns.test."), - test.CNAME("cname2.region2.skydns.test. 300 IN CNAME cname3.region2.skydns.test."), - test.MX("mx.region2.skydns.test. 300 IN MX 1 mx2.region2.skydns.test."), - test.A("endpoint.region2.skydns.test. 300 IN A 10.240.0.3"), - test.AAAA("endpoint.region2.skydns.test. 300 IN AAAA ::2"), - test.MX("mx.region2.skydns.test. 300 IN MX 1 mx3.region2.skydns.test."), - }, - cnameAnswer: 1, - cnameExtra: 1, - addressAnswer: 3, - addressExtra: 4, - mxAnswer: 3, - mxExtra: 3, - }, - } - - rec := dnsrecorder.New(&test.ResponseWriter{}) - - for i, test := range tests { - req := new(dns.Msg) - req.SetQuestion("region2.skydns.test.", dns.TypeSRV) - req.Answer = test.answer - req.Extra = test.extra - - _, err := rm.ServeDNS(context.TODO(), rec, req) - if err != nil { - t.Errorf("Test %d: Expected no error, but got %s", i, err) - continue - - } - - cname, address, mx, sorted := countRecords(rec.Msg.Answer) - if !sorted { - t.Errorf("Test %d: Expected CNAMEs, then AAAAs, then MX in Answer, but got mixed", i) - } - if cname != test.cnameAnswer { - t.Errorf("Test %d: Expected %d CNAMEs in Answer, but got %d", i, test.cnameAnswer, cname) - } - if address != test.addressAnswer { - t.Errorf("Test %d: Expected %d A/AAAAs in Answer, but got %d", i, test.addressAnswer, address) - } - if mx != test.mxAnswer { - t.Errorf("Test %d: Expected %d MXs in Answer, but got %d", i, test.mxAnswer, mx) - } - - cname, address, mx, sorted = countRecords(rec.Msg.Extra) - if !sorted { - t.Errorf("Test %d: Expected CNAMEs, then AAAAs, then MX in Extra, but got mixed", i) - } - if cname != test.cnameExtra { - t.Errorf("Test %d: Expected %d CNAMEs in Extra, but got %d", i, test.cnameAnswer, cname) - } - if address != test.addressExtra { - t.Errorf("Test %d: Expected %d A/AAAAs in Extra, but got %d", i, test.addressAnswer, address) - } - if mx != test.mxExtra { - t.Errorf("Test %d: Expected %d MXs in Extra, but got %d", i, test.mxAnswer, mx) - } - } -} - -func countRecords(result []dns.RR) (cname int, address int, mx int, sorted bool) { - const ( - Start = iota - CNAMERecords - ARecords - MXRecords - Any - ) - - // The order of the records is used to determine if the round-robin actually did anything. - sorted = true - cname = 0 - address = 0 - mx = 0 - state := Start - for _, r := range result { - switch r.Header().Rrtype { - case dns.TypeCNAME: - sorted = sorted && state <= CNAMERecords - state = CNAMERecords - cname++ - case dns.TypeA, dns.TypeAAAA: - sorted = sorted && state <= ARecords - state = ARecords - address++ - case dns.TypeMX: - sorted = sorted && state <= MXRecords - state = MXRecords - mx++ - default: - state = Any - } - } - return -} - -func handler() middleware.Handler { - return middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - w.WriteMsg(r) - return dns.RcodeSuccess, nil - }) -} diff --git a/middleware/loadbalance/setup.go b/middleware/loadbalance/setup.go deleted file mode 100644 index 334d288a0..000000000 --- a/middleware/loadbalance/setup.go +++ /dev/null @@ -1,26 +0,0 @@ -package loadbalance - -import ( - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("loadbalance", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - for c.Next() { - // TODO(miek): block and option parsing - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return RoundRobin{Next: next} - }) - - return nil -} diff --git a/middleware/log/README.md b/middleware/log/README.md deleted file mode 100644 index 223888ccc..000000000 --- a/middleware/log/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# log - -*log* enables query logging to standard output. - -## Syntax - -~~~ txt -log -~~~ - -* With no arguments, a query log entry is written to *stdout* in the common log format for all requests - -~~~ txt -log FILE -~~~ - -* **FILE** is the log file to create (or append to). The *only* valid name for **FILE** is *stdout*. - -~~~ txt -log [NAME] FILE [FORMAT] -~~~ - -* `NAME` is the name to match in order to be logged -* `FILE` is the log file (again only *stdout* is allowed here). -* `FORMAT` is the log format to use (default is Common Log Format) - -You can further specify the class of responses that get logged: - -~~~ txt -log [NAME] FILE [FORMAT] { - class [success|denial|error|all] -} -~~~ - -Here `success` `denial` and `error` denotes the class of responses that should be logged. The -classes have the following meaning: - -* `success`: successful response -* `denial`: either NXDOMAIN or NODATA (name exists, type does not) -* `error`: SERVFAIL, NOTIMP, REFUSED, etc. Anything that indicates the remote server is not willing to - resolve the request. -* `all`: the default - nothing is specified. - -If no class is specified, it defaults to *all*. - -## Log File - -The "log file" can only be *stdout*. CoreDNS expects another service to pick up this output and deal -with it, i.e. journald when using systemd or Docker's logging capabilities. - -## Log Format - -You can specify a custom log format with any placeholder values. Log supports both request and -response placeholders. - -The following place holders are supported: - -* `{type}`: qtype of the request -* `{name}`: qname of the request -* `{class}`: qclass of the request -* `{proto}`: protocol used (tcp or udp) -* `{when}`: time of the query -* `{remote}`: client's IP address -* `{size}`: request size in bytes -* `{port}`: client's port -* `{duration}`: response duration -* `{rcode}`: response RCODE -* `{rsize}`: response size -* `{>rflags}`: response flags, each set flag will be displayed, e.g. "aa, tc". This includes the qr - bit as well. -* `{>bufsize}`: the EDNS0 buffer size advertised in the query -* `{>do}`: is the EDNS0 DO (DNSSEC OK) bit set in the query -* `{>id}`: query ID -* `{>opcode}`: query OPCODE - -The default Common Log Format is: - -~~~ txt -`{remote} - [{when}] "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {>rflags} {rsize} {duration}` -~~~ - -## Examples - -Log all requests to stdout - -~~~ -log stdout -~~~ - -Custom log format, for all zones (`.`) - -~~~ -log . stdout "{proto} Request: {name} {type} {>id}" -~~~ - -Only log denials for example.org (and below to a file) - -~~~ -log example.org stdout { - class denial -} -~~~ diff --git a/middleware/log/log.go b/middleware/log/log.go deleted file mode 100644 index f4a976002..000000000 --- a/middleware/log/log.go +++ /dev/null @@ -1,91 +0,0 @@ -// Package log implements basic but useful request (access) logging middleware. -package log - -import ( - "log" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/metrics/vars" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/pkg/rcode" - "github.com/coredns/coredns/middleware/pkg/replacer" - "github.com/coredns/coredns/middleware/pkg/response" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Logger is a basic request logging middleware. -type Logger struct { - Next middleware.Handler - Rules []Rule - ErrorFunc func(dns.ResponseWriter, *dns.Msg, int) // failover error handler -} - -// ServeDNS implements the middleware.Handler interface. -func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - for _, rule := range l.Rules { - if !middleware.Name(rule.NameScope).Matches(state.Name()) { - continue - } - - rrw := dnsrecorder.New(w) - rc, err := middleware.NextOrFailure(l.Name(), l.Next, ctx, rrw, r) - - if rc > 0 { - // There was an error up the chain, but no response has been written yet. - // The error must be handled here so the log entry will record the response size. - if l.ErrorFunc != nil { - l.ErrorFunc(rrw, r, rc) - } else { - answer := new(dns.Msg) - answer.SetRcode(r, rc) - state.SizeAndDo(answer) - - vars.Report(state, vars.Dropped, rcode.ToString(rc), answer.Len(), time.Now()) - - w.WriteMsg(answer) - } - rc = 0 - } - - tpe, _ := response.Typify(rrw.Msg, time.Now().UTC()) - class := response.Classify(tpe) - if rule.Class == response.All || rule.Class == class { - rep := replacer.New(r, rrw, CommonLogEmptyValue) - rule.Log.Println(rep.Replace(rule.Format)) - } - - return rc, err - - } - return middleware.NextOrFailure(l.Name(), l.Next, ctx, w, r) -} - -// Name implements the Handler interface. -func (l Logger) Name() string { return "log" } - -// Rule configures the logging middleware. -type Rule struct { - NameScope string - Class response.Class - OutputFile string - Format string - Log *log.Logger -} - -const ( - // DefaultLogFilename is the default output name. This is the only supported value. - DefaultLogFilename = "stdout" - // CommonLogFormat is the common log format. - CommonLogFormat = `{remote} ` + CommonLogEmptyValue + ` [{when}] "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {>rflags} {rsize} {duration}` - // CommonLogEmptyValue is the common empty log value. - CommonLogEmptyValue = "-" - // CombinedLogFormat is the combined log format. - CombinedLogFormat = CommonLogFormat + ` "{>opcode}"` - // DefaultLogFormat is the default log format. - DefaultLogFormat = CommonLogFormat -) diff --git a/middleware/log/log_test.go b/middleware/log/log_test.go deleted file mode 100644 index 80efc971c..000000000 --- a/middleware/log/log_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package log - -import ( - "bytes" - "log" - "strings" - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/pkg/response" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestLoggedStatus(t *testing.T) { - var f bytes.Buffer - rule := Rule{ - NameScope: ".", - Format: DefaultLogFormat, - Log: log.New(&f, "", 0), - } - - logger := Logger{ - Rules: []Rule{rule}, - Next: test.ErrorHandler(), - } - - ctx := context.TODO() - r := new(dns.Msg) - r.SetQuestion("example.org.", dns.TypeA) - - rec := dnsrecorder.New(&test.ResponseWriter{}) - - rcode, _ := logger.ServeDNS(ctx, rec, r) - if rcode != 0 { - t.Errorf("Expected rcode to be 0 - was: %d", rcode) - } - - logged := f.String() - if !strings.Contains(logged, "A IN example.org. udp 29 false 512") { - t.Errorf("Expected it to be logged. Logged string: %s", logged) - } -} - -func TestLoggedClassDenial(t *testing.T) { - var f bytes.Buffer - rule := Rule{ - NameScope: ".", - Format: DefaultLogFormat, - Log: log.New(&f, "", 0), - Class: response.Denial, - } - - logger := Logger{ - Rules: []Rule{rule}, - Next: test.ErrorHandler(), - } - - ctx := context.TODO() - r := new(dns.Msg) - r.SetQuestion("example.org.", dns.TypeA) - - rec := dnsrecorder.New(&test.ResponseWriter{}) - - logger.ServeDNS(ctx, rec, r) - - logged := f.String() - if len(logged) != 0 { - t.Errorf("Expected it not to be logged, but got string: %s", logged) - } -} - -func TestLoggedClassError(t *testing.T) { - var f bytes.Buffer - rule := Rule{ - NameScope: ".", - Format: DefaultLogFormat, - Log: log.New(&f, "", 0), - Class: response.Error, - } - - logger := Logger{ - Rules: []Rule{rule}, - Next: test.ErrorHandler(), - } - - ctx := context.TODO() - r := new(dns.Msg) - r.SetQuestion("example.org.", dns.TypeA) - - rec := dnsrecorder.New(&test.ResponseWriter{}) - - logger.ServeDNS(ctx, rec, r) - - logged := f.String() - if !strings.Contains(logged, "SERVFAIL") { - t.Errorf("Expected it to be logged. Logged string: %s", logged) - } -} diff --git a/middleware/log/setup.go b/middleware/log/setup.go deleted file mode 100644 index 498829888..000000000 --- a/middleware/log/setup.go +++ /dev/null @@ -1,116 +0,0 @@ -package log - -import ( - "fmt" - "log" - "os" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/response" - - "github.com/mholt/caddy" - "github.com/miekg/dns" -) - -func init() { - caddy.RegisterPlugin("log", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - rules, err := logParse(c) - if err != nil { - return middleware.Error("log", err) - } - - // Open the log files for writing when the server starts - c.OnStartup(func() error { - for i := 0; i < len(rules); i++ { - // We only support stdout - writer := os.Stdout - if rules[i].OutputFile != "stdout" { - return middleware.Error("log", fmt.Errorf("invalid log file: %s", rules[i].OutputFile)) - } - - rules[i].Log = log.New(writer, "", 0) - } - - return nil - }) - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return Logger{Next: next, Rules: rules, ErrorFunc: dnsserver.DefaultErrorFunc} - }) - - return nil -} - -func logParse(c *caddy.Controller) ([]Rule, error) { - var rules []Rule - - for c.Next() { - args := c.RemainingArgs() - - if len(args) == 0 { - // Nothing specified; use defaults - rules = append(rules, Rule{ - NameScope: ".", - OutputFile: DefaultLogFilename, - Format: DefaultLogFormat, - }) - } else if len(args) == 1 { - // Only an output file specified. - rules = append(rules, Rule{ - NameScope: ".", - OutputFile: args[0], - Format: DefaultLogFormat, - }) - } else { - // Name scope, output file, and maybe a format specified - - format := DefaultLogFormat - - if len(args) > 2 { - switch args[2] { - case "{common}": - format = CommonLogFormat - case "{combined}": - format = CombinedLogFormat - default: - format = args[2] - } - } - - rules = append(rules, Rule{ - NameScope: dns.Fqdn(args[0]), - OutputFile: args[1], - Format: format, - }) - } - - // Class refinements in an extra block. - for c.NextBlock() { - switch c.Val() { - // class followed by all, denial, error or success. - case "class": - classes := c.RemainingArgs() - if len(classes) == 0 { - return nil, c.ArgErr() - } - cls, err := response.ClassFromString(classes[0]) - if err != nil { - return nil, err - } - // update class and the last added Rule (bit icky) - rules[len(rules)-1].Class = cls - default: - return nil, c.ArgErr() - } - } - } - - return rules, nil -} diff --git a/middleware/log/setup_test.go b/middleware/log/setup_test.go deleted file mode 100644 index 596239312..000000000 --- a/middleware/log/setup_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package log - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/response" - - "github.com/mholt/caddy" -) - -func TestLogParse(t *testing.T) { - tests := []struct { - inputLogRules string - shouldErr bool - expectedLogRules []Rule - }{ - {`log`, false, []Rule{{ - NameScope: ".", - OutputFile: DefaultLogFilename, - Format: DefaultLogFormat, - }}}, - {`log log.txt`, false, []Rule{{ - NameScope: ".", - OutputFile: "log.txt", - Format: DefaultLogFormat, - }}}, - {`log example.org log.txt`, false, []Rule{{ - NameScope: "example.org.", - OutputFile: "log.txt", - Format: DefaultLogFormat, - }}}, - {`log example.org. stdout`, false, []Rule{{ - NameScope: "example.org.", - OutputFile: "stdout", - Format: DefaultLogFormat, - }}}, - {`log example.org log.txt {common}`, false, []Rule{{ - NameScope: "example.org.", - OutputFile: "log.txt", - Format: CommonLogFormat, - }}}, - {`log example.org accesslog.txt {combined}`, false, []Rule{{ - NameScope: "example.org.", - OutputFile: "accesslog.txt", - Format: CombinedLogFormat, - }}}, - {`log example.org. log.txt - log example.net accesslog.txt {combined}`, false, []Rule{{ - NameScope: "example.org.", - OutputFile: "log.txt", - Format: DefaultLogFormat, - }, { - NameScope: "example.net.", - OutputFile: "accesslog.txt", - Format: CombinedLogFormat, - }}}, - {`log example.org stdout {host} - log example.org log.txt {when}`, false, []Rule{{ - NameScope: "example.org.", - OutputFile: "stdout", - Format: "{host}", - }, { - NameScope: "example.org.", - OutputFile: "log.txt", - Format: "{when}", - }}}, - - {`log example.org log.txt { - class all - }`, false, []Rule{{ - NameScope: "example.org.", - OutputFile: "log.txt", - Format: CommonLogFormat, - Class: response.All, - }}}, - {`log example.org log.txt { - class denial - }`, false, []Rule{{ - NameScope: "example.org.", - OutputFile: "log.txt", - Format: CommonLogFormat, - Class: response.Denial, - }}}, - {`log { - class denial - }`, false, []Rule{{ - NameScope: ".", - OutputFile: DefaultLogFilename, - Format: CommonLogFormat, - Class: response.Denial, - }}}, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.inputLogRules) - actualLogRules, err := logParse(c) - - if err == nil && test.shouldErr { - t.Errorf("Test %d didn't error, but it should have", i) - } else if err != nil && !test.shouldErr { - t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) - } - if len(actualLogRules) != len(test.expectedLogRules) { - t.Fatalf("Test %d expected %d no of Log rules, but got %d ", - i, len(test.expectedLogRules), len(actualLogRules)) - } - for j, actualLogRule := range actualLogRules { - - if actualLogRule.NameScope != test.expectedLogRules[j].NameScope { - t.Errorf("Test %d expected %dth LogRule NameScope to be %s , but got %s", - i, j, test.expectedLogRules[j].NameScope, actualLogRule.NameScope) - } - - if actualLogRule.OutputFile != test.expectedLogRules[j].OutputFile { - t.Errorf("Test %d expected %dth LogRule OutputFile to be %s , but got %s", - i, j, test.expectedLogRules[j].OutputFile, actualLogRule.OutputFile) - } - - if actualLogRule.Format != test.expectedLogRules[j].Format { - t.Errorf("Test %d expected %dth LogRule Format to be %s , but got %s", - i, j, test.expectedLogRules[j].Format, actualLogRule.Format) - } - - if actualLogRule.Class != test.expectedLogRules[j].Class { - t.Errorf("Test %d expected %dth LogRule Class to be %s , but got %s", - i, j, test.expectedLogRules[j].Class, actualLogRule.Class) - } - } - } - -} diff --git a/middleware/metrics/README.md b/middleware/metrics/README.md deleted file mode 100644 index a1ab52b88..000000000 --- a/middleware/metrics/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# prometheus - -This module enables prometheus metrics for CoreDNS. - -The default location for the metrics is `localhost:9153`. The metrics path is fixed to `/metrics`. -The following metrics are exported: - -* coredns_dns_request_count_total{zone, proto, family} -* coredns_dns_request_duration_milliseconds{zone} -* coredns_dns_request_size_bytes{zone, proto} -* coredns_dns_request_do_count_total{zone} -* coredns_dns_request_type_count_total{zone, type} -* coredns_dns_response_size_bytes{zone, proto} -* coredns_dns_response_rcode_count_total{zone, rcode} - -Each counter has a label `zone` which is the zonename used for the request/response. - -Extra labels used are: - -* `proto` which holds the transport of the response ("udp" or "tcp") -* The address family (`family`) of the transport (1 = IP (IP version 4), 2 = IP6 (IP version 6)). -* `type` which holds the query type. It holds most common types (A, AAAA, MX, SOA, CNAME, PTR, TXT, - NS, SRV, DS, DNSKEY, RRSIG, NSEC, NSEC3, IXFR, AXFR and ANY) and "other" which lumps together all - other types. -* The `response_rcode_count_total` has an extra label `rcode` which holds the rcode of the response. - -If monitoring is enabled, queries that do not enter the middleware chain are exported under the fake -name "dropped" (without a closing dot - this is never a valid domain name). - - -## Syntax - -~~~ -prometheus [ADDRESS] -~~~ - -For each zone that you want to see metrics for. - -It optionally takes an address to which the metrics are exported; the default -is `localhost:9153`. The metrics path is fixed to `/metrics`. - -## Examples - -Use an alternative address: - -~~~ -prometheus localhost:9253 -~~~ - -# Bugs - -When reloading, we keep the handler running, meaning that any changes to the handler's address -aren't picked up. You'll need to restart CoreDNS for that to happen. diff --git a/middleware/metrics/handler.go b/middleware/metrics/handler.go deleted file mode 100644 index 50dc4141e..000000000 --- a/middleware/metrics/handler.go +++ /dev/null @@ -1,34 +0,0 @@ -package metrics - -import ( - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/metrics/vars" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/pkg/rcode" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// ServeDNS implements the Handler interface. -func (m *Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - - qname := state.QName() - zone := middleware.Zones(m.ZoneNames()).Matches(qname) - if zone == "" { - zone = "." - } - - // Record response to get status code and size of the reply. - rw := dnsrecorder.New(w) - status, err := middleware.NextOrFailure(m.Name(), m.Next, ctx, rw, r) - - vars.Report(state, zone, rcode.ToString(rw.Rcode), rw.Len, rw.Start) - - return status, err -} - -// Name implements the Handler interface. -func (m *Metrics) Name() string { return "prometheus" } diff --git a/middleware/metrics/metrics.go b/middleware/metrics/metrics.go deleted file mode 100644 index 6e06a2bf4..000000000 --- a/middleware/metrics/metrics.go +++ /dev/null @@ -1,101 +0,0 @@ -// Package metrics implement a handler and middleware that provides Prometheus metrics. -package metrics - -import ( - "log" - "net" - "net/http" - "sync" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/metrics/vars" - - "github.com/prometheus/client_golang/prometheus" -) - -func init() { - prometheus.MustRegister(vars.RequestCount) - prometheus.MustRegister(vars.RequestDuration) - prometheus.MustRegister(vars.RequestSize) - prometheus.MustRegister(vars.RequestDo) - prometheus.MustRegister(vars.RequestType) - - prometheus.MustRegister(vars.ResponseSize) - prometheus.MustRegister(vars.ResponseRcode) -} - -// Metrics holds the prometheus configuration. The metrics' path is fixed to be /metrics -type Metrics struct { - Next middleware.Handler - Addr string - ln net.Listener - mux *http.ServeMux - - zoneNames []string - zoneMap map[string]bool - zoneMu sync.RWMutex -} - -// AddZone adds zone z to m. -func (m *Metrics) AddZone(z string) { - m.zoneMu.Lock() - m.zoneMap[z] = true - m.zoneNames = keys(m.zoneMap) - m.zoneMu.Unlock() -} - -// RemoveZone remove zone z from m. -func (m *Metrics) RemoveZone(z string) { - m.zoneMu.Lock() - delete(m.zoneMap, z) - m.zoneNames = keys(m.zoneMap) - m.zoneMu.Unlock() -} - -// ZoneNames returns the zones of m. -func (m *Metrics) ZoneNames() []string { - m.zoneMu.RLock() - s := m.zoneNames - m.zoneMu.RUnlock() - return s -} - -// OnStartup sets up the metrics on startup. -func (m *Metrics) OnStartup() error { - ln, err := net.Listen("tcp", m.Addr) - if err != nil { - log.Printf("[ERROR] Failed to start metrics handler: %s", err) - return err - } - - m.ln = ln - ListenAddr = m.ln.Addr().String() - - m.mux = http.NewServeMux() - m.mux.Handle("/metrics", prometheus.Handler()) - - go func() { - http.Serve(m.ln, m.mux) - }() - return nil -} - -// OnShutdown tears down the metrics on shutdown and restart. -func (m *Metrics) OnShutdown() error { - if m.ln != nil { - return m.ln.Close() - } - return nil -} - -func keys(m map[string]bool) []string { - sx := []string{} - for k := range m { - sx = append(sx, k) - } - return sx -} - -// ListenAddr is assigned the address of the prometheus listener. Its use is mainly in tests where -// we listen on "localhost:0" and need to retrieve the actual address. -var ListenAddr string diff --git a/middleware/metrics/metrics_test.go b/middleware/metrics/metrics_test.go deleted file mode 100644 index 18a9011ed..000000000 --- a/middleware/metrics/metrics_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package metrics - -import ( - "testing" - - "github.com/coredns/coredns/middleware" - mtest "github.com/coredns/coredns/middleware/metrics/test" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestMetrics(t *testing.T) { - met := &Metrics{Addr: "localhost:0", zoneMap: make(map[string]bool)} - if err := met.OnStartup(); err != nil { - t.Fatalf("Failed to start metrics handler: %s", err) - } - defer met.OnShutdown() - - met.AddZone("example.org.") - - tests := []struct { - next middleware.Handler - qname string - qtype uint16 - metric string - expectedValue string - }{ - // This all works because 1 bucket (1 zone, 1 type) - { - next: test.NextHandler(dns.RcodeSuccess, nil), - qname: "example.org", - metric: "coredns_dns_request_count_total", - expectedValue: "1", - }, - { - next: test.NextHandler(dns.RcodeSuccess, nil), - qname: "example.org", - metric: "coredns_dns_request_count_total", - expectedValue: "2", - }, - { - next: test.NextHandler(dns.RcodeSuccess, nil), - qname: "example.org", - metric: "coredns_dns_request_type_count_total", - expectedValue: "3", - }, - { - next: test.NextHandler(dns.RcodeSuccess, nil), - qname: "example.org", - metric: "coredns_dns_response_rcode_count_total", - expectedValue: "4", - }, - } - - ctx := context.TODO() - - for i, tc := range tests { - req := new(dns.Msg) - if tc.qtype == 0 { - tc.qtype = dns.TypeA - } - req.SetQuestion(dns.Fqdn(tc.qname), tc.qtype) - met.Next = tc.next - - rec := dnsrecorder.New(&test.ResponseWriter{}) - _, err := met.ServeDNS(ctx, rec, req) - if err != nil { - t.Fatalf("Test %d: Expected no error, but got %s", i, err) - } - - result := mtest.Scrape(t, "http://"+ListenAddr+"/metrics") - - if tc.expectedValue != "" { - got, _ := mtest.MetricValue(tc.metric, result) - if got != tc.expectedValue { - t.Errorf("Test %d: Expected value %s for metrics %s, but got %s", i, tc.expectedValue, tc.metric, got) - } - } - } -} diff --git a/middleware/metrics/setup.go b/middleware/metrics/setup.go deleted file mode 100644 index 30b05d4ff..000000000 --- a/middleware/metrics/setup.go +++ /dev/null @@ -1,100 +0,0 @@ -package metrics - -import ( - "net" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("prometheus", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) - - uniqAddr = addrs{a: make(map[string]int)} -} - -func setup(c *caddy.Controller) error { - m, err := prometheusParse(c) - if err != nil { - return middleware.Error("prometheus", err) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - m.Next = next - return m - }) - - for a, v := range uniqAddr.a { - if v == todo { - // During restarts we will keep this handler running, BUG. - c.OncePerServerBlock(m.OnStartup) - } - uniqAddr.a[a] = done - } - c.OnFinalShutdown(m.OnShutdown) - - return nil -} - -func prometheusParse(c *caddy.Controller) (*Metrics, error) { - var ( - met = &Metrics{Addr: addr, zoneMap: make(map[string]bool)} - err error - ) - - defer func() { - uniqAddr.SetAddress(met.Addr) - }() - - for c.Next() { - if len(met.ZoneNames()) > 0 { - return met, c.Err("can only have one metrics module per server") - } - - for _, z := range c.ServerBlockKeys { - met.AddZone(middleware.Host(z).Normalize()) - } - args := c.RemainingArgs() - - switch len(args) { - case 0: - case 1: - met.Addr = args[0] - _, _, e := net.SplitHostPort(met.Addr) - if e != nil { - return met, e - } - default: - return met, c.ArgErr() - } - } - return met, err -} - -var uniqAddr addrs - -// Keep track on which addrs we listen, so we only start one listener. -type addrs struct { - a map[string]int -} - -func (a *addrs) SetAddress(addr string) { - // If already there and set to done, we've already started this listener. - if a.a[addr] == done { - return - } - a.a[addr] = todo -} - -// Addr is the address the where the metrics are exported by default. -const addr = "localhost:9153" - -const ( - todo = 1 - done = 2 -) diff --git a/middleware/metrics/setup_test.go b/middleware/metrics/setup_test.go deleted file mode 100644 index 73555427e..000000000 --- a/middleware/metrics/setup_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package metrics - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestPrometheusParse(t *testing.T) { - tests := []struct { - input string - shouldErr bool - addr string - }{ - // oks - {`prometheus`, false, "localhost:9153"}, - {`prometheus localhost:53`, false, "localhost:53"}, - // fails - {`prometheus {}`, true, ""}, - {`prometheus /foo`, true, ""}, - {`prometheus a b c`, true, ""}, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - m, err := prometheusParse(c) - if test.shouldErr && err == nil { - t.Errorf("Test %v: Expected error but found nil", i) - continue - } else if !test.shouldErr && err != nil { - t.Errorf("Test %v: Expected no error but found error: %v", i, err) - continue - } - - if test.shouldErr { - continue - } - - if test.addr != m.Addr { - t.Errorf("Test %v: Expected address %s but found: %s", i, test.addr, m.Addr) - } - } -} diff --git a/middleware/metrics/test/scrape.go b/middleware/metrics/test/scrape.go deleted file mode 100644 index a21c0061d..000000000 --- a/middleware/metrics/test/scrape.go +++ /dev/null @@ -1,225 +0,0 @@ -// Adapted by Miek Gieben for CoreDNS testing. -// -// License from prom2json -// Copyright 2014 Prometheus Team -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package test will scrape a target and you can inspect the variables. -// Basic usage: -// -// result := Scrape("http://localhost:9153/metrics") -// v := MetricValue("coredns_cache_capacity", result) -// -package test - -import ( - "fmt" - "io" - "mime" - "net/http" - "testing" - - "github.com/matttproud/golang_protobuf_extensions/pbutil" - "github.com/prometheus/common/expfmt" - - dto "github.com/prometheus/client_model/go" -) - -type ( - // MetricFamily holds a prometheus metric. - MetricFamily struct { - Name string `json:"name"` - Help string `json:"help"` - Type string `json:"type"` - Metrics []interface{} `json:"metrics,omitempty"` // Either metric or summary. - } - - // metric is for all "single value" metrics. - metric struct { - Labels map[string]string `json:"labels,omitempty"` - Value string `json:"value"` - } - - summary struct { - Labels map[string]string `json:"labels,omitempty"` - Quantiles map[string]string `json:"quantiles,omitempty"` - Count string `json:"count"` - Sum string `json:"sum"` - } - - histogram struct { - Labels map[string]string `json:"labels,omitempty"` - Buckets map[string]string `json:"buckets,omitempty"` - Count string `json:"count"` - Sum string `json:"sum"` - } -) - -// Scrape returns the all the vars a []*metricFamily. -func Scrape(t *testing.T, url string) []*MetricFamily { - mfChan := make(chan *dto.MetricFamily, 1024) - - go fetchMetricFamilies(url, mfChan) - - result := []*MetricFamily{} - for mf := range mfChan { - result = append(result, newMetricFamily(mf)) - } - return result -} - -// MetricValue returns the value associated with name as a string as well as the labels. -// It only returns the first metrics of the slice. -func MetricValue(name string, mfs []*MetricFamily) (string, map[string]string) { - for _, mf := range mfs { - if mf.Name == name { - // Only works with Gauge and Counter... - return mf.Metrics[0].(metric).Value, mf.Metrics[0].(metric).Labels - } - } - return "", nil -} - -// MetricValueLabel returns the value for name *and* label *value*. -func MetricValueLabel(name, label string, mfs []*MetricFamily) (string, map[string]string) { - // bit hacky is this really handy...? - for _, mf := range mfs { - if mf.Name == name { - for _, m := range mf.Metrics { - for _, v := range m.(metric).Labels { - if v == label { - return m.(metric).Value, m.(metric).Labels - } - } - - } - } - } - return "", nil -} - -func newMetricFamily(dtoMF *dto.MetricFamily) *MetricFamily { - mf := &MetricFamily{ - Name: dtoMF.GetName(), - Help: dtoMF.GetHelp(), - Type: dtoMF.GetType().String(), - Metrics: make([]interface{}, len(dtoMF.Metric)), - } - for i, m := range dtoMF.Metric { - if dtoMF.GetType() == dto.MetricType_SUMMARY { - mf.Metrics[i] = summary{ - Labels: makeLabels(m), - Quantiles: makeQuantiles(m), - Count: fmt.Sprint(m.GetSummary().GetSampleCount()), - Sum: fmt.Sprint(m.GetSummary().GetSampleSum()), - } - } else if dtoMF.GetType() == dto.MetricType_HISTOGRAM { - mf.Metrics[i] = histogram{ - Labels: makeLabels(m), - Buckets: makeBuckets(m), - Count: fmt.Sprint(m.GetHistogram().GetSampleCount()), - Sum: fmt.Sprint(m.GetSummary().GetSampleSum()), - } - } else { - mf.Metrics[i] = metric{ - Labels: makeLabels(m), - Value: fmt.Sprint(value(m)), - } - } - } - return mf -} - -func value(m *dto.Metric) float64 { - if m.Gauge != nil { - return m.GetGauge().GetValue() - } - if m.Counter != nil { - return m.GetCounter().GetValue() - } - if m.Untyped != nil { - return m.GetUntyped().GetValue() - } - return 0. -} - -func makeLabels(m *dto.Metric) map[string]string { - result := map[string]string{} - for _, lp := range m.Label { - result[lp.GetName()] = lp.GetValue() - } - return result -} - -func makeQuantiles(m *dto.Metric) map[string]string { - result := map[string]string{} - for _, q := range m.GetSummary().Quantile { - result[fmt.Sprint(q.GetQuantile())] = fmt.Sprint(q.GetValue()) - } - return result -} - -func makeBuckets(m *dto.Metric) map[string]string { - result := map[string]string{} - for _, b := range m.GetHistogram().Bucket { - result[fmt.Sprint(b.GetUpperBound())] = fmt.Sprint(b.GetCumulativeCount()) - } - return result -} - -func fetchMetricFamilies(url string, ch chan<- *dto.MetricFamily) { - defer close(ch) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return - } - req.Header.Add("Accept", acceptHeader) - resp, err := http.DefaultClient.Do(req) - if err != nil { - return - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return - } - - mediatype, params, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) - if err == nil && mediatype == "application/vnd.google.protobuf" && - params["encoding"] == "delimited" && - params["proto"] == "io.prometheus.client.MetricFamily" { - for { - mf := &dto.MetricFamily{} - if _, err = pbutil.ReadDelimited(resp.Body, mf); err != nil { - if err == io.EOF { - break - } - return - } - ch <- mf - } - } else { - // We could do further content-type checks here, but the - // fallback for now will anyway be the text format - // version 0.0.4, so just go for it and see if it works. - var parser expfmt.TextParser - metricFamilies, err := parser.TextToMetricFamilies(resp.Body) - if err != nil { - return - } - for _, mf := range metricFamilies { - ch <- mf - } - } -} - -const acceptHeader = `application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.7,text/plain;version=0.0.4;q=0.3` diff --git a/middleware/metrics/vars/report.go b/middleware/metrics/vars/report.go deleted file mode 100644 index 5d8f2ba64..000000000 --- a/middleware/metrics/vars/report.go +++ /dev/null @@ -1,62 +0,0 @@ -package vars - -import ( - "time" - - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// Report reports the metrics data associcated with request. -func Report(req request.Request, zone, rcode string, size int, start time.Time) { - // Proto and Family. - net := req.Proto() - fam := "1" - if req.Family() == 2 { - fam = "2" - } - - typ := req.QType() - - RequestCount.WithLabelValues(zone, net, fam).Inc() - RequestDuration.WithLabelValues(zone).Observe(float64(time.Since(start) / time.Millisecond)) - - if req.Do() { - RequestDo.WithLabelValues(zone).Inc() - } - - if _, known := monitorType[typ]; known { - RequestType.WithLabelValues(zone, dns.Type(typ).String()).Inc() - } else { - RequestType.WithLabelValues(zone, other).Inc() - } - - ResponseSize.WithLabelValues(zone, net).Observe(float64(size)) - RequestSize.WithLabelValues(zone, net).Observe(float64(req.Len())) - - ResponseRcode.WithLabelValues(zone, rcode).Inc() -} - -var monitorType = map[uint16]bool{ - dns.TypeAAAA: true, - dns.TypeA: true, - dns.TypeCNAME: true, - dns.TypeDNSKEY: true, - dns.TypeDS: true, - dns.TypeMX: true, - dns.TypeNSEC3: true, - dns.TypeNSEC: true, - dns.TypeNS: true, - dns.TypePTR: true, - dns.TypeRRSIG: true, - dns.TypeSOA: true, - dns.TypeSRV: true, - dns.TypeTXT: true, - // Meta Qtypes - dns.TypeIXFR: true, - dns.TypeAXFR: true, - dns.TypeANY: true, -} - -const other = "other" diff --git a/middleware/metrics/vars/vars.go b/middleware/metrics/vars/vars.go deleted file mode 100644 index 42af10007..000000000 --- a/middleware/metrics/vars/vars.go +++ /dev/null @@ -1,69 +0,0 @@ -package vars - -import ( - "github.com/coredns/coredns/middleware" - - "github.com/prometheus/client_golang/prometheus" -) - -// Request* and Response* are the prometheus counters and gauges we are using for exporting metrics. -var ( - RequestCount = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "request_count_total", - Help: "Counter of DNS requests made per zone, protocol and family.", - }, []string{"zone", "proto", "family"}) - - RequestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "request_duration_milliseconds", - Buckets: append(prometheus.DefBuckets, []float64{50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 10000}...), - Help: "Histogram of the time (in milliseconds) each request took.", - }, []string{"zone"}) - - RequestSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "request_size_bytes", - Help: "Size of the EDNS0 UDP buffer in bytes (64K for TCP).", - Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3}, - }, []string{"zone", "proto"}) - - RequestDo = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "request_do_count_total", - Help: "Counter of DNS requests with DO bit set per zone.", - }, []string{"zone"}) - - RequestType = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "request_type_count_total", - Help: "Counter of DNS requests per type, per zone.", - }, []string{"zone", "type"}) - - ResponseSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "response_size_bytes", - Help: "Size of the returned response in bytes.", - Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3}, - }, []string{"zone", "proto"}) - - ResponseRcode = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: middleware.Namespace, - Subsystem: subsystem, - Name: "response_rcode_count_total", - Help: "Counter of response status codes.", - }, []string{"zone", "rcode"}) -) - -const ( - subsystem = "dns" - - // Dropped indicates we dropped the query before any handling. It has no closing dot, so it can not be a valid zone. - Dropped = "dropped" -) diff --git a/middleware/middleware.go b/middleware/middleware.go deleted file mode 100644 index 9e236c6e0..000000000 --- a/middleware/middleware.go +++ /dev/null @@ -1,102 +0,0 @@ -// Package middleware provides some types and functions common among middleware. -package middleware - -import ( - "errors" - "fmt" - - "github.com/miekg/dns" - ot "github.com/opentracing/opentracing-go" - "golang.org/x/net/context" -) - -type ( - // Middleware is the middle layer which represents the traditional - // idea of middleware: it chains one Handler to the next by being - // passed the next Handler in the chain. - Middleware func(Handler) Handler - - // Handler is like dns.Handler except ServeDNS may return an rcode - // and/or error. - // - // If ServeDNS writes to the response body, it should return a status - // code. If the status code is not one of the following: - // - // * SERVFAIL (dns.RcodeServerFailure) - // - // * REFUSED (dns.RecodeRefused) - // - // * FORMERR (dns.RcodeFormatError) - // - // * NOTIMP (dns.RcodeNotImplemented) - // - // CoreDNS assumes *no* reply has yet been written. All other response - // codes signal other handlers above it that the response message is - // already written, and that they should not write to it also. - // - // If ServeDNS encounters an error, it should return the error value - // so it can be logged by designated error-handling middleware. - // - // If writing a response after calling another ServeDNS method, the - // returned rcode SHOULD be used when writing the response. - // - // If handling errors after calling another ServeDNS method, the - // returned error value SHOULD be logged or handled accordingly. - // - // Otherwise, return values should be propagated down the middleware - // chain by returning them unchanged. - Handler interface { - ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error) - Name() string - } - - // HandlerFunc is a convenience type like dns.HandlerFunc, except - // ServeDNS returns an rcode and an error. See Handler - // documentation for more information. - HandlerFunc func(context.Context, dns.ResponseWriter, *dns.Msg) (int, error) -) - -// ServeDNS implements the Handler interface. -func (f HandlerFunc) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - return f(ctx, w, r) -} - -// Name implements the Handler interface. -func (f HandlerFunc) Name() string { return "handlerfunc" } - -// Error returns err with 'middleware/name: ' prefixed to it. -func Error(name string, err error) error { return fmt.Errorf("%s/%s: %s", "middleware", name, err) } - -// NextOrFailure calls next.ServeDNS when next is not nill, otherwise it will return, a ServerFailure -// and a nil error. -func NextOrFailure(name string, next Handler, ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - if next != nil { - if span := ot.SpanFromContext(ctx); span != nil { - child := span.Tracer().StartSpan(next.Name(), ot.ChildOf(span.Context())) - defer child.Finish() - ctx = ot.ContextWithSpan(ctx, child) - } - return next.ServeDNS(ctx, w, r) - } - - return dns.RcodeServerFailure, Error(name, errors.New("no next middleware found")) -} - -// ClientWrite returns true if the response has been written to the client. -// Each middleware to adhire to this protocol. -func ClientWrite(rcode int) bool { - switch rcode { - case dns.RcodeServerFailure: - fallthrough - case dns.RcodeRefused: - fallthrough - case dns.RcodeFormatError: - fallthrough - case dns.RcodeNotImplemented: - return false - } - return true -} - -// Namespace is the namespace used for the metrics. -const Namespace = "coredns" diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go deleted file mode 100644 index c870d7c16..000000000 --- a/middleware/middleware_test.go +++ /dev/null @@ -1 +0,0 @@ -package middleware diff --git a/middleware/normalize.go b/middleware/normalize.go deleted file mode 100644 index 92cc57937..000000000 --- a/middleware/normalize.go +++ /dev/null @@ -1,137 +0,0 @@ -package middleware - -import ( - "fmt" - "net" - "strconv" - "strings" - - "github.com/miekg/dns" -) - -// See core/dnsserver/address.go - we should unify these two impls. - -// Zones respresents a lists of zone names. -type Zones []string - -// Matches checks is qname is a subdomain of any of the zones in z. The match -// will return the most specific zones that matches other. The empty string -// signals a not found condition. -func (z Zones) Matches(qname string) string { - zone := "" - for _, zname := range z { - if dns.IsSubDomain(zname, qname) { - // We want the *longest* matching zone, otherwise we may end up in a parent - if len(zname) > len(zone) { - zone = zname - } - } - } - return zone -} - -// Normalize fully qualifies all zones in z. The zones in Z must be domain names, without -// a port or protocol prefix. -func (z Zones) Normalize() { - for i := range z { - z[i] = Name(z[i]).Normalize() - } -} - -// Name represents a domain name. -type Name string - -// Matches checks to see if other is a subdomain (or the same domain) of n. -// This method assures that names can be easily and consistently matched. -func (n Name) Matches(child string) bool { - if dns.Name(n) == dns.Name(child) { - return true - } - return dns.IsSubDomain(string(n), child) -} - -// Normalize lowercases and makes n fully qualified. -func (n Name) Normalize() string { return strings.ToLower(dns.Fqdn(string(n))) } - -type ( - // Host represents a host from the Corefile, may contain port. - Host string -) - -// Normalize will return the host portion of host, stripping -// of any port or transport. The host will also be fully qualified and lowercased. -func (h Host) Normalize() string { - - s := string(h) - - switch { - case strings.HasPrefix(s, TransportTLS+"://"): - s = s[len(TransportTLS+"://"):] - case strings.HasPrefix(s, TransportDNS+"://"): - s = s[len(TransportDNS+"://"):] - case strings.HasPrefix(s, TransportGRPC+"://"): - s = s[len(TransportGRPC+"://"):] - } - - // The error can be ignore here, because this function is called after the corefile - // has already been vetted. - host, _, _ := SplitHostPort(s) - return Name(host).Normalize() -} - -// SplitHostPort splits s up in a host and port portion, taking reverse address notation into account. -// String the string s should *not* be prefixed with any protocols, i.e. dns:// -func SplitHostPort(s string) (host, port string, err error) { - // If there is: :[0-9]+ on the end we assume this is the port. This works for (ascii) domain - // names and our reverse syntax, which always needs a /mask *before* the port. - // So from the back, find first colon, and then check if its a number. - host = s - - colon := strings.LastIndex(s, ":") - if colon == len(s)-1 { - return "", "", fmt.Errorf("expecting data after last colon: %q", s) - } - if colon != -1 { - if p, err := strconv.Atoi(s[colon+1:]); err == nil { - port = strconv.Itoa(p) - host = s[:colon] - } - } - - // TODO(miek): this should take escaping into account. - if len(host) > 255 { - return "", "", fmt.Errorf("specified zone is too long: %d > 255", len(host)) - } - - _, d := dns.IsDomainName(host) - if !d { - return "", "", fmt.Errorf("zone is not a valid domain name: %s", host) - } - - // Check if it parses as a reverse zone, if so we use that. Must be fully - // specified IP and mask and mask % 8 = 0. - ip, net, err := net.ParseCIDR(host) - if err == nil { - if rev, e := dns.ReverseAddr(ip.String()); e == nil { - ones, bits := net.Mask.Size() - if (bits-ones)%8 == 0 { - offset, end := 0, false - for i := 0; i < (bits-ones)/8; i++ { - offset, end = dns.NextLabel(rev, offset) - if end { - break - } - } - host = rev[offset:] - } - } - } - return host, port, nil -} - -// Duplicated from core/dnsserver/address.go ! -const ( - TransportDNS = "dns" - TransportTLS = "tls" - TransportGRPC = "grpc" -) diff --git a/middleware/normalize_test.go b/middleware/normalize_test.go deleted file mode 100644 index e3d2268d3..000000000 --- a/middleware/normalize_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package middleware - -import "testing" - -func TestZoneMatches(t *testing.T) { - child := "example.org." - zones := Zones([]string{"org.", "."}) - actual := zones.Matches(child) - if actual != "org." { - t.Errorf("Expected %v, got %v", "org.", actual) - } - - child = "bla.example.org." - zones = Zones([]string{"bla.example.org.", "org.", "."}) - actual = zones.Matches(child) - - if actual != "bla.example.org." { - t.Errorf("Expected %v, got %v", "org.", actual) - } -} - -func TestZoneNormalize(t *testing.T) { - zones := Zones([]string{"example.org", "Example.ORG.", "example.org."}) - expected := "example.org." - zones.Normalize() - - for _, actual := range zones { - if actual != expected { - t.Errorf("Expected %v, got %v\n", expected, actual) - } - } -} - -func TestNameMatches(t *testing.T) { - matches := []struct { - child string - parent string - expected bool - }{ - {".", ".", true}, - {"example.org.", ".", true}, - {"example.org.", "example.org.", true}, - {"example.org.", "org.", true}, - {"org.", "example.org.", false}, - } - - for _, m := range matches { - actual := Name(m.parent).Matches(m.child) - if actual != m.expected { - t.Errorf("Expected %v for %s/%s, got %v", m.expected, m.parent, m.child, actual) - } - - } -} - -func TestNameNormalize(t *testing.T) { - names := []string{ - "example.org", "example.org.", - "Example.ORG.", "example.org."} - - for i := 0; i < len(names); i += 2 { - ts := names[i] - expected := names[i+1] - actual := Name(ts).Normalize() - if expected != actual { - t.Errorf("Expected %v, got %v\n", expected, actual) - } - } -} - -func TestHostNormalize(t *testing.T) { - hosts := []string{".:53", ".", "example.org:53", "example.org.", "example.org.:53", "example.org.", - "10.0.0.0/8:53", "10.in-addr.arpa.", "10.0.0.0/9", "10.0.0.0/9.", - "dns://example.org", "example.org."} - - for i := 0; i < len(hosts); i += 2 { - ts := hosts[i] - expected := hosts[i+1] - actual := Host(ts).Normalize() - if expected != actual { - t.Errorf("Expected %v, got %v\n", expected, actual) - } - } -} diff --git a/middleware/pkg/cache/cache.go b/middleware/pkg/cache/cache.go deleted file mode 100644 index 56cae2180..000000000 --- a/middleware/pkg/cache/cache.go +++ /dev/null @@ -1,129 +0,0 @@ -// Package cache implements a cache. The cache hold 256 shards, each shard -// holds a cache: a map with a mutex. There is no fancy expunge algorithm, it -// just randomly evicts elements when it gets full. -package cache - -import ( - "hash/fnv" - "sync" -) - -// Hash returns the FNV hash of what. -func Hash(what []byte) uint32 { - h := fnv.New32() - h.Write(what) - return h.Sum32() -} - -// Cache is cache. -type Cache struct { - shards [shardSize]*shard -} - -// shard is a cache with random eviction. -type shard struct { - items map[uint32]interface{} - size int - - sync.RWMutex -} - -// New returns a new cache. -func New(size int) *Cache { - ssize := size / shardSize - if ssize < 512 { - ssize = 512 - } - - c := &Cache{} - - // Initialize all the shards - for i := 0; i < shardSize; i++ { - c.shards[i] = newShard(ssize) - } - return c -} - -// Add adds a new element to the cache. If the element already exists it is overwritten. -func (c *Cache) Add(key uint32, el interface{}) { - shard := key & (shardSize - 1) - c.shards[shard].Add(key, el) -} - -// Get looks up element index under key. -func (c *Cache) Get(key uint32) (interface{}, bool) { - shard := key & (shardSize - 1) - return c.shards[shard].Get(key) -} - -// Remove removes the element indexed with key. -func (c *Cache) Remove(key uint32) { - shard := key & (shardSize - 1) - c.shards[shard].Remove(key) -} - -// Len returns the number of elements in the cache. -func (c *Cache) Len() int { - l := 0 - for _, s := range c.shards { - l += s.Len() - } - return l -} - -// newShard returns a new shard with size. -func newShard(size int) *shard { return &shard{items: make(map[uint32]interface{}), size: size} } - -// Add adds element indexed by key into the cache. Any existing element is overwritten -func (s *shard) Add(key uint32, el interface{}) { - l := s.Len() - if l+1 > s.size { - s.Evict() - } - - s.Lock() - s.items[key] = el - s.Unlock() -} - -// Remove removes the element indexed by key from the cache. -func (s *shard) Remove(key uint32) { - s.Lock() - delete(s.items, key) - s.Unlock() -} - -// Evict removes a random element from the cache. -func (s *shard) Evict() { - s.Lock() - defer s.Unlock() - - key := -1 - for k := range s.items { - key = int(k) - break - } - if key == -1 { - // empty cache - return - } - delete(s.items, uint32(key)) -} - -// Get looks up the element indexed under key. -func (s *shard) Get(key uint32) (interface{}, bool) { - s.RLock() - el, found := s.items[key] - s.RUnlock() - return el, found -} - -// Len returns the current length of the cache. -func (s *shard) Len() int { - s.RLock() - l := len(s.items) - s.RUnlock() - return l -} - -const shardSize = 256 diff --git a/middleware/pkg/cache/cache_test.go b/middleware/pkg/cache/cache_test.go deleted file mode 100644 index 2c92bf438..000000000 --- a/middleware/pkg/cache/cache_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package cache - -import "testing" - -func TestCacheAddAndGet(t *testing.T) { - c := New(4) - c.Add(1, 1) - - if _, found := c.Get(1); !found { - t.Fatal("Failed to find inserted record") - } -} - -func TestCacheLen(t *testing.T) { - c := New(4) - - c.Add(1, 1) - if l := c.Len(); l != 1 { - t.Fatalf("Cache size should %d, got %d", 1, l) - } - - c.Add(1, 1) - if l := c.Len(); l != 1 { - t.Fatalf("Cache size should %d, got %d", 1, l) - } - - c.Add(2, 2) - if l := c.Len(); l != 2 { - t.Fatalf("Cache size should %d, got %d", 2, l) - } -} diff --git a/middleware/pkg/cache/shard_test.go b/middleware/pkg/cache/shard_test.go deleted file mode 100644 index 26675cee1..000000000 --- a/middleware/pkg/cache/shard_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package cache - -import "testing" - -func TestShardAddAndGet(t *testing.T) { - s := newShard(4) - s.Add(1, 1) - - if _, found := s.Get(1); !found { - t.Fatal("Failed to find inserted record") - } -} - -func TestShardLen(t *testing.T) { - s := newShard(4) - - s.Add(1, 1) - if l := s.Len(); l != 1 { - t.Fatalf("Shard size should %d, got %d", 1, l) - } - - s.Add(1, 1) - if l := s.Len(); l != 1 { - t.Fatalf("Shard size should %d, got %d", 1, l) - } - - s.Add(2, 2) - if l := s.Len(); l != 2 { - t.Fatalf("Shard size should %d, got %d", 2, l) - } -} - -func TestShardEvict(t *testing.T) { - s := newShard(1) - s.Add(1, 1) - s.Add(2, 2) - // 1 should be gone - - if _, found := s.Get(1); found { - t.Fatal("Found item that should have been evicted") - } -} - -func TestShardLenEvict(t *testing.T) { - s := newShard(4) - s.Add(1, 1) - s.Add(2, 1) - s.Add(3, 1) - s.Add(4, 1) - - if l := s.Len(); l != 4 { - t.Fatalf("Shard size should %d, got %d", 4, l) - } - - // This should evict one element - s.Add(5, 1) - if l := s.Len(); l != 4 { - t.Fatalf("Shard size should %d, got %d", 4, l) - } -} diff --git a/middleware/pkg/dnsrecorder/recorder.go b/middleware/pkg/dnsrecorder/recorder.go deleted file mode 100644 index 3ca5f00d0..000000000 --- a/middleware/pkg/dnsrecorder/recorder.go +++ /dev/null @@ -1,58 +0,0 @@ -// Package dnsrecorder allows you to record a DNS response when it is send to the client. -package dnsrecorder - -import ( - "time" - - "github.com/miekg/dns" -) - -// Recorder is a type of ResponseWriter that captures -// the rcode code written to it and also the size of the message -// written in the response. A rcode code does not have -// to be written, however, in which case 0 must be assumed. -// It is best to have the constructor initialize this type -// with that default status code. -type Recorder struct { - dns.ResponseWriter - Rcode int - Len int - Msg *dns.Msg - Start time.Time -} - -// New makes and returns a new Recorder, -// which captures the DNS rcode from the ResponseWriter -// and also the length of the response message written through it. -func New(w dns.ResponseWriter) *Recorder { - return &Recorder{ - ResponseWriter: w, - Rcode: 0, - Msg: nil, - Start: time.Now(), - } -} - -// WriteMsg records the status code and calls the -// underlying ResponseWriter's WriteMsg method. -func (r *Recorder) WriteMsg(res *dns.Msg) error { - r.Rcode = res.Rcode - // We may get called multiple times (axfr for instance). - // Save the last message, but add the sizes. - r.Len += res.Len() - r.Msg = res - return r.ResponseWriter.WriteMsg(res) -} - -// Write is a wrapper that records the length of the message that gets written. -func (r *Recorder) Write(buf []byte) (int, error) { - n, err := r.ResponseWriter.Write(buf) - if err == nil { - r.Len += n - } - return n, err -} - -// Hijack implements dns.Hijacker. It simply wraps the underlying -// ResponseWriter's Hijack method if there is one, or returns an error. -func (r *Recorder) Hijack() { r.ResponseWriter.Hijack(); return } diff --git a/middleware/pkg/dnsrecorder/recorder_test.go b/middleware/pkg/dnsrecorder/recorder_test.go deleted file mode 100644 index c9c2f6ce4..000000000 --- a/middleware/pkg/dnsrecorder/recorder_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package dnsrecorder - -/* -func TestNewResponseRecorder(t *testing.T) { - w := httptest.NewRecorder() - recordRequest := NewResponseRecorder(w) - if !(recordRequest.ResponseWriter == w) { - t.Fatalf("Expected Response writer in the Recording to be same as the one sent\n") - } - if recordRequest.status != http.StatusOK { - t.Fatalf("Expected recorded status to be http.StatusOK (%d) , but found %d\n ", http.StatusOK, recordRequest.status) - } -} - -func TestWrite(t *testing.T) { - w := httptest.NewRecorder() - responseTestString := "test" - recordRequest := NewResponseRecorder(w) - buf := []byte(responseTestString) - recordRequest.Write(buf) - if recordRequest.size != len(buf) { - t.Fatalf("Expected the bytes written counter to be %d, but instead found %d\n", len(buf), recordRequest.size) - } - if w.Body.String() != responseTestString { - t.Fatalf("Expected Response Body to be %s , but found %s\n", responseTestString, w.Body.String()) - } -} -*/ diff --git a/middleware/pkg/dnsutil/cname.go b/middleware/pkg/dnsutil/cname.go deleted file mode 100644 index 281e03218..000000000 --- a/middleware/pkg/dnsutil/cname.go +++ /dev/null @@ -1,15 +0,0 @@ -package dnsutil - -import "github.com/miekg/dns" - -// DuplicateCNAME returns true if r already exists in records. -func DuplicateCNAME(r *dns.CNAME, records []dns.RR) bool { - for _, rec := range records { - if v, ok := rec.(*dns.CNAME); ok { - if v.Target == r.Target { - return true - } - } - } - return false -} diff --git a/middleware/pkg/dnsutil/cname_test.go b/middleware/pkg/dnsutil/cname_test.go deleted file mode 100644 index 5fb8d3029..000000000 --- a/middleware/pkg/dnsutil/cname_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package dnsutil - -import ( - "testing" - - "github.com/miekg/dns" -) - -func TestDuplicateCNAME(t *testing.T) { - tests := []struct { - cname string - records []string - expected bool - }{ - { - "1.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 1.0.0.0.192.IN-ADDR.ARPA.", - []string{ - "US. 86400 IN NSEC 0-.us. NS SOA RRSIG NSEC DNSKEY TYPE65534", - "1.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 1.0.0.0.192.IN-ADDR.ARPA.", - }, - true, - }, - { - "1.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 1.0.0.0.192.IN-ADDR.ARPA.", - []string{ - "US. 86400 IN NSEC 0-.us. NS SOA RRSIG NSEC DNSKEY TYPE65534", - }, - false, - }, - { - "1.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 1.0.0.0.192.IN-ADDR.ARPA.", - []string{}, - false, - }, - } - for i, test := range tests { - cnameRR, err := dns.NewRR(test.cname) - if err != nil { - t.Fatalf("Test %d, cname ('%s') error (%s)!", i, test.cname, err) - } - cname := cnameRR.(*dns.CNAME) - records := []dns.RR{} - for j, r := range test.records { - rr, err := dns.NewRR(r) - if err != nil { - t.Fatalf("Test %d, record %d ('%s') error (%s)!", i, j, r, err) - } - records = append(records, rr) - } - got := DuplicateCNAME(cname, records) - if got != test.expected { - t.Errorf("Test %d, expected '%v', got '%v' for CNAME ('%s') and RECORDS (%v)", i, test.expected, got, test.cname, test.records) - } - } -} diff --git a/middleware/pkg/dnsutil/dedup.go b/middleware/pkg/dnsutil/dedup.go deleted file mode 100644 index dae656a01..000000000 --- a/middleware/pkg/dnsutil/dedup.go +++ /dev/null @@ -1,12 +0,0 @@ -package dnsutil - -import "github.com/miekg/dns" - -// Dedup de-duplicates a message. -func Dedup(m *dns.Msg) *dns.Msg { - // TODO(miek): expensive! - m.Answer = dns.Dedup(m.Answer, nil) - m.Ns = dns.Dedup(m.Ns, nil) - m.Extra = dns.Dedup(m.Extra, nil) - return m -} diff --git a/middleware/pkg/dnsutil/doc.go b/middleware/pkg/dnsutil/doc.go deleted file mode 100644 index 75d1e8c7a..000000000 --- a/middleware/pkg/dnsutil/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package dnsutil contains DNS related helper functions. -package dnsutil diff --git a/middleware/pkg/dnsutil/host.go b/middleware/pkg/dnsutil/host.go deleted file mode 100644 index aaab586e8..000000000 --- a/middleware/pkg/dnsutil/host.go +++ /dev/null @@ -1,82 +0,0 @@ -package dnsutil - -import ( - "fmt" - "net" - "os" - - "github.com/miekg/dns" -) - -// ParseHostPortOrFile parses the strings in s, each string can either be a address, -// address:port or a filename. The address part is checked and the filename case a -// resolv.conf like file is parsed and the nameserver found are returned. -func ParseHostPortOrFile(s ...string) ([]string, error) { - var servers []string - for _, host := range s { - addr, _, err := net.SplitHostPort(host) - if err != nil { - // Parse didn't work, it is not a addr:port combo - if net.ParseIP(host) == nil { - // Not an IP address. - ss, err := tryFile(host) - if err == nil { - servers = append(servers, ss...) - continue - } - return servers, fmt.Errorf("not an IP address or file: %q", host) - } - ss := net.JoinHostPort(host, "53") - servers = append(servers, ss) - continue - } - - if net.ParseIP(addr) == nil { - // No an IP address. - ss, err := tryFile(host) - if err == nil { - servers = append(servers, ss...) - continue - } - return servers, fmt.Errorf("not an IP address or file: %q", host) - } - servers = append(servers, host) - } - return servers, nil -} - -// Try to open this is a file first. -func tryFile(s string) ([]string, error) { - c, err := dns.ClientConfigFromFile(s) - if err == os.ErrNotExist { - return nil, fmt.Errorf("failed to open file %q: %q", s, err) - } else if err != nil { - return nil, err - } - - servers := []string{} - for _, s := range c.Servers { - servers = append(servers, net.JoinHostPort(s, c.Port)) - } - return servers, nil -} - -// ParseHostPort will check if the host part is a valid IP address, if the -// IP address is valid, but no port is found, defaultPort is added. -func ParseHostPort(s, defaultPort string) (string, error) { - addr, port, err := net.SplitHostPort(s) - if port == "" { - port = defaultPort - } - if err != nil { - if net.ParseIP(s) == nil { - return "", fmt.Errorf("must specify an IP address: `%s'", s) - } - return net.JoinHostPort(s, port), nil - } - - if net.ParseIP(addr) == nil { - return "", fmt.Errorf("must specify an IP address: `%s'", addr) - } - return net.JoinHostPort(addr, port), nil -} diff --git a/middleware/pkg/dnsutil/host_test.go b/middleware/pkg/dnsutil/host_test.go deleted file mode 100644 index cc55f4570..000000000 --- a/middleware/pkg/dnsutil/host_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package dnsutil - -import ( - "io/ioutil" - "os" - "testing" -) - -func TestParseHostPortOrFile(t *testing.T) { - tests := []struct { - in string - expected string - shouldErr bool - }{ - { - "8.8.8.8", - "8.8.8.8:53", - false, - }, - { - "8.8.8.8:153", - "8.8.8.8:153", - false, - }, - { - "/etc/resolv.conf:53", - "", - true, - }, - { - "resolv.conf", - "127.0.0.1:53", - false, - }, - } - - err := ioutil.WriteFile("resolv.conf", []byte("nameserver 127.0.0.1\n"), 0600) - if err != nil { - t.Fatalf("Failed to write test resolv.conf") - } - defer os.Remove("resolv.conf") - - for i, tc := range tests { - got, err := ParseHostPortOrFile(tc.in) - if err == nil && tc.shouldErr { - t.Errorf("Test %d, expected error, got nil", i) - continue - } - if err != nil && tc.shouldErr { - continue - } - if got[0] != tc.expected { - t.Errorf("Test %d, expected %q, got %q", i, tc.expected, got[0]) - } - } -} - -func TestParseHostPort(t *testing.T) { - tests := []struct { - in string - expected string - shouldErr bool - }{ - {"8.8.8.8:53", "8.8.8.8:53", false}, - {"a.a.a.a:153", "", true}, - {"8.8.8.8", "8.8.8.8:53", false}, - {"8.8.8.8:", "8.8.8.8:53", false}, - {"8.8.8.8::53", "", true}, - {"resolv.conf", "", true}, - } - - for i, tc := range tests { - got, err := ParseHostPort(tc.in, "53") - if err == nil && tc.shouldErr { - t.Errorf("Test %d, expected error, got nil", i) - continue - } - if err != nil && !tc.shouldErr { - t.Errorf("Test %d, expected no error, got %q", i, err) - } - if got != tc.expected { - t.Errorf("Test %d, expected %q, got %q", i, tc.expected, got) - } - } -} diff --git a/middleware/pkg/dnsutil/join.go b/middleware/pkg/dnsutil/join.go deleted file mode 100644 index 515bf3dad..000000000 --- a/middleware/pkg/dnsutil/join.go +++ /dev/null @@ -1,19 +0,0 @@ -package dnsutil - -import ( - "strings" - - "github.com/miekg/dns" -) - -// Join joins labels to form a fully qualified domain name. If the last label is -// the root label it is ignored. Not other syntax checks are performed. -func Join(labels []string) string { - ll := len(labels) - if labels[ll-1] == "." { - s := strings.Join(labels[:ll-1], ".") - return dns.Fqdn(s) - } - s := strings.Join(labels, ".") - return dns.Fqdn(s) -} diff --git a/middleware/pkg/dnsutil/join_test.go b/middleware/pkg/dnsutil/join_test.go deleted file mode 100644 index 26eeb5897..000000000 --- a/middleware/pkg/dnsutil/join_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package dnsutil - -import "testing" - -func TestJoin(t *testing.T) { - tests := []struct { - in []string - out string - }{ - {[]string{"bla", "bliep", "example", "org"}, "bla.bliep.example.org."}, - {[]string{"example", "."}, "example."}, - {[]string{"."}, "."}, - } - - for i, tc := range tests { - if x := Join(tc.in); x != tc.out { - t.Errorf("Test %d, expected %s, got %s", i, tc.out, x) - } - } -} diff --git a/middleware/pkg/dnsutil/reverse.go b/middleware/pkg/dnsutil/reverse.go deleted file mode 100644 index daf9cc600..000000000 --- a/middleware/pkg/dnsutil/reverse.go +++ /dev/null @@ -1,68 +0,0 @@ -package dnsutil - -import ( - "net" - "strings" -) - -// ExtractAddressFromReverse turns a standard PTR reverse record name -// into an IP address. This works for ipv4 or ipv6. -// -// 54.119.58.176.in-addr.arpa. becomes 176.58.119.54. If the conversion -// failes the empty string is returned. -func ExtractAddressFromReverse(reverseName string) string { - search := "" - - f := reverse - - switch { - case strings.HasSuffix(reverseName, v4arpaSuffix): - search = strings.TrimSuffix(reverseName, v4arpaSuffix) - case strings.HasSuffix(reverseName, v6arpaSuffix): - search = strings.TrimSuffix(reverseName, v6arpaSuffix) - f = reverse6 - default: - return "" - } - - // Reverse the segments and then combine them. - return f(strings.Split(search, ".")) -} - -func reverse(slice []string) string { - for i := 0; i < len(slice)/2; i++ { - j := len(slice) - i - 1 - slice[i], slice[j] = slice[j], slice[i] - } - ip := net.ParseIP(strings.Join(slice, ".")).To4() - if ip == nil { - return "" - } - return ip.String() -} - -// reverse6 reverse the segments and combine them according to RFC3596: -// b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2 -// is reversed to 2001:db8::567:89ab -func reverse6(slice []string) string { - for i := 0; i < len(slice)/2; i++ { - j := len(slice) - i - 1 - slice[i], slice[j] = slice[j], slice[i] - } - slice6 := []string{} - for i := 0; i < len(slice)/4; i++ { - slice6 = append(slice6, strings.Join(slice[i*4:i*4+4], "")) - } - ip := net.ParseIP(strings.Join(slice6, ":")).To16() - if ip == nil { - return "" - } - return ip.String() -} - -const ( - // v4arpaSuffix is the reverse tree suffix for v4 IP addresses. - v4arpaSuffix = ".in-addr.arpa." - // v6arpaSuffix is the reverse tree suffix for v6 IP addresses. - v6arpaSuffix = ".ip6.arpa." -) diff --git a/middleware/pkg/dnsutil/reverse_test.go b/middleware/pkg/dnsutil/reverse_test.go deleted file mode 100644 index 25bd897ac..000000000 --- a/middleware/pkg/dnsutil/reverse_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package dnsutil - -import ( - "testing" -) - -func TestExtractAddressFromReverse(t *testing.T) { - tests := []struct { - reverseName string - expectedAddress string - }{ - { - "54.119.58.176.in-addr.arpa.", - "176.58.119.54", - }, - { - ".58.176.in-addr.arpa.", - "", - }, - { - "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.in-addr.arpa.", - "", - }, - { - "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", - "2001:db8::567:89ab", - }, - { - "d.0.1.0.0.2.ip6.arpa.", - "", - }, - { - "54.119.58.176.ip6.arpa.", - "", - }, - { - "NONAME", - "", - }, - { - "", - "", - }, - } - for i, test := range tests { - got := ExtractAddressFromReverse(test.reverseName) - if got != test.expectedAddress { - t.Errorf("Test %d, expected '%s', got '%s'", i, test.expectedAddress, got) - } - } -} diff --git a/middleware/pkg/dnsutil/zone.go b/middleware/pkg/dnsutil/zone.go deleted file mode 100644 index 579fef1ba..000000000 --- a/middleware/pkg/dnsutil/zone.go +++ /dev/null @@ -1,20 +0,0 @@ -package dnsutil - -import ( - "errors" - - "github.com/miekg/dns" -) - -// TrimZone removes the zone component from q. It returns the trimmed -// name or an error is zone is longer then qname. The trimmed name will be returned -// without a trailing dot. -func TrimZone(q string, z string) (string, error) { - zl := dns.CountLabel(z) - i, ok := dns.PrevLabel(q, zl) - if ok || i-1 < 0 { - return "", errors.New("trimzone: overshot qname: " + q + "for zone " + z) - } - // This includes the '.', remove on return - return q[:i-1], nil -} diff --git a/middleware/pkg/dnsutil/zone_test.go b/middleware/pkg/dnsutil/zone_test.go deleted file mode 100644 index 81cd1adad..000000000 --- a/middleware/pkg/dnsutil/zone_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package dnsutil - -import ( - "errors" - "testing" - - "github.com/miekg/dns" -) - -func TestTrimZone(t *testing.T) { - tests := []struct { - qname string - zone string - expected string - err error - }{ - {"a.example.org", "example.org", "a", nil}, - {"a.b.example.org", "example.org", "a.b", nil}, - {"b.", ".", "b", nil}, - {"example.org", "example.org", "", errors.New("should err")}, - {"org", "example.org", "", errors.New("should err")}, - } - - for i, tc := range tests { - got, err := TrimZone(dns.Fqdn(tc.qname), dns.Fqdn(tc.zone)) - if tc.err != nil && err == nil { - t.Errorf("Test %d, expected error got nil", i) - continue - } - if tc.err == nil && err != nil { - t.Errorf("Test %d, expected no error got %v", i, err) - continue - } - if got != tc.expected { - t.Errorf("Test %d, expected %s, got %s", i, tc.expected, got) - continue - } - } -} diff --git a/middleware/pkg/edns/edns.go b/middleware/pkg/edns/edns.go deleted file mode 100644 index 6cbe2cdae..000000000 --- a/middleware/pkg/edns/edns.go +++ /dev/null @@ -1,46 +0,0 @@ -// Package edns provides function useful for adding/inspecting OPT records to/in messages. -package edns - -import ( - "errors" - - "github.com/miekg/dns" -) - -// Version checks the EDNS version in the request. If error -// is nil everything is OK and we can invoke the middleware. If non-nil, the -// returned Msg is valid to be returned to the client (and should). For some -// reason this response should not contain a question RR in the question section. -func Version(req *dns.Msg) (*dns.Msg, error) { - opt := req.IsEdns0() - if opt == nil { - return nil, nil - } - if opt.Version() == 0 { - return nil, nil - } - m := new(dns.Msg) - m.SetReply(req) - // zero out question section, wtf. - m.Question = nil - - o := new(dns.OPT) - o.Hdr.Name = "." - o.Hdr.Rrtype = dns.TypeOPT - o.SetVersion(0) - o.SetExtendedRcode(dns.RcodeBadVers) - m.Extra = []dns.RR{o} - - return m, errors.New("EDNS0 BADVERS") -} - -// Size returns a normalized size based on proto. -func Size(proto string, size int) int { - if proto == "tcp" { - return dns.MaxMsgSize - } - if size < dns.MinMsgSize { - return dns.MinMsgSize - } - return size -} diff --git a/middleware/pkg/edns/edns_test.go b/middleware/pkg/edns/edns_test.go deleted file mode 100644 index 89ac6d2ec..000000000 --- a/middleware/pkg/edns/edns_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package edns - -import ( - "testing" - - "github.com/miekg/dns" -) - -func TestVersion(t *testing.T) { - m := ednsMsg() - m.Extra[0].(*dns.OPT).SetVersion(2) - - _, err := Version(m) - if err == nil { - t.Errorf("expected wrong version, but got OK") - } -} - -func TestVersionNoEdns(t *testing.T) { - m := ednsMsg() - m.Extra = nil - - _, err := Version(m) - if err != nil { - t.Errorf("expected no error, but got one: %s", err) - } -} - -func ednsMsg() *dns.Msg { - m := new(dns.Msg) - m.SetQuestion("example.com.", dns.TypeA) - o := new(dns.OPT) - o.Hdr.Name = "." - o.Hdr.Rrtype = dns.TypeOPT - m.Extra = append(m.Extra, o) - return m -} diff --git a/middleware/pkg/healthcheck/healthcheck.go b/middleware/pkg/healthcheck/healthcheck.go deleted file mode 100644 index 18f09087c..000000000 --- a/middleware/pkg/healthcheck/healthcheck.go +++ /dev/null @@ -1,243 +0,0 @@ -package healthcheck - -import ( - "io" - "io/ioutil" - "log" - "net" - "net/http" - "net/url" - "sync" - "sync/atomic" - "time" -) - -// UpstreamHostDownFunc can be used to customize how Down behaves. -type UpstreamHostDownFunc func(*UpstreamHost) bool - -// UpstreamHost represents a single proxy upstream -type UpstreamHost struct { - Conns int64 // must be first field to be 64-bit aligned on 32-bit systems - Name string // IP address (and port) of this upstream host - Network string // Network (tcp, unix, etc) of the host, default "" is "tcp" - Fails int32 - FailTimeout time.Duration - OkUntil time.Time - CheckDown UpstreamHostDownFunc - CheckURL string - WithoutPathPrefix string - Checking bool - CheckMu sync.Mutex -} - -// Down checks whether the upstream host is down or not. -// Down will try to use uh.CheckDown first, and will fall -// back to some default criteria if necessary. -func (uh *UpstreamHost) Down() bool { - if uh.CheckDown == nil { - // Default settings - fails := atomic.LoadInt32(&uh.Fails) - after := false - - uh.CheckMu.Lock() - until := uh.OkUntil - uh.CheckMu.Unlock() - - if !until.IsZero() && time.Now().After(until) { - after = true - } - - return after || fails > 0 - } - return uh.CheckDown(uh) -} - -// HostPool is a collection of UpstreamHosts. -type HostPool []*UpstreamHost - -// HealthCheck is used for performing healthcheck -// on a collection of upstream hosts and select -// one based on the policy. -type HealthCheck struct { - wg sync.WaitGroup // Used to wait for running goroutines to stop. - stop chan struct{} // Signals running goroutines to stop. - Hosts HostPool - Policy Policy - Spray Policy - FailTimeout time.Duration - MaxFails int32 - Future time.Duration - Path string - Port string - Interval time.Duration -} - -// Start starts the healthcheck -func (u *HealthCheck) Start() { - u.stop = make(chan struct{}) - if u.Path != "" { - u.wg.Add(1) - go func() { - defer u.wg.Done() - u.healthCheckWorker(u.stop) - }() - } -} - -// Stop sends a signal to all goroutines started by this staticUpstream to exit -// and waits for them to finish before returning. -func (u *HealthCheck) Stop() error { - close(u.stop) - u.wg.Wait() - return nil -} - -// This was moved into a thread so that each host could throw a health -// check at the same time. The reason for this is that if we are checking -// 3 hosts, and the first one is gone, and we spend minutes timing out to -// fail it, we would not have been doing any other health checks in that -// time. So we now have a per-host lock and a threaded health check. -// -// We use the Checking bool to avoid concurrent checks against the same -// host; if one is taking a long time, the next one will find a check in -// progress and simply return before trying. -// -// We are carefully avoiding having the mutex locked while we check, -// otherwise checks will back up, potentially a lot of them if a host is -// absent for a long time. This arrangement makes checks quickly see if -// they are the only one running and abort otherwise. -func healthCheckURL(nextTs time.Time, host *UpstreamHost) { - - // lock for our bool check. We don't just defer the unlock because - // we don't want the lock held while http.Get runs - host.CheckMu.Lock() - - // are we mid check? Don't run another one - if host.Checking { - host.CheckMu.Unlock() - return - } - - host.Checking = true - host.CheckMu.Unlock() - - //log.Printf("[DEBUG] Healthchecking %s, nextTs is %s\n", url, nextTs.Local()) - - // fetch that url. This has been moved into a go func because - // when the remote host is not merely not serving, but actually - // absent, then tcp syn timeouts can be very long, and so one - // fetch could last several check intervals - if r, err := http.Get(host.CheckURL); err == nil { - io.Copy(ioutil.Discard, r.Body) - r.Body.Close() - - if r.StatusCode < 200 || r.StatusCode >= 400 { - log.Printf("[WARNING] Host %s health check returned HTTP code %d\n", - host.Name, r.StatusCode) - nextTs = time.Unix(0, 0) - } - } else { - log.Printf("[WARNING] Host %s health check probe failed: %v\n", host.Name, err) - nextTs = time.Unix(0, 0) - } - - host.CheckMu.Lock() - host.Checking = false - host.OkUntil = nextTs - host.CheckMu.Unlock() -} - -func (u *HealthCheck) healthCheck() { - for _, host := range u.Hosts { - - if host.CheckURL == "" { - var hostName, checkPort string - - // The DNS server might be an HTTP server. If so, extract its name. - ret, err := url.Parse(host.Name) - if err == nil && len(ret.Host) > 0 { - hostName = ret.Host - } else { - hostName = host.Name - } - - // Extract the port number from the parsed server name. - checkHostName, checkPort, err := net.SplitHostPort(hostName) - if err != nil { - checkHostName = hostName - } - - if u.Port != "" { - checkPort = u.Port - } - - host.CheckURL = "http://" + net.JoinHostPort(checkHostName, checkPort) + u.Path - } - - // calculate this before the get - nextTs := time.Now().Add(u.Future) - - // locks/bools should prevent requests backing up - go healthCheckURL(nextTs, host) - } -} - -func (u *HealthCheck) healthCheckWorker(stop chan struct{}) { - ticker := time.NewTicker(u.Interval) - u.healthCheck() - for { - select { - case <-ticker.C: - u.healthCheck() - case <-stop: - ticker.Stop() - return - } - } -} - -// Select selects an upstream host based on the policy -// and the healthcheck result. -func (u *HealthCheck) Select() *UpstreamHost { - pool := u.Hosts - if len(pool) == 1 { - if pool[0].Down() && u.Spray == nil { - return nil - } - return pool[0] - } - allDown := true - for _, host := range pool { - if !host.Down() { - allDown = false - break - } - } - if allDown { - if u.Spray == nil { - return nil - } - return u.Spray.Select(pool) - } - - if u.Policy == nil { - h := (&Random{}).Select(pool) - if h != nil { - return h - } - if h == nil && u.Spray == nil { - return nil - } - return u.Spray.Select(pool) - } - - h := u.Policy.Select(pool) - if h != nil { - return h - } - - if u.Spray == nil { - return nil - } - return u.Spray.Select(pool) -} diff --git a/middleware/pkg/healthcheck/policy.go b/middleware/pkg/healthcheck/policy.go deleted file mode 100644 index 6a828fc4d..000000000 --- a/middleware/pkg/healthcheck/policy.go +++ /dev/null @@ -1,120 +0,0 @@ -package healthcheck - -import ( - "log" - "math/rand" - "sync/atomic" -) - -var ( - // SupportedPolicies is the collection of policies registered - SupportedPolicies = make(map[string]func() Policy) -) - -// RegisterPolicy adds a custom policy to the proxy. -func RegisterPolicy(name string, policy func() Policy) { - SupportedPolicies[name] = policy -} - -// Policy decides how a host will be selected from a pool. When all hosts are unhealthy, it is assumed the -// healthchecking failed. In this case each policy will *randomly* return a host from the pool to prevent -// no traffic to go through at all. -type Policy interface { - Select(pool HostPool) *UpstreamHost -} - -func init() { - RegisterPolicy("random", func() Policy { return &Random{} }) - RegisterPolicy("least_conn", func() Policy { return &LeastConn{} }) - RegisterPolicy("round_robin", func() Policy { return &RoundRobin{} }) -} - -// Random is a policy that selects up hosts from a pool at random. -type Random struct{} - -// Select selects an up host at random from the specified pool. -func (r *Random) Select(pool HostPool) *UpstreamHost { - // instead of just generating a random index - // this is done to prevent selecting a down host - var randHost *UpstreamHost - count := 0 - for _, host := range pool { - if host.Down() { - continue - } - count++ - if count == 1 { - randHost = host - } else { - r := rand.Int() % count - if r == (count - 1) { - randHost = host - } - } - } - return randHost -} - -// Spray is a policy that selects a host from a pool at random. This should be used as a last ditch -// attempt to get a host when all hosts are reporting unhealthy. -type Spray struct{} - -// Select selects an up host at random from the specified pool. -func (r *Spray) Select(pool HostPool) *UpstreamHost { - rnd := rand.Int() % len(pool) - randHost := pool[rnd] - log.Printf("[WARNING] All hosts reported as down, spraying to target: %s", randHost.Name) - return randHost -} - -// LeastConn is a policy that selects the host with the least connections. -type LeastConn struct{} - -// Select selects the up host with the least number of connections in the -// pool. If more than one host has the same least number of connections, -// one of the hosts is chosen at random. -func (r *LeastConn) Select(pool HostPool) *UpstreamHost { - var bestHost *UpstreamHost - count := 0 - leastConn := int64(1<<63 - 1) - for _, host := range pool { - if host.Down() { - continue - } - hostConns := host.Conns - if hostConns < leastConn { - bestHost = host - leastConn = hostConns - count = 1 - } else if hostConns == leastConn { - // randomly select host among hosts with least connections - count++ - if count == 1 { - bestHost = host - } else { - r := rand.Int() % count - if r == (count - 1) { - bestHost = host - } - } - } - } - return bestHost -} - -// RoundRobin is a policy that selects hosts based on round robin ordering. -type RoundRobin struct { - Robin uint32 -} - -// Select selects an up host from the pool using a round robin ordering scheme. -func (r *RoundRobin) Select(pool HostPool) *UpstreamHost { - poolLen := uint32(len(pool)) - selection := atomic.AddUint32(&r.Robin, 1) % poolLen - host := pool[selection] - // if the currently selected host is down, just ffwd to up host - for i := uint32(1); host.Down() && i < poolLen; i++ { - host = pool[(selection+i)%poolLen] - } - return host -} diff --git a/middleware/pkg/healthcheck/policy_test.go b/middleware/pkg/healthcheck/policy_test.go deleted file mode 100644 index 4c667952c..000000000 --- a/middleware/pkg/healthcheck/policy_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package healthcheck - -import ( - "io/ioutil" - "log" - "net/http" - "net/http/httptest" - "os" - "testing" - "time" -) - -var workableServer *httptest.Server - -func TestMain(m *testing.M) { - workableServer = httptest.NewServer(http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - // do nothing - })) - r := m.Run() - workableServer.Close() - os.Exit(r) -} - -type customPolicy struct{} - -func (r *customPolicy) Select(pool HostPool) *UpstreamHost { - return pool[0] -} - -func testPool() HostPool { - pool := []*UpstreamHost{ - { - Name: workableServer.URL, // this should resolve (healthcheck test) - }, - { - Name: "http://shouldnot.resolve", // this shouldn't - }, - { - Name: "http://C", - }, - } - return HostPool(pool) -} - -func TestRegisterPolicy(t *testing.T) { - name := "custom" - customPolicy := &customPolicy{} - RegisterPolicy(name, func() Policy { return customPolicy }) - if _, ok := SupportedPolicies[name]; !ok { - t.Error("Expected supportedPolicies to have a custom policy.") - } - -} - -// TODO(miek): Disabled for now, we should get out of the habit of using -// realtime in these tests . -func testHealthCheck(t *testing.T) { - log.SetOutput(ioutil.Discard) - - u := &HealthCheck{ - Hosts: testPool(), - FailTimeout: 10 * time.Second, - Future: 60 * time.Second, - MaxFails: 1, - } - - u.healthCheck() - // sleep a bit, it's async now - time.Sleep(time.Duration(2 * time.Second)) - - if u.Hosts[0].Down() { - t.Error("Expected first host in testpool to not fail healthcheck.") - } - if !u.Hosts[1].Down() { - t.Error("Expected second host in testpool to fail healthcheck.") - } -} - -func TestSelect(t *testing.T) { - u := &HealthCheck{ - Hosts: testPool()[:3], - FailTimeout: 10 * time.Second, - Future: 60 * time.Second, - MaxFails: 1, - } - u.Hosts[0].OkUntil = time.Unix(0, 0) - u.Hosts[1].OkUntil = time.Unix(0, 0) - u.Hosts[2].OkUntil = time.Unix(0, 0) - if h := u.Select(); h != nil { - t.Error("Expected select to return nil as all host are down") - } - u.Hosts[2].OkUntil = time.Time{} - if h := u.Select(); h == nil { - t.Error("Expected select to not return nil") - } -} - -func TestRoundRobinPolicy(t *testing.T) { - pool := testPool() - rrPolicy := &RoundRobin{} - h := rrPolicy.Select(pool) - // First selected host is 1, because counter starts at 0 - // and increments before host is selected - if h != pool[1] { - t.Error("Expected first round robin host to be second host in the pool.") - } - h = rrPolicy.Select(pool) - if h != pool[2] { - t.Error("Expected second round robin host to be third host in the pool.") - } - // mark host as down - pool[0].OkUntil = time.Unix(0, 0) - h = rrPolicy.Select(pool) - if h != pool[1] { - t.Error("Expected third round robin host to be first host in the pool.") - } -} - -func TestLeastConnPolicy(t *testing.T) { - pool := testPool() - lcPolicy := &LeastConn{} - pool[0].Conns = 10 - pool[1].Conns = 10 - h := lcPolicy.Select(pool) - if h != pool[2] { - t.Error("Expected least connection host to be third host.") - } - pool[2].Conns = 100 - h = lcPolicy.Select(pool) - if h != pool[0] && h != pool[1] { - t.Error("Expected least connection host to be first or second host.") - } -} - -func TestCustomPolicy(t *testing.T) { - pool := testPool() - customPolicy := &customPolicy{} - h := customPolicy.Select(pool) - if h != pool[0] { - t.Error("Expected custom policy host to be the first host.") - } -} diff --git a/middleware/pkg/nonwriter/nonwriter.go b/middleware/pkg/nonwriter/nonwriter.go deleted file mode 100644 index 7819a320f..000000000 --- a/middleware/pkg/nonwriter/nonwriter.go +++ /dev/null @@ -1,23 +0,0 @@ -// Package nonwriter implements a dns.ResponseWriter that never writes, but captures the dns.Msg being written. -package nonwriter - -import ( - "github.com/miekg/dns" -) - -// Writer is a type of ResponseWriter that captures the message, but never writes to the client. -type Writer struct { - dns.ResponseWriter - Msg *dns.Msg -} - -// New makes and returns a new NonWriter. -func New(w dns.ResponseWriter) *Writer { return &Writer{ResponseWriter: w} } - -// WriteMsg records the message, but doesn't write it itself. -func (w *Writer) WriteMsg(res *dns.Msg) error { - w.Msg = res - return nil -} - -func (w *Writer) Write(buf []byte) (int, error) { return len(buf), nil } diff --git a/middleware/pkg/nonwriter/nonwriter_test.go b/middleware/pkg/nonwriter/nonwriter_test.go deleted file mode 100644 index d8433af55..000000000 --- a/middleware/pkg/nonwriter/nonwriter_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package nonwriter - -import ( - "testing" - - "github.com/miekg/dns" -) - -func TestNonWriter(t *testing.T) { - nw := New(nil) - m := new(dns.Msg) - m.SetQuestion("example.org.", dns.TypeA) - if err := nw.WriteMsg(m); err != nil { - t.Errorf("Got error when writing to nonwriter: %s", err) - } - if x := nw.Msg.Question[0].Name; x != "example.org." { - t.Errorf("Expacted 'example.org.' got %q:", x) - } -} diff --git a/middleware/pkg/rcode/rcode.go b/middleware/pkg/rcode/rcode.go deleted file mode 100644 index 32863f0b2..000000000 --- a/middleware/pkg/rcode/rcode.go +++ /dev/null @@ -1,16 +0,0 @@ -package rcode - -import ( - "strconv" - - "github.com/miekg/dns" -) - -// ToString convert the rcode to the official DNS string, or to "RCODE"+value if the RCODE -// value is unknown. -func ToString(rcode int) string { - if str, ok := dns.RcodeToString[rcode]; ok { - return str - } - return "RCODE" + strconv.Itoa(rcode) -} diff --git a/middleware/pkg/rcode/rcode_test.go b/middleware/pkg/rcode/rcode_test.go deleted file mode 100644 index bfca32f1d..000000000 --- a/middleware/pkg/rcode/rcode_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package rcode - -import ( - "testing" - - "github.com/miekg/dns" -) - -func TestToString(t *testing.T) { - tests := []struct { - in int - expected string - }{ - { - dns.RcodeSuccess, - "NOERROR", - }, - { - 28, - "RCODE28", - }, - } - for i, test := range tests { - got := ToString(test.in) - if got != test.expected { - t.Errorf("Test %d, expected %s, got %s", i, test.expected, got) - } - } -} diff --git a/middleware/pkg/replacer/replacer.go b/middleware/pkg/replacer/replacer.go deleted file mode 100644 index 91f17e17c..000000000 --- a/middleware/pkg/replacer/replacer.go +++ /dev/null @@ -1,161 +0,0 @@ -package replacer - -import ( - "strconv" - "strings" - "time" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// Replacer is a type which can replace placeholder -// substrings in a string with actual values from a -// dns.Msg and responseRecorder. Always use -// NewReplacer to get one of these. -type Replacer interface { - Replace(string) string - Set(key, value string) -} - -type replacer struct { - replacements map[string]string - emptyValue string -} - -// New makes a new replacer based on r and rr. -// Do not create a new replacer until r and rr have all -// the needed values, because this function copies those -// values into the replacer. rr may be nil if it is not -// available. emptyValue should be the string that is used -// in place of empty string (can still be empty string). -func New(r *dns.Msg, rr *dnsrecorder.Recorder, emptyValue string) Replacer { - req := request.Request{W: rr, Req: r} - rep := replacer{ - replacements: map[string]string{ - "{type}": req.Type(), - "{name}": req.Name(), - "{class}": req.Class(), - "{proto}": req.Proto(), - "{when}": func() string { - return time.Now().Format(timeFormat) - }(), - "{size}": strconv.Itoa(req.Len()), - "{remote}": req.IP(), - "{port}": req.Port(), - }, - emptyValue: emptyValue, - } - if rr != nil { - rcode := dns.RcodeToString[rr.Rcode] - if rcode == "" { - rcode = strconv.Itoa(rr.Rcode) - } - rep.replacements["{rcode}"] = rcode - rep.replacements["{rsize}"] = strconv.Itoa(rr.Len) - rep.replacements["{duration}"] = time.Since(rr.Start).String() - if rr.Msg != nil { - rep.replacements[headerReplacer+"rflags}"] = flagsToString(rr.Msg.MsgHdr) - } - } - - // Header placeholders (case-insensitive) - rep.replacements[headerReplacer+"id}"] = strconv.Itoa(int(r.Id)) - rep.replacements[headerReplacer+"opcode}"] = strconv.Itoa(r.Opcode) - rep.replacements[headerReplacer+"do}"] = boolToString(req.Do()) - rep.replacements[headerReplacer+"bufsize}"] = strconv.Itoa(req.Size()) - - return rep -} - -// Replace performs a replacement of values on s and returns -// the string with the replaced values. -func (r replacer) Replace(s string) string { - // Header replacements - these are case-insensitive, so we can't just use strings.Replace() - for strings.Contains(s, headerReplacer) { - idxStart := strings.Index(s, headerReplacer) - endOffset := idxStart + len(headerReplacer) - idxEnd := strings.Index(s[endOffset:], "}") - if idxEnd > -1 { - placeholder := strings.ToLower(s[idxStart : endOffset+idxEnd+1]) - replacement := r.replacements[placeholder] - if replacement == "" { - replacement = r.emptyValue - } - s = s[:idxStart] + replacement + s[endOffset+idxEnd+1:] - } else { - break - } - } - - // Regular replacements - these are easier because they're case-sensitive - for placeholder, replacement := range r.replacements { - if replacement == "" { - replacement = r.emptyValue - } - s = strings.Replace(s, placeholder, replacement, -1) - } - - return s -} - -// Set sets key to value in the replacements map. -func (r replacer) Set(key, value string) { - r.replacements["{"+key+"}"] = value -} - -func boolToString(b bool) string { - if b { - return "true" - } - return "false" -} - -// flagsToString checks all header flags and returns those -// that are set as a string separated with commas -func flagsToString(h dns.MsgHdr) string { - flags := make([]string, 7) - i := 0 - - if h.Response { - flags[i] = "qr" - i++ - } - - if h.Authoritative { - flags[i] = "aa" - i++ - } - if h.Truncated { - flags[i] = "tc" - i++ - } - if h.RecursionDesired { - flags[i] = "rd" - i++ - } - if h.RecursionAvailable { - flags[i] = "ra" - i++ - } - if h.Zero { - flags[i] = "z" - i++ - } - if h.AuthenticatedData { - flags[i] = "ad" - i++ - } - if h.CheckingDisabled { - flags[i] = "cd" - i++ - } - return strings.Join(flags[:i], ",") -} - -const ( - timeFormat = "02/Jan/2006:15:04:05 -0700" - headerReplacer = "{>" -) diff --git a/middleware/pkg/replacer/replacer_test.go b/middleware/pkg/replacer/replacer_test.go deleted file mode 100644 index a72f429a8..000000000 --- a/middleware/pkg/replacer/replacer_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package replacer - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" -) - -func TestNewReplacer(t *testing.T) { - w := dnsrecorder.New(&test.ResponseWriter{}) - - r := new(dns.Msg) - r.SetQuestion("example.org.", dns.TypeHINFO) - r.MsgHdr.AuthenticatedData = true - - replaceValues := New(r, w, "") - - switch v := replaceValues.(type) { - case replacer: - - if v.replacements["{type}"] != "HINFO" { - t.Errorf("Expected type to be HINFO, got %q", v.replacements["{type}"]) - } - if v.replacements["{name}"] != "example.org." { - t.Errorf("Expected request name to be example.org., got %q", v.replacements["{name}"]) - } - if v.replacements["{size}"] != "29" { // size of request - t.Errorf("Expected size to be 29, got %q", v.replacements["{size}"]) - } - - default: - t.Fatal("Return Value from New Replacer expected pass type assertion into a replacer type\n") - } -} - -func TestSet(t *testing.T) { - w := dnsrecorder.New(&test.ResponseWriter{}) - - r := new(dns.Msg) - r.SetQuestion("example.org.", dns.TypeHINFO) - r.MsgHdr.AuthenticatedData = true - - repl := New(r, w, "") - - repl.Set("name", "coredns.io.") - repl.Set("type", "A") - repl.Set("size", "20") - - if repl.Replace("This name is {name}") != "This name is coredns.io." { - t.Error("Expected name replacement failed") - } - if repl.Replace("This type is {type}") != "This type is A" { - t.Error("Expected type replacement failed") - } - if repl.Replace("The request size is {size}") != "The request size is 20" { - t.Error("Expected size replacement failed") - } -} diff --git a/middleware/pkg/response/classify.go b/middleware/pkg/response/classify.go deleted file mode 100644 index 2e705cb0b..000000000 --- a/middleware/pkg/response/classify.go +++ /dev/null @@ -1,61 +0,0 @@ -package response - -import "fmt" - -// Class holds sets of Types -type Class int - -const ( - // All is a meta class encompassing all the classes. - All Class = iota - // Success is a class for a successful response. - Success - // Denial is a class for denying existence (NXDOMAIN, or a nodata: type does not exist) - Denial - // Error is a class for errors, right now defined as not Success and not Denial - Error -) - -func (c Class) String() string { - switch c { - case All: - return "all" - case Success: - return "success" - case Denial: - return "denial" - case Error: - return "error" - } - return "" -} - -// ClassFromString returns the class from the string s. If not class matches -// the All class and an error are returned -func ClassFromString(s string) (Class, error) { - switch s { - case "all": - return All, nil - case "success": - return Success, nil - case "denial": - return Denial, nil - case "error": - return Error, nil - } - return All, fmt.Errorf("invalid Class: %s", s) -} - -// Classify classifies the Type t, it returns its Class. -func Classify(t Type) Class { - switch t { - case NoError, Delegation: - return Success - case NameError, NoData: - return Denial - case OtherError: - fallthrough - default: - return Error - } -} diff --git a/middleware/pkg/response/typify.go b/middleware/pkg/response/typify.go deleted file mode 100644 index 7cfaab497..000000000 --- a/middleware/pkg/response/typify.go +++ /dev/null @@ -1,146 +0,0 @@ -package response - -import ( - "fmt" - "time" - - "github.com/miekg/dns" -) - -// Type is the type of the message. -type Type int - -const ( - // NoError indicates a positive reply - NoError Type = iota - // NameError is a NXDOMAIN in header, SOA in auth. - NameError - // NoData indicates name found, but not the type: NOERROR in header, SOA in auth. - NoData - // Delegation is a msg with a pointer to another nameserver: NOERROR in header, NS in auth, optionally fluff in additional (not checked). - Delegation - // Meta indicates a meta message, NOTIFY, or a transfer: qType is IXFR or AXFR. - Meta - // Update is an dynamic update message. - Update - // OtherError indicates any other error: don't cache these. - OtherError -) - -var toString = map[Type]string{ - NoError: "NOERROR", - NameError: "NXDOMAIN", - NoData: "NODATA", - Delegation: "DELEGATION", - Meta: "META", - Update: "UPDATE", - OtherError: "OTHERERROR", -} - -func (t Type) String() string { return toString[t] } - -// TypeFromString returns the type from the string s. If not type matches -// the OtherError type and an error are returned. -func TypeFromString(s string) (Type, error) { - for t, str := range toString { - if s == str { - return t, nil - } - } - return NoError, fmt.Errorf("invalid Type: %s", s) -} - -// Typify classifies a message, it returns the Type. -func Typify(m *dns.Msg, t time.Time) (Type, *dns.OPT) { - if m == nil { - return OtherError, nil - } - opt := m.IsEdns0() - do := false - if opt != nil { - do = opt.Do() - } - - if m.Opcode == dns.OpcodeUpdate { - return Update, opt - } - - // Check transfer and update first - if m.Opcode == dns.OpcodeNotify { - return Meta, opt - } - - if len(m.Question) > 0 { - if m.Question[0].Qtype == dns.TypeAXFR || m.Question[0].Qtype == dns.TypeIXFR { - return Meta, opt - } - } - - // If our message contains any expired sigs and we care about that, we should return expired - if do { - if expired := typifyExpired(m, t); expired { - return OtherError, opt - } - } - - if len(m.Answer) > 0 && m.Rcode == dns.RcodeSuccess { - return NoError, opt - } - - soa := false - ns := 0 - for _, r := range m.Ns { - if r.Header().Rrtype == dns.TypeSOA { - soa = true - continue - } - if r.Header().Rrtype == dns.TypeNS { - ns++ - } - } - - // Check length of different sections, and drop stuff that is just to large? TODO(miek). - - if soa && m.Rcode == dns.RcodeSuccess { - return NoData, opt - } - if soa && m.Rcode == dns.RcodeNameError { - return NameError, opt - } - - if ns > 0 && m.Rcode == dns.RcodeSuccess { - return Delegation, opt - } - - if m.Rcode == dns.RcodeSuccess { - return NoError, opt - } - - return OtherError, opt -} - -func typifyExpired(m *dns.Msg, t time.Time) bool { - if expired := typifyExpiredRRSIG(m.Answer, t); expired { - return true - } - if expired := typifyExpiredRRSIG(m.Ns, t); expired { - return true - } - if expired := typifyExpiredRRSIG(m.Extra, t); expired { - return true - } - return false -} - -func typifyExpiredRRSIG(rrs []dns.RR, t time.Time) bool { - for _, r := range rrs { - if r.Header().Rrtype != dns.TypeRRSIG { - continue - } - ok := r.(*dns.RRSIG).ValidityPeriod(t) - if !ok { - return true - } - } - return false -} diff --git a/middleware/pkg/response/typify_test.go b/middleware/pkg/response/typify_test.go deleted file mode 100644 index 738c6066d..000000000 --- a/middleware/pkg/response/typify_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package response - -import ( - "testing" - "time" - - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" -) - -func TestTypifyNilMsg(t *testing.T) { - var m *dns.Msg - - ty, _ := Typify(m, time.Now().UTC()) - if ty != OtherError { - t.Errorf("message wrongly typified, expected OtherError, got %s", ty) - } -} - -func TestTypifyDelegation(t *testing.T) { - m := delegationMsg() - mt, _ := Typify(m, time.Now().UTC()) - if mt != Delegation { - t.Errorf("message is wrongly typified, expected Delegation, got %s", mt) - } -} - -func TestTypifyRRSIG(t *testing.T) { - now, _ := time.Parse(time.UnixDate, "Fri Apr 21 10:51:21 BST 2017") - utc := now.UTC() - - m := delegationMsgRRSIGOK() - if mt, _ := Typify(m, utc); mt != Delegation { - t.Errorf("message is wrongly typified, expected Delegation, got %s", mt) - } - - // Still a Delegation because EDNS0 OPT DO bool is not set, so we won't check the sigs. - m = delegationMsgRRSIGFail() - if mt, _ := Typify(m, utc); mt != Delegation { - t.Errorf("message is wrongly typified, expected Delegation, got %s", mt) - } - - m = delegationMsgRRSIGFail() - m = addOpt(m) - if mt, _ := Typify(m, utc); mt != OtherError { - t.Errorf("message is wrongly typified, expected OtherError, got %s", mt) - } -} - -func delegationMsg() *dns.Msg { - return &dns.Msg{ - Ns: []dns.RR{ - test.NS("miek.nl. 3600 IN NS linode.atoom.net."), - test.NS("miek.nl. 3600 IN NS ns-ext.nlnetlabs.nl."), - test.NS("miek.nl. 3600 IN NS omval.tednet.nl."), - }, - Extra: []dns.RR{ - test.A("omval.tednet.nl. 3600 IN A 185.49.141.42"), - test.AAAA("omval.tednet.nl. 3600 IN AAAA 2a04:b900:0:100::42"), - }, - } -} - -func delegationMsgRRSIGOK() *dns.Msg { - del := delegationMsg() - del.Ns = append(del.Ns, - test.RRSIG("miek.nl. 1800 IN RRSIG NS 8 2 1800 20170521031301 20170421031301 12051 miek.nl. PIUu3TKX/sB/N1n1E1yWxHHIcPnc2q6Wq9InShk+5ptRqChqKdZNMLDm gCq+1bQAZ7jGvn2PbwTwE65JzES7T+hEiqR5PU23DsidvZyClbZ9l0xG JtKwgzGXLtUHxp4xv/Plq+rq/7pOG61bNCxRyS7WS7i7QcCCWT1BCcv+ wZ0="), - ) - return del -} - -func delegationMsgRRSIGFail() *dns.Msg { - del := delegationMsg() - del.Ns = append(del.Ns, - test.RRSIG("miek.nl. 1800 IN RRSIG NS 8 2 1800 20160521031301 20160421031301 12051 miek.nl. PIUu3TKX/sB/N1n1E1yWxHHIcPnc2q6Wq9InShk+5ptRqChqKdZNMLDm gCq+1bQAZ7jGvn2PbwTwE65JzES7T+hEiqR5PU23DsidvZyClbZ9l0xG JtKwgzGXLtUHxp4xv/Plq+rq/7pOG61bNCxRyS7WS7i7QcCCWT1BCcv+ wZ0="), - ) - return del -} - -func addOpt(m *dns.Msg) *dns.Msg { - m.Extra = append(m.Extra, test.OPT(4096, true)) - return m -} diff --git a/middleware/pkg/singleflight/singleflight.go b/middleware/pkg/singleflight/singleflight.go deleted file mode 100644 index 365e3ef58..000000000 --- a/middleware/pkg/singleflight/singleflight.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2012 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package singleflight provides a duplicate function call suppression -// mechanism. -package singleflight - -import "sync" - -// call is an in-flight or completed Do call -type call struct { - wg sync.WaitGroup - val interface{} - err error -} - -// Group represents a class of work and forms a namespace in which -// units of work can be executed with duplicate suppression. -type Group struct { - mu sync.Mutex // protects m - m map[uint32]*call // lazily initialized -} - -// Do executes and returns the results of the given function, making -// sure that only one execution is in-flight for a given key at a -// time. If a duplicate comes in, the duplicate caller waits for the -// original to complete and receives the same results. -func (g *Group) Do(key uint32, fn func() (interface{}, error)) (interface{}, error) { - g.mu.Lock() - if g.m == nil { - g.m = make(map[uint32]*call) - } - if c, ok := g.m[key]; ok { - g.mu.Unlock() - c.wg.Wait() - return c.val, c.err - } - c := new(call) - c.wg.Add(1) - g.m[key] = c - g.mu.Unlock() - - c.val, c.err = fn() - c.wg.Done() - - g.mu.Lock() - delete(g.m, key) - g.mu.Unlock() - - return c.val, c.err -} diff --git a/middleware/pkg/singleflight/singleflight_test.go b/middleware/pkg/singleflight/singleflight_test.go deleted file mode 100644 index d1d406e0b..000000000 --- a/middleware/pkg/singleflight/singleflight_test.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2012 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package singleflight - -import ( - "errors" - "fmt" - "sync" - "sync/atomic" - "testing" - "time" -) - -func TestDo(t *testing.T) { - var g Group - v, err := g.Do(1, func() (interface{}, error) { - return "bar", nil - }) - if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { - t.Errorf("Do = %v; want %v", got, want) - } - if err != nil { - t.Errorf("Do error = %v", err) - } -} - -func TestDoErr(t *testing.T) { - var g Group - someErr := errors.New("Some error") - v, err := g.Do(1, func() (interface{}, error) { - return nil, someErr - }) - if err != someErr { - t.Errorf("Do error = %v; want someErr", err) - } - if v != nil { - t.Errorf("unexpected non-nil value %#v", v) - } -} - -func TestDoDupSuppress(t *testing.T) { - var g Group - c := make(chan string) - var calls int32 - fn := func() (interface{}, error) { - atomic.AddInt32(&calls, 1) - return <-c, nil - } - - const n = 10 - var wg sync.WaitGroup - for i := 0; i < n; i++ { - wg.Add(1) - go func() { - v, err := g.Do(1, fn) - if err != nil { - t.Errorf("Do error: %v", err) - } - if v.(string) != "bar" { - t.Errorf("got %q; want %q", v, "bar") - } - wg.Done() - }() - } - time.Sleep(100 * time.Millisecond) // let goroutines above block - c <- "bar" - wg.Wait() - if got := atomic.LoadInt32(&calls); got != 1 { - t.Errorf("number of calls = %d; want 1", got) - } -} diff --git a/middleware/pkg/tls/tls.go b/middleware/pkg/tls/tls.go deleted file mode 100644 index 6fc10dd8e..000000000 --- a/middleware/pkg/tls/tls.go +++ /dev/null @@ -1,128 +0,0 @@ -package tls - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "io/ioutil" - "net" - "net/http" - "time" -) - -// NewTLSConfigFromArgs returns a TLS config based upon the passed -// in list of arguments. Typically these come straight from the -// Corefile. -// no args -// - creates a Config with no cert and using system CAs -// - use for a client that talks to a server with a public signed cert (CA installed in system) -// - the client will not be authenticated by the server since there is no cert -// one arg: the path to CA PEM file -// - creates a Config with no cert using a specific CA -// - use for a client that talks to a server with a private signed cert (CA not installed in system) -// - the client will not be authenticated by the server since there is no cert -// two args: path to cert PEM file, the path to private key PEM file -// - creates a Config with a cert, using system CAs to validate the other end -// - use for: -// - a server; or, -// - a client that talks to a server with a public cert and needs certificate-based authentication -// - the other end will authenticate this end via the provided cert -// - the cert of the other end will be verified via system CAs -// three args: path to cert PEM file, path to client private key PEM file, path to CA PEM file -// - creates a Config with the cert, using specified CA to validate the other end -// - use for: -// - a server; or, -// - a client that talks to a server with a privately signed cert and needs certificate-based -// authentication -// - the other end will authenticate this end via the provided cert -// - this end will verify the other end's cert using the specified CA -func NewTLSConfigFromArgs(args ...string) (*tls.Config, error) { - var err error - var c *tls.Config - switch len(args) { - case 0: - // No client cert, use system CA - c, err = NewTLSClientConfig("") - case 1: - // No client cert, use specified CA - c, err = NewTLSClientConfig(args[0]) - case 2: - // Client cert, use system CA - c, err = NewTLSConfig(args[0], args[1], "") - case 3: - // Client cert, use specified CA - c, err = NewTLSConfig(args[0], args[1], args[2]) - default: - err = fmt.Errorf("maximum of three arguments allowed for TLS config, found %d", len(args)) - } - if err != nil { - return nil, err - } - return c, nil -} - -// NewTLSConfig returns a TLS config that includes a certificate -// Use for server TLS config or when using a client certificate -// If caPath is empty, system CAs will be used -func NewTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) { - cert, err := tls.LoadX509KeyPair(certPath, keyPath) - if err != nil { - return nil, fmt.Errorf("could not load TLS cert: %s", err) - } - - roots, err := loadRoots(caPath) - if err != nil { - return nil, err - } - - return &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: roots}, nil -} - -// NewTLSClientConfig returns a TLS config for a client connection -// If caPath is empty, system CAs will be used -func NewTLSClientConfig(caPath string) (*tls.Config, error) { - roots, err := loadRoots(caPath) - if err != nil { - return nil, err - } - - return &tls.Config{RootCAs: roots}, nil -} - -func loadRoots(caPath string) (*x509.CertPool, error) { - if caPath == "" { - return nil, nil - } - - roots := x509.NewCertPool() - pem, err := ioutil.ReadFile(caPath) - if err != nil { - return nil, fmt.Errorf("error reading %s: %s", caPath, err) - } - ok := roots.AppendCertsFromPEM(pem) - if !ok { - return nil, fmt.Errorf("could not read root certs: %s", err) - } - return roots, nil -} - -// NewHTTPSTransport returns an HTTP transport configured using tls.Config -func NewHTTPSTransport(cc *tls.Config) *http.Transport { - // this seems like a bad idea but was here in the previous version - if cc != nil { - cc.InsecureSkipVerify = true - } - - tr := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - TLSHandshakeTimeout: 10 * time.Second, - TLSClientConfig: cc, - MaxIdleConnsPerHost: 25, - } - - return tr -} diff --git a/middleware/pkg/tls/tls_test.go b/middleware/pkg/tls/tls_test.go deleted file mode 100644 index 408469045..000000000 --- a/middleware/pkg/tls/tls_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package tls - -import ( - "path/filepath" - "testing" - - "github.com/coredns/coredns/middleware/test" -) - -func getPEMFiles(t *testing.T) (rmFunc func(), cert, key, ca string) { - tempDir, rmFunc, err := test.WritePEMFiles("") - if err != nil { - t.Fatalf("Could not write PEM files: %s", err) - } - - cert = filepath.Join(tempDir, "cert.pem") - key = filepath.Join(tempDir, "key.pem") - ca = filepath.Join(tempDir, "ca.pem") - - return -} - -func TestNewTLSConfig(t *testing.T) { - rmFunc, cert, key, ca := getPEMFiles(t) - defer rmFunc() - - _, err := NewTLSConfig(cert, key, ca) - if err != nil { - t.Errorf("Failed to create TLSConfig: %s", err) - } -} - -func TestNewTLSClientConfig(t *testing.T) { - rmFunc, _, _, ca := getPEMFiles(t) - defer rmFunc() - - _, err := NewTLSClientConfig(ca) - if err != nil { - t.Errorf("Failed to create TLSConfig: %s", err) - } -} - -func TestNewTLSConfigFromArgs(t *testing.T) { - rmFunc, cert, key, ca := getPEMFiles(t) - defer rmFunc() - - _, err := NewTLSConfigFromArgs() - if err != nil { - t.Errorf("Failed to create TLSConfig: %s", err) - } - - c, err := NewTLSConfigFromArgs(ca) - if err != nil { - t.Errorf("Failed to create TLSConfig: %s", err) - } - if c.RootCAs == nil { - t.Error("RootCAs should not be nil when one arg passed") - } - - c, err = NewTLSConfigFromArgs(cert, key) - if err != nil { - t.Errorf("Failed to create TLSConfig: %s", err) - } - if c.RootCAs != nil { - t.Error("RootCAs should be nil when two args passed") - } - if len(c.Certificates) != 1 { - t.Error("Certificates should have a single entry when two args passed") - } - args := []string{cert, key, ca} - c, err = NewTLSConfigFromArgs(args...) - if err != nil { - t.Errorf("Failed to create TLSConfig: %s", err) - } - if c.RootCAs == nil { - t.Error("RootCAs should not be nil when three args passed") - } - if len(c.Certificates) != 1 { - t.Error("Certificateis should have a single entry when three args passed") - } -} - -func TestNewHTTPSTransport(t *testing.T) { - rmFunc, _, _, ca := getPEMFiles(t) - defer rmFunc() - - cc, err := NewTLSClientConfig(ca) - if err != nil { - t.Errorf("Failed to create TLSConfig: %s", err) - } - - tr := NewHTTPSTransport(cc) - if tr == nil { - t.Errorf("Failed to create https transport with cc") - } - - tr = NewHTTPSTransport(nil) - if tr == nil { - t.Errorf("Failed to create https transport without cc") - } -} diff --git a/middleware/pkg/trace/trace.go b/middleware/pkg/trace/trace.go deleted file mode 100644 index e4858942b..000000000 --- a/middleware/pkg/trace/trace.go +++ /dev/null @@ -1,12 +0,0 @@ -package trace - -import ( - "github.com/coredns/coredns/middleware" - ot "github.com/opentracing/opentracing-go" -) - -// Trace holds the tracer and endpoint info -type Trace interface { - middleware.Handler - Tracer() ot.Tracer -} diff --git a/middleware/pprof/README.md b/middleware/pprof/README.md deleted file mode 100644 index 06a36e442..000000000 --- a/middleware/pprof/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# pprof - -*pprof* publishes runtime profiling data at endpoints under /debug/pprof. - -You can visit `/debug/pprof` on your site for an index of the available endpoints. By default it -will listen on localhost:6053. - -> This is a debugging tool. Certain requests (such as collecting execution traces) can be slow. If -> you use pprof on a live site, consider restricting access or enabling it only temporarily. - -For more information, please see [Go's pprof -documentation](https://golang.org/pkg/net/http/pprof/) and read -[Profiling Go Programs](https://blog.golang.org/profiling-go-programs). - -## Syntax - -~~~ -pprof [ADDRESS] -~~~ - -If not specified, ADDRESS defaults to localhost:6053. - -## Examples - -Enable pprof endpoints: - -~~~ -pprof -~~~ - -Listen on an alternate address: - -~~~ -pprof 10.9.8.7:6060 -~~~ - -Listen on an all addresses on port 6060: - -~~~ -pprof :6060 -~~~ diff --git a/middleware/pprof/pprof.go b/middleware/pprof/pprof.go deleted file mode 100644 index 020776ecf..000000000 --- a/middleware/pprof/pprof.go +++ /dev/null @@ -1,49 +0,0 @@ -// Package pprof implement a debug endpoint for getting profiles using the -// go pprof tooling. -package pprof - -import ( - "log" - "net" - "net/http" - pp "net/http/pprof" -) - -type handler struct { - addr string - ln net.Listener - mux *http.ServeMux -} - -func (h *handler) Startup() error { - ln, err := net.Listen("tcp", h.addr) - if err != nil { - log.Printf("[ERROR] Failed to start pprof handler: %s", err) - return err - } - - h.ln = ln - - h.mux = http.NewServeMux() - h.mux.HandleFunc(path+"/", pp.Index) - h.mux.HandleFunc(path+"/cmdline", pp.Cmdline) - h.mux.HandleFunc(path+"/profile", pp.Profile) - h.mux.HandleFunc(path+"/symbol", pp.Symbol) - h.mux.HandleFunc(path+"/trace", pp.Trace) - - go func() { - http.Serve(h.ln, h.mux) - }() - return nil -} - -func (h *handler) Shutdown() error { - if h.ln != nil { - return h.ln.Close() - } - return nil -} - -const ( - path = "/debug/pprof" -) diff --git a/middleware/pprof/setup.go b/middleware/pprof/setup.go deleted file mode 100644 index f94eef710..000000000 --- a/middleware/pprof/setup.go +++ /dev/null @@ -1,53 +0,0 @@ -package pprof - -import ( - "net" - "sync" - - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -const defaultAddr = "localhost:6053" - -func init() { - caddy.RegisterPlugin("pprof", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - found := false - h := &handler{addr: defaultAddr} - for c.Next() { - if found { - return middleware.Error("pprof", c.Err("pprof can only be specified once")) - } - args := c.RemainingArgs() - if len(args) == 1 { - h.addr = args[0] - _, _, e := net.SplitHostPort(h.addr) - if e != nil { - return e - } - } - if len(args) > 1 { - return middleware.Error("pprof", c.ArgErr()) - } - if c.NextBlock() { - return middleware.Error("pprof", c.ArgErr()) - } - found = true - } - - pprofOnce.Do(func() { - c.OnStartup(h.Startup) - c.OnShutdown(h.Shutdown) - }) - - return nil -} - -var pprofOnce sync.Once diff --git a/middleware/pprof/setup_test.go b/middleware/pprof/setup_test.go deleted file mode 100644 index eaa4cb37e..000000000 --- a/middleware/pprof/setup_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package pprof - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestPProf(t *testing.T) { - tests := []struct { - input string - shouldErr bool - }{ - {`pprof`, false}, - {`pprof 1.2.3.4:1234`, false}, - {`pprof :1234`, false}, - {`pprof {}`, true}, - {`pprof /foo`, true}, - {`pprof { - a b - }`, true}, - {`pprof - pprof`, true}, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - err := setup(c) - if test.shouldErr && err == nil { - t.Errorf("Test %v: Expected error but found nil", i) - } else if !test.shouldErr && err != nil { - t.Errorf("Test %v: Expected no error but found error: %v", i, err) - } - } -} diff --git a/middleware/proxy/README.md b/middleware/proxy/README.md deleted file mode 100644 index 17a43f68e..000000000 --- a/middleware/proxy/README.md +++ /dev/null @@ -1,175 +0,0 @@ -# proxy - -*proxy* facilitates both a basic reverse proxy and a robust load balancer. - -The proxy has support for multiple backends. The load balancing features include multiple policies, -health checks, and failovers. If all hosts fail their health check the proxy middleware will fail -back to randomly selecting a target and sending packets to it. - -## Syntax - -In its most basic form, a simple reverse proxy uses this syntax: - -~~~ -proxy FROM TO -~~~ - -* **FROM** is the base domain to match for the request to be proxied. -* **TO** is the destination endpoint to proxy to. - -However, advanced features including load balancing can be utilized with an expanded syntax: - -~~~ -proxy FROM TO... { - policy random|least_conn|round_robin - fail_timeout DURATION - max_fails INTEGER - health_check PATH:PORT [DURATION] - except IGNORED_NAMES... - spray - protocol [dns [force_tcp]|https_google [bootstrap ADDRESS...]|grpc [insecure|CACERT|KEY CERT|KEY CERT CACERT]] -} -~~~ - -* **FROM** is the name to match for the request to be proxied. -* **TO** is the destination endpoint to proxy to. At least one is required, but multiple may be - specified. **TO** may be an IP:Port pair, or may reference a file in resolv.conf format -* `policy` is the load balancing policy to use; applies only with multiple backends. May be one of - random, least_conn, or round_robin. Default is random. -* `fail_timeout` specifies how long to consider a backend as down after it has failed. While it is - down, requests will not be routed to that backend. A backend is "down" if CoreDNS fails to - communicate with it. The default value is 10 seconds ("10s"). -* `max_fails` is the number of failures within fail_timeout that are needed before considering - a backend to be down. If 0, the backend will never be marked as down. Default is 1. -* `health_check` will check path (on port) on each backend. If a backend returns a status code of - 200-399, then that backend is marked healthy for double the healthcheck duration. If it doesn't, - it is marked as unhealthy and no requests are routed to it. If this option is not provided then - health checks are disabled. The default duration is 30 seconds ("30s"). -* **IGNORED_NAMES** in `except` is a space-separated list of domains to exclude from proxying. - Requests that match none of these names will be passed through. -* `spray` when all backends are unhealthy, randomly pick one to send the traffic to. (This is - a failsafe.) -* `protocol` specifies what protocol to use to speak to an upstream, `dns` (the default) is plain - old DNS, and `https_google` uses `https://dns.google.com` and speaks a JSON DNS dialect. Note when - using this **TO** will be ignored. The `grpc` option will talk to a server that has implemented - the [DnsService](https://github.com/coredns/coredns/pb/dns.proto). - An out-of-tree middleware that implements the server side of this can be found at - [here](https://github.com/infobloxopen/coredns-grpc). - -## Policies - -There are three load-balancing policies available: -* `random` (default) - Randomly select a backend -* `least_conn` - Select the backend with the fewest active connections -* `round_robin` - Select the backend in round-robin fashion - -All polices implement randomly spraying packets to backend hosts when *no healthy* hosts are -available. This is to preeempt the case where the healthchecking (as a mechanism) fails. - -## Upstream Protocols - -Currently `protocol` supports `dns` (i.e., standard DNS over UDP/TCP) and `https_google` (JSON -payload over HTTPS). Note that with `https_google` the entire transport is encrypted. Only *you* and -*Google* can see your DNS activity. - -* `dns`: uses the standard DNS exchange. You can pass `force_tcp` to make sure that the proxied connection is performed - over TCP, regardless of the inbound request's protocol. -* `https_google`: bootstrap **ADDRESS...** is used to (re-)resolve `dns.google.com` to an address to - connect to. This happens every 300s. If not specified the default is used: 8.8.8.8:53/8.8.4.4:53. - Note that **TO** is *ignored* when `https_google` is used, as its upstream is defined as - `dns.google.com`. - - Debug queries are enabled by default and currently there is no way to turn them off. When CoreDNS - receives a debug query (i.e. the name is prefixed with `o-o.debug.`) a TXT record with Comment - from `dns.google.com` is added. Note this is not always set. -* `grpc`: options are used to control how the TLS connection is made to the gRPC server. - * None - No client authentication is used, and the system CAs are used to verify the server certificate. - * `insecure` - TLS is not used, the connection is made in plaintext (not good in production). - * **CACERT** - No client authentication is used, and the file **CACERT** is used to verify the server certificate. - * **KEY** **CERT** - Client authentication is used with the specified key/cert pair. The server - certificate is verified with the system CAs. - * **KEY** **CERT** **CACERT** - Client authentication is used with the specified key/cert pair. The - server certificate is verified using the **CACERT** file. - - An out-of-tree middleware that implements the server side of this can be found at - [here](https://github.com/infobloxopen/coredns-grpc). - -## Metrics - -If monitoring is enabled (via the *prometheus* directive) then the following metric is exported: - -* coredns_proxy_request_count_total{proto, proxy_proto, from} - -Where `proxy_proto` is the protocol used (`dns`, `grpc`, or `https_google`) and `from` is **FROM** -specified in the config, `proto` is the protocol used by the incoming query ("tcp" or "udp"). - -## Examples - -Proxy all requests within example.org. to a backend system: - -~~~ -proxy example.org 127.0.0.1:9005 -~~~ - -Load-balance all requests between three backends (using random policy): - -~~~ -proxy . 10.0.0.10:53 10.0.0.11:1053 10.0.0.12 -~~~ - -Same as above, but round-robin style: - -~~~ -proxy . 10.0.0.10:53 10.0.0.11:1053 10.0.0.12 { - policy round_robin -} -~~~ - -With health checks and proxy headers to pass hostname, IP, and scheme upstream: - -~~~ -proxy . 10.0.0.11:53 10.0.0.11:53 10.0.0.12:53 { - policy round_robin - health_check /health:8080 -} -~~~ - -Proxy everything except requests to miek.nl or example.org - -~~~ -proxy . 10.0.0.10:1234 { - except miek.nl example.org -} -~~~ - -Proxy everything except example.org using the host resolv.conf nameservers: - -~~~ -proxy . /etc/resolv.conf { - except miek.nl example.org -} -~~~ - -Proxy all requests within example.org to Google's dns.google.com. - -~~~ -proxy example.org 1.2.3.4:53 { - protocol https_google -} -~~~ - -Proxy everything with HTTPS to `dns.google.com`, except `example.org`. Then have another proxy in -another stanza that uses plain DNS to resolve names under `example.org`. - -~~~ -. { - proxy . 1.2.3.4:53 { - except example.org - protocol https_google - } -} - -example.org { - proxy . 8.8.8.8:53 -} -~~~ diff --git a/middleware/proxy/dns.go b/middleware/proxy/dns.go deleted file mode 100644 index 4d8038422..000000000 --- a/middleware/proxy/dns.go +++ /dev/null @@ -1,106 +0,0 @@ -package proxy - -import ( - "context" - "net" - "time" - - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -type dnsEx struct { - Timeout time.Duration - Options -} - -// Options define the options understood by dns.Exchange. -type Options struct { - ForceTCP bool // If true use TCP for upstream no matter what -} - -func newDNSEx() *dnsEx { - return newDNSExWithOption(Options{}) -} - -func newDNSExWithOption(opt Options) *dnsEx { - return &dnsEx{Timeout: defaultTimeout * time.Second, Options: opt} -} - -func (d *dnsEx) Transport() string { - if d.Options.ForceTCP { - return "tcp" - } - - // The protocol will be determined by `state.Proto()` during Exchange. - return "" -} -func (d *dnsEx) Protocol() string { return "dns" } -func (d *dnsEx) OnShutdown(p *Proxy) error { return nil } -func (d *dnsEx) OnStartup(p *Proxy) error { return nil } - -// Exchange implements the Exchanger interface. -func (d *dnsEx) Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) { - proto := state.Proto() - if d.Options.ForceTCP { - proto = "tcp" - } - co, err := net.DialTimeout(proto, addr, d.Timeout) - if err != nil { - return nil, err - } - - reply, _, err := d.ExchangeConn(state.Req, co) - - co.Close() - - if reply != nil && reply.Truncated { - // Suppress proxy error for truncated responses - err = nil - } - - if err != nil { - return nil, err - } - // Make sure it fits in the DNS response. - reply, _ = state.Scrub(reply) - reply.Compress = true - reply.Id = state.Req.Id - - return reply, nil -} - -func (d *dnsEx) ExchangeConn(m *dns.Msg, co net.Conn) (*dns.Msg, time.Duration, error) { - start := time.Now() - r, err := exchange(m, co) - rtt := time.Since(start) - - return r, rtt, err -} - -func exchange(m *dns.Msg, co net.Conn) (*dns.Msg, error) { - opt := m.IsEdns0() - - udpsize := uint16(dns.MinMsgSize) - // If EDNS0 is used use that for size. - if opt != nil && opt.UDPSize() >= dns.MinMsgSize { - udpsize = opt.UDPSize() - } - - dnsco := &dns.Conn{Conn: co, UDPSize: udpsize} - - writeDeadline := time.Now().Add(defaultTimeout) - dnsco.SetWriteDeadline(writeDeadline) - dnsco.WriteMsg(m) - - readDeadline := time.Now().Add(defaultTimeout) - co.SetReadDeadline(readDeadline) - r, err := dnsco.ReadMsg() - - dnsco.Close() - if r == nil { - return nil, err - } - return r, err -} diff --git a/middleware/proxy/dnstap_test.go b/middleware/proxy/dnstap_test.go deleted file mode 100644 index b3c31c207..000000000 --- a/middleware/proxy/dnstap_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package proxy - -import ( - "testing" - - "github.com/coredns/coredns/middleware/dnstap/msg" - "github.com/coredns/coredns/middleware/dnstap/test" - mwtest "github.com/coredns/coredns/middleware/test" - "github.com/coredns/coredns/request" - - tap "github.com/dnstap/golang-dnstap" - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func testCase(t *testing.T, ex Exchanger, q, r *dns.Msg, datq, datr *msg.Data) { - tapq := datq.ToOutsideQuery(tap.Message_FORWARDER_QUERY) - tapr := datr.ToOutsideResponse(tap.Message_FORWARDER_RESPONSE) - ctx := test.Context{} - err := toDnstap(&ctx, "10.240.0.1:40212", ex, - request.Request{W: &mwtest.ResponseWriter{}, Req: q}, r, 0, 0) - if err != nil { - t.Fatal(err) - } - if len(ctx.Trap) != 2 { - t.Fatalf("messages: %d", len(ctx.Trap)) - } - if !test.MsgEqual(ctx.Trap[0], tapq) { - t.Errorf("want: %v\nhave: %v", tapq, ctx.Trap[0]) - } - if !test.MsgEqual(ctx.Trap[1], tapr) { - t.Errorf("want: %v\nhave: %v", tapr, ctx.Trap[1]) - } -} - -func TestDnstap(t *testing.T) { - q := mwtest.Case{Qname: "example.org", Qtype: dns.TypeA}.Msg() - r := mwtest.Case{ - Qname: "example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - mwtest.A("example.org. 3600 IN A 10.0.0.1"), - }, - }.Msg() - tapq, tapr := test.TestingData(), test.TestingData() - testCase(t, newDNSEx(), q, r, tapq, tapr) - tapq.SocketProto = tap.SocketProtocol_TCP - tapr.SocketProto = tap.SocketProtocol_TCP - testCase(t, newDNSExWithOption(Options{ForceTCP: true}), q, r, tapq, tapr) - testCase(t, newGoogle("", []string{"8.8.8.8:53", "8.8.4.4:53"}), q, r, tapq, tapr) -} - -func TestNoDnstap(t *testing.T) { - err := toDnstap(context.TODO(), "", nil, request.Request{}, nil, 0, 0) - if err != nil { - t.Fatal(err) - } -} diff --git a/middleware/proxy/exchanger.go b/middleware/proxy/exchanger.go deleted file mode 100644 index b98a687e7..000000000 --- a/middleware/proxy/exchanger.go +++ /dev/null @@ -1,22 +0,0 @@ -package proxy - -import ( - "context" - - "github.com/coredns/coredns/request" - "github.com/miekg/dns" -) - -// Exchanger is an interface that specifies a type implementing a DNS resolver that -// can use whatever transport it likes. -type Exchanger interface { - Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) - Protocol() string - - // Transport returns the only transport protocol used by this Exchanger or "". - // If the return value is "", Exchange must use `state.Proto()`. - Transport() string - - OnStartup(*Proxy) error - OnShutdown(*Proxy) error -} diff --git a/middleware/proxy/google.go b/middleware/proxy/google.go deleted file mode 100644 index b7e605fcb..000000000 --- a/middleware/proxy/google.go +++ /dev/null @@ -1,244 +0,0 @@ -package proxy - -import ( - "context" - "crypto/tls" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net" - "net/http" - "net/url" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -type google struct { - client *http.Client - - endpoint string // Name to resolve via 'bootstrapProxy' - - bootstrapProxy Proxy - quit chan bool -} - -func newGoogle(endpoint string, bootstrap []string) *google { - if endpoint == "" { - endpoint = ghost - } - tls := &tls.Config{ServerName: endpoint} - client := &http.Client{ - Timeout: time.Second * defaultTimeout, - Transport: &http.Transport{TLSClientConfig: tls}, - } - - boot := NewLookup(bootstrap) - - return &google{client: client, endpoint: dns.Fqdn(endpoint), bootstrapProxy: boot, quit: make(chan bool)} -} - -func (g *google) Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) { - v := url.Values{} - - v.Set("name", state.Name()) - v.Set("type", fmt.Sprintf("%d", state.QType())) - - buf, backendErr := g.exchangeJSON(addr, v.Encode()) - - if backendErr == nil { - gm := new(googleMsg) - if err := json.Unmarshal(buf, gm); err != nil { - return nil, err - } - - m, err := toMsg(gm) - if err != nil { - return nil, err - } - - m.Id = state.Req.Id - return m, nil - } - - log.Printf("[WARNING] Failed to connect to HTTPS backend %q: %s", g.endpoint, backendErr) - return nil, backendErr -} - -func (g *google) exchangeJSON(addr, json string) ([]byte, error) { - url := "https://" + addr + "/resolve?" + json - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - req.Host = g.endpoint // TODO(miek): works with the extra dot at the end? - - resp, err := g.client.Do(req) - if err != nil { - return nil, err - } - - buf, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - return nil, err - } - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to get 200 status code, got %d", resp.StatusCode) - } - - return buf, nil -} - -func (g *google) Transport() string { return "tcp" } -func (g *google) Protocol() string { return "https_google" } - -func (g *google) OnShutdown(p *Proxy) error { - g.quit <- true - return nil -} - -func (g *google) OnStartup(p *Proxy) error { - // We fake a state because normally the proxy is called after we already got a incoming query. - // This is a non-edns0, udp request to g.endpoint. - req := new(dns.Msg) - req.SetQuestion(g.endpoint, dns.TypeA) - state := request.Request{W: new(fakeBootWriter), Req: req} - - if len(*p.Upstreams) == 0 { - return fmt.Errorf("no upstreams defined") - } - - oldUpstream := (*p.Upstreams)[0] - - log.Printf("[INFO] Bootstrapping A records %q", g.endpoint) - - new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA) - if err != nil { - log.Printf("[WARNING] Failed to bootstrap A records %q: %s", g.endpoint, err) - } else { - addrs, err1 := extractAnswer(new) - if err1 != nil { - log.Printf("[WARNING] Failed to bootstrap A records %q: %s", g.endpoint, err1) - } else { - - up := newUpstream(addrs, oldUpstream.(*staticUpstream)) - p.Upstreams = &[]Upstream{up} - - log.Printf("[INFO] Bootstrapping A records %q found: %v", g.endpoint, addrs) - } - } - - go func() { - tick := time.NewTicker(120 * time.Second) - - for { - select { - case <-tick.C: - - log.Printf("[INFO] Resolving A records %q", g.endpoint) - - new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA) - if err != nil { - log.Printf("[WARNING] Failed to resolve A records %q: %s", g.endpoint, err) - continue - } - - addrs, err1 := extractAnswer(new) - if err1 != nil { - log.Printf("[WARNING] Failed to resolve A records %q: %s", g.endpoint, err1) - continue - } - - up := newUpstream(addrs, oldUpstream.(*staticUpstream)) - p.Upstreams = &[]Upstream{up} - - log.Printf("[INFO] Resolving A records %q found: %v", g.endpoint, addrs) - - case <-g.quit: - return - } - } - }() - - return nil -} - -func extractAnswer(m *dns.Msg) ([]string, error) { - if len(m.Answer) == 0 { - return nil, fmt.Errorf("no answer section in response") - } - ret := []string{} - for _, an := range m.Answer { - if a, ok := an.(*dns.A); ok { - ret = append(ret, net.JoinHostPort(a.A.String(), "443")) - } - } - if len(ret) > 0 { - return ret, nil - } - - return nil, fmt.Errorf("no address records in answer section") -} - -// newUpstream returns an upstream initialized with hosts. -func newUpstream(hosts []string, old *staticUpstream) Upstream { - upstream := &staticUpstream{ - from: old.from, - HealthCheck: healthcheck.HealthCheck{ - FailTimeout: 10 * time.Second, - MaxFails: 3, - Future: 60 * time.Second, - }, - ex: old.ex, - WithoutPathPrefix: old.WithoutPathPrefix, - IgnoredSubDomains: old.IgnoredSubDomains, - } - - upstream.Hosts = make([]*healthcheck.UpstreamHost, len(hosts)) - for i, h := range hosts { - uh := &healthcheck.UpstreamHost{ - Name: h, - Conns: 0, - Fails: 0, - FailTimeout: upstream.FailTimeout, - - CheckDown: func(upstream *staticUpstream) healthcheck.UpstreamHostDownFunc { - return func(uh *healthcheck.UpstreamHost) bool { - - down := false - - uh.CheckMu.Lock() - until := uh.OkUntil - uh.CheckMu.Unlock() - - if !until.IsZero() && time.Now().After(until) { - down = true - } - - fails := atomic.LoadInt32(&uh.Fails) - if fails >= upstream.MaxFails && upstream.MaxFails != 0 { - down = true - } - return down - } - }(upstream), - WithoutPathPrefix: upstream.WithoutPathPrefix, - } - - upstream.Hosts[i] = uh - } - return upstream -} - -const ( - // Default endpoint for this service. - ghost = "dns.google.com." -) diff --git a/middleware/proxy/google_rr.go b/middleware/proxy/google_rr.go deleted file mode 100644 index 3b9233b7b..000000000 --- a/middleware/proxy/google_rr.go +++ /dev/null @@ -1,89 +0,0 @@ -package proxy - -import ( - "fmt" - - "github.com/miekg/dns" -) - -// toMsg converts a googleMsg into the dns message. -func toMsg(g *googleMsg) (*dns.Msg, error) { - m := new(dns.Msg) - m.Response = true - m.Rcode = g.Status - m.Truncated = g.TC - m.RecursionDesired = g.RD - m.RecursionAvailable = g.RA - m.AuthenticatedData = g.AD - m.CheckingDisabled = g.CD - - m.Question = make([]dns.Question, 1) - m.Answer = make([]dns.RR, len(g.Answer)) - m.Ns = make([]dns.RR, len(g.Authority)) - m.Extra = make([]dns.RR, len(g.Additional)) - - m.Question[0] = dns.Question{Name: g.Question[0].Name, Qtype: g.Question[0].Type, Qclass: dns.ClassINET} - - var err error - for i := 0; i < len(m.Answer); i++ { - m.Answer[i], err = toRR(g.Answer[i]) - if err != nil { - return nil, err - } - } - for i := 0; i < len(m.Ns); i++ { - m.Ns[i], err = toRR(g.Authority[i]) - if err != nil { - return nil, err - } - } - for i := 0; i < len(m.Extra); i++ { - m.Extra[i], err = toRR(g.Additional[i]) - if err != nil { - return nil, err - } - } - - return m, nil -} - -// toRR transforms a "google" RR to a dns.RR. -func toRR(g googleRR) (dns.RR, error) { - typ, ok := dns.TypeToString[g.Type] - if !ok { - return nil, fmt.Errorf("failed to convert type %q", g.Type) - } - - str := fmt.Sprintf("%s %d %s %s", g.Name, g.TTL, typ, g.Data) - rr, err := dns.NewRR(str) - if err != nil { - return nil, fmt.Errorf("failed to parse %q: %s", str, err) - } - return rr, nil -} - -// googleRR represents a dns.RR in another form. -type googleRR struct { - Name string - Type uint16 - TTL uint32 - Data string -} - -// googleMsg is a JSON representation of the dns.Msg. -type googleMsg struct { - Status int - TC bool - RD bool - RA bool - AD bool - CD bool - Question []struct { - Name string - Type uint16 - } - Answer []googleRR - Authority []googleRR - Additional []googleRR - Comment string -} diff --git a/middleware/proxy/google_test.go b/middleware/proxy/google_test.go deleted file mode 100644 index 1ce591664..000000000 --- a/middleware/proxy/google_test.go +++ /dev/null @@ -1,5 +0,0 @@ -package proxy - -// TODO(miek): -// Test cert failures - put those in SERVFAIL messages, but attach error code in TXT -// Test connecting to a a bad host. diff --git a/middleware/proxy/grpc.go b/middleware/proxy/grpc.go deleted file mode 100644 index 8aabf0eb0..000000000 --- a/middleware/proxy/grpc.go +++ /dev/null @@ -1,96 +0,0 @@ -package proxy - -import ( - "context" - "crypto/tls" - "log" - - "github.com/coredns/coredns/middleware/pkg/trace" - "github.com/coredns/coredns/pb" - "github.com/coredns/coredns/request" - - "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" - "github.com/miekg/dns" - opentracing "github.com/opentracing/opentracing-go" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" -) - -type grpcClient struct { - dialOpts []grpc.DialOption - clients map[string]pb.DnsServiceClient - conns []*grpc.ClientConn - upstream *staticUpstream -} - -func newGrpcClient(tls *tls.Config, u *staticUpstream) *grpcClient { - g := &grpcClient{upstream: u} - - if tls == nil { - g.dialOpts = append(g.dialOpts, grpc.WithInsecure()) - } else { - g.dialOpts = append(g.dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(tls))) - } - g.clients = map[string]pb.DnsServiceClient{} - - return g -} - -func (g *grpcClient) Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) { - msg, err := state.Req.Pack() - if err != nil { - return nil, err - } - - reply, err := g.clients[addr].Query(ctx, &pb.DnsPacket{Msg: msg}) - if err != nil { - return nil, err - } - d := new(dns.Msg) - err = d.Unpack(reply.Msg) - if err != nil { - return nil, err - } - return d, nil -} - -func (g *grpcClient) Transport() string { return "tcp" } - -func (g *grpcClient) Protocol() string { return "grpc" } - -func (g *grpcClient) OnShutdown(p *Proxy) error { - g.clients = map[string]pb.DnsServiceClient{} - for i, conn := range g.conns { - err := conn.Close() - if err != nil { - log.Printf("[WARNING] Error closing connection %d: %s\n", i, err) - } - } - g.conns = []*grpc.ClientConn{} - return nil -} - -func (g *grpcClient) OnStartup(p *Proxy) error { - dialOpts := g.dialOpts - if p.Trace != nil { - if t, ok := p.Trace.(trace.Trace); ok { - onlyIfParent := func(parentSpanCtx opentracing.SpanContext, method string, req, resp interface{}) bool { - return parentSpanCtx != nil - } - intercept := otgrpc.OpenTracingClientInterceptor(t.Tracer(), otgrpc.IncludingSpans(onlyIfParent)) - dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(intercept)) - } else { - log.Printf("[WARNING] Wrong type for trace middleware reference: %s", p.Trace) - } - } - for _, host := range g.upstream.Hosts { - conn, err := grpc.Dial(host.Name, dialOpts...) - if err != nil { - log.Printf("[WARNING] Skipping gRPC host '%s' due to Dial error: %s\n", host.Name, err) - } else { - g.clients[host.Name] = pb.NewDnsServiceClient(conn) - g.conns = append(g.conns, conn) - } - } - return nil -} diff --git a/middleware/proxy/grpc_test.go b/middleware/proxy/grpc_test.go deleted file mode 100644 index dcde7cc0e..000000000 --- a/middleware/proxy/grpc_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package proxy - -import ( - "testing" - "time" - - "github.com/coredns/coredns/middleware/pkg/healthcheck" - - "google.golang.org/grpc/grpclog" -) - -func pool() []*healthcheck.UpstreamHost { - return []*healthcheck.UpstreamHost{ - { - Name: "localhost:10053", - }, - { - Name: "localhost:10054", - }, - } -} - -func TestStartupShutdown(t *testing.T) { - grpclog.SetLogger(discard{}) - - upstream := &staticUpstream{ - from: ".", - HealthCheck: healthcheck.HealthCheck{ - Hosts: pool(), - FailTimeout: 10 * time.Second, - Future: 60 * time.Second, - MaxFails: 1, - }, - } - g := newGrpcClient(nil, upstream) - upstream.ex = g - - p := &Proxy{} - p.Upstreams = &[]Upstream{upstream} - - err := g.OnStartup(p) - if err != nil { - t.Errorf("Error starting grpc client exchanger: %s", err) - return - } - if len(g.clients) != len(pool()) { - t.Errorf("Expected %d grpc clients but found %d", len(pool()), len(g.clients)) - } - - err = g.OnShutdown(p) - if err != nil { - t.Errorf("Error stopping grpc client exchanger: %s", err) - return - } - if len(g.clients) != 0 { - t.Errorf("Shutdown didn't remove clients, found %d", len(g.clients)) - } - if len(g.conns) != 0 { - t.Errorf("Shutdown didn't remove conns, found %d", len(g.conns)) - } -} - -// discard is a Logger that outputs nothing. -type discard struct{} - -func (d discard) Fatal(args ...interface{}) {} -func (d discard) Fatalf(format string, args ...interface{}) {} -func (d discard) Fatalln(args ...interface{}) {} -func (d discard) Print(args ...interface{}) {} -func (d discard) Printf(format string, args ...interface{}) {} -func (d discard) Println(args ...interface{}) {} diff --git a/middleware/proxy/lookup.go b/middleware/proxy/lookup.go deleted file mode 100644 index 1963e7dbd..000000000 --- a/middleware/proxy/lookup.go +++ /dev/null @@ -1,132 +0,0 @@ -package proxy - -// functions other middleware might want to use to do lookup in the same style as the proxy. - -import ( - "context" - "fmt" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// NewLookup create a new proxy with the hosts in host and a Random policy. -func NewLookup(hosts []string) Proxy { return NewLookupWithOption(hosts, Options{}) } - -// NewLookupWithOption process creates a simple round robin forward with potentially forced proto for upstream. -func NewLookupWithOption(hosts []string, opts Options) Proxy { - p := Proxy{Next: nil} - - // TODO(miek): this needs to be unified with upstream.go's NewStaticUpstreams, caddy uses NewHost - // we should copy/make something similar. - upstream := &staticUpstream{ - from: ".", - HealthCheck: healthcheck.HealthCheck{ - FailTimeout: 10 * time.Second, - MaxFails: 3, // TODO(miek): disable error checking for simple lookups? - Future: 60 * time.Second, - }, - ex: newDNSExWithOption(opts), - } - upstream.Hosts = make([]*healthcheck.UpstreamHost, len(hosts)) - - for i, host := range hosts { - uh := &healthcheck.UpstreamHost{ - Name: host, - Conns: 0, - Fails: 0, - FailTimeout: upstream.FailTimeout, - - CheckDown: func(upstream *staticUpstream) healthcheck.UpstreamHostDownFunc { - return func(uh *healthcheck.UpstreamHost) bool { - - down := false - - uh.CheckMu.Lock() - until := uh.OkUntil - uh.CheckMu.Unlock() - - if !until.IsZero() && time.Now().After(until) { - down = true - } - - fails := atomic.LoadInt32(&uh.Fails) - if fails >= upstream.MaxFails && upstream.MaxFails != 0 { - down = true - } - return down - } - }(upstream), - WithoutPathPrefix: upstream.WithoutPathPrefix, - } - - upstream.Hosts[i] = uh - } - p.Upstreams = &[]Upstream{upstream} - return p -} - -// Lookup will use name and type to forge a new message and will send that upstream. It will -// set any EDNS0 options correctly so that downstream will be able to process the reply. -func (p Proxy) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) { - req := new(dns.Msg) - req.SetQuestion(name, typ) - state.SizeAndDo(req) - - state2 := request.Request{W: state.W, Req: req} - - return p.lookup(state2) -} - -// Forward forward the request in state as-is. Unlike Lookup that adds EDNS0 suffix to the message. -func (p Proxy) Forward(state request.Request) (*dns.Msg, error) { - return p.lookup(state) -} - -func (p Proxy) lookup(state request.Request) (*dns.Msg, error) { - upstream := p.match(state) - if upstream == nil { - return nil, errInvalidDomain - } - for { - start := time.Now() - reply := new(dns.Msg) - var backendErr error - - // Since Select() should give us "up" hosts, keep retrying - // hosts until timeout (or until we get a nil host). - for time.Since(start) < tryDuration { - host := upstream.Select() - if host == nil { - return nil, fmt.Errorf("%s: %s", errUnreachable, "no upstream host") - } - - // duplicated from proxy.go, but with a twist, we don't write the - // reply back to the client, we return it and there is no monitoring. - - atomic.AddInt64(&host.Conns, 1) - - reply, backendErr = upstream.Exchanger().Exchange(context.TODO(), host.Name, state) - - atomic.AddInt64(&host.Conns, -1) - - if backendErr == nil { - return reply, nil - } - timeout := host.FailTimeout - if timeout == 0 { - timeout = 10 * time.Second - } - atomic.AddInt32(&host.Fails, 1) - go func(host *healthcheck.UpstreamHost, timeout time.Duration) { - time.Sleep(timeout) - atomic.AddInt32(&host.Fails, -1) - }(host, timeout) - } - return nil, fmt.Errorf("%s: %s", errUnreachable, backendErr) - } -} diff --git a/middleware/proxy/metrics.go b/middleware/proxy/metrics.go deleted file mode 100644 index e9bb48d6f..000000000 --- a/middleware/proxy/metrics.go +++ /dev/null @@ -1,30 +0,0 @@ -package proxy - -import ( - "sync" - - "github.com/coredns/coredns/middleware" - - "github.com/prometheus/client_golang/prometheus" -) - -// Metrics the proxy middleware exports. -var ( - RequestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: middleware.Namespace, - Subsystem: "proxy", - Name: "request_duration_milliseconds", - Buckets: append(prometheus.DefBuckets, []float64{50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 10000}...), - Help: "Histogram of the time (in milliseconds) each request took.", - }, []string{"proto", "proxy_proto", "from"}) -) - -// OnStartupMetrics sets up the metrics on startup. This is done for all proxy protocols. -func OnStartupMetrics() error { - metricsOnce.Do(func() { - prometheus.MustRegister(RequestDuration) - }) - return nil -} - -var metricsOnce sync.Once diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go deleted file mode 100644 index 2a3c8002d..000000000 --- a/middleware/proxy/proxy.go +++ /dev/null @@ -1,195 +0,0 @@ -// Package proxy is middleware that proxies requests. -package proxy - -import ( - "errors" - "fmt" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/dnstap" - "github.com/coredns/coredns/middleware/dnstap/msg" - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/coredns/request" - - tap "github.com/dnstap/golang-dnstap" - "github.com/miekg/dns" - ot "github.com/opentracing/opentracing-go" - "golang.org/x/net/context" -) - -var ( - errUnreachable = errors.New("unreachable backend") - errInvalidProtocol = errors.New("invalid protocol") - errInvalidDomain = errors.New("invalid path for proxy") -) - -// Proxy represents a middleware instance that can proxy requests to another (DNS) server. -type Proxy struct { - Next middleware.Handler - - // Upstreams is a pointer to a slice, so we can update the upstream (used for Google) - // midway. - - Upstreams *[]Upstream - - // Trace is the Trace middleware, if it is installed - // This is used by the grpc exchanger to trace through the grpc calls - Trace middleware.Handler -} - -// Upstream manages a pool of proxy upstream hosts. Select should return a -// suitable upstream host, or nil if no such hosts are available. -type Upstream interface { - // The domain name this upstream host should be routed on. - From() string - // Selects an upstream host to be routed to. - Select() *healthcheck.UpstreamHost - // Checks if subpdomain is not an ignored. - IsAllowedDomain(string) bool - // Exchanger returns the exchanger to be used for this upstream. - Exchanger() Exchanger - // Stops the upstream from proxying requests to shutdown goroutines cleanly. - Stop() error -} - -// tryDuration is how long to try upstream hosts; failures result in -// immediate retries until this duration ends or we get a nil host. -var tryDuration = 60 * time.Second - -// ServeDNS satisfies the middleware.Handler interface. -func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - var span, child ot.Span - span = ot.SpanFromContext(ctx) - state := request.Request{W: w, Req: r} - - upstream := p.match(state) - if upstream == nil { - return middleware.NextOrFailure(p.Name(), p.Next, ctx, w, r) - } - - for { - start := time.Now() - reply := new(dns.Msg) - var backendErr error - - // Since Select() should give us "up" hosts, keep retrying - // hosts until timeout (or until we get a nil host). - for time.Since(start) < tryDuration { - host := upstream.Select() - if host == nil { - - RequestDuration.WithLabelValues(state.Proto(), upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond)) - - return dns.RcodeServerFailure, fmt.Errorf("%s: %s", errUnreachable, "no upstream host") - } - - if span != nil { - child = span.Tracer().StartSpan("exchange", ot.ChildOf(span.Context())) - ctx = ot.ContextWithSpan(ctx, child) - } - - atomic.AddInt64(&host.Conns, 1) - queryEpoch := msg.Epoch() - - reply, backendErr = upstream.Exchanger().Exchange(ctx, host.Name, state) - - respEpoch := msg.Epoch() - atomic.AddInt64(&host.Conns, -1) - - if child != nil { - child.Finish() - } - - taperr := toDnstap(ctx, host.Name, upstream.Exchanger(), state, reply, queryEpoch, respEpoch) - - if backendErr == nil { - w.WriteMsg(reply) - - RequestDuration.WithLabelValues(state.Proto(), upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond)) - - return 0, taperr - } - - timeout := host.FailTimeout - if timeout == 0 { - timeout = 10 * time.Second - } - atomic.AddInt32(&host.Fails, 1) - go func(host *healthcheck.UpstreamHost, timeout time.Duration) { - time.Sleep(timeout) - atomic.AddInt32(&host.Fails, -1) - }(host, timeout) - } - - RequestDuration.WithLabelValues(state.Proto(), upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond)) - - return dns.RcodeServerFailure, fmt.Errorf("%s: %s", errUnreachable, backendErr) - } -} - -func (p Proxy) match(state request.Request) (u Upstream) { - if p.Upstreams == nil { - return nil - } - - longestMatch := 0 - for _, upstream := range *p.Upstreams { - from := upstream.From() - - if !middleware.Name(from).Matches(state.Name()) || !upstream.IsAllowedDomain(state.Name()) { - continue - } - - if lf := len(from); lf > longestMatch { - longestMatch = lf - u = upstream - } - } - return u - -} - -// Name implements the Handler interface. -func (p Proxy) Name() string { return "proxy" } - -// defaultTimeout is the default networking timeout for DNS requests. -const defaultTimeout = 5 * time.Second - -func toDnstap(ctx context.Context, host string, ex Exchanger, state request.Request, reply *dns.Msg, queryEpoch, respEpoch uint64) (err error) { - if tapper := dnstap.TapperFromContext(ctx); tapper != nil { - // Query - b := tapper.TapBuilder() - b.TimeSec = queryEpoch - if err = b.HostPort(host); err != nil { - return - } - t := ex.Transport() - if t == "" { - t = state.Proto() - } - if t == "tcp" { - b.SocketProto = tap.SocketProtocol_TCP - } else { - b.SocketProto = tap.SocketProtocol_UDP - } - if err = b.Msg(state.Req); err != nil { - return - } - err = tapper.TapMessage(b.ToOutsideQuery(tap.Message_FORWARDER_QUERY)) - if err != nil { - return - } - - // Response - if reply != nil { - b.TimeSec = respEpoch - if err = b.Msg(reply); err != nil { - return - } - err = tapper.TapMessage(b.ToOutsideResponse(tap.Message_FORWARDER_RESPONSE)) - } - } - return -} diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go deleted file mode 100644 index b0cb9c3cb..000000000 --- a/middleware/proxy/proxy_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package proxy - -import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" - "sync/atomic" - "testing" - "time" - - "github.com/mholt/caddy/caddyfile" -) - -func TestStop(t *testing.T) { - config := "proxy . %s {\n health_check /healthcheck:%s %dms \n}" - tests := []struct { - name string - intervalInMilliseconds int - numHealthcheckIntervals int - }{ - { - "No Healthchecks After Stop - 5ms, 1 intervals", - 5, - 1, - }, - { - "No Healthchecks After Stop - 5ms, 2 intervals", - 5, - 2, - }, - { - "No Healthchecks After Stop - 5ms, 3 intervals", - 5, - 3, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - - // Set up proxy. - var counter int64 - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r.Body.Close() - atomic.AddInt64(&counter, 1) - })) - - defer backend.Close() - - port := backend.URL[17:] // Remove all crap up to the port - back := backend.URL[7:] // Remove http:// - c := caddyfile.NewDispenser("Testfile", strings.NewReader(fmt.Sprintf(config, back, port, test.intervalInMilliseconds))) - upstreams, err := NewStaticUpstreams(&c) - if err != nil { - t.Error("Expected no error. Got:", err.Error()) - } - - // Give some time for healthchecks to hit the server. - time.Sleep(time.Duration(test.intervalInMilliseconds*test.numHealthcheckIntervals) * time.Millisecond) - - for _, upstream := range upstreams { - if err := upstream.Stop(); err != nil { - t.Error("Expected no error stopping upstream. Got: ", err.Error()) - } - } - - counterValueAfterShutdown := atomic.LoadInt64(&counter) - - // Give some time to see if healthchecks are still hitting the server. - time.Sleep(time.Duration(test.intervalInMilliseconds*test.numHealthcheckIntervals) * time.Millisecond) - - if counterValueAfterShutdown == 0 { - t.Error("Expected healthchecks to hit test server. Got no healthchecks.") - } - - // health checks are in a go routine now, so one may well occur after we shutdown, - // but we only ever expect one more - counterValueAfterWaiting := atomic.LoadInt64(&counter) - if counterValueAfterWaiting > (counterValueAfterShutdown + 1) { - t.Errorf("Expected no more healthchecks after shutdown. Got: %d healthchecks after shutdown", counterValueAfterWaiting-counterValueAfterShutdown) - } - - }) - - } -} diff --git a/middleware/proxy/response.go b/middleware/proxy/response.go deleted file mode 100644 index 2ad553c41..000000000 --- a/middleware/proxy/response.go +++ /dev/null @@ -1,21 +0,0 @@ -package proxy - -import ( - "net" - - "github.com/miekg/dns" -) - -type fakeBootWriter struct { - dns.ResponseWriter -} - -func (w *fakeBootWriter) LocalAddr() net.Addr { - local := net.ParseIP("127.0.0.1") - return &net.UDPAddr{IP: local, Port: 53} // Port is not used here -} - -func (w *fakeBootWriter) RemoteAddr() net.Addr { - remote := net.ParseIP("8.8.8.8") - return &net.UDPAddr{IP: remote, Port: 53} // Port is not used here -} diff --git a/middleware/proxy/setup.go b/middleware/proxy/setup.go deleted file mode 100644 index d55065734..000000000 --- a/middleware/proxy/setup.go +++ /dev/null @@ -1,46 +0,0 @@ -package proxy - -import ( - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("proxy", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - upstreams, err := NewStaticUpstreams(&c.Dispenser) - if err != nil { - return middleware.Error("proxy", err) - } - - t := dnsserver.GetConfig(c).Handler("trace") - P := &Proxy{Trace: t} - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - P.Next = next - P.Upstreams = &upstreams - return P - }) - - c.OnStartup(OnStartupMetrics) - - for i := range upstreams { - u := upstreams[i] - c.OnStartup(func() error { - return u.Exchanger().OnStartup(P) - }) - c.OnShutdown(func() error { - return u.Exchanger().OnShutdown(P) - }) - // Register shutdown handlers. - c.OnShutdown(u.Stop) - } - - return nil -} diff --git a/middleware/proxy/upstream.go b/middleware/proxy/upstream.go deleted file mode 100644 index 677b8e2fc..000000000 --- a/middleware/proxy/upstream.go +++ /dev/null @@ -1,234 +0,0 @@ -package proxy - -import ( - "fmt" - "net" - "strconv" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/coredns/middleware/pkg/tls" - "github.com/mholt/caddy/caddyfile" - "github.com/miekg/dns" -) - -type staticUpstream struct { - from string - - healthcheck.HealthCheck - - WithoutPathPrefix string - IgnoredSubDomains []string - ex Exchanger -} - -// NewStaticUpstreams parses the configuration input and sets up -// static upstreams for the proxy middleware. -func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) { - var upstreams []Upstream - for c.Next() { - upstream := &staticUpstream{ - from: ".", - HealthCheck: healthcheck.HealthCheck{ - FailTimeout: 10 * time.Second, - MaxFails: 1, - Future: 60 * time.Second, - }, - ex: newDNSEx(), - } - - if !c.Args(&upstream.from) { - return upstreams, c.ArgErr() - } - upstream.from = middleware.Host(upstream.from).Normalize() - - to := c.RemainingArgs() - if len(to) == 0 { - return upstreams, c.ArgErr() - } - - // process the host list, substituting in any nameservers in files - toHosts, err := dnsutil.ParseHostPortOrFile(to...) - if err != nil { - return upstreams, err - } - - for c.NextBlock() { - if err := parseBlock(c, upstream); err != nil { - return upstreams, err - } - } - - upstream.Hosts = make([]*healthcheck.UpstreamHost, len(toHosts)) - for i, host := range toHosts { - uh := &healthcheck.UpstreamHost{ - Name: host, - Conns: 0, - Fails: 0, - FailTimeout: upstream.FailTimeout, - - CheckDown: func(upstream *staticUpstream) healthcheck.UpstreamHostDownFunc { - return func(uh *healthcheck.UpstreamHost) bool { - - down := false - - uh.CheckMu.Lock() - until := uh.OkUntil - uh.CheckMu.Unlock() - - if !until.IsZero() && time.Now().After(until) { - down = true - } - - fails := atomic.LoadInt32(&uh.Fails) - if fails >= upstream.MaxFails && upstream.MaxFails != 0 { - down = true - } - return down - } - }(upstream), - WithoutPathPrefix: upstream.WithoutPathPrefix, - } - - upstream.Hosts[i] = uh - } - upstream.Start() - - upstreams = append(upstreams, upstream) - } - return upstreams, nil -} - -func (u *staticUpstream) From() string { - return u.from -} - -func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error { - switch c.Val() { - case "policy": - if !c.NextArg() { - return c.ArgErr() - } - policyCreateFunc, ok := healthcheck.SupportedPolicies[c.Val()] - if !ok { - return c.ArgErr() - } - u.Policy = policyCreateFunc() - case "fail_timeout": - if !c.NextArg() { - return c.ArgErr() - } - dur, err := time.ParseDuration(c.Val()) - if err != nil { - return err - } - u.FailTimeout = dur - case "max_fails": - if !c.NextArg() { - return c.ArgErr() - } - n, err := strconv.Atoi(c.Val()) - if err != nil { - return err - } - u.MaxFails = int32(n) - case "health_check": - if !c.NextArg() { - return c.ArgErr() - } - var err error - u.HealthCheck.Path, u.HealthCheck.Port, err = net.SplitHostPort(c.Val()) - if err != nil { - return err - } - u.HealthCheck.Interval = 30 * time.Second - if c.NextArg() { - dur, err := time.ParseDuration(c.Val()) - if err != nil { - return err - } - u.HealthCheck.Interval = dur - u.Future = 2 * dur - - // set a minimum of 3 seconds - if u.Future < (3 * time.Second) { - u.Future = 3 * time.Second - } - } - case "without": - if !c.NextArg() { - return c.ArgErr() - } - u.WithoutPathPrefix = c.Val() - case "except": - ignoredDomains := c.RemainingArgs() - if len(ignoredDomains) == 0 { - return c.ArgErr() - } - for i := 0; i < len(ignoredDomains); i++ { - ignoredDomains[i] = middleware.Host(ignoredDomains[i]).Normalize() - } - u.IgnoredSubDomains = ignoredDomains - case "spray": - u.Spray = &healthcheck.Spray{} - case "protocol": - encArgs := c.RemainingArgs() - if len(encArgs) == 0 { - return c.ArgErr() - } - switch encArgs[0] { - case "dns": - if len(encArgs) > 1 { - if encArgs[1] == "force_tcp" { - opts := Options{ForceTCP: true} - u.ex = newDNSExWithOption(opts) - } else { - return fmt.Errorf("only force_tcp allowed as parameter to dns") - } - } else { - u.ex = newDNSEx() - } - case "https_google": - boot := []string{"8.8.8.8:53", "8.8.4.4:53"} - if len(encArgs) > 2 && encArgs[1] == "bootstrap" { - boot = encArgs[2:] - } - - u.ex = newGoogle("", boot) // "" for default in google.go - case "grpc": - if len(encArgs) == 2 && encArgs[1] == "insecure" { - u.ex = newGrpcClient(nil, u) - return nil - } - tls, err := tls.NewTLSConfigFromArgs(encArgs[1:]...) - if err != nil { - return err - } - u.ex = newGrpcClient(tls, u) - default: - return fmt.Errorf("%s: %s", errInvalidProtocol, encArgs[0]) - } - - default: - return c.Errf("unknown property '%s'", c.Val()) - } - return nil -} - -func (u *staticUpstream) IsAllowedDomain(name string) bool { - if dns.Name(name) == dns.Name(u.From()) { - return true - } - - for _, ignoredSubDomain := range u.IgnoredSubDomains { - if middleware.Name(ignoredSubDomain).Matches(name) { - return false - } - } - return true -} - -func (u *staticUpstream) Exchanger() Exchanger { return u.ex } diff --git a/middleware/proxy/upstream_test.go b/middleware/proxy/upstream_test.go deleted file mode 100644 index 3ee225c2d..000000000 --- a/middleware/proxy/upstream_test.go +++ /dev/null @@ -1,324 +0,0 @@ -package proxy - -import ( - "path/filepath" - "strings" - "testing" - - "github.com/coredns/coredns/middleware/test" - - "github.com/mholt/caddy" -) - -func TestAllowedDomain(t *testing.T) { - upstream := &staticUpstream{ - from: "miek.nl.", - IgnoredSubDomains: []string{"download.miek.nl.", "static.miek.nl."}, // closing dot mandatory - } - tests := []struct { - name string - expected bool - }{ - {"miek.nl.", true}, - {"download.miek.nl.", false}, - {"static.miek.nl.", false}, - {"blaat.miek.nl.", true}, - } - - for i, test := range tests { - isAllowed := upstream.IsAllowedDomain(test.name) - if test.expected != isAllowed { - t.Errorf("Test %d: expected %v found %v for %s", i+1, test.expected, isAllowed, test.name) - } - } -} - -func TestProxyParse(t *testing.T) { - rmFunc, cert, key, ca := getPEMFiles(t) - defer rmFunc() - - grpc1 := "proxy . 8.8.8.8:53 {\n protocol grpc " + ca + "\n}" - grpc2 := "proxy . 8.8.8.8:53 {\n protocol grpc " + cert + " " + key + "\n}" - grpc3 := "proxy . 8.8.8.8:53 {\n protocol grpc " + cert + " " + key + " " + ca + "\n}" - grpc4 := "proxy . 8.8.8.8:53 {\n protocol grpc " + key + "\n}" - - tests := []struct { - inputUpstreams string - shouldErr bool - }{ - { - `proxy . 8.8.8.8:53`, - false, - }, - { - `proxy 10.0.0.0/24 8.8.8.8:53`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - policy round_robin -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - fail_timeout 5s -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - max_fails 10 -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - health_check /health:8080 -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - without without -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - except miek.nl example.org 10.0.0.0/24 -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - spray -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - error_option -}`, - true, - }, - { - ` -proxy . some_bogus_filename`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol dns -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol grpc -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol grpc insecure -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol dns force_tcp -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol grpc a b c d -}`, - true, - }, - { - grpc1, - false, - }, - { - grpc2, - false, - }, - { - grpc3, - false, - }, - { - grpc4, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol foobar -}`, - true, - }, - { - `proxy`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol foobar -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - policy -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - fail_timeout -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - fail_timeout junky -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - health_check -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol dns force -}`, - true, - }, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.inputUpstreams) - _, err := NewStaticUpstreams(&c.Dispenser) - if (err != nil) != test.shouldErr { - t.Errorf("Test %d expected no error, got %v for %s", i+1, err, test.inputUpstreams) - } - } -} - -func TestResolvParse(t *testing.T) { - tests := []struct { - inputUpstreams string - filedata string - shouldErr bool - expected []string - }{ - { - ` -proxy . FILE -`, - ` -nameserver 1.2.3.4 -nameserver 4.3.2.1 -`, - false, - []string{"1.2.3.4:53", "4.3.2.1:53"}, - }, - { - ` -proxy example.com 1.1.1.1:5000 -proxy . FILE -proxy example.org 2.2.2.2:1234 -`, - ` -nameserver 1.2.3.4 -`, - false, - []string{"1.1.1.1:5000", "1.2.3.4:53", "2.2.2.2:1234"}, - }, - { - ` -proxy example.com 1.1.1.1:5000 -proxy . FILE -proxy example.org 2.2.2.2:1234 -`, - ` -junky resolve.conf -`, - false, - []string{"1.1.1.1:5000", "2.2.2.2:1234"}, - }, - } - for i, tc := range tests { - - path, rm, err := test.TempFile(".", tc.filedata) - if err != nil { - t.Fatalf("Test %d could not creat temp file %v", i, err) - } - defer rm() - - config := strings.Replace(tc.inputUpstreams, "FILE", path, -1) - c := caddy.NewTestController("dns", config) - upstreams, err := NewStaticUpstreams(&c.Dispenser) - if (err != nil) != tc.shouldErr { - t.Errorf("Test %d expected no error, got %v", i+1, err) - } - var hosts []string - for _, u := range upstreams { - for _, h := range u.(*staticUpstream).Hosts { - hosts = append(hosts, h.Name) - } - } - if !tc.shouldErr { - if len(hosts) != len(tc.expected) { - t.Errorf("Test %d expected %d hosts got %d", i+1, len(tc.expected), len(upstreams)) - } else { - ok := true - for i, v := range tc.expected { - if v != hosts[i] { - ok = false - } - } - if !ok { - t.Errorf("Test %d expected %v got %v", i+1, tc.expected, upstreams) - } - } - } - } -} - -func getPEMFiles(t *testing.T) (rmFunc func(), cert, key, ca string) { - tempDir, rmFunc, err := test.WritePEMFiles("") - if err != nil { - t.Fatalf("Could not write PEM files: %s", err) - } - - cert = filepath.Join(tempDir, "cert.pem") - key = filepath.Join(tempDir, "key.pem") - ca = filepath.Join(tempDir, "ca.pem") - - return -} diff --git a/middleware/reverse/README.md b/middleware/reverse/README.md deleted file mode 100644 index 2bc4b2e1d..000000000 --- a/middleware/reverse/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# reverse - -The *reverse* middleware allows CoreDNS to respond dynamically to a PTR request and the related A/AAAA request. - -## Syntax - -~~~ -reverse NETWORK... { - hostname TEMPLATE - [ttl TTL] - [fallthrough] - [wildcard] -~~~ - -* **NETWORK** one or more CIDR formatted networks to respond on. -* `hostname` injects the IP and zone to a template for the hostname. Defaults to "ip-{IP}.{zone[1]}". See below for template. -* `ttl` defaults to 60 -* `fallthrough` if zone matches and no record can be generated, pass request to the next middleware. -* `wildcard` allows matches to catch all subdomains as well. - -### Template Syntax - -The template for the hostname is used for generating the PTR for a reverse lookup and matching the -forward lookup back to an IP. - -#### `{ip}` - -The `{ip}` symbol is **required** to make reverse work. -For IPv4 lookups the IP is directly extracted -With IPv6 lookups the ":" is removed, and any zero ranged are expanded, e.g., -"ffff::ffff" results in "ffff000000000000000000000000ffff" - -#### `{zone[i]}` - -The `{zone[i]}` symbol is **optional** and can be replaced by a fixed (zone) string. -The zone will be matched by the zones listed in *this* configuration stanza. -`i` needs to be replaced with the index of the configured listener zones, starting with 1. - -## Examples - -~~~ txt -arpa compute.internal { - # proxy unmatched requests - proxy . 8.8.8.8 - - # answer requests for IPs in this network - # PTR 1.0.32.10.in-addr.arpa. 3600 ip-10.0.32.1.compute.internal. - # A ip-10.0.32.1.compute.internal. 3600 10.0.32.1 - # v6 is also possible - # PTR 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.d.f.ip6.arpa. 3600 ip-fd010000000000000000000000000001.compute.internal. - # AAAA ip-fd010000000000000000000000000001.compute.internal. 3600 fd01::1 - reverse 10.32.0.0/16 fd01::/16 { - # template of the ip injection to hostname, zone resolved to compute.internal. - hostname ip-{ip}.{zone[2]} - - ttl 3600 - - # Forward unanswered or unmatched requests to proxy - # without this flag, requesting A/AAAA records on compute.internal. will end here. - fallthrough - } -} -~~~ - - -~~~ txt -32.10.in-addr.arpa.arpa arpa.company.org { - - reverse 10.32.0.0/16 { - # template of the ip injection to hostname, zone resolved to arpa.company.org. - hostname "ip-{ip}.v4.{zone[2]}" - - ttl 3600 - - # fallthrough is not required, v4.arpa.company.org. will be only answered here - } - - # cidr closer to the ip wins, so we can overwrite the "default" - reverse 10.32.2.0/24 { - # its also possible to set fix domain suffix - hostname ip-{ip}.fix.arpa.company.org. - - ttl 3600 - } -} -~~~ diff --git a/middleware/reverse/network.go b/middleware/reverse/network.go deleted file mode 100644 index 80d533382..000000000 --- a/middleware/reverse/network.go +++ /dev/null @@ -1,87 +0,0 @@ -package reverse - -import ( - "bytes" - "net" - "regexp" - "strings" -) - -type network struct { - IPnet *net.IPNet - Zone string // forward lookup zone - Template string - TTL uint32 - RegexMatchIP *regexp.Regexp -} - -// TODO: we might want to get rid of these regexes. -const hexDigit = "0123456789abcdef" -const templateNameIP = "{ip}" -const regexMatchV4 = "((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))" -const regexMatchV6 = "([0-9a-fA-F]{32})" - -// hostnameToIP converts the hostname back to an ip, based on the template -// returns nil if there is no IP found. -func (network *network) hostnameToIP(rname string) net.IP { - var matchedIP net.IP - - match := network.RegexMatchIP.FindStringSubmatch(rname) - if len(match) != 2 { - return nil - } - - if network.IPnet.IP.To4() != nil { - matchedIP = net.ParseIP(match[1]) - } else { - // TODO: can probably just allocate a []byte and use that. - var buf bytes.Buffer - // convert back to an valid ipv6 string with colons - for i := 0; i < 8*4; i += 4 { - buf.WriteString(match[1][i : i+4]) - if i < 28 { - buf.WriteString(":") - } - } - matchedIP = net.ParseIP(buf.String()) - } - - // No valid ip or it does not belong to this network - if matchedIP == nil || !network.IPnet.Contains(matchedIP) { - return nil - } - - return matchedIP -} - -// ipToHostname converts an IP to an DNS compatible hostname and injects it into the template.domain. -func (network *network) ipToHostname(ip net.IP) (name string) { - if ipv4 := ip.To4(); ipv4 != nil { - // replace . to - - name = ipv4.String() - } else { - // assume v6 - // ensure zeros are present in string - buf := make([]byte, 0, len(ip)*4) - for i := 0; i < len(ip); i++ { - v := ip[i] - buf = append(buf, hexDigit[v>>4]) - buf = append(buf, hexDigit[v&0xF]) - } - name = string(buf) - } - // inject the converted ip into the fqdn template - return strings.Replace(network.Template, templateNameIP, name, 1) -} - -type networks []network - -func (n networks) Len() int { return len(n) } -func (n networks) Swap(i, j int) { n[i], n[j] = n[j], n[i] } - -// cidr closer to the ip wins (by netmask) -func (n networks) Less(i, j int) bool { - isize, _ := n[i].IPnet.Mask.Size() - jsize, _ := n[j].IPnet.Mask.Size() - return isize > jsize -} diff --git a/middleware/reverse/network_test.go b/middleware/reverse/network_test.go deleted file mode 100644 index a826707e5..000000000 --- a/middleware/reverse/network_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package reverse - -import ( - "net" - "reflect" - "regexp" - "testing" -) - -// Test converting from hostname to IP and back again to hostname -func TestNetworkConversion(t *testing.T) { - - _, net4, _ := net.ParseCIDR("10.1.1.0/24") - _, net6, _ := net.ParseCIDR("fd01::/64") - - regexIP4, _ := regexp.Compile("^dns-" + regexMatchV4 + "\\.domain\\.internal\\.$") - regexIP6, _ := regexp.Compile("^dns-" + regexMatchV6 + "\\.domain\\.internal\\.$") - - tests := []struct { - network network - resultHost string - resultIP net.IP - }{ - { - network{ - IPnet: net4, - Template: "dns-{ip}.domain.internal.", - RegexMatchIP: regexIP4, - }, - "dns-10.1.1.23.domain.internal.", - net.ParseIP("10.1.1.23"), - }, - { - network{ - IPnet: net6, - Template: "dns-{ip}.domain.internal.", - RegexMatchIP: regexIP6, - }, - "dns-fd01000000000000000000000000a32f.domain.internal.", - net.ParseIP("fd01::a32f"), - }, - } - - for i, test := range tests { - resultIP := test.network.hostnameToIP(test.resultHost) - if !reflect.DeepEqual(test.resultIP, resultIP) { - t.Fatalf("Test %d expected %v, got %v", i, test.resultIP, resultIP) - } - - resultHost := test.network.ipToHostname(test.resultIP) - if !reflect.DeepEqual(test.resultHost, resultHost) { - t.Fatalf("Test %d expected %v, got %v", i, test.resultHost, resultHost) - } - } -} - -func TestNetworkHostnameToIP(t *testing.T) { - - _, net4, _ := net.ParseCIDR("10.1.1.0/24") - _, net6, _ := net.ParseCIDR("fd01::/64") - - regexIP4, _ := regexp.Compile("^dns-" + regexMatchV4 + "\\.domain\\.internal\\.$") - regexIP6, _ := regexp.Compile("^dns-" + regexMatchV6 + "\\.domain\\.internal\\.$") - - // Test regex does NOT match - // All this test should return nil - testsNil := []struct { - network network - hostname string - }{ - { - network{ - IPnet: net4, - RegexMatchIP: regexIP4, - }, - // domain does not match - "dns-10.1.1.23.domain.internals.", - }, - { - network{ - IPnet: net4, - RegexMatchIP: regexIP4, - }, - // IP does match / contain in subnet - "dns-200.1.1.23.domain.internals.", - }, - { - network{ - IPnet: net4, - RegexMatchIP: regexIP4, - }, - // template does not match - "dns-10.1.1.23-x.domain.internal.", - }, - { - network{ - IPnet: net4, - RegexMatchIP: regexIP4, - }, - // template does not match - "IP-dns-10.1.1.23.domain.internal.", - }, - { - network{ - IPnet: net6, - RegexMatchIP: regexIP6, - }, - // template does not match - "dnx-fd01000000000000000000000000a32f.domain.internal.", - }, - { - network{ - IPnet: net6, - RegexMatchIP: regexIP6, - }, - // no valid v6 (missing one 0, only 31 chars) - "dns-fd0100000000000000000000000a32f.domain.internal.", - }, - { - network{ - IPnet: net6, - RegexMatchIP: regexIP6, - }, - // IP does match / contain in subnet - "dns-ed01000000000000000000000000a32f.domain.internal.", - }, - } - - for i, test := range testsNil { - resultIP := test.network.hostnameToIP(test.hostname) - if resultIP != nil { - t.Fatalf("Test %d expected nil, got %v", i, resultIP) - } - } -} diff --git a/middleware/reverse/reverse.go b/middleware/reverse/reverse.go deleted file mode 100644 index eb14ae155..000000000 --- a/middleware/reverse/reverse.go +++ /dev/null @@ -1,107 +0,0 @@ -package reverse - -import ( - "net" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Reverse provides dynamic reverse DNS and the related forward RR. -type Reverse struct { - Next middleware.Handler - Networks networks - Fallthrough bool -} - -// ServeDNS implements the middleware.Handler interface. -func (re Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - var rr dns.RR - - state := request.Request{W: w, Req: r} - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true - - switch state.QType() { - case dns.TypePTR: - address := dnsutil.ExtractAddressFromReverse(state.Name()) - - if address == "" { - // Not an reverse lookup, but can still be an pointer for an domain - break - } - - ip := net.ParseIP(address) - // loop through the configured networks - for _, n := range re.Networks { - if n.IPnet.Contains(ip) { - rr = &dns.PTR{ - Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: n.TTL}, - Ptr: n.ipToHostname(ip), - } - break - } - } - - case dns.TypeA: - for _, n := range re.Networks { - if dns.IsSubDomain(n.Zone, state.Name()) { - - // skip if requesting an v4 address and network is not v4 - if n.IPnet.IP.To4() == nil { - continue - } - - result := n.hostnameToIP(state.Name()) - if result != nil { - rr = &dns.A{ - Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: n.TTL}, - A: result, - } - break - } - } - } - - case dns.TypeAAAA: - for _, n := range re.Networks { - if dns.IsSubDomain(n.Zone, state.Name()) { - - // Do not use To16 which tries to make v4 in v6 - if n.IPnet.IP.To4() != nil { - continue - } - - result := n.hostnameToIP(state.Name()) - if result != nil { - rr = &dns.AAAA{ - Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: n.TTL}, - AAAA: result, - } - break - } - } - } - - } - - if rr != nil { - m.Answer = append(m.Answer, rr) - state.SizeAndDo(m) - w.WriteMsg(m) - return dns.RcodeSuccess, nil - } - - if re.Fallthrough { - return middleware.NextOrFailure(re.Name(), re.Next, ctx, w, r) - } - return dns.RcodeServerFailure, nil -} - -// Name implements the Handler interface. -func (re Reverse) Name() string { return "reverse" } diff --git a/middleware/reverse/reverse_test.go b/middleware/reverse/reverse_test.go deleted file mode 100644 index 4b17f0971..000000000 --- a/middleware/reverse/reverse_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package reverse - -import ( - "net" - "regexp" - "testing" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestReverse(t *testing.T) { - _, net4, _ := net.ParseCIDR("10.1.1.0/24") - regexIP4, _ := regexp.Compile("^.*ip-" + regexMatchV4 + "\\.example\\.org\\.$") - - em := Reverse{ - Networks: networks{network{ - IPnet: net4, - Zone: "example.org", - Template: "ip-{ip}.example.org.", - RegexMatchIP: regexIP4, - }}, - Fallthrough: false, - } - - tests := []struct { - next middleware.Handler - qname string - qtype uint16 - expectedCode int - expectedReply string - expectedErr error - }{ - { - next: test.NextHandler(dns.RcodeSuccess, nil), - qname: "test.ip-10.1.1.2.example.org", - expectedCode: dns.RcodeSuccess, - expectedReply: "10.1.1.2", - expectedErr: nil, - }, - } - - ctx := context.TODO() - - for i, tr := range tests { - req := new(dns.Msg) - - tr.qtype = dns.TypeA - req.SetQuestion(dns.Fqdn(tr.qname), tr.qtype) - - rec := dnsrecorder.New(&test.ResponseWriter{}) - code, err := em.ServeDNS(ctx, rec, req) - - if err != tr.expectedErr { - t.Errorf("Test %d: Expected error %v, but got %v", i, tr.expectedErr, err) - } - if code != int(tr.expectedCode) { - t.Errorf("Test %d: Expected status code %d, but got %d", i, tr.expectedCode, code) - } - if tr.expectedReply != "" { - answer := rec.Msg.Answer[0].(*dns.A).A.String() - if answer != tr.expectedReply { - t.Errorf("Test %d: Expected answer %s, but got %s", i, tr.expectedReply, answer) - } - } - } -} diff --git a/middleware/reverse/setup.go b/middleware/reverse/setup.go deleted file mode 100644 index 8d8cc6548..000000000 --- a/middleware/reverse/setup.go +++ /dev/null @@ -1,147 +0,0 @@ -package reverse - -import ( - "net" - "regexp" - "sort" - "strconv" - "strings" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("reverse", caddy.Plugin{ - ServerType: "dns", - Action: setupReverse, - }) -} - -func setupReverse(c *caddy.Controller) error { - networks, fallThrough, err := reverseParse(c) - if err != nil { - return middleware.Error("reverse", err) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return Reverse{Next: next, Networks: networks, Fallthrough: fallThrough} - }) - - return nil -} - -func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) { - zones := make([]string, len(c.ServerBlockKeys)) - wildcard := false - - // We copy from the serverblock, these contains Hosts. - for i, str := range c.ServerBlockKeys { - zones[i] = middleware.Host(str).Normalize() - } - - for c.Next() { - var cidrs []*net.IPNet - - // parse all networks - for _, cidr := range c.RemainingArgs() { - if cidr == "{" { - break - } - _, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return nil, false, c.Errf("network needs to be CIDR formatted: %q\n", cidr) - } - cidrs = append(cidrs, ipnet) - } - if len(cidrs) == 0 { - return nil, false, c.ArgErr() - } - - // set defaults - var ( - template = "ip-" + templateNameIP + ".{zone[1]}" - ttl = 60 - ) - for c.NextBlock() { - switch c.Val() { - case "hostname": - if !c.NextArg() { - return nil, false, c.ArgErr() - } - template = c.Val() - - case "ttl": - if !c.NextArg() { - return nil, false, c.ArgErr() - } - ttl, err = strconv.Atoi(c.Val()) - if err != nil { - return nil, false, err - } - - case "wildcard": - wildcard = true - - case "fallthrough": - fall = true - - default: - return nil, false, c.ArgErr() - } - } - - // prepare template - // replace {zone[index]} by the listen zone/domain of this config block - for i, zone := range zones { - // TODO: we should be smarter about actually replacing this. This works, but silently allows "zone[-1]" - // for instance. - template = strings.Replace(template, "{zone["+strconv.Itoa(i+1)+"]}", zone, 1) - } - if !strings.HasSuffix(template, ".") { - template += "." - } - - // extract zone from template - templateZone := strings.SplitAfterN(template, ".", 2) - if len(templateZone) != 2 || templateZone[1] == "" { - return nil, false, c.Errf("cannot find domain in template '%v'", template) - } - - // Create for each configured network in this stanza - for _, ipnet := range cidrs { - // precompile regex for hostname to ip matching - regexIP := regexMatchV4 - if ipnet.IP.To4() == nil { - regexIP = regexMatchV6 - } - prefix := "^" - if wildcard { - prefix += ".*" - } - regex, err := regexp.Compile( - prefix + strings.Replace( // inject ip regex into template - regexp.QuoteMeta(template), // escape dots - regexp.QuoteMeta(templateNameIP), - regexIP, - 1) + "$") - if err != nil { - return nil, false, err - } - - nets = append(nets, network{ - IPnet: ipnet, - Zone: templateZone[1], - Template: template, - RegexMatchIP: regex, - TTL: uint32(ttl), - }) - } - } - - // sort by cidr - sort.Sort(nets) - return nets, fall, nil -} diff --git a/middleware/reverse/setup_test.go b/middleware/reverse/setup_test.go deleted file mode 100644 index 5b4c04e82..000000000 --- a/middleware/reverse/setup_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package reverse - -import ( - "net" - "reflect" - "regexp" - "testing" - - "github.com/mholt/caddy" -) - -func TestSetupParse(t *testing.T) { - - _, net4, _ := net.ParseCIDR("10.1.1.0/24") - _, net6, _ := net.ParseCIDR("fd01::/64") - - regexIP4wildcard, _ := regexp.Compile("^.*ip-" + regexMatchV4 + "\\.domain\\.com\\.$") - regexIP6, _ := regexp.Compile("^ip-" + regexMatchV6 + "\\.domain\\.com\\.$") - regexIpv4dynamic, _ := regexp.Compile("^dynamic-" + regexMatchV4 + "-intern\\.dynamic\\.domain\\.com\\.$") - regexIpv6dynamic, _ := regexp.Compile("^dynamic-" + regexMatchV6 + "-intern\\.dynamic\\.domain\\.com\\.$") - regexIpv4vpndynamic, _ := regexp.Compile("^dynamic-" + regexMatchV4 + "-vpn\\.dynamic\\.domain\\.com\\.$") - - serverBlockKeys := []string{"domain.com.:8053", "dynamic.domain.com.:8053"} - - tests := []struct { - inputFileRules string - shouldErr bool - networks networks - }{ - { - // with defaults - `reverse fd01::/64`, - false, - networks{network{ - IPnet: net6, - Template: "ip-{ip}.domain.com.", - Zone: "domain.com.", - TTL: 60, - RegexMatchIP: regexIP6, - }}, - }, - { - `reverse`, - true, - networks{}, - }, - { - //no cidr - `reverse 10.1.1.1`, - true, - networks{}, - }, - { - //no cidr - `reverse 10.1.1.0/16 fd00::`, - true, - networks{}, - }, - { - // invalid key - `reverse 10.1.1.0/24 { - notavailable - }`, - true, - networks{}, - }, - { - // no domain suffix - `reverse 10.1.1.0/24 { - hostname ip-{ip}. - }`, - true, - networks{}, - }, - { - // hostname requires an second arg - `reverse 10.1.1.0/24 { - hostname - }`, - true, - networks{}, - }, - { - // template breaks regex compile - `reverse 10.1.1.0/24 { - hostname ip-{[-x - }`, - true, - networks{}, - }, - { - // ttl requires an (u)int - `reverse 10.1.1.0/24 { - ttl string - }`, - true, - networks{}, - }, - { - `reverse fd01::/64 { - hostname dynamic-{ip}-intern.{zone[2]} - ttl 50 - } - reverse 10.1.1.0/24 { - hostname dynamic-{ip}-vpn.{zone[2]} - fallthrough - }`, - false, - networks{network{ - IPnet: net6, - Template: "dynamic-{ip}-intern.dynamic.domain.com.", - Zone: "dynamic.domain.com.", - TTL: 50, - RegexMatchIP: regexIpv6dynamic, - }, network{ - IPnet: net4, - Template: "dynamic-{ip}-vpn.dynamic.domain.com.", - Zone: "dynamic.domain.com.", - TTL: 60, - RegexMatchIP: regexIpv4vpndynamic, - }}, - }, - { - // multiple networks in one stanza - `reverse fd01::/64 10.1.1.0/24 { - hostname dynamic-{ip}-intern.{zone[2]} - ttl 50 - fallthrough - }`, - false, - networks{network{ - IPnet: net6, - Template: "dynamic-{ip}-intern.dynamic.domain.com.", - Zone: "dynamic.domain.com.", - TTL: 50, - RegexMatchIP: regexIpv6dynamic, - }, network{ - IPnet: net4, - Template: "dynamic-{ip}-intern.dynamic.domain.com.", - Zone: "dynamic.domain.com.", - TTL: 50, - RegexMatchIP: regexIpv4dynamic, - }}, - }, - { - // fix domain in template - `reverse fd01::/64 { - hostname dynamic-{ip}-intern.dynamic.domain.com - ttl 300 - fallthrough - }`, - false, - networks{network{ - IPnet: net6, - Template: "dynamic-{ip}-intern.dynamic.domain.com.", - Zone: "dynamic.domain.com.", - TTL: 300, - RegexMatchIP: regexIpv6dynamic, - }}, - }, - { - `reverse 10.1.1.0/24 { - hostname ip-{ip}.{zone[1]} - ttl 50 - wildcard - fallthrough - }`, - false, - networks{network{ - IPnet: net4, - Template: "ip-{ip}.domain.com.", - Zone: "domain.com.", - TTL: 50, - RegexMatchIP: regexIP4wildcard, - }}, - }, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.inputFileRules) - c.ServerBlockKeys = serverBlockKeys - networks, _, err := reverseParse(c) - - if err == nil && test.shouldErr { - t.Fatalf("Test %d expected errors, but got no error", i) - } else if err != nil && !test.shouldErr { - t.Fatalf("Test %d expected no errors, but got '%v'", i, err) - } - for j, n := range networks { - reflect.DeepEqual(test.networks[j], n) - if !reflect.DeepEqual(test.networks[j], n) { - t.Fatalf("Test %d/%d expected %v, got %v", i, j, test.networks[j], n) - } - } - } -} diff --git a/middleware/rewrite/README.md b/middleware/rewrite/README.md deleted file mode 100644 index 63334d09c..000000000 --- a/middleware/rewrite/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# rewrite - -*rewrite* performs internal message rewriting. - -Rewrites are invisible to the client. There are simple rewrites (fast) and complex rewrites -(slower), but they're powerful enough to accommodate most dynamic back-end applications. - -## Syntax - -~~~ -rewrite FIELD FROM TO -~~~ - -* **FIELD** is (`type`, `class`, `name`, ...) -* **FROM** is the exact name of type to match -* **TO** is the destination name or type to rewrite to - -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 type ANY HINFO`. - -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 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 - -Using FIELD edns0, you can set, append, or replace specific EDNS0 options on the request. - -* `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 - -Currently supported are `EDNS0_LOCAL`, `EDNS0_NSID` and `EDNS0_SUBNET`. - -### `EDNS0_LOCAL` - -This has two fields, code and data. A match is defined as having the same code. Data may be a string or a variable. - -* A string data can be treated as hex if it starts with `0x`. Example: - -~~~ -rewrite edns0 local set 0xffee 0x61626364 -~~~ - -rewrites the first local option with code 0xffee, setting the data to "abcd". Equivalent: - -~~~ -rewrite edns0 local set 0xffee abcd -~~~ - -* A variable data is specified with a pair of curly brackets `{}`. Following are the supported variables: - * {qname} - * {qtype} - * {client_ip} - * {client_port} - * {protocol} - * {server_ip} - * {server_port} - -Example: - -~~~ -rewrite edns0 local set 0xffee {client_ip} -~~~ - -### `EDNS0_NSID` - -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. - -### `EDNS0_SUBNET` - -This has two fields, IPv4 bitmask length and IPv6 bitmask length. The bitmask -length is used to extract the client subnet from the source IP address in the query. - -Example: - -~~~ - 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. - diff --git a/middleware/rewrite/class.go b/middleware/rewrite/class.go deleted file mode 100644 index 8cc7d26b7..000000000 --- a/middleware/rewrite/class.go +++ /dev/null @@ -1,35 +0,0 @@ -package rewrite - -import ( - "fmt" - "strings" - - "github.com/miekg/dns" -) - -type classRule struct { - fromClass, toClass uint16 -} - -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(w dns.ResponseWriter, 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 -} diff --git a/middleware/rewrite/condition.go b/middleware/rewrite/condition.go deleted file mode 100644 index d4a54d44a..000000000 --- a/middleware/rewrite/condition.go +++ /dev/null @@ -1,132 +0,0 @@ -package rewrite - -import ( - "fmt" - "regexp" - "strings" - - "github.com/coredns/coredns/middleware/pkg/replacer" - - "github.com/miekg/dns" -) - -// Operators -const ( - Is = "is" - Not = "not" - Has = "has" - NotHas = "not_has" - StartsWith = "starts_with" - EndsWith = "ends_with" - Match = "match" - NotMatch = "not_match" -) - -func operatorError(operator string) error { - return fmt.Errorf("invalid operator %v", operator) -} - -func newReplacer(r *dns.Msg) replacer.Replacer { - return replacer.New(r, nil, "") -} - -// condition is a rewrite condition. -type condition func(string, string) bool - -var conditions = map[string]condition{ - Is: isFunc, - Not: notFunc, - Has: hasFunc, - NotHas: notHasFunc, - StartsWith: startsWithFunc, - EndsWith: endsWithFunc, - Match: matchFunc, - NotMatch: notMatchFunc, -} - -// isFunc is condition for Is operator. -// It checks for equality. -func isFunc(a, b string) bool { - return a == b -} - -// notFunc is condition for Not operator. -// It checks for inequality. -func notFunc(a, b string) bool { - return a != b -} - -// hasFunc is condition for Has operator. -// It checks if b is a substring of a. -func hasFunc(a, b string) bool { - return strings.Contains(a, b) -} - -// notHasFunc is condition for NotHas operator. -// It checks if b is not a substring of a. -func notHasFunc(a, b string) bool { - return !strings.Contains(a, b) -} - -// startsWithFunc is condition for StartsWith operator. -// It checks if b is a prefix of a. -func startsWithFunc(a, b string) bool { - return strings.HasPrefix(a, b) -} - -// endsWithFunc is condition for EndsWith operator. -// It checks if b is a suffix of a. -func endsWithFunc(a, b string) bool { - // TODO(miek): IsSubDomain - return strings.HasSuffix(a, b) -} - -// matchFunc is condition for Match operator. -// It does regexp matching of a against pattern in b -// and returns if they match. -func matchFunc(a, b string) bool { - matched, _ := regexp.MatchString(b, a) - return matched -} - -// notMatchFunc is condition for NotMatch operator. -// It does regexp matching of a against pattern in b -// and returns if they do not match. -func notMatchFunc(a, b string) bool { - matched, _ := regexp.MatchString(b, a) - return !matched -} - -// If is statement for a rewrite condition. -type If struct { - A string - Operator string - B string -} - -// True returns true if the condition is true and false otherwise. -// If r is not nil, it replaces placeholders before comparison. -func (i If) True(r *dns.Msg) bool { - if c, ok := conditions[i.Operator]; ok { - a, b := i.A, i.B - if r != nil { - replacer := newReplacer(r) - a = replacer.Replace(i.A) - b = replacer.Replace(i.B) - } - return c(a, b) - } - return false -} - -// NewIf creates a new If condition. -func NewIf(a, operator, b string) (If, error) { - if _, ok := conditions[operator]; !ok { - return If{}, operatorError(operator) - } - return If{ - A: a, - Operator: operator, - B: b, - }, nil -} diff --git a/middleware/rewrite/condition_test.go b/middleware/rewrite/condition_test.go deleted file mode 100644 index 91004f9d7..000000000 --- a/middleware/rewrite/condition_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package rewrite - -/* -func TestConditions(t *testing.T) { - tests := []struct { - condition string - isTrue bool - }{ - {"a is b", false}, - {"a is a", true}, - {"a not b", true}, - {"a not a", false}, - {"a has a", true}, - {"a has b", false}, - {"ba has b", true}, - {"bab has b", true}, - {"bab has bb", false}, - {"a not_has a", false}, - {"a not_has b", true}, - {"ba not_has b", false}, - {"bab not_has b", false}, - {"bab not_has bb", true}, - {"bab starts_with bb", false}, - {"bab starts_with ba", true}, - {"bab starts_with bab", true}, - {"bab ends_with bb", false}, - {"bab ends_with bab", true}, - {"bab ends_with ab", true}, - {"a match *", false}, - {"a match a", true}, - {"a match .*", true}, - {"a match a.*", true}, - {"a match b.*", false}, - {"ba match b.*", true}, - {"ba match b[a-z]", true}, - {"b0 match b[a-z]", false}, - {"b0a match b[a-z]", false}, - {"b0a match b[a-z]+", false}, - {"b0a match b[a-z0-9]+", true}, - {"a not_match *", true}, - {"a not_match a", false}, - {"a not_match .*", false}, - {"a not_match a.*", false}, - {"a not_match b.*", true}, - {"ba not_match b.*", false}, - {"ba not_match b[a-z]", false}, - {"b0 not_match b[a-z]", true}, - {"b0a not_match b[a-z]", true}, - {"b0a not_match b[a-z]+", true}, - {"b0a not_match b[a-z0-9]+", false}, - } - - for i, test := range tests { - str := strings.Fields(test.condition) - ifCond, err := NewIf(str[0], str[1], str[2]) - if err != nil { - t.Error(err) - } - isTrue := ifCond.True(nil) - if isTrue != test.isTrue { - t.Errorf("Test %v: expected %v found %v", i, test.isTrue, isTrue) - } - } - - invalidOperators := []string{"ss", "and", "if"} - for _, op := range invalidOperators { - _, err := NewIf("a", op, "b") - if err == nil { - t.Errorf("Invalid operator %v used, expected error.", op) - } - } - - replaceTests := []struct { - url string - condition string - isTrue bool - }{ - {"/home", "{uri} match /home", true}, - {"/hom", "{uri} match /home", false}, - {"/hom", "{uri} starts_with /home", false}, - {"/hom", "{uri} starts_with /h", true}, - {"/home/.hiddenfile", `{uri} match \/\.(.*)`, true}, - {"/home/.hiddendir/afile", `{uri} match \/\.(.*)`, true}, - } - - for i, test := range replaceTests { - r, err := http.NewRequest("GET", test.url, nil) - if err != nil { - t.Error(err) - } - str := strings.Fields(test.condition) - ifCond, err := NewIf(str[0], str[1], str[2]) - if err != nil { - t.Error(err) - } - isTrue := ifCond.True(r) - if isTrue != test.isTrue { - t.Errorf("Test %v: expected %v found %v", i, test.isTrue, isTrue) - } - } -} -*/ diff --git a/middleware/rewrite/edns0.go b/middleware/rewrite/edns0.go deleted file mode 100644 index bdfcac6fd..000000000 --- a/middleware/rewrite/edns0.go +++ /dev/null @@ -1,425 +0,0 @@ -// Package rewrite is middleware for rewriting requests internally to something different. -package rewrite - -import ( - "encoding/binary" - "encoding/hex" - "fmt" - "net" - "strconv" - "strings" - - "github.com/coredns/coredns/request" - "github.com/miekg/dns" -) - -// edns0LocalRule is a rewrite rule for EDNS0_LOCAL options -type edns0LocalRule struct { - action string - code uint16 - data []byte -} - -// edns0VariableRule is a rewrite rule for EDNS0_LOCAL options with variable -type edns0VariableRule struct { - action string - code uint16 - variable string -} - -// 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(w dns.ResponseWriter, r *dns.Msg) Result { - result := RewriteIgnored - o := setupEdns0Opt(r) - found := false -Option: - 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 Option - } - } - - // add option if not found - if !found && (rule.action == Append || rule.action == Set) { - o.SetDo() - 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(w dns.ResponseWriter, 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() - 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") - } - //Check for variable option - if strings.HasPrefix(args[3], "{") && strings.HasSuffix(args[3], "}") { - return newEdns0VariableRule(action, args[2], args[3]) - } - 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 - case "subnet": - if len(args) != 4 { - return nil, fmt.Errorf("EDNS0 subnet rules require exactly three args") - } - return newEdns0SubnetRule(action, args[2], args[3]) - 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 -} - -// newEdns0VariableRule creates an EDNS0 rule that handles variable substitution -func newEdns0VariableRule(action, code, variable string) (*edns0VariableRule, error) { - c, err := strconv.ParseUint(code, 0, 16) - if err != nil { - return nil, err - } - //Validate - if !isValidVariable(variable) { - return nil, fmt.Errorf("unsupported variable name %q", variable) - } - return &edns0VariableRule{action: action, code: uint16(c), variable: variable}, nil -} - -// ipToWire writes IP address to wire/binary format, 4 or 16 bytes depends on IPV4 or IPV6. -func (rule *edns0VariableRule) ipToWire(family int, ipAddr string) ([]byte, error) { - - switch family { - case 1: - return net.ParseIP(ipAddr).To4(), nil - case 2: - return net.ParseIP(ipAddr).To16(), nil - } - return nil, fmt.Errorf("Invalid IP address family (i.e. version) %d", family) -} - -// uint16ToWire writes unit16 to wire/binary format -func (rule *edns0VariableRule) uint16ToWire(data uint16) []byte { - buf := make([]byte, 2) - binary.BigEndian.PutUint16(buf, uint16(data)) - return buf -} - -// portToWire writes port to wire/binary format, 2 bytes -func (rule *edns0VariableRule) portToWire(portStr string) ([]byte, error) { - - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return nil, err - } - return rule.uint16ToWire(uint16(port)), nil -} - -// Family returns the family of the transport, 1 for IPv4 and 2 for IPv6. -func (rule *edns0VariableRule) family(ip net.Addr) int { - var a net.IP - if i, ok := ip.(*net.UDPAddr); ok { - a = i.IP - } - if i, ok := ip.(*net.TCPAddr); ok { - a = i.IP - } - if a.To4() != nil { - return 1 - } - return 2 -} - -// ruleData returns the data specified by the variable -func (rule *edns0VariableRule) ruleData(w dns.ResponseWriter, r *dns.Msg) ([]byte, error) { - - req := request.Request{W: w, Req: r} - switch rule.variable { - case queryName: - //Query name is written as ascii string - return []byte(req.QName()), nil - - case queryType: - return rule.uint16ToWire(req.QType()), nil - - case clientIP: - return rule.ipToWire(req.Family(), req.IP()) - - case clientPort: - return rule.portToWire(req.Port()) - - case protocol: - // Proto is written as ascii string - return []byte(req.Proto()), nil - - case serverIP: - ip, _, err := net.SplitHostPort(w.LocalAddr().String()) - if err != nil { - ip = w.RemoteAddr().String() - } - return rule.ipToWire(rule.family(w.RemoteAddr()), ip) - - case serverPort: - _, port, err := net.SplitHostPort(w.LocalAddr().String()) - if err != nil { - port = "0" - } - return rule.portToWire(port) - } - - return nil, fmt.Errorf("Unable to extract data for variable %s", rule.variable) -} - -// Rewrite will alter the request EDNS0 local options with specified variables -func (rule *edns0VariableRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result { - result := RewriteIgnored - - data, err := rule.ruleData(w, r) - if err != nil || data == nil { - return result - } - - 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 = data - result = RewriteDone - } - found = true - break - } - } - } - - // add option if not found - if !found && (rule.action == Append || rule.action == Set) { - o.SetDo() - var opt dns.EDNS0_LOCAL - opt.Code = rule.code - opt.Data = data - o.Option = append(o.Option, &opt) - result = RewriteDone - } - - return result -} - -func isValidVariable(variable string) bool { - switch variable { - case - queryName, - queryType, - clientIP, - clientPort, - protocol, - serverIP, - serverPort: - return true - } - return false -} - -// ends0SubnetRule is a rewrite rule for EDNS0 subnet options -type edns0SubnetRule struct { - v4BitMaskLen uint8 - v6BitMaskLen uint8 - action string -} - -func newEdns0SubnetRule(action, v4BitMaskLen, v6BitMaskLen string) (*edns0SubnetRule, error) { - v4Len, err := strconv.ParseUint(v4BitMaskLen, 0, 16) - if err != nil { - return nil, err - } - // Validate V4 length - if v4Len > maxV4BitMaskLen { - return nil, fmt.Errorf("invalid IPv4 bit mask length %d", v4Len) - } - - v6Len, err := strconv.ParseUint(v6BitMaskLen, 0, 16) - if err != nil { - return nil, err - } - //Validate V6 length - if v6Len > maxV6BitMaskLen { - return nil, fmt.Errorf("invalid IPv6 bit mask length %d", v6Len) - } - - return &edns0SubnetRule{action: action, - v4BitMaskLen: uint8(v4Len), v6BitMaskLen: uint8(v6Len)}, nil -} - -// fillEcsData sets the subnet data into the ecs option -func (rule *edns0SubnetRule) fillEcsData(w dns.ResponseWriter, r *dns.Msg, - ecs *dns.EDNS0_SUBNET) error { - - req := request.Request{W: w, Req: r} - family := req.Family() - if (family != 1) && (family != 2) { - return fmt.Errorf("unable to fill data for EDNS0 subnet due to invalid IP family") - } - - ecs.DraftOption = false - ecs.Family = uint16(family) - ecs.SourceScope = 0 - - ipAddr := req.IP() - switch family { - case 1: - ipv4Mask := net.CIDRMask(int(rule.v4BitMaskLen), 32) - ipv4Addr := net.ParseIP(ipAddr) - ecs.SourceNetmask = rule.v4BitMaskLen - ecs.Address = ipv4Addr.Mask(ipv4Mask).To4() - case 2: - ipv6Mask := net.CIDRMask(int(rule.v6BitMaskLen), 128) - ipv6Addr := net.ParseIP(ipAddr) - ecs.SourceNetmask = rule.v6BitMaskLen - ecs.Address = ipv6Addr.Mask(ipv6Mask).To16() - } - return nil -} - -// Rewrite will alter the request EDNS0 subnet option -func (rule *edns0SubnetRule) Rewrite(w dns.ResponseWriter, r *dns.Msg) Result { - result := RewriteIgnored - o := setupEdns0Opt(r) - found := false - for _, s := range o.Option { - switch e := s.(type) { - case *dns.EDNS0_SUBNET: - if rule.action == Replace || rule.action == Set { - if rule.fillEcsData(w, r, e) == nil { - result = RewriteDone - } - } - found = true - break - } - } - - // add option if not found - if !found && (rule.action == Append || rule.action == Set) { - o.SetDo() - opt := dns.EDNS0_SUBNET{Code: dns.EDNS0SUBNET} - if rule.fillEcsData(w, r, &opt) == nil { - o.Option = append(o.Option, &opt) - result = RewriteDone - } - } - - return result -} - -// These are all defined actions. -const ( - Replace = "replace" - Set = "set" - Append = "append" -) - -// Supported local EDNS0 variables -const ( - queryName = "{qname}" - queryType = "{qtype}" - clientIP = "{client_ip}" - clientPort = "{client_port}" - protocol = "{protocol}" - serverIP = "{server_ip}" - serverPort = "{server_port}" -) - -// Subnet maximum bit mask length -const ( - maxV4BitMaskLen = 32 - maxV6BitMaskLen = 128 -) diff --git a/middleware/rewrite/name.go b/middleware/rewrite/name.go deleted file mode 100644 index 6233197d6..000000000 --- a/middleware/rewrite/name.go +++ /dev/null @@ -1,24 +0,0 @@ -package rewrite - -import ( - "github.com/coredns/coredns/middleware" - - "github.com/miekg/dns" -) - -type nameRule struct { - From, To string -} - -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(w dns.ResponseWriter, r *dns.Msg) Result { - if rule.From == r.Question[0].Name { - r.Question[0].Name = rule.To - return RewriteDone - } - return RewriteIgnored -} diff --git a/middleware/rewrite/reverter.go b/middleware/rewrite/reverter.go deleted file mode 100644 index 400fb5fff..000000000 --- a/middleware/rewrite/reverter.go +++ /dev/null @@ -1,39 +0,0 @@ -package rewrite - -import "github.com/miekg/dns" - -// 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. -// dig will complain with ';; Question section mismatch: got miek.nl/HINFO/IN' -type ResponseReverter struct { - dns.ResponseWriter - original dns.Question -} - -// NewResponseReverter returns a pointer to a new ResponseReverter. -func NewResponseReverter(w dns.ResponseWriter, r *dns.Msg) *ResponseReverter { - return &ResponseReverter{ - ResponseWriter: w, - original: r.Question[0], - } -} - -// WriteMsg records the status code and calls the -// underlying ResponseWriter's WriteMsg method. -func (r *ResponseReverter) WriteMsg(res *dns.Msg) error { - res.Question[0] = r.original - return r.ResponseWriter.WriteMsg(res) -} - -// Write is a wrapper that records the size of the message that gets written. -func (r *ResponseReverter) Write(buf []byte) (int, error) { - n, err := r.ResponseWriter.Write(buf) - return n, err -} - -// Hijack implements dns.Hijacker. It simply wraps the underlying -// ResponseWriter's Hijack method if there is one, or returns an error. -func (r *ResponseReverter) Hijack() { - r.ResponseWriter.Hijack() - return -} diff --git a/middleware/rewrite/rewrite.go b/middleware/rewrite/rewrite.go deleted file mode 100644 index 44e8e43c7..000000000 --- a/middleware/rewrite/rewrite.go +++ /dev/null @@ -1,86 +0,0 @@ -package rewrite - -import ( - "fmt" - "strings" - - "github.com/coredns/coredns/middleware" - - "github.com/miekg/dns" - - "golang.org/x/net/context" -) - -// Result is the result of a rewrite -type Result int - -const ( - // RewriteIgnored is returned when rewrite is not done on request. - RewriteIgnored Result = iota - // RewriteDone is returned when rewrite is done on request. - RewriteDone - // RewriteStatus is returned when rewrite is not needed and status code should be set - // for the request. - RewriteStatus -) - -// Rewrite is middleware to rewrite requests internally before being handled. -type Rewrite struct { - Next middleware.Handler - Rules []Rule - noRevert bool -} - -// ServeDNS implements the middleware.Handler interface. -func (rw Rewrite) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - wr := NewResponseReverter(w, r) - for _, rule := range rw.Rules { - switch result := rule.Rewrite(w, r); result { - case RewriteDone: - if rw.noRevert { - return middleware.NextOrFailure(rw.Name(), rw.Next, ctx, w, r) - } - return middleware.NextOrFailure(rw.Name(), rw.Next, ctx, wr, r) - case RewriteIgnored: - break - case RewriteStatus: - // only valid for complex rules. - // if cRule, ok := rule.(*ComplexRule); ok && cRule.Status != 0 { - // return cRule.Status, nil - // } - } - } - return middleware.NextOrFailure(rw.Name(), rw.Next, ctx, w, r) -} - -// Name implements the Handler interface. -func (rw Rewrite) Name() string { return "rewrite" } - -// Rule describes a rewrite rule. -type Rule interface { - // Rewrite rewrites the current request. - Rewrite(dns.ResponseWriter, *dns.Msg) Result -} - -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 deleted file mode 100644 index 39648711e..000000000 --- a/middleware/rewrite/rewrite_test.go +++ /dev/null @@ -1,532 +0,0 @@ -package rewrite - -import ( - "bytes" - "reflect" - "testing" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func msgPrinter(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - w.WriteMsg(r) - 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}, - {[]string{"edns0", "local", "set", "0xffee", "{dummy}"}, true, nil}, - {[]string{"edns0", "local", "set", "0xffee", "{qname}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "set", "0xffee", "{qtype}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "set", "0xffee", "{client_ip}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "set", "0xffee", "{client_port}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "set", "0xffee", "{protocol}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "set", "0xffee", "{server_ip}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "set", "0xffee", "{server_port}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "append", "0xffee", "{dummy}"}, true, nil}, - {[]string{"edns0", "local", "append", "0xffee", "{qname}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "append", "0xffee", "{qtype}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "append", "0xffee", "{client_ip}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "append", "0xffee", "{client_port}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "append", "0xffee", "{protocol}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "append", "0xffee", "{server_ip}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "append", "0xffee", "{server_port}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "replace", "0xffee", "{dummy}"}, true, nil}, - {[]string{"edns0", "local", "replace", "0xffee", "{qname}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "replace", "0xffee", "{qtype}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "replace", "0xffee", "{client_ip}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "replace", "0xffee", "{client_port}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "replace", "0xffee", "{protocol}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "replace", "0xffee", "{server_ip}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "local", "replace", "0xffee", "{server_port}"}, false, reflect.TypeOf(&edns0VariableRule{})}, - {[]string{"edns0", "subnet", "set", "-1", "56"}, true, nil}, - {[]string{"edns0", "subnet", "set", "24", "-56"}, true, nil}, - {[]string{"edns0", "subnet", "set", "33", "56"}, true, nil}, - {[]string{"edns0", "subnet", "set", "24", "129"}, true, nil}, - {[]string{"edns0", "subnet", "set", "24", "56"}, false, reflect.TypeOf(&edns0SubnetRule{})}, - {[]string{"edns0", "subnet", "append", "24", "56"}, false, reflect.TypeOf(&edns0SubnetRule{})}, - {[]string{"edns0", "subnet", "replace", "24", "56"}, false, reflect.TypeOf(&edns0SubnetRule{})}, - } - - 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: rules, - noRevert: true, - } - - tests := []struct { - from string - fromT uint16 - fromC uint16 - to string - toT uint16 - toC uint16 - }{ - {"from.nl.", dns.TypeA, dns.ClassINET, "to.nl.", dns.TypeA, dns.ClassINET}, - {"a.nl.", dns.TypeA, dns.ClassINET, "a.nl.", dns.TypeA, dns.ClassINET}, - {"a.nl.", dns.TypeA, dns.ClassCHAOS, "a.nl.", dns.TypeA, dns.ClassINET}, - {"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}, - // 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}, - } - - ctx := context.TODO() - for i, tc := range tests { - m := new(dns.Msg) - m.SetQuestion(tc.from, tc.fromT) - m.Question[0].Qclass = tc.fromC - - rec := dnsrecorder.New(&test.ResponseWriter{}) - rw.ServeDNS(ctx, rec, m) - - resp := rec.Msg - if resp.Question[0].Name != tc.to { - 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) - } - 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) - } - } -} - -func TestRewriteEDNS0Local(t *testing.T) { - rw := Rewrite{ - Next: middleware.HandlerFunc(msgPrinter), - noRevert: true, - } - - 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} - - 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 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")}}, - }, - } - - 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() - } - 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 !bytes.Equal(aa.Data, bb.Data) { - return false - } - } else { - 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 - } - case *dns.EDNS0_SUBNET: - if bb, ok := b[i].(*dns.EDNS0_SUBNET); ok { - if aa.Code != bb.Code { - return false - } - if aa.Family != bb.Family { - return false - } - if aa.SourceNetmask != bb.SourceNetmask { - return false - } - if aa.SourceScope != bb.SourceScope { - return false - } - if !bytes.Equal(aa.Address, bb.Address) { - return false - } - if aa.DraftOption != bb.DraftOption { - return false - } - } else { - return false - } - - default: - return false - } - } - return true -} - -func TestRewriteEDNS0LocalVariable(t *testing.T) { - rw := Rewrite{ - Next: middleware.HandlerFunc(msgPrinter), - noRevert: true, - } - - // test.ResponseWriter has the following values: - // The remote will always be 10.240.0.1 and port 40212. - // The local address is always 127.0.0.1 and port 53. - - tests := []struct { - fromOpts []dns.EDNS0 - args []string - toOpts []dns.EDNS0 - }{ - { - []dns.EDNS0{}, - []string{"local", "set", "0xffee", "{qname}"}, - []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte("example.com.")}}, - }, - { - []dns.EDNS0{}, - []string{"local", "set", "0xffee", "{qtype}"}, - []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte{0x00, 0x01}}}, - }, - { - []dns.EDNS0{}, - []string{"local", "set", "0xffee", "{client_ip}"}, - []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte{0x0A, 0xF0, 0x00, 0x01}}}, - }, - { - []dns.EDNS0{}, - []string{"local", "set", "0xffee", "{client_port}"}, - []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte{0x9D, 0x14}}}, - }, - { - []dns.EDNS0{}, - []string{"local", "set", "0xffee", "{protocol}"}, - []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte("udp")}}, - }, - { - []dns.EDNS0{}, - []string{"local", "set", "0xffee", "{server_ip}"}, - []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte{0x7F, 0x00, 0x00, 0x01}}}, - }, - { - []dns.EDNS0{}, - []string{"local", "set", "0xffee", "{server_port}"}, - []dns.EDNS0{&dns.EDNS0_LOCAL{Code: 0xffee, Data: []byte{0x00, 0x35}}}, - }, - } - - 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} - - 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 TestRewriteEDNS0Subnet(t *testing.T) { - rw := Rewrite{ - Next: middleware.HandlerFunc(msgPrinter), - noRevert: true, - } - - tests := []struct { - writer dns.ResponseWriter - fromOpts []dns.EDNS0 - args []string - toOpts []dns.EDNS0 - }{ - { - &test.ResponseWriter{}, - []dns.EDNS0{}, - []string{"subnet", "set", "24", "56"}, - []dns.EDNS0{&dns.EDNS0_SUBNET{Code: 0x8, - Family: 0x1, - SourceNetmask: 0x18, - SourceScope: 0x0, - Address: []byte{0x0A, 0xF0, 0x00, 0x00}, - DraftOption: false}}, - }, - { - &test.ResponseWriter{}, - []dns.EDNS0{}, - []string{"subnet", "set", "32", "56"}, - []dns.EDNS0{&dns.EDNS0_SUBNET{Code: 0x8, - Family: 0x1, - SourceNetmask: 0x20, - SourceScope: 0x0, - Address: []byte{0x0A, 0xF0, 0x00, 0x01}, - DraftOption: false}}, - }, - { - &test.ResponseWriter{}, - []dns.EDNS0{}, - []string{"subnet", "set", "0", "56"}, - []dns.EDNS0{&dns.EDNS0_SUBNET{Code: 0x8, - Family: 0x1, - SourceNetmask: 0x0, - SourceScope: 0x0, - Address: []byte{0x00, 0x00, 0x00, 0x00}, - DraftOption: false}}, - }, - { - &test.ResponseWriter6{}, - []dns.EDNS0{}, - []string{"subnet", "set", "24", "56"}, - []dns.EDNS0{&dns.EDNS0_SUBNET{Code: 0x8, - Family: 0x2, - SourceNetmask: 0x38, - SourceScope: 0x0, - Address: []byte{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - DraftOption: false}}, - }, - { - &test.ResponseWriter6{}, - []dns.EDNS0{}, - []string{"subnet", "set", "24", "128"}, - []dns.EDNS0{&dns.EDNS0_SUBNET{Code: 0x8, - Family: 0x2, - SourceNetmask: 0x80, - SourceScope: 0x0, - Address: []byte{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x42, 0x00, 0xff, 0xfe, 0xca, 0x4c, 0x65}, - DraftOption: false}}, - }, - { - &test.ResponseWriter6{}, - []dns.EDNS0{}, - []string{"subnet", "set", "24", "0"}, - []dns.EDNS0{&dns.EDNS0_SUBNET{Code: 0x8, - Family: 0x2, - SourceNetmask: 0x0, - SourceScope: 0x0, - Address: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - DraftOption: false}}, - }, - } - - 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} - rec := dnsrecorder.New(tc.writer) - 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) - } - } -} diff --git a/middleware/rewrite/setup.go b/middleware/rewrite/setup.go deleted file mode 100644 index 156129f70..000000000 --- a/middleware/rewrite/setup.go +++ /dev/null @@ -1,42 +0,0 @@ -package rewrite - -import ( - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("rewrite", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - rewrites, err := rewriteParse(c) - if err != nil { - return middleware.Error("rewrite", err) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return Rewrite{Next: next, Rules: rewrites} - }) - - return nil -} - -func rewriteParse(c *caddy.Controller) ([]Rule, error) { - var rules []Rule - - for c.Next() { - args := c.RemainingArgs() - rule, err := newRule(args...) - if err != nil { - return nil, err - } - rules = append(rules, rule) - } - return rules, nil -} diff --git a/middleware/rewrite/setup_test.go b/middleware/rewrite/setup_test.go deleted file mode 100644 index 67ef88e18..000000000 --- a/middleware/rewrite/setup_test.go +++ /dev/null @@ -1,25 +0,0 @@ -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/testdata/testdir/empty b/middleware/rewrite/testdata/testdir/empty deleted file mode 100644 index e69de29bb..000000000 --- a/middleware/rewrite/testdata/testdir/empty +++ /dev/null diff --git a/middleware/rewrite/testdata/testfile b/middleware/rewrite/testdata/testfile deleted file mode 100644 index 7b4d68d70..000000000 --- a/middleware/rewrite/testdata/testfile +++ /dev/null @@ -1 +0,0 @@ -empty
\ No newline at end of file diff --git a/middleware/rewrite/type.go b/middleware/rewrite/type.go deleted file mode 100644 index 58eedd51e..000000000 --- a/middleware/rewrite/type.go +++ /dev/null @@ -1,37 +0,0 @@ -// Package rewrite is middleware for rewriting requests internally to something different. -package rewrite - -import ( - "fmt" - "strings" - - "github.com/miekg/dns" -) - -// typeRule is a type rewrite rule. -type typeRule struct { - fromType, toType uint16 -} - -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(w dns.ResponseWriter, 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 -} diff --git a/middleware/root/README.md b/middleware/root/README.md deleted file mode 100644 index 23d35b2d3..000000000 --- a/middleware/root/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# root - -*root* simply specifies the root of where CoreDNS finds (e.g.) zone files. - -The default root is the current working directory of CoreDNS. A relative root path is relative to -the current working directory. - -## Syntax - -~~~ txt -root PATH -~~~ - -**PATH** is the directory to set as CoreDNS' root. - -## Examples - -Serve zone data (when the *file* middleware is used) from `/etc/coredns/zones`: - -~~~ txt -root /etc/coredns/zones -~~~ diff --git a/middleware/root/root.go b/middleware/root/root.go deleted file mode 100644 index d03ecb8a4..000000000 --- a/middleware/root/root.go +++ /dev/null @@ -1,43 +0,0 @@ -package root - -import ( - "log" - "os" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("root", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - config := dnsserver.GetConfig(c) - - for c.Next() { - if !c.NextArg() { - return middleware.Error("root", c.ArgErr()) - } - config.Root = c.Val() - } - - // Check if root path exists - _, err := os.Stat(config.Root) - if err != nil { - if os.IsNotExist(err) { - // Allow this, because the folder might appear later. - // But make sure the user knows! - log.Printf("[WARNING] Root path does not exist: %s", config.Root) - } else { - return middleware.Error("root", c.Errf("unable to access root path '%s': %v", config.Root, err)) - } - } - - return nil -} diff --git a/middleware/root/root_test.go b/middleware/root/root_test.go deleted file mode 100644 index ea0e53b5e..000000000 --- a/middleware/root/root_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package root - -import ( - "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/coredns/coredns/core/dnsserver" - - "github.com/mholt/caddy" -) - -func TestRoot(t *testing.T) { - log.SetOutput(ioutil.Discard) - - // Predefined error substrings - parseErrContent := "Error during parsing:" - unableToAccessErrContent := "unable to access root path" - - existingDirPath, err := getTempDirPath() - if err != nil { - t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err) - } - - nonExistingDir := filepath.Join(existingDirPath, "highly_unlikely_to_exist_dir") - - existingFile, err := ioutil.TempFile("", "root_test") - if err != nil { - t.Fatalf("BeforeTest: Failed to create temp file for testing! Error was: %v", err) - } - defer func() { - existingFile.Close() - os.Remove(existingFile.Name()) - }() - - inaccessiblePath := getInaccessiblePath(existingFile.Name()) - - tests := []struct { - input string - shouldErr bool - expectedRoot string // expected root, set to the controller. Empty for negative cases. - expectedErrContent string // substring from the expected error. Empty for positive cases. - }{ - // positive - { - fmt.Sprintf(`root %s`, nonExistingDir), false, nonExistingDir, "", - }, - { - fmt.Sprintf(`root %s`, existingDirPath), false, existingDirPath, "", - }, - // negative - { - `root `, true, "", parseErrContent, - }, - { - fmt.Sprintf(`root %s`, inaccessiblePath), true, "", unableToAccessErrContent, - }, - { - fmt.Sprintf(`root { - %s - }`, existingDirPath), true, "", parseErrContent, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - err := setup(c) - cfg := dnsserver.GetConfig(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - } - - if !strings.Contains(err.Error(), test.expectedErrContent) { - t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) - } - } - - // check root only if we are in a positive test. - if !test.shouldErr && test.expectedRoot != cfg.Root { - t.Errorf("Root not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedRoot, cfg.Root) - } - } -} - -// getTempDirPath returnes the path to the system temp directory. If it does not exists - an error is returned. -func getTempDirPath() (string, error) { - tempDir := os.TempDir() - _, err := os.Stat(tempDir) - if err != nil { - return "", err - } - return tempDir, nil -} - -func getInaccessiblePath(file string) string { - return filepath.Join("C:", "file\x00name") // null byte in filename is not allowed on Windows AND unix -} diff --git a/middleware/secondary/README.md b/middleware/secondary/README.md deleted file mode 100644 index d6cbe465a..000000000 --- a/middleware/secondary/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# secondary - -*secondary* enables serving a zone retrieved from a primary server. - -## Syntax - -~~~ -secondary [ZONES...] -~~~ - -* **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block - are used. Note that without a remote address to *get* the zone from, the above is not that useful. - -A working syntax would be: - -~~~ -secondary [zones...] { - transfer from ADDRESS - transfer to ADDRESS - upstream ADDRESS... -} -~~~ - -* `transfer from` specifies from which address to fetch the zone. It can be specified multiple times; - if one does not work, another will be tried. -* `transfer to` can be enabled to allow this secondary zone to be transferred again. -* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs) - pointing to external names. This is only really useful when CoreDNS is configured as a proxy, for - normal authoritative serving you don't need *or* want to use this. **ADDRESS** can be an IP - address, and IP:port or a string pointing to a file that is structured as /etc/resolv.conf. - -## Examples - -Transfer `example.org` from 10.0.1.1, and if that fails try 10.1.2.1. - -~~~ corefile -example.org { - secondary { - transfer from 10.0.1.1 - transfer from 10.1.2.1 - } -} -~~~ - -Or re-export the retrieved zone to other secondaries. - -~~~ corefile -. { - secondary example.net { - transfer from 10.1.2.1 - transfer to * - } -} -~~~ diff --git a/middleware/secondary/secondary.go b/middleware/secondary/secondary.go deleted file mode 100644 index da31d5171..000000000 --- a/middleware/secondary/secondary.go +++ /dev/null @@ -1,10 +0,0 @@ -// Package secondary implements a secondary middleware. -package secondary - -import "github.com/coredns/coredns/middleware/file" - -// Secondary implements a secondary middleware that allows CoreDNS to retrieve (via AXFR) -// zone information from a primary server. -type Secondary struct { - file.File -} diff --git a/middleware/secondary/setup.go b/middleware/secondary/setup.go deleted file mode 100644 index adba86f98..000000000 --- a/middleware/secondary/setup.go +++ /dev/null @@ -1,108 +0,0 @@ -package secondary - -import ( - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/file" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/proxy" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("secondary", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - zones, err := secondaryParse(c) - if err != nil { - return middleware.Error("secondary", err) - } - - // Add startup functions to retrieve the zone and keep it up to date. - for _, n := range zones.Names { - z := zones.Z[n] - if len(z.TransferFrom) > 0 { - c.OnStartup(func() error { - z.StartupOnce.Do(func() { - z.TransferIn() - go func() { - z.Update() - }() - }) - return nil - }) - } - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return Secondary{file.File{Next: next, Zones: zones}} - }) - - return nil -} - -func secondaryParse(c *caddy.Controller) (file.Zones, error) { - z := make(map[string]*file.Zone) - names := []string{} - origins := []string{} - prxy := proxy.Proxy{} - for c.Next() { - - if c.Val() == "secondary" { - // secondary [origin] - origins = make([]string, len(c.ServerBlockKeys)) - copy(origins, c.ServerBlockKeys) - args := c.RemainingArgs() - if len(args) > 0 { - origins = args - } - for i := range origins { - origins[i] = middleware.Host(origins[i]).Normalize() - z[origins[i]] = file.NewZone(origins[i], "stdin") - names = append(names, origins[i]) - } - - for c.NextBlock() { - - t, f := []string{}, []string{} - var e error - - switch c.Val() { - case "transfer": - t, f, e = file.TransferParse(c, true) - if e != nil { - return file.Zones{}, e - } - case "upstream": - args := c.RemainingArgs() - if len(args) == 0 { - return file.Zones{}, c.ArgErr() - } - ups, err := dnsutil.ParseHostPortOrFile(args...) - if err != nil { - return file.Zones{}, err - } - prxy = proxy.NewLookup(ups) - default: - return file.Zones{}, c.Errf("unknown property '%s'", c.Val()) - } - - for _, origin := range origins { - if t != nil { - z[origin].TransferTo = append(z[origin].TransferTo, t...) - } - if f != nil { - z[origin].TransferFrom = append(z[origin].TransferFrom, f...) - } - z[origin].Proxy = prxy - } - } - } - } - return file.Zones{Z: z, Names: names}, nil -} diff --git a/middleware/secondary/setup_test.go b/middleware/secondary/setup_test.go deleted file mode 100644 index bf2b203ad..000000000 --- a/middleware/secondary/setup_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package secondary - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestSecondaryParse(t *testing.T) { - tests := []struct { - inputFileRules string - shouldErr bool - transferFrom string - zones []string - }{ - { - `secondary`, - false, // TODO(miek): should actually be true, because without transfer lines this does not make sense - "", - nil, - }, - { - `secondary { - transfer from 127.0.0.1 - transfer to 127.0.0.1 - }`, - false, - "127.0.0.1:53", - nil, - }, - { - `secondary example.org { - transfer from 127.0.0.1 - transfer to 127.0.0.1 - }`, - false, - "127.0.0.1:53", - []string{"example.org."}, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.inputFileRules) - s, err := secondaryParse(c) - - if err == nil && test.shouldErr { - t.Fatalf("Test %d expected errors, but got no error", i) - } else if err != nil && !test.shouldErr { - t.Fatalf("Test %d expected no errors, but got '%v'", i, err) - } - - for i, name := range test.zones { - if x := s.Names[i]; x != name { - t.Fatalf("Test %d zone names don't match expected %q, but got %q", i, name, x) - } - } - - // This is only set *iff* we have a zone (i.e. not in all tests above) - for _, v := range s.Z { - if x := v.TransferFrom[0]; x != test.transferFrom { - t.Fatalf("Test %d transform from names don't match expected %q, but got %q", i, test.transferFrom, x) - } - } - } -} diff --git a/middleware/test/doc.go b/middleware/test/doc.go deleted file mode 100644 index e2f90262b..000000000 --- a/middleware/test/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package test contains helper functions for writing middleware tests. -package test diff --git a/middleware/test/file.go b/middleware/test/file.go deleted file mode 100644 index f87300e55..000000000 --- a/middleware/test/file.go +++ /dev/null @@ -1,107 +0,0 @@ -package test - -import ( - "io/ioutil" - "os" - "path/filepath" -) - -// TempFile will create a temporary file on disk and returns the name and a cleanup function to remove it later. -func TempFile(dir, content string) (string, func(), error) { - f, err := ioutil.TempFile(dir, "go-test-tmpfile") - if err != nil { - return "", nil, err - } - if err := ioutil.WriteFile(f.Name(), []byte(content), 0644); err != nil { - return "", nil, err - } - rmFunc := func() { os.Remove(f.Name()) } - return f.Name(), rmFunc, nil -} - -// WritePEMFiles creates a tmp dir with ca.pem, cert.pem, and key.pem and the func to remove it -func WritePEMFiles(dir string) (string, func(), error) { - tempDir, err := ioutil.TempDir(dir, "go-test-pemfiles") - if err != nil { - return "", nil, err - } - - data := `-----BEGIN CERTIFICATE----- -MIIC9zCCAd+gAwIBAgIJALGtqdMzpDemMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV -BAMMB2t1YmUtY2EwHhcNMTYxMDE5MTU1NDI0WhcNNDQwMzA2MTU1NDI0WjASMRAw -DgYDVQQDDAdrdWJlLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA -pa4Wu/WkpJNRr8pMVE6jjwzNUOx5mIyoDr8WILSxVQcEeyVPPmAqbmYXtVZO11p9 -jTzoEqF7Kgts3HVYGCk5abqbE14a8Ru/DmV5avU2hJ/NvSjtNi/O+V6SzCbg5yR9 -lBR53uADDlzuJEQT9RHq7A5KitFkx4vUcXnjOQCbDogWFoYuOgNEwJPy0Raz3NJc -ViVfDqSJ0QHg02kCOMxcGFNRQ9F5aoW7QXZXZXD0tn3wLRlu4+GYyqt8fw5iNdLJ -t79yKp8I+vMTmMPz4YKUO+eCl5EY10Qs7wvoG/8QNbjH01BRN3L8iDT2WfxdvjTu -1RjPxFL92i+B7HZO7jGLfQIDAQABo1AwTjAdBgNVHQ4EFgQUZTrg+Xt87tkxDhlB -gKk9FdTOW3IwHwYDVR0jBBgwFoAUZTrg+Xt87tkxDhlBgKk9FdTOW3IwDAYDVR0T -BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApB7JFVrZpGSOXNO3W7SlN6OCPXv9 -C7rIBc8rwOrzi2mZWcBmWheQrqBo8xHif2rlFNVQxtq3JcQ8kfg/m1fHeQ/Ygzel -Z+U1OqozynDySBZdNn9i+kXXgAUCqDPp3hEQWe0os/RRpIwo9yOloBxdiX6S0NIf -VB8n8kAynFPkH7pYrGrL1HQgDFCSfa4tUJ3+9sppnCu0pNtq5AdhYx9xFb2sn+8G -xGbtCkhVk2VQ+BiCWnjYXJ6ZMzabP7wiOFDP9Pvr2ik22PRItsW/TLfHFXM1jDmc -I1rs/VUGKzcJGVIWbHrgjP68CTStGAvKgbsTqw7aLXTSqtPw88N9XVSyRg== ------END CERTIFICATE-----` - path := filepath.Join(tempDir, "ca.pem") - if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil { - return "", nil, err - } - data = `-----BEGIN CERTIFICATE----- -MIICozCCAYsCCQCRlf5BrvPuqjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdr -dWJlLWNhMB4XDTE2MTAxOTE2MDUxOFoXDTE3MTAxOTE2MDUxOFowFTETMBEGA1UE -AwwKa3ViZS1hZG1pbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTw -a7wCFoiCad/N53aURfjrme+KR7FS0yf5Ur9OR/oM3BoS9stYu5Flzr35oL5T6t5G -c2ey78mUs/Cs07psnjUdKH55bDpJSdG7zW9mXNyeLwIefFcj/38SS5NBSotmLo8u -scJMGXeQpCQtfVuVJSP2bfU5u5d0KTLSg/Cor6UYonqrRB82HbOuuk8Wjaww4VHo -nCq7X8o948V6HN5ZibQOgMMo+nf0wORREHBjvwc4W7ewbaTcfoe1VNAo/QnkqxTF -ueMb2HxgghArqQSK8b44O05V0zrde25dVnmnte6sPjcV0plqMJ37jViISxsOPUFh -/ZW7zbIM/7CMcDekCiECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYZE8OxwRR7GR -kdd5aIriDwWfcl56cq5ICyx87U8hAZhBxk46a6a901LZPzt3xKyWIFQSRj/NYiQ+ -/thjGLZI2lhkVgYtyAD4BNxDiuppQSCbkjY9tLVDdExGttEVN7+UYDWJBHy6X16Y -xSG9FE3Dvp9LI89Nq8E3dRh+Q8wu52q9HaQXjS5YtzQOtDFKPBkihXu/c6gEHj4Y -bZVk8rFiH8/CvcQxAuvNI3VVCFUKd2LeQtqwYQQ//qoiuA15krTq5Ut9eXJ8zxAw -zhDEPP4FhY+Sz+y1yWirphl7A1aZwhXVPcfWIGqpQ3jzNwUeocbH27kuLh+U4hQo -qeg10RdFnw== ------END CERTIFICATE-----` - path = filepath.Join(tempDir, "cert.pem") - if err = ioutil.WriteFile(path, []byte(data), 0644); err != nil { - return "", nil, err - } - - data = `-----BEGIN RSA PRIVATE KEY----- -MIIEpgIBAAKCAQEAxPBrvAIWiIJp383ndpRF+OuZ74pHsVLTJ/lSv05H+gzcGhL2 -y1i7kWXOvfmgvlPq3kZzZ7LvyZSz8KzTumyeNR0ofnlsOklJ0bvNb2Zc3J4vAh58 -VyP/fxJLk0FKi2Yujy6xwkwZd5CkJC19W5UlI/Zt9Tm7l3QpMtKD8KivpRiieqtE -HzYds666TxaNrDDhUeicKrtfyj3jxXoc3lmJtA6Awyj6d/TA5FEQcGO/Bzhbt7Bt -pNx+h7VU0Cj9CeSrFMW54xvYfGCCECupBIrxvjg7TlXTOt17bl1Weae17qw+NxXS -mWownfuNWIhLGw49QWH9lbvNsgz/sIxwN6QKIQIDAQABAoIBAQDCXq9V7ZGjxWMN -OkFaLVkqJg3V91puztoMt+xNV8t+JTcOnOzrIXZuOFbl9PwLHPPP0SSRkm9LOvKl -dU26zv0OWureeKSymia7U2mcqyC3tX+bzc7WinbeSYZBnc0e7AjD1EgpBcaU1TLL -agIxY3A2oD9CKmrVPhZzTIZf/XztqTYjhvs5I2kBeT0imdYGpXkdndRyGX4I5/JQ -fnp3Czj+AW3zX7RvVnXOh4OtIAcfoG9xoNyD5LOSlJkkX0MwTS8pEBeZA+A4nb+C -ivjnOSgXWD+liisI+LpBgBbwYZ/E49x5ghZYrJt8QXSk7Bl/+UOyv6XZAm2mev6j -RLAZtoABAoGBAP2P+1PoKOwsk+d/AmHqyTCUQm0UG18LOLB/5PyWfXs/6caDmdIe -DZWeZWng1jUQLEadmoEw/CBY5+tPfHlzwzMNhT7KwUfIDQCIBoS7dzHYnwrJ3VZh -qYA05cuGHAAHqwb6UWz3y6Pa4AEVSHX6CM83CAi9jdWZ1rdZybWG+qYBAoGBAMbV -FsR/Ft+tK5ALgXGoG83TlmxzZYuZ1SnNje1OSdCQdMFCJB10gwoaRrw1ICzi40Xk -ydJwV1upGz1om9ReDAD1zQM9artmQx6+TVLiVPALuARdZE70+NrA6w3ZvxUgJjdN -ngvXUr+8SdvaYUAwFu7BulfJlwXjUS711hHW/KQhAoGBALY41QuV2mLwHlLNie7I -hlGtGpe9TXZeYB0nrG6B0CfU5LJPPSotguG1dXhDpm138/nDpZeWlnrAqdsHwpKd -yPhVjR51I7XsZLuvBdA50Q03egSM0c4UXXXPjh1XgaPb3uMi3YWMBwL4ducQXoS6 -bb5M9C8j2lxZNF+L3VPhbxwBAoGBAIEWDvX7XKpTDxkxnxRfA84ZNGusb5y2fsHp -Bd+vGBUj8+kUO8Yzwm9op8vA4ebCVrMl2jGZZd3IaDryE1lIxZpJ+pPD5+tKdQEc -o67P6jz+HrYWu+zW9klvPit71qasfKMi7Rza6oo4f+sQWFsH3ZucgpJD+pyD/Ez0 -pcpnPRaBAoGBANT/xgHBfIWt4U2rtmRLIIiZxKr+3mGnQdpA1J2BCh+/6AvrEx// -E/WObVJXDnBdViu0L9abE9iaTToBVri4cmlDlZagLuKVR+TFTCN/DSlVZTDkqkLI -8chzqtkH6b2b2R73hyRysWjsomys34ma3mEEPTX/aXeAF2MSZ/EWT9yL ------END RSA PRIVATE KEY-----` - path = filepath.Join(tempDir, "key.pem") - if err = ioutil.WriteFile(path, []byte(data), 0644); err != nil { - return "", nil, err - } - - rmFunc := func() { os.RemoveAll(tempDir) } - return tempDir, rmFunc, nil -} diff --git a/middleware/test/file_test.go b/middleware/test/file_test.go deleted file mode 100644 index ed86a8260..000000000 --- a/middleware/test/file_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package test - -import "testing" - -func TestTempFile(t *testing.T) { - _, f, e := TempFile(".", "test") - if e != nil { - t.Fatalf("failed to create temp file: %s", e) - } - defer f() -} diff --git a/middleware/test/helpers.go b/middleware/test/helpers.go deleted file mode 100644 index 35316dd38..000000000 --- a/middleware/test/helpers.go +++ /dev/null @@ -1,348 +0,0 @@ -package test - -import ( - "sort" - "testing" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -type sect int - -const ( - // Answer is the answer section in an Msg. - Answer sect = iota - // Ns is the authoritative section in an Msg. - Ns - // Extra is the additional section in an Msg. - Extra -) - -// RRSet represents a list of RRs. -type RRSet []dns.RR - -func (p RRSet) Len() int { return len(p) } -func (p RRSet) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p RRSet) Less(i, j int) bool { return p[i].String() < p[j].String() } - -// Case represents a test case that encapsulates various data from a query and response. -// Note that is the TTL of a record is 303 we don't compare it with the TTL. -type Case struct { - Qname string - Qtype uint16 - Rcode int - Do bool - Answer []dns.RR - Ns []dns.RR - Extra []dns.RR - Error error -} - -// Msg returns a *dns.Msg embedded in c. -func (c Case) Msg() *dns.Msg { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(c.Qname), c.Qtype) - if c.Do { - o := new(dns.OPT) - o.Hdr.Name = "." - o.Hdr.Rrtype = dns.TypeOPT - o.SetDo() - o.SetUDPSize(4096) - m.Extra = []dns.RR{o} - } - return m -} - -// A returns an A record from rr. It panics on errors. -func A(rr string) *dns.A { r, _ := dns.NewRR(rr); return r.(*dns.A) } - -// AAAA returns an AAAA record from rr. It panics on errors. -func AAAA(rr string) *dns.AAAA { r, _ := dns.NewRR(rr); return r.(*dns.AAAA) } - -// CNAME returns a CNAME record from rr. It panics on errors. -func CNAME(rr string) *dns.CNAME { r, _ := dns.NewRR(rr); return r.(*dns.CNAME) } - -// DNAME returns a DNAME record from rr. It panics on errors. -func DNAME(rr string) *dns.DNAME { r, _ := dns.NewRR(rr); return r.(*dns.DNAME) } - -// SRV returns a SRV record from rr. It panics on errors. -func SRV(rr string) *dns.SRV { r, _ := dns.NewRR(rr); return r.(*dns.SRV) } - -// SOA returns a SOA record from rr. It panics on errors. -func SOA(rr string) *dns.SOA { r, _ := dns.NewRR(rr); return r.(*dns.SOA) } - -// NS returns an NS record from rr. It panics on errors. -func NS(rr string) *dns.NS { r, _ := dns.NewRR(rr); return r.(*dns.NS) } - -// PTR returns a PTR record from rr. It panics on errors. -func PTR(rr string) *dns.PTR { r, _ := dns.NewRR(rr); return r.(*dns.PTR) } - -// TXT returns a TXT record from rr. It panics on errors. -func TXT(rr string) *dns.TXT { r, _ := dns.NewRR(rr); return r.(*dns.TXT) } - -// MX returns an MX record from rr. It panics on errors. -func MX(rr string) *dns.MX { r, _ := dns.NewRR(rr); return r.(*dns.MX) } - -// RRSIG returns an RRSIG record from rr. It panics on errors. -func RRSIG(rr string) *dns.RRSIG { r, _ := dns.NewRR(rr); return r.(*dns.RRSIG) } - -// NSEC returns an NSEC record from rr. It panics on errors. -func NSEC(rr string) *dns.NSEC { r, _ := dns.NewRR(rr); return r.(*dns.NSEC) } - -// DNSKEY returns a DNSKEY record from rr. It panics on errors. -func DNSKEY(rr string) *dns.DNSKEY { r, _ := dns.NewRR(rr); return r.(*dns.DNSKEY) } - -// DS returns a DS record from rr. It panics on errors. -func DS(rr string) *dns.DS { r, _ := dns.NewRR(rr); return r.(*dns.DS) } - -// OPT returns an OPT record with UDP buffer size set to bufsize and the DO bit set to do. -func OPT(bufsize int, do bool) *dns.OPT { - o := new(dns.OPT) - o.Hdr.Name = "." - o.Hdr.Rrtype = dns.TypeOPT - o.SetVersion(0) - o.SetUDPSize(uint16(bufsize)) - if do { - o.SetDo() - } - return o -} - -// Header test if the header in resp matches the header as defined in tc. -func Header(t *testing.T, tc Case, resp *dns.Msg) bool { - if resp.Rcode != tc.Rcode { - t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) - return false - } - - if len(resp.Answer) != len(tc.Answer) { - t.Errorf("answer for %q contained %d results, %d expected", tc.Qname, len(resp.Answer), len(tc.Answer)) - return false - } - if len(resp.Ns) != len(tc.Ns) { - t.Errorf("authority for %q contained %d results, %d expected", tc.Qname, len(resp.Ns), len(tc.Ns)) - return false - } - if len(resp.Extra) != len(tc.Extra) { - t.Errorf("additional for %q contained %d results, %d expected", tc.Qname, len(resp.Extra), len(tc.Extra)) - return false - } - return true -} - -// Section tests if the the section in tc matches rr. -func Section(t *testing.T, tc Case, sec sect, rr []dns.RR) bool { - section := []dns.RR{} - switch sec { - case 0: - section = tc.Answer - case 1: - section = tc.Ns - case 2: - section = tc.Extra - } - - for i, a := range rr { - if a.Header().Name != section[i].Header().Name { - t.Errorf("rr %d should have a Header Name of %q, but has %q", i, section[i].Header().Name, a.Header().Name) - return false - } - // 303 signals: don't care what the ttl is. - if section[i].Header().Ttl != 303 && a.Header().Ttl != section[i].Header().Ttl { - if _, ok := section[i].(*dns.OPT); !ok { - // we check edns0 bufize on this one - t.Errorf("rr %d should have a Header TTL of %d, but has %d", i, section[i].Header().Ttl, a.Header().Ttl) - return false - } - } - if a.Header().Rrtype != section[i].Header().Rrtype { - t.Errorf("rr %d should have a header rr type of %d, but has %d", i, section[i].Header().Rrtype, a.Header().Rrtype) - return false - } - - switch x := a.(type) { - case *dns.SRV: - if x.Priority != section[i].(*dns.SRV).Priority { - t.Errorf("rr %d should have a Priority of %d, but has %d", i, section[i].(*dns.SRV).Priority, x.Priority) - return false - } - if x.Weight != section[i].(*dns.SRV).Weight { - t.Errorf("rr %d should have a Weight of %d, but has %d", i, section[i].(*dns.SRV).Weight, x.Weight) - return false - } - if x.Port != section[i].(*dns.SRV).Port { - t.Errorf("rr %d should have a Port of %d, but has %d", i, section[i].(*dns.SRV).Port, x.Port) - return false - } - if x.Target != section[i].(*dns.SRV).Target { - t.Errorf("rr %d should have a Target of %q, but has %q", i, section[i].(*dns.SRV).Target, x.Target) - return false - } - case *dns.RRSIG: - if x.TypeCovered != section[i].(*dns.RRSIG).TypeCovered { - t.Errorf("rr %d should have a TypeCovered of %d, but has %d", i, section[i].(*dns.RRSIG).TypeCovered, x.TypeCovered) - return false - } - if x.Labels != section[i].(*dns.RRSIG).Labels { - t.Errorf("rr %d should have a Labels of %d, but has %d", i, section[i].(*dns.RRSIG).Labels, x.Labels) - return false - } - if x.SignerName != section[i].(*dns.RRSIG).SignerName { - t.Errorf("rr %d should have a SignerName of %s, but has %s", i, section[i].(*dns.RRSIG).SignerName, x.SignerName) - return false - } - case *dns.NSEC: - if x.NextDomain != section[i].(*dns.NSEC).NextDomain { - t.Errorf("rr %d should have a NextDomain of %s, but has %s", i, section[i].(*dns.NSEC).NextDomain, x.NextDomain) - return false - } - // TypeBitMap - case *dns.A: - if x.A.String() != section[i].(*dns.A).A.String() { - t.Errorf("rr %d should have a Address of %q, but has %q", i, section[i].(*dns.A).A.String(), x.A.String()) - return false - } - case *dns.AAAA: - if x.AAAA.String() != section[i].(*dns.AAAA).AAAA.String() { - t.Errorf("rr %d should have a Address of %q, but has %q", i, section[i].(*dns.AAAA).AAAA.String(), x.AAAA.String()) - return false - } - case *dns.TXT: - for j, txt := range x.Txt { - if txt != section[i].(*dns.TXT).Txt[j] { - t.Errorf("rr %d should have a Txt of %q, but has %q", i, section[i].(*dns.TXT).Txt[j], txt) - return false - } - } - case *dns.SOA: - tt := section[i].(*dns.SOA) - if x.Ns != tt.Ns { - t.Errorf("SOA nameserver should be %q, but is %q", tt.Ns, x.Ns) - return false - } - case *dns.PTR: - tt := section[i].(*dns.PTR) - if x.Ptr != tt.Ptr { - t.Errorf("PTR ptr should be %q, but is %q", tt.Ptr, x.Ptr) - return false - } - case *dns.CNAME: - tt := section[i].(*dns.CNAME) - if x.Target != tt.Target { - t.Errorf("CNAME target should be %q, but is %q", tt.Target, x.Target) - return false - } - case *dns.MX: - tt := section[i].(*dns.MX) - if x.Mx != tt.Mx { - t.Errorf("MX Mx should be %q, but is %q", tt.Mx, x.Mx) - return false - } - if x.Preference != tt.Preference { - t.Errorf("MX Preference should be %q, but is %q", tt.Preference, x.Preference) - return false - } - case *dns.NS: - tt := section[i].(*dns.NS) - if x.Ns != tt.Ns { - t.Errorf("NS nameserver should be %q, but is %q", tt.Ns, x.Ns) - return false - } - case *dns.OPT: - tt := section[i].(*dns.OPT) - if x.UDPSize() != tt.UDPSize() { - t.Errorf("OPT UDPSize should be %d, but is %d", tt.UDPSize(), x.UDPSize()) - return false - } - if x.Do() != tt.Do() { - t.Errorf("OPT DO should be %t, but is %t", tt.Do(), x.Do()) - return false - } - } - } - return true -} - -// CNAMEOrder makes sure that CNAMES do not appear after their target records -func CNAMEOrder(t *testing.T, res *dns.Msg) { - for i, c := range res.Answer { - if c.Header().Rrtype != dns.TypeCNAME { - continue - } - for _, a := range res.Answer[:i] { - if a.Header().Name != c.(*dns.CNAME).Target { - continue - } - t.Errorf("CNAME found after target record\n") - t.Logf("%v\n", res) - - } - } -} - -// SortAndCheck sorts resp and the checks the header and three sections against the testcase in tc. -func SortAndCheck(t *testing.T, resp *dns.Msg, tc Case) { - sort.Sort(RRSet(resp.Answer)) - sort.Sort(RRSet(resp.Ns)) - sort.Sort(RRSet(resp.Extra)) - - if !Header(t, tc, resp) { - t.Logf("%v\n", resp) - return - } - - if !Section(t, tc, Answer, resp.Answer) { - t.Logf("%v\n", resp) - return - } - if !Section(t, tc, Ns, resp.Ns) { - t.Logf("%v\n", resp) - return - - } - if !Section(t, tc, Extra, resp.Extra) { - t.Logf("%v\n", resp) - return - } - return -} - -// ErrorHandler returns a Handler that returns ServerFailure error when called. -func ErrorHandler() Handler { - return HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - m := new(dns.Msg) - m.SetRcode(r, dns.RcodeServerFailure) - w.WriteMsg(m) - return dns.RcodeServerFailure, nil - }) -} - -// NextHandler returns a Handler that returns rcode and err. -func NextHandler(rcode int, err error) Handler { - return HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - return rcode, err - }) -} - -// Copied here to prevent an import cycle, so that we can define to above handlers. - -type ( - // HandlerFunc is a convenience type like dns.HandlerFunc, except - // ServeDNS returns an rcode and an error. - HandlerFunc func(context.Context, dns.ResponseWriter, *dns.Msg) (int, error) - - // Handler interface defines a middleware. - Handler interface { - ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error) - Name() string - } -) - -// ServeDNS implements the Handler interface. -func (f HandlerFunc) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - return f(ctx, w, r) -} - -// Name implements the Handler interface. -func (f HandlerFunc) Name() string { return "handlerfunc" } diff --git a/middleware/test/responsewriter.go b/middleware/test/responsewriter.go deleted file mode 100644 index 79eaa00f3..000000000 --- a/middleware/test/responsewriter.go +++ /dev/null @@ -1,61 +0,0 @@ -package test - -import ( - "net" - - "github.com/miekg/dns" -) - -// ResponseWriter is useful for writing tests. It uses some fixed values for the client. The -// remote will always be 10.240.0.1 and port 40212. The local address is always 127.0.0.1 and -// port 53. -type ResponseWriter struct{} - -// LocalAddr returns the local address, always 127.0.0.1:53 (UDP). -func (t *ResponseWriter) LocalAddr() net.Addr { - ip := net.ParseIP("127.0.0.1") - port := 53 - return &net.UDPAddr{IP: ip, Port: port, Zone: ""} -} - -// RemoteAddr returns the remote address, always 10.240.0.1:40212 (UDP). -func (t *ResponseWriter) RemoteAddr() net.Addr { - ip := net.ParseIP("10.240.0.1") - port := 40212 - return &net.UDPAddr{IP: ip, Port: port, Zone: ""} -} - -// WriteMsg implement dns.ResponseWriter interface. -func (t *ResponseWriter) WriteMsg(m *dns.Msg) error { return nil } - -// Write implement dns.ResponseWriter interface. -func (t *ResponseWriter) Write(buf []byte) (int, error) { return len(buf), nil } - -// Close implement dns.ResponseWriter interface. -func (t *ResponseWriter) Close() error { return nil } - -// TsigStatus implement dns.ResponseWriter interface. -func (t *ResponseWriter) TsigStatus() error { return nil } - -// TsigTimersOnly implement dns.ResponseWriter interface. -func (t *ResponseWriter) TsigTimersOnly(bool) { return } - -// Hijack implement dns.ResponseWriter interface. -func (t *ResponseWriter) Hijack() { return } - -// RepsponseWrite6 returns fixed client and remote address in IPv6. The remote -// address is always fe80::42:ff:feca:4c65 and port 40212. The local address -// is always ::1 and port 53. -type ResponseWriter6 struct { - ResponseWriter -} - -// LocalAddr returns the local address, always ::1, port 53 (UDP). -func (t *ResponseWriter6) LocalAddr() net.Addr { - return &net.UDPAddr{IP: net.ParseIP("::1"), Port: 53, Zone: ""} -} - -// RemoteAddr returns the remote address, always fe80::42:ff:feca:4c65 port 40212 (UDP). -func (t *ResponseWriter6) RemoteAddr() net.Addr { - return &net.UDPAddr{IP: net.ParseIP("fe80::42:ff:feca:4c65"), Port: 40212, Zone: ""} -} diff --git a/middleware/test/server.go b/middleware/test/server.go deleted file mode 100644 index eb39c7a5b..000000000 --- a/middleware/test/server.go +++ /dev/null @@ -1,52 +0,0 @@ -package test - -import ( - "net" - "sync" - "time" - - "github.com/miekg/dns" -) - -// TCPServer starts a DNS server with a TCP listener on laddr. -func TCPServer(laddr string) (*dns.Server, string, error) { - l, err := net.Listen("tcp", laddr) - if err != nil { - return nil, "", err - } - - server := &dns.Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour} - - waitLock := sync.Mutex{} - waitLock.Lock() - server.NotifyStartedFunc = func() { waitLock.Unlock() } - - go func() { - server.ActivateAndServe() - l.Close() - }() - - waitLock.Lock() - return server, l.Addr().String(), nil -} - -// UDPServer starts a DNS server with an UDP listener on laddr. -func UDPServer(laddr string) (*dns.Server, string, error) { - pc, err := net.ListenPacket("udp", laddr) - if err != nil { - return nil, "", err - } - server := &dns.Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour} - - waitLock := sync.Mutex{} - waitLock.Lock() - server.NotifyStartedFunc = func() { waitLock.Unlock() } - - go func() { - server.ActivateAndServe() - pc.Close() - }() - - waitLock.Lock() - return server, pc.LocalAddr().String(), nil -} diff --git a/middleware/tls/README.md b/middleware/tls/README.md deleted file mode 100644 index a5c02c4c7..000000000 --- a/middleware/tls/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# tls - -*tls* allows you to configure the server certificates for the TLS and gRPC servers. -For other types of servers it is ignored. - -CoreDNS supports queries that are encrypted using TLS (DNS over Transport Layer Security, RFC 7858) -or are using gRPC (https://grpc.io/, not an IETF standard). Normally DNS traffic isn't encrypted at -all (DNSSEC only signs resource records). - -The *proxy* middleware also support gRPC (`protocol gRPC`), meaning you can chain CoreDNS servers -using this protocol. - -The *tls* "middleware" allows you to configure the cryptographic keys that are needed for both -DNS-over-TLS and DNS-over-gRPC. If the `tls` directive is omitted, then no encryption takes place. - -The gRPC protobuffer is defined in `pb/dns.proto`. It defines the proto as a simple wrapper for the -wire data of a DNS message. - -## Syntax - -~~~ txt -tls CERT KEY CA -~~~ - -## Examples - -Start a DNS-over-TLS server that picks up incoming DNS-over-TLS queries on port 5553 and uses the -nameservers defined in `/etc/resolv.conf` to resolve the query. This proxy path uses plain old DNS. - -~~~ -tls://.:5553 { - tls cert.pem key.pem ca.pem - proxy . /etc/resolv.conf -} -~~~ - -Start a DNS-over-gRPC server that is similar to the previous example, but using DNS-over-gRPC for -incoming queries. - -~~~ -grpc://. { - tls cert.pem key.pem ca.pem - proxy . /etc/resolv.conf -} -~~~ - -Only Knot DNS' `kdig` supports DNS-over-TLS queries, no command line client supports gRPC making -debugging these transports harder than it should be. - -## Also See - -RFC 7858 and https://grpc.io. diff --git a/middleware/tls/tls.go b/middleware/tls/tls.go deleted file mode 100644 index 55f2856c1..000000000 --- a/middleware/tls/tls.go +++ /dev/null @@ -1,37 +0,0 @@ -package tls - -import ( - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/tls" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("tls", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - config := dnsserver.GetConfig(c) - - if config.TLSConfig != nil { - return middleware.Error("tls", c.Errf("TLS already configured for this server instance")) - } - - for c.Next() { - args := c.RemainingArgs() - if len(args) != 3 { - return middleware.Error("tls", c.ArgErr()) - } - tls, err := tls.NewTLSConfig(args[0], args[1], args[2]) - if err != nil { - return middleware.Error("tls", err) - } - config.TLSConfig = tls - } - return nil -} diff --git a/middleware/tls/tls_test.go b/middleware/tls/tls_test.go deleted file mode 100644 index 2374d772c..000000000 --- a/middleware/tls/tls_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package tls - -import ( - "io/ioutil" - "log" - "strings" - "testing" - - "github.com/mholt/caddy" -) - -func TestTLS(t *testing.T) { - log.SetOutput(ioutil.Discard) - - tests := []struct { - input string - shouldErr bool - expectedRoot string // expected root, set to the controller. Empty for negative cases. - expectedErrContent string // substring from the expected error. Empty for positive cases. - }{ - // positive - // negative - } - - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - err := setup(c) - //cfg := dnsserver.GetConfig(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - } - - if !strings.Contains(err.Error(), test.expectedErrContent) { - t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) - } - } - } -} diff --git a/middleware/trace/README.md b/middleware/trace/README.md deleted file mode 100644 index aa157e1e2..000000000 --- a/middleware/trace/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# trace - -This module enables OpenTracing-based tracing of DNS requests as they go through the -middleware chain. - -## Syntax - -The simplest form is just: - -~~~ -trace [ENDPOINT-TYPE] [ENDPOINT] -~~~ - -* **ENDPOINT-TYPE** is the type of tracing destination. Currently only `zipkin` is supported - and that is what it defaults to. -* **ENDPOINT** is the tracing destination, and defaults to `localhost:9411`. For Zipkin, if - ENDPOINT does not begin with `http`, then it will be transformed to `http://ENDPOINT/api/v1/spans`. - -With this form, all queries will be traced. - -Additional features can be enabled with this syntax: - -~~~ -trace [ENDPOINT-TYPE] [ENDPOINT] { - every AMOUNT - service NAME - client_server -} -~~~ - -* `every` **AMOUNT** will only trace one query of each AMOUNT queries. For example, to trace 1 in every - 100 queries, use AMOUNT of 100. The default is 1. -* `service` **NAME** allows you to specify the service name reported to the tracing server. - Default is `coredns`. -* `client_server` will enable the `ClientServerSameSpan` OpenTracing feature. - -## Zipkin -You can run Zipkin on a Docker host like this: - -``` -docker run -d -p 9411:9411 openzipkin/zipkin -``` - -## Examples - -Use an alternative Zipkin address: - -~~~ -trace tracinghost:9253 -~~~ - -or - -~~~ -trace zipkin tracinghost:9253 -~~~ - -If for some reason you are using an API reverse proxy or something and need to remap -the standard Zipkin URL you can do something like: - -~~~ -trace http://tracinghost:9411/zipkin/api/v1/spans -~~~ - -Trace one query every 10000 queries, rename the service, and enable same span: - -~~~ -trace tracinghost:9411 { - every 10000 - service dnsproxy - client_server -} -~~~ diff --git a/middleware/trace/setup.go b/middleware/trace/setup.go deleted file mode 100644 index 601472fee..000000000 --- a/middleware/trace/setup.go +++ /dev/null @@ -1,113 +0,0 @@ -package trace - -import ( - "fmt" - "strconv" - "strings" - - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("trace", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - t, err := traceParse(c) - if err != nil { - return middleware.Error("trace", err) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - t.Next = next - return t - }) - - c.OnStartup(t.OnStartup) - - return nil -} - -func traceParse(c *caddy.Controller) (*trace, error) { - var ( - tr = &trace{Endpoint: defEP, EndpointType: defEpType, every: 1, serviceName: defServiceName} - err error - ) - - cfg := dnsserver.GetConfig(c) - tr.ServiceEndpoint = cfg.ListenHost + ":" + cfg.Port - for c.Next() { // trace - var err error - args := c.RemainingArgs() - switch len(args) { - case 0: - tr.Endpoint, err = normalizeEndpoint(tr.EndpointType, defEP) - case 1: - tr.Endpoint, err = normalizeEndpoint(defEpType, args[0]) - case 2: - tr.EndpointType = strings.ToLower(args[0]) - tr.Endpoint, err = normalizeEndpoint(tr.EndpointType, args[1]) - default: - err = c.ArgErr() - } - if err != nil { - return tr, err - } - for c.NextBlock() { - switch c.Val() { - case "every": - args := c.RemainingArgs() - if len(args) != 1 { - return nil, c.ArgErr() - } - tr.every, err = strconv.ParseUint(args[0], 10, 64) - if err != nil { - return nil, err - } - case "service": - args := c.RemainingArgs() - if len(args) != 1 { - return nil, c.ArgErr() - } - tr.serviceName = args[0] - case "client_server": - args := c.RemainingArgs() - if len(args) > 1 { - return nil, c.ArgErr() - } - tr.clientServer = true - if len(args) == 1 { - tr.clientServer, err = strconv.ParseBool(args[0]) - } - if err != nil { - return nil, err - } - } - } - } - return tr, err -} - -func normalizeEndpoint(epType, ep string) (string, error) { - switch epType { - case "zipkin": - if !strings.Contains(ep, "http") { - ep = "http://" + ep + "/api/v1/spans" - } - return ep, nil - default: - return "", fmt.Errorf("tracing endpoint type '%s' is not supported", epType) - } -} - -const ( - defEP = "localhost:9411" - defEpType = "zipkin" - defServiceName = "coredns" -) diff --git a/middleware/trace/setup_test.go b/middleware/trace/setup_test.go deleted file mode 100644 index 3c12b76e4..000000000 --- a/middleware/trace/setup_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package trace - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestTraceParse(t *testing.T) { - tests := []struct { - input string - shouldErr bool - endpoint string - every uint64 - serviceName string - clientServer bool - }{ - // oks - {`trace`, false, "http://localhost:9411/api/v1/spans", 1, `coredns`, false}, - {`trace localhost:1234`, false, "http://localhost:1234/api/v1/spans", 1, `coredns`, false}, - {`trace http://localhost:1234/somewhere/else`, false, "http://localhost:1234/somewhere/else", 1, `coredns`, false}, - {`trace zipkin localhost:1234`, false, "http://localhost:1234/api/v1/spans", 1, `coredns`, false}, - {`trace zipkin http://localhost:1234/somewhere/else`, false, "http://localhost:1234/somewhere/else", 1, `coredns`, false}, - {"trace {\n every 100\n}", false, "http://localhost:9411/api/v1/spans", 100, `coredns`, false}, - {"trace {\n every 100\n service foobar\nclient_server\n}", false, "http://localhost:9411/api/v1/spans", 100, `foobar`, true}, - {"trace {\n every 2\n client_server true\n}", false, "http://localhost:9411/api/v1/spans", 2, `coredns`, true}, - {"trace {\n client_server false\n}", false, "http://localhost:9411/api/v1/spans", 1, `coredns`, false}, - // fails - {`trace footype localhost:4321`, true, "", 1, "", false}, - {"trace {\n every 2\n client_server junk\n}", true, "", 1, "", false}, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.input) - m, err := traceParse(c) - if test.shouldErr && err == nil { - t.Errorf("Test %v: Expected error but found nil", i) - continue - } else if !test.shouldErr && err != nil { - t.Errorf("Test %v: Expected no error but found error: %v", i, err) - continue - } - - if test.shouldErr { - continue - } - - if test.endpoint != m.Endpoint { - t.Errorf("Test %v: Expected endpoint %s but found: %s", i, test.endpoint, m.Endpoint) - } - if test.every != m.every { - t.Errorf("Test %v: Expected every %d but found: %d", i, test.every, m.every) - } - if test.serviceName != m.serviceName { - t.Errorf("Test %v: Expected service name %s but found: %s", i, test.serviceName, m.serviceName) - } - if test.clientServer != m.clientServer { - t.Errorf("Test %v: Expected client_server %t but found: %t", i, test.clientServer, m.clientServer) - } - } -} diff --git a/middleware/trace/trace.go b/middleware/trace/trace.go deleted file mode 100644 index 86561871a..000000000 --- a/middleware/trace/trace.go +++ /dev/null @@ -1,84 +0,0 @@ -// Package trace implements OpenTracing-based tracing -package trace - -import ( - "fmt" - "sync" - "sync/atomic" - - "github.com/coredns/coredns/middleware" - // Plugin the trace package. - _ "github.com/coredns/coredns/middleware/pkg/trace" - - "github.com/miekg/dns" - ot "github.com/opentracing/opentracing-go" - zipkin "github.com/openzipkin/zipkin-go-opentracing" - "golang.org/x/net/context" -) - -type trace struct { - Next middleware.Handler - ServiceEndpoint string - Endpoint string - EndpointType string - tracer ot.Tracer - serviceName string - clientServer bool - every uint64 - count uint64 - Once sync.Once -} - -func (t *trace) Tracer() ot.Tracer { - return t.tracer -} - -// OnStartup sets up the tracer -func (t *trace) OnStartup() error { - var err error - t.Once.Do(func() { - switch t.EndpointType { - case "zipkin": - err = t.setupZipkin() - default: - err = fmt.Errorf("unknown endpoint type: %s", t.EndpointType) - } - }) - return err -} - -func (t *trace) setupZipkin() error { - - collector, err := zipkin.NewHTTPCollector(t.Endpoint) - if err != nil { - return err - } - - recorder := zipkin.NewRecorder(collector, false, t.ServiceEndpoint, t.serviceName) - t.tracer, err = zipkin.NewTracer(recorder, zipkin.ClientServerSameSpan(t.clientServer)) - - return err -} - -// Name implements the Handler interface. -func (t *trace) Name() string { - return "trace" -} - -// ServeDNS implements the middleware.Handle interface. -func (t *trace) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - trace := false - if t.every > 0 { - queryNr := atomic.AddUint64(&t.count, 1) - - if queryNr%t.every == 0 { - trace = true - } - } - if span := ot.SpanFromContext(ctx); span == nil && trace { - span := t.Tracer().StartSpan("servedns") - defer span.Finish() - ctx = ot.ContextWithSpan(ctx, span) - } - return middleware.NextOrFailure(t.Name(), t.Next, ctx, w, r) -} diff --git a/middleware/trace/trace_test.go b/middleware/trace/trace_test.go deleted file mode 100644 index 06b2aafcf..000000000 --- a/middleware/trace/trace_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package trace - -import ( - "testing" - - "github.com/mholt/caddy" -) - -// createTestTrace creates a trace middleware to be used in tests -func createTestTrace(config string) (*caddy.Controller, *trace, error) { - c := caddy.NewTestController("dns", config) - m, err := traceParse(c) - return c, m, err -} - -func TestTrace(t *testing.T) { - _, m, err := createTestTrace(`trace`) - if err != nil { - t.Errorf("Error parsing test input: %s", err) - return - } - if m.Name() != "trace" { - t.Errorf("Wrong name from GetName: %s", m.Name()) - } - err = m.OnStartup() - if err != nil { - t.Errorf("Error starting tracing middleware: %s", err) - return - } - if m.Tracer() == nil { - t.Errorf("Error, no tracer created") - } -} diff --git a/middleware/whoami/README.md b/middleware/whoami/README.md deleted file mode 100644 index 4b56a2e77..000000000 --- a/middleware/whoami/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# whoami - -*whoami* returns your resolver's local IP address, port and transport. Your IP address is returned - in the additional section as either an A or AAAA record. - -The reply always has an empty answer section. The port and transport are included in the additional -section as a SRV record, transport can be "tcp" or "udp". - -~~~ txt -._<transport>.qname. 0 IN SRV 0 0 <port> . -~~~ - -If CoreDNS can't find a Corefile on startup this is the *default* middleware that gets loaded. As -such it can be used to check that CoreDNS is responding to queries. Other than that this middleware -is of limited use in production. - -The *whoami* middleware will respond to every A or AAAA query, regardless of the query name. - -## Syntax - -~~~ txt -whoami -~~~ - -## Examples - -Start a server on the default port and load the *whoami* middleware. - -~~~ corefile -. { - whoami -} -~~~ - -When queried for "example.org A", CoreDNS will respond with: - -~~~ txt -;; QUESTION SECTION: -;example.org. IN A - -;; ADDITIONAL SECTION: -example.org. 0 IN A 10.240.0.1 -_udp.example.org. 0 IN SRV 0 0 40212 -~~~ diff --git a/middleware/whoami/setup.go b/middleware/whoami/setup.go deleted file mode 100644 index e11ac6567..000000000 --- a/middleware/whoami/setup.go +++ /dev/null @@ -1,28 +0,0 @@ -package whoami - -import ( - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("whoami", caddy.Plugin{ - ServerType: "dns", - Action: setupWhoami, - }) -} - -func setupWhoami(c *caddy.Controller) error { - c.Next() // 'whoami' - if c.NextArg() { - return middleware.Error("whoami", c.ArgErr()) - } - - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - return Whoami{} - }) - - return nil -} diff --git a/middleware/whoami/setup_test.go b/middleware/whoami/setup_test.go deleted file mode 100644 index 73db67d88..000000000 --- a/middleware/whoami/setup_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package whoami - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestSetupWhoami(t *testing.T) { - c := caddy.NewTestController("dns", `whoami`) - if err := setupWhoami(c); err != nil { - t.Fatalf("Expected no errors, but got: %v", err) - } - - c = caddy.NewTestController("dns", `whoami example.org`) - if err := setupWhoami(c); err == nil { - t.Fatalf("Expected errors, but got: %v", err) - } -} diff --git a/middleware/whoami/whoami.go b/middleware/whoami/whoami.go deleted file mode 100644 index 6b5ed332a..000000000 --- a/middleware/whoami/whoami.go +++ /dev/null @@ -1,57 +0,0 @@ -// Package whoami implements a middleware that returns details about the resolving -// querying it. -package whoami - -import ( - "net" - "strconv" - - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -// Whoami is a middleware that returns your IP address, port and the protocol used for connecting -// to CoreDNS. -type Whoami struct{} - -// ServeDNS implements the middleware.Handler interface. -func (wh Whoami) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := request.Request{W: w, Req: r} - - a := new(dns.Msg) - a.SetReply(r) - a.Compress = true - a.Authoritative = true - - ip := state.IP() - var rr dns.RR - - switch state.Family() { - case 1: - rr = new(dns.A) - rr.(*dns.A).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: state.QClass()} - rr.(*dns.A).A = net.ParseIP(ip).To4() - case 2: - rr = new(dns.AAAA) - rr.(*dns.AAAA).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Class: state.QClass()} - rr.(*dns.AAAA).AAAA = net.ParseIP(ip) - } - - srv := new(dns.SRV) - srv.Hdr = dns.RR_Header{Name: "_" + state.Proto() + "." + state.QName(), Rrtype: dns.TypeSRV, Class: state.QClass()} - port, _ := strconv.Atoi(state.Port()) - srv.Port = uint16(port) - srv.Target = "." - - a.Extra = []dns.RR{rr, srv} - - state.SizeAndDo(a) - w.WriteMsg(a) - - return 0, nil -} - -// Name implements the Handler interface. -func (wh Whoami) Name() string { return "whoami" } diff --git a/middleware/whoami/whoami_test.go b/middleware/whoami/whoami_test.go deleted file mode 100644 index d9357e497..000000000 --- a/middleware/whoami/whoami_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package whoami - -import ( - "testing" - - "github.com/coredns/coredns/middleware/pkg/dnsrecorder" - "github.com/coredns/coredns/middleware/test" - - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func TestWhoami(t *testing.T) { - wh := Whoami{} - - tests := []struct { - qname string - qtype uint16 - expectedCode int - expectedReply []string // ownernames for the records in the additional section. - expectedErr error - }{ - { - qname: "example.org", - qtype: dns.TypeA, - expectedCode: dns.RcodeSuccess, - expectedReply: []string{"example.org.", "_udp.example.org."}, - expectedErr: nil, - }, - } - - ctx := context.TODO() - - for i, tc := range tests { - req := new(dns.Msg) - req.SetQuestion(dns.Fqdn(tc.qname), tc.qtype) - - rec := dnsrecorder.New(&test.ResponseWriter{}) - code, err := wh.ServeDNS(ctx, rec, req) - - if err != tc.expectedErr { - t.Errorf("Test %d: Expected error %v, but got %v", i, tc.expectedErr, err) - } - if code != int(tc.expectedCode) { - t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code) - } - if len(tc.expectedReply) != 0 { - for i, expected := range tc.expectedReply { - actual := rec.Msg.Extra[i].Header().Name - if actual != expected { - t.Errorf("Test %d: Expected answer %s, but got %s", i, expected, actual) - } - } - } - } -} |