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.
This commit is contained in:
Miek Gieben 2016-03-28 12:08:05 +01:00
parent 6324bb1fa7
commit e56d206542
10 changed files with 195 additions and 27 deletions

View file

@ -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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {

61
middleware/file/xfr.go Normal file
View file

@ -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.

View file

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

View file

@ -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?

View file

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