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:
parent
6324bb1fa7
commit
e56d206542
10 changed files with 195 additions and 27 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
8
middleware/file/notify.go
Normal file
8
middleware/file/notify.go
Normal 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
|
||||
|
||||
}
|
21
middleware/file/tree/all.go
Normal file
21
middleware/file/tree/all.go
Normal 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
|
||||
}
|
|
@ -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
61
middleware/file/xfr.go
Normal 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.
|
34
middleware/file/xfr_test.go
Normal file
34
middleware/file/xfr_test.go
Normal 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
|
||||
}
|
|
@ -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?
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue