Commit e56d2065 authored by Miek Gieben's avatar Miek Gieben

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.
parent 6324bb1f
...@@ -32,7 +32,6 @@ func fileParse(c *Controller) (file.Zones, error) { ...@@ -32,7 +32,6 @@ func fileParse(c *Controller) (file.Zones, error) {
origin := c.ServerBlockHosts[c.ServerBlockHostIndex] origin := c.ServerBlockHosts[c.ServerBlockHostIndex]
if c.NextArg() { if c.NextArg() {
c.Next()
origin = c.Val() origin = c.Val()
} }
// normalize this origin // normalize this origin
...@@ -42,12 +41,31 @@ func fileParse(c *Controller) (file.Zones, error) { ...@@ -42,12 +41,31 @@ func fileParse(c *Controller) (file.Zones, error) {
if err != nil { if err != nil {
return file.Zones{}, err return file.Zones{}, err
} }
zone, err := file.Parse(reader, origin, fileName) zone, err := file.Parse(reader, origin, fileName)
if err == nil { if err == nil {
z[origin] = zone z[origin] = zone
} }
names = append(names, origin) 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 return file.Zones{Z: z, Names: names}, nil
......
package file 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 ( import (
"io" "io"
"log" "log"
...@@ -19,7 +14,6 @@ type ( ...@@ -19,7 +14,6 @@ type (
File struct { File struct {
Next middleware.Handler Next middleware.Handler
Zones Zones Zones Zones
// Maybe a list of all zones as well, as a []string?
} }
Zones struct { Zones struct {
...@@ -40,6 +34,11 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i ...@@ -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) 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()) rrs, extra, result := z.Lookup(qname, state.QType(), state.Do())
m := new(dns.Msg) m := new(dns.Msg)
......
...@@ -15,26 +15,15 @@ file dbfile [zones...] ...@@ -15,26 +15,15 @@ file dbfile [zones...]
* `zones` zones it should be authoritative for. If empty the zones from the configuration block * `zones` zones it should be authoritative for. If empty the zones from the configuration block
are used. 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 { file dbfile [zones... ] {
db <dsds> transfer in|out
masters [...masters...]
} }
~~~ ~~~
* `transfer` enable zone transfers, for now only `transfer out` does something. It enables outgoing
zone transfers when defined.
* `path` /skydns
* `endpoint` endpoints...
* `stubzones`
## Examples ## Examples
dnssec {
file blaat, transparant allow already signed responses
ksk bliep.dsdsk
}
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
}
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
}
...@@ -528,6 +528,8 @@ func (n *Node) floor(rr dns.RR) *Node { ...@@ -528,6 +528,8 @@ func (n *Node) floor(rr dns.RR) *Node {
return n return n
} }
// TODO(successor, predecessor)
// Ceil returns the smallest value equal to or greater than the query q according to q.Compare(). // 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 { func (t *Tree) Ceil(rr dns.RR) *Elem {
if t.Root == nil { if t.Root == nil {
......
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.
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
}
package file package file
import ( import (
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/file/tree" "github.com/miekg/coredns/middleware/file/tree"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
type Transfer struct {
Out bool
In bool
// more later
}
type Zone struct { type Zone struct {
SOA *dns.SOA SOA *dns.SOA
SIG []*dns.RRSIG SIG []dns.RR
name string name string
*tree.Tree *tree.Tree
Masters []string
Transfer *Transfer
} }
func NewZone(name string) *Zone { 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) { func (z *Zone) Insert(r dns.RR) {
...@@ -24,3 +33,28 @@ 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) { func (z *Zone) Delete(r dns.RR) {
z.Tree.Delete(r) 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?
...@@ -37,7 +37,9 @@ func NewResponseRecorder(w dns.ResponseWriter) *ResponseRecorder { ...@@ -37,7 +37,9 @@ func NewResponseRecorder(w dns.ResponseWriter) *ResponseRecorder {
// underlying ResponseWriter's WriteMsg method. // underlying ResponseWriter's WriteMsg method.
func (r *ResponseRecorder) WriteMsg(res *dns.Msg) error { func (r *ResponseRecorder) WriteMsg(res *dns.Msg) error {
r.rcode = res.Rcode 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 r.msg = res
return r.ResponseWriter.WriteMsg(res) return r.ResponseWriter.WriteMsg(res)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment