aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/directives.go1
-rw-r--r--core/setup/file.go53
-rw-r--r--core/setup/secondary.go59
-rw-r--r--middleware/file/README.md23
-rw-r--r--middleware/file/file.go6
-rw-r--r--middleware/file/lookup_test.go9
-rw-r--r--middleware/file/notify.go16
-rw-r--r--middleware/file/secondary.go63
-rw-r--r--middleware/file/xfr.go4
-rw-r--r--middleware/file/zone.go27
-rw-r--r--middleware/secondary/README.md34
-rw-r--r--middleware/secondary/secondary.go7
12 files changed, 252 insertions, 50 deletions
diff --git a/core/directives.go b/core/directives.go
index 14f52ced8..c6b7a4b4a 100644
--- a/core/directives.go
+++ b/core/directives.go
@@ -59,6 +59,7 @@ var directiveOrder = []directive{
{"errors", setup.Errors},
{"file", setup.File},
+ {"secondary", setup.Secondary},
{"etcd", setup.Etcd},
{"proxy", setup.Proxy},
}
diff --git a/core/setup/file.go b/core/setup/file.go
index aca9f8f9f..dc0d61e18 100644
--- a/core/setup/file.go
+++ b/core/setup/file.go
@@ -36,9 +36,9 @@ func fileParse(c *Controller) (file.Zones, error) {
if c.NextArg() {
origin = c.Val()
}
- // normalize this origin
- origin = middleware.Host(origin).Standard()
+ origin = middleware.Host(origin).Normalize()
+ // TODO(miek): we should allow more. Issue #54.
reader, err := os.Open(fileName)
if err != nil {
return file.Zones{}, err
@@ -48,27 +48,40 @@ func fileParse(c *Controller) (file.Zones, error) {
z[origin] = zone
}
names = append(names, origin)
- if c.NextBlock() {
- what := c.Val()
- if !c.NextArg() {
- return file.Zones{}, c.ArgErr()
- }
- value := c.Val()
- var err error
- switch what {
- case "transfer":
- if value == "out" {
- z[origin].Transfer.Out = true
- }
- if value == "in" {
- z[origin].Transfer.In = true
- }
- }
- if err != nil {
- return file.Zones{}, err
+
+ for c.NextBlock() {
+ t, _, e := parseTransfer(c)
+ if e != nil {
+ return file.Zones{}, e
}
+ // discard from, here, maybe check and show log when we do?
+ z[origin].TransferTo = append(z[origin].TransferTo, t)
}
}
}
return file.Zones{Z: z, Names: names}, nil
}
+
+// transfer to [address]
+func parseTransfer(c *Controller) (to, from string, err error) {
+ what := c.Val()
+ if !c.NextArg() {
+ return "", "", c.ArgErr()
+ }
+ value := c.Val()
+ switch what {
+ case "transfer":
+ if !c.NextArg() {
+ return "", "", c.ArgErr()
+ }
+ if value == "to" {
+ to = c.Val()
+ to = middleware.Addr(to).Normalize()
+ }
+ if value == "from" {
+ from = c.Val()
+ from = middleware.Addr(from).Normalize()
+ }
+ }
+ return
+}
diff --git a/core/setup/secondary.go b/core/setup/secondary.go
new file mode 100644
index 000000000..3282f92b1
--- /dev/null
+++ b/core/setup/secondary.go
@@ -0,0 +1,59 @@
+package setup
+
+import (
+ "github.com/miekg/coredns/middleware"
+ "github.com/miekg/coredns/middleware/file"
+ "github.com/miekg/coredns/middleware/secondary"
+)
+
+// Secondary sets up the secondary middleware.
+func Secondary(c *Controller) (middleware.Middleware, error) {
+ zones, err := secondaryParse(c)
+ if err != nil {
+ return nil, err
+ }
+
+ // Setup retrieve the zone.
+ for _, n := range zones.Names {
+ if len(zones.Z[n].TransferFrom) > 0 {
+ c.Startup = append(c.Startup, func() error {
+ err := zones.Z[n].TransferIn()
+ return err
+ })
+ }
+ }
+
+ return func(next middleware.Handler) middleware.Handler {
+ return secondary.Secondary{file.File{Next: next, Zones: zones}}
+ }, nil
+
+}
+
+func secondaryParse(c *Controller) (file.Zones, error) {
+ z := make(map[string]*file.Zone)
+ names := []string{}
+ for c.Next() {
+ if c.Val() == "secondary" {
+ // secondary [origin]
+ origin := c.ServerBlockHosts[c.ServerBlockHostIndex]
+ if c.NextArg() {
+ origin = c.Val()
+ }
+ // TODO(miek): we should allow more. Issue #54.
+ origin = middleware.Host(origin).Normalize()
+
+ z[origin] = file.NewZone(origin)
+ names = append(names, origin)
+
+ for c.NextBlock() {
+ t, f, e := parseTransfer(c)
+ if e != nil {
+ return file.Zones{}, e
+ }
+ z[origin].TransferTo = append(z[origin].TransferTo, t)
+ z[origin].TransferFrom = append(z[origin].TransferFrom, f)
+ }
+ }
+ }
+ return file.Zones{Z: z, Names: names}, nil
+}
diff --git a/middleware/file/README.md b/middleware/file/README.md
index 407c1119c..36b76a1aa 100644
--- a/middleware/file/README.md
+++ b/middleware/file/README.md
@@ -1,9 +1,9 @@
# file
-`file` enabled reading zone data from a RFC-1035 styled file.
+`file` enables serving zone data from a RFC-1035 styled file.
-The etcd middleware makes extensive use of the proxy middleware to forward and query
-other servers in the network.
+The file middleware is used for "old-style" DNS server. It serves from a preloaded file that exists
+on disk.
## Syntax
@@ -17,15 +17,26 @@ file dbfile [zones...]
If you want to round robin A and AAAA responses look at the `loadbalance` middleware.
-TSIG key configuration is TODO; directive format will change.
+TSIG key configuration is TODO; directive format for transfer will probably be extended with
+TSIG key information, something like `transfer out [address] key [name] [base64]`
~~~
file dbfile [zones... ] {
transfer out [address...]
+ transfer to [address]
}
~~~
-* `transfer` enable zone transfers, for now only `transfer out` does something. It enables outgoing
- zone transfers when defined.
+* `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.). The special
+ wildcard "*" means: the entire internet.
## Examples
+
+Load the `miek.nl` zone from `miek.nl.signed` and allow transfers to the internet.
+
+~~~
+file miek.nl.signed miek.nl {
+ transfer to *
+}
+~~~
diff --git a/middleware/file/file.go b/middleware/file/file.go
index c005b41cc..caf0c5fbc 100644
--- a/middleware/file/file.go
+++ b/middleware/file/file.go
@@ -33,6 +33,12 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
if !ok {
return f.Next.ServeDNS(ctx, w, r)
}
+ if z == nil {
+ return dns.RcodeServerFailure, nil
+ }
+ if z.Expired != nil && *z.Expired {
+ return dns.RcodeServerFailure, nil
+ }
if state.Proto() != "udp" && state.QType() == dns.TypeAXFR {
xfr := Xfr{z}
diff --git a/middleware/file/lookup_test.go b/middleware/file/lookup_test.go
index df65d0150..11ec5bab0 100644
--- a/middleware/file/lookup_test.go
+++ b/middleware/file/lookup_test.go
@@ -107,6 +107,15 @@ func TestLookup(t *testing.T) {
}
}
+func TestLookupNil(t *testing.T) {
+ fm := File{Next: coretest.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: nil}, Names: []string{testzone}}}
+ ctx := context.TODO()
+
+ m := dnsTestCases[0].Msg()
+ rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{})
+ fm.ServeDNS(ctx, rec, m)
+}
+
func BenchmarkLookup(b *testing.B) {
zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin")
if err != nil {
diff --git a/middleware/file/notify.go b/middleware/file/notify.go
index cc8f493e4..bbdafe022 100644
--- a/middleware/file/notify.go
+++ b/middleware/file/notify.go
@@ -10,31 +10,31 @@ import (
// Notify will send notifies to all configured IP addresses.
func (z *Zone) Notify() {
- go notify(z.name, z.Peers)
+ go notify(z.name, z.TransferTo)
}
-// notify sends notifies to the configured remotes. It will try up to three times
-// before giving up on a specific remote. We will sequentially loop through the remotes
+// 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, remotes []string) error {
+func notify(zone string, to []string) error {
m := new(dns.Msg)
m.SetNotify(zone)
c := new(dns.Client)
// TODO(miek): error handling? Run this in a goroutine?
- for _, remote := range remotes {
- notifyRemote(c, m, middleware.Addr(remote).Standard())
+ for _, t := range to {
+ notifyAddr(c, m, t)
}
return nil
}
-func notifyRemote(c *dns.Client, m *dns.Msg, s string) error {
+func notifyAddr(c *dns.Client, m *dns.Msg, s string) error {
for i := 0; i < 3; i++ {
ret, err := middleware.Exchange(c, m, s)
if err == nil && ret.Rcode == dns.RcodeSuccess || ret.Rcode == dns.RcodeNotImplemented {
return nil
}
- // timeout? mean don't want it. should stop sending as well
+ // timeout? mean don't want it. should stop sending as well?
}
return fmt.Errorf("failed to send notify for zone '%s' to '%s'", m.Question[0].Name, s)
}
diff --git a/middleware/file/secondary.go b/middleware/file/secondary.go
new file mode 100644
index 000000000..95c063a9b
--- /dev/null
+++ b/middleware/file/secondary.go
@@ -0,0 +1,63 @@
+package file
+
+import (
+ "log"
+
+ "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
+ }
+ t := new(dns.Transfer)
+ m := new(dns.Msg)
+ m.SetAxfr(z.name)
+ /*
+ t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
+ m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
+ */
+
+ var Err error
+Transfer:
+ for _, tr := range z.TransferFrom {
+ c, err := t.In(m, tr)
+ if err != nil {
+ log.Printf("[ERROR] failed to setup transfer %s with %s: %v", z.name, z.TransferFrom[0], err)
+ Err = err
+ continue Transfer
+ }
+ for env := range c {
+ if env.Error != nil {
+ log.Printf("[ERROR] failed to parse transfer %s: %v", z.name, env.Error)
+ Err = env.Error
+ continue Transfer
+ }
+ for _, rr := range env.RR {
+ if rr.Header().Rrtype == dns.TypeSOA {
+ z.SOA = rr.(*dns.SOA)
+ continue
+ }
+ if rr.Header().Rrtype == dns.TypeRRSIG {
+ if x, ok := rr.(*dns.RRSIG); ok && x.TypeCovered == dns.TypeSOA {
+ z.SIG = append(z.SIG, x)
+ }
+ }
+ z.Insert(rr)
+ }
+ }
+ }
+ return Err
+}
+
+/*
+
+ 28800 ; refresh (8 hours)
+ 7200 ; retry (2 hours)
+ 604800 ; expire (1 week)
+ 3600 ; minimum (1 hour)
+// Check SOA
+// Just check every refresh hours, if fail set to retry until succeeds
+// expire is need: to give SERVFAIL.
+*/
diff --git a/middleware/file/xfr.go b/middleware/file/xfr.go
index 297de2fc5..5ef205f6e 100644
--- a/middleware/file/xfr.go
+++ b/middleware/file/xfr.go
@@ -22,7 +22,7 @@ func (x Xfr) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (in
return dns.RcodeServerFailure, nil
}
if state.QType() != dns.TypeAXFR {
- return 0, fmt.Errorf("file: xfr called with non xfr type: %d", state.QType())
+ return 0, fmt.Errorf("file: xfr called with non transfer type: %d", state.QType())
}
if state.Proto() == "udp" {
return 0, fmt.Errorf("file: xfr called with udp")
@@ -39,7 +39,7 @@ func (x Xfr) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (in
go tr.Out(w, r, ch)
j, l := 0, 0
- records = append(records, records[0])
+ records = append(records, records[0]) // add closing SOA to the end
for i, r := range records {
l += dns.Len(r)
if l > transferLength {
diff --git a/middleware/file/zone.go b/middleware/file/zone.go
index 8c56b6bf9..c3567b45f 100644
--- a/middleware/file/zone.go
+++ b/middleware/file/zone.go
@@ -7,23 +7,22 @@ import (
"github.com/miekg/dns"
)
-type Transfer struct {
- Out bool
- In bool
-}
-
type Zone struct {
SOA *dns.SOA
SIG []dns.RR
name string
*tree.Tree
- Peers []string
- Transfer *Transfer
+
+ TransferTo []string
+ TransferFrom []string
+ Expired *bool
}
// NewZone returns a new zone.
func NewZone(name string) *Zone {
- return &Zone{name: dns.Fqdn(name), Tree: &tree.Tree{}, Transfer: &Transfer{}}
+ z := &Zone{name: dns.Fqdn(name), Tree: &tree.Tree{}, Expired: new(bool)}
+ *z.Expired = false
+ return z
}
// Insert inserts r into z.
@@ -32,12 +31,14 @@ func (z *Zone) Insert(r dns.RR) { z.Tree.Insert(r) }
// Delete deletes r from z.
func (z *Zone) Delete(r dns.RR) { z.Tree.Delete(r) }
-// It the transfer request allowed.
+// TransferAllowed checks if incoming request for transferring the zone is allowed according to the ACLs.
func (z *Zone) TransferAllowed(state middleware.State) bool {
- if z.Transfer == nil {
- return false
+ for _, t := range z.TransferTo {
+ if t == "*" {
+ return true
+ }
}
- return z.Transfer.Out
+ return false
}
// All returns all records from the zone, the first record will be the SOA record,
@@ -54,5 +55,3 @@ func (z *Zone) All() []dns.RR {
}
return append([]dns.RR{z.SOA}, records...)
}
-
-// Apex function?
diff --git a/middleware/secondary/README.md b/middleware/secondary/README.md
new file mode 100644
index 000000000..c2b1366e7
--- /dev/null
+++ b/middleware/secondary/README.md
@@ -0,0 +1,34 @@
+# 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. Not that with an remote address to *get* the zone the above is not that useful.
+
+A working syntax would be:
+
+~~~
+secondary [zones...] {
+ transfer from address
+ [transfer to address]
+}
+~~~
+
+* `transfer from` tell from which address to fetch the zone. It can be specified multiple time,
+ if one does not work another will be tried.
+* `transfer to` can be enabled to allow this secondary zone to be transfered again.
+
+## Examples
+
+~~~
+secondary [zones...] {
+ transfer from 10.0.1.1
+ transfer from 10.1.2.1
+}
+~~~
diff --git a/middleware/secondary/secondary.go b/middleware/secondary/secondary.go
new file mode 100644
index 000000000..60006b69c
--- /dev/null
+++ b/middleware/secondary/secondary.go
@@ -0,0 +1,7 @@
+package secondary
+
+import "github.com/miekg/coredns/middleware/file"
+
+type Secondary struct {
+ file.File
+}