aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Miek Gieben <miek@miek.nl> 2016-03-28 12:08:05 +0100
committerGravatar Miek Gieben <miek@miek.nl> 2016-03-28 18:23:17 +0100
commite56d206542c901a48b28c4501fe5805e9e9e1a10 (patch)
tree77a644f6fe28f3de8eaf6f7c766cea73b438b487
parent6324bb1fa7c0516ef3bebfb822a0cdc767764ad2 (diff)
downloadcoredns-e56d206542c901a48b28c4501fe5805e9e9e1a10.tar.gz
coredns-e56d206542c901a48b28c4501fe5805e9e9e1a10.tar.zst
coredns-e56d206542c901a48b28c4501fe5805e9e9e1a10.zip
Support outgoing zone transfers
These can be enabled by adding "transfer out" to the Corefile. Without it no AXFR is allowed. For now only AXFR and no IXFR. No TSIG and no ACLs.
-rw-r--r--core/setup/file.go22
-rw-r--r--middleware/file/file.go11
-rw-r--r--middleware/file/file.md21
-rw-r--r--middleware/file/notify.go8
-rw-r--r--middleware/file/tree/all.go21
-rw-r--r--middleware/file/tree/tree.go2
-rw-r--r--middleware/file/xfr.go61
-rw-r--r--middleware/file/xfr_test.go34
-rw-r--r--middleware/file/zone.go38
-rw-r--r--middleware/recorder.go4
10 files changed, 195 insertions, 27 deletions
diff --git a/core/setup/file.go b/core/setup/file.go
index 858b784c2..7128b77aa 100644
--- a/core/setup/file.go
+++ b/core/setup/file.go
@@ -32,7 +32,6 @@ func fileParse(c *Controller) (file.Zones, error) {
origin := c.ServerBlockHosts[c.ServerBlockHostIndex]
if c.NextArg() {
- c.Next()
origin = c.Val()
}
// normalize this origin
@@ -42,12 +41,31 @@ func fileParse(c *Controller) (file.Zones, error) {
if err != nil {
return file.Zones{}, err
}
-
zone, err := file.Parse(reader, origin, fileName)
if err == nil {
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
+ }
+ }
}
}
return file.Zones{Z: z, Names: names}, nil
diff --git a/middleware/file/file.go b/middleware/file/file.go
index 5e4e0b357..237515a57 100644
--- a/middleware/file/file.go
+++ b/middleware/file/file.go
@@ -1,10 +1,5 @@
package file
-// TODO(miek): the zone's implementation is basically non-existent
-// we return a list and when searching for an answer we iterate
-// over the list. This must be moved to a tree-like structure and
-// have some fluff for DNSSEC (and be memory efficient).
-
import (
"io"
"log"
@@ -19,7 +14,6 @@ type (
File struct {
Next middleware.Handler
Zones Zones
- // Maybe a list of all zones as well, as a []string?
}
Zones struct {
@@ -40,6 +34,11 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
return f.Next.ServeDNS(ctx, w, r)
}
+ if state.Proto() != "udp" && state.QType() == dns.TypeAXFR {
+ xfr := Xfr{z}
+ return xfr.ServeDNS(ctx, w, r)
+ }
+
rrs, extra, result := z.Lookup(qname, state.QType(), state.Do())
m := new(dns.Msg)
diff --git a/middleware/file/file.md b/middleware/file/file.md
index 2e23b0332..bd9a24d1e 100644
--- a/middleware/file/file.md
+++ b/middleware/file/file.md
@@ -15,26 +15,15 @@ file dbfile [zones...]
* `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.
+If you want to round robin A and AAAA responses look at the `loadbalance` middleware.
~~~
-file {
- db <dsds>
- masters [...masters...]
+file dbfile [zones... ] {
+ transfer in|out
}
~~~
-
-
-
-
-* `path` /skydns
-* `endpoint` endpoints...
-* `stubzones`
+* `transfer` enable zone transfers, for now only `transfer out` does something. It enables outgoing
+ zone transfers when defined.
## Examples
-
-dnssec {
- file blaat, transparant allow already signed responses
- ksk bliep.dsdsk
-}
diff --git a/middleware/file/notify.go b/middleware/file/notify.go
new file mode 100644
index 000000000..cb61cc050
--- /dev/null
+++ b/middleware/file/notify.go
@@ -0,0 +1,8 @@
+package file
+
+// Notify sends notifies to the configured remotes. It will try up to three times
+// before giving up on a specific remote.
+func Notify(remotes []string) error {
+ return nil
+
+}
diff --git a/middleware/file/tree/all.go b/middleware/file/tree/all.go
new file mode 100644
index 000000000..f621e3465
--- /dev/null
+++ b/middleware/file/tree/all.go
@@ -0,0 +1,21 @@
+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
+}
diff --git a/middleware/file/tree/tree.go b/middleware/file/tree/tree.go
index db57c2092..234060eba 100644
--- a/middleware/file/tree/tree.go
+++ b/middleware/file/tree/tree.go
@@ -528,6 +528,8 @@ func (n *Node) floor(rr dns.RR) *Node {
return n
}
+// TODO(successor, predecessor)
+
// Ceil returns the smallest value equal to or greater than the query q according to q.Compare().
func (t *Tree) Ceil(rr dns.RR) *Elem {
if t.Root == nil {
diff --git a/middleware/file/xfr.go b/middleware/file/xfr.go
new file mode 100644
index 000000000..297de2fc5
--- /dev/null
+++ b/middleware/file/xfr.go
@@ -0,0 +1,61 @@
+package file
+
+import (
+ "fmt"
+
+ "github.com/miekg/coredns/middleware"
+
+ "github.com/miekg/dns"
+ "golang.org/x/net/context"
+)
+
+type (
+ Xfr struct {
+ *Zone
+ }
+)
+
+// Serve an AXFR (or maybe later an IXFR) as well.
+func (x Xfr) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
+ state := middleware.State{W: w, Req: r}
+ if !x.TransferAllowed(state) {
+ return dns.RcodeServerFailure, nil
+ }
+ if state.QType() != dns.TypeAXFR {
+ return 0, fmt.Errorf("file: xfr called with non xfr type: %d", state.QType())
+ }
+ if state.Proto() == "udp" {
+ return 0, fmt.Errorf("file: xfr called with udp")
+ }
+
+ 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])
+ 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
+}
+
+//const transferLength = 10e3 // Start a new envelop after message reaches this size.
+const transferLength = 100 // Start a new envelop after message reaches this size.
diff --git a/middleware/file/xfr_test.go b/middleware/file/xfr_test.go
new file mode 100644
index 000000000..07caaf1d9
--- /dev/null
+++ b/middleware/file/xfr_test.go
@@ -0,0 +1,34 @@
+package file
+
+import (
+ "fmt"
+ "strings"
+)
+
+func ExampleZone_All() {
+ zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin")
+ 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
index 57eb8d997..bac420669 100644
--- a/middleware/file/zone.go
+++ b/middleware/file/zone.go
@@ -1,20 +1,29 @@
package file
import (
+ "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/file/tree"
"github.com/miekg/dns"
)
+type Transfer struct {
+ Out bool
+ In bool
+ // more later
+}
+
type Zone struct {
SOA *dns.SOA
- SIG []*dns.RRSIG
+ SIG []dns.RR
name string
*tree.Tree
+ Masters []string
+ Transfer *Transfer
}
func NewZone(name string) *Zone {
- return &Zone{name: dns.Fqdn(name), Tree: &tree.Tree{}}
+ return &Zone{name: dns.Fqdn(name), Tree: &tree.Tree{}, Transfer: &Transfer{}}
}
func (z *Zone) Insert(r dns.RR) {
@@ -24,3 +33,28 @@ func (z *Zone) Insert(r dns.RR) {
func (z *Zone) Delete(r dns.RR) {
z.Tree.Delete(r)
}
+
+// It the transfer request allowed.
+func (z *Zone) TransferAllowed(state middleware.State) bool {
+ if z.Transfer == nil {
+ return false
+ }
+ return z.Transfer.Out
+}
+
+// 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 {
+ records := []dns.RR{}
+ allNodes := z.Tree.All()
+ for _, a := range allNodes {
+ records = append(records, a.All()...)
+ }
+
+ if len(z.SIG) > 0 {
+ records = append(z.SIG, records...)
+ }
+ return append([]dns.RR{z.SOA}, records...)
+}
+
+// Apex function?
diff --git a/middleware/recorder.go b/middleware/recorder.go
index 19a15463e..6b926e48d 100644
--- a/middleware/recorder.go
+++ b/middleware/recorder.go
@@ -37,7 +37,9 @@ func NewResponseRecorder(w dns.ResponseWriter) *ResponseRecorder {
// underlying ResponseWriter's WriteMsg method.
func (r *ResponseRecorder) WriteMsg(res *dns.Msg) error {
r.rcode = res.Rcode
- r.size = res.Len()
+ // We may get called multiple times (axfr for instance).
+ // Save the last message, but add the sizes.
+ r.size += res.Len()
r.msg = res
return r.ResponseWriter.WriteMsg(res)
}