middleware/secondary: multiple fixes (#745)

Fix transferring the zone from a master and the matching of notifies
to source and dst IP addresses.

Add `upstream` keyword as well, because it is needed for the same
reasons as in the *file* middlware.
Add some dire warning about upstream in the readme of both middlewares.

Out of band testing, hidden by net build tag was added. Integration
testing still needs to be setup.
This commit is contained in:
Miek Gieben
2017-06-21 23:46:20 -07:00
committed by GitHub
parent 9e463e0bca
commit 9fb266aebe
8 changed files with 171 additions and 12 deletions

View File

@@ -38,7 +38,8 @@ file DBFILE [ZONES... ] {
* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the
file. This option disables that behavior.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names.
pointing to external names. This is only really useful when CoreDNS is configured as a proxy, for
normal authoritative serving you don't need *or* want to use this.
## Examples

View File

@@ -3,8 +3,8 @@ package file
import (
"fmt"
"log"
"net"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/pkg/rcode"
"github.com/coredns/coredns/request"
@@ -21,8 +21,13 @@ func (z *Zone) isNotify(state request.Request) bool {
if len(z.TransferFrom) == 0 {
return false
}
remote := middleware.Addr(state.IP()).Normalize()
for _, from := range z.TransferFrom {
// If remote IP matches we accept.
remote := state.IP()
for _, f := range z.TransferFrom {
from, _, err := net.SplitHostPort(f)
if err != nil {
continue
}
if from == remote {
return true
}

View File

@@ -26,19 +26,19 @@ Transfer:
t := new(dns.Transfer)
c, err := t.In(m, tr)
if err != nil {
log.Printf("[ERROR] Failed to setup transfer `%s' with `%s': %v", z.origin, tr, err)
log.Printf("[ERROR] Failed to setup transfer `%s' with `%q': %v", z.origin, tr, err)
Err = err
continue Transfer
}
for env := range c {
if env.Error != nil {
log.Printf("[ERROR] Failed to parse transfer `%s': %v", z.origin, env.Error)
log.Printf("[ERROR] Failed to transfer `%s' from %q: %v", z.origin, tr, env.Error)
Err = env.Error
continue Transfer
}
for _, rr := range env.RR {
if err := z1.Insert(rr); err != nil {
log.Printf("[ERROR] Failed to parse transfer `%s': %v", z.origin, err)
log.Printf("[ERROR] Failed to parse transfer `%s' from: %q: %v", z.origin, tr, err)
Err = err
continue Transfer
}
@@ -48,7 +48,6 @@ Transfer:
break
}
if Err != nil {
log.Printf("[ERROR] Failed to transfer %s: %s", z.origin, Err)
return Err
}

View File

@@ -57,6 +57,6 @@ func (x Xfr) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (in
}
// Name implements the middleware.Hander interface.
func (x Xfr) Name() string { return "xfr" } // Or should we return "file" here?
func (x Xfr) Name() string { return "xfr" }
const transferLength = 1000 // Start a new envelop after message reaches this size in bytes. Intentionally small to test multi envelope parsing.

View File

@@ -2,6 +2,7 @@ package file
import (
"fmt"
"net"
"path"
"strings"
"sync"
@@ -55,12 +56,12 @@ func NewZone(name, file string) *Zone {
return z
}
// Copy copies a zone *without* copying the zone's content. It is not a deep copy.
func (z *Zone) Copy() *Zone {
z1 := NewZone(z.origin, z.file)
z1.TransferTo = z.TransferTo
z1.TransferFrom = z.TransferFrom
z1.Expired = z.Expired
z1.Apex = z.Apex
return z1
}
@@ -113,11 +114,20 @@ func (z *Zone) Insert(r dns.RR) error {
func (z *Zone) Delete(r dns.RR) { z.Tree.Delete(r) }
// TransferAllowed checks if incoming request for transferring the zone is allowed according to the ACLs.
func (z *Zone) TransferAllowed(req request.Request) bool {
func (z *Zone) TransferAllowed(state request.Request) bool {
for _, t := range z.TransferTo {
if t == "*" {
return true
}
// If remote IP matches we accept.
remote := state.IP()
to, _, err := net.SplitHostPort(t)
if err != nil {
continue
}
if to == remote {
return true
}
}
// TODO(miek): future matching against IP/CIDR notations
return false

View File

@@ -16,13 +16,17 @@ A working syntax would be:
~~~
secondary [zones...] {
transfer from ADDRESS
[transfer to ADDRESS]
transfer to ADDRESS
upstream ADDRESS...
}
~~~
* `transfer from` specifies from which address to fetch the zone. It can be specified multiple times;
if one does not work, another will be tried.
* `transfer to` can be enabled to allow this secondary zone to be transferred again.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names. This is only really useful when CoreDNS is configured as a proxy, for
normal authoritative serving you don't need *or* want to use this.
## Examples

View File

@@ -4,6 +4,8 @@ import (
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/file"
"github.com/coredns/coredns/middleware/pkg/dnsutil"
"github.com/coredns/coredns/middleware/proxy"
"github.com/mholt/caddy"
)
@@ -47,6 +49,7 @@ func secondaryParse(c *caddy.Controller) (file.Zones, error) {
z := make(map[string]*file.Zone)
names := []string{}
origins := []string{}
prxy := proxy.Proxy{}
for c.Next() {
if c.Val() == "secondary" {
@@ -74,6 +77,16 @@ func secondaryParse(c *caddy.Controller) (file.Zones, error) {
if e != nil {
return file.Zones{}, e
}
case "upstream":
args := c.RemainingArgs()
if len(args) == 0 {
return file.Zones{}, c.ArgErr()
}
ups, err := dnsutil.ParseHostPortOrFile(args...)
if err != nil {
return file.Zones{}, err
}
prxy = proxy.NewLookup(ups)
}
for _, origin := range origins {
@@ -83,6 +96,7 @@ func secondaryParse(c *caddy.Controller) (file.Zones, error) {
if f != nil {
z[origin].TransferFrom = append(z[origin].TransferFrom, f...)
}
z[origin].Proxy = prxy
}
}
}

126
test/secondary_net_test.go Normal file
View File

@@ -0,0 +1,126 @@
// +build net
package test
import (
"testing"
"github.com/miekg/dns"
)
func TestSecondaryZoneTransfer(t *testing.T) {
/*
Test will only work when there is a CoreDNS running on part 32054
with example.com and willing to transfer
coredns -conf Corefile -dns.port 32054
Corefile:
example.com {
file example.com {
transfer to 127.0.0.1:32053
}
}
example.com:
example.com. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2017042730 7200 3600 1209600 3600
example.com. 65118 IN NS a.iana-servers.net.
example.com. 65118 IN NS b.iana-servers.net.
cname.example.com. 434334 IN CNAME a.miek.nl.
*/
corefile := `example.com:32053 {
secondary {
transfer from 127.0.0.1:32054
}
}
`
sec, err := CoreDNSServer(corefile)
if err != nil {
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
}
defer sec.Stop()
m := new(dns.Msg)
m.SetQuestion("cname.example.com.", dns.TypeCNAME)
r, err := dns.Exchange(m, "127.0.0.1:32053")
if err != nil {
t.Fatalf("Expected to receive reply, but didn't: %s", err)
}
if len(r.Answer) == 0 {
t.Fatalf("Expected answer section")
}
if r.Answer[0].(*dns.CNAME).Target != "a.miek.nl." {
t.Fatalf("Expected target of %s, got %s", "a.miek.nl.", r.Answer[0].(*dns.CNAME).Target)
}
m = new(dns.Msg)
m.SetQuestion("example.com.", dns.TypeSOA)
r, err = dns.Exchange(m, "127.0.0.1:32053")
if err != nil {
t.Fatalf("Expected to receive reply, but didn't: %s", err)
}
if len(r.Answer) == 0 {
t.Fatalf("Expected answer section")
}
if r.Answer[0].(*dns.SOA).Serial != 2017042730 {
t.Fatalf("Expected serial of %d, got %d", 2017042730, r.Answer[0].(*dns.SOA).Serial)
}
}
func TestSecondaryZoneTransferUpstream(t *testing.T) {
/*
Test will only work when there is a CoreDNS running on part 32054
with example.com and willing to transfer
coredns -conf Corefile -dns.port 32054
Corefile:
example.com {
file example.com {
transfer to 127.0.0.1:32053
}
}
example.com:
example.com. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2017042730 7200 3600 1209600 3600
example.com. 65118 IN NS a.iana-servers.net.
example.com. 65118 IN NS b.iana-servers.net.
cname.example.com. 434334 IN CNAME a.miek.nl.
*/
corefile := `example.com:32053 {
secondary {
transfer from 127.0.0.1:32054
upstream 8.8.8.8
}
}
`
sec, err := CoreDNSServer(corefile)
if err != nil {
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
}
defer sec.Stop()
m := new(dns.Msg)
m.SetQuestion("cname.example.com.", dns.TypeA)
r, err := dns.Exchange(m, "127.0.0.1:32053")
if err != nil {
t.Fatalf("Expected to receive reply, but didn't: %s", err)
}
if len(r.Answer) != 2 {
t.Fatalf("Expected answer section, with 2 records, got %d", len(r.Answer))
}
if r.Answer[0].(*dns.CNAME).Target != "a.miek.nl." {
t.Fatalf("Expected target of %s, got %s", "a.miek.nl.", r.Answer[0].(*dns.CNAME).Target)
}
if r.Answer[1].Header().Name != "a.miek.nl." {
t.Fatalf("Expected name of %s, got %s", "a.miek.nl.", r.Answer[1].Header().Name)
}
}