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 - 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) }