Merge pull request #55 from miekg/transfer-out

Transfer out
This commit is contained in:
Miek Gieben
2016-03-28 18:26:14 +01:00
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] 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) {
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

View File

@@ -1,10 +1,5 @@
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 (
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
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)

View File

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

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

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 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) {
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?

View File

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